/*
* AlsaMidiDevice.java
*
* This file is part of Tritonus: http://www.tritonus.org/
*/
/*
* Copyright (c) 1999 - 2001 by Matthias Pfisterer
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
/*
|<--- this code is formatted to fit into 80 columns --->|
*/
package org.tritonus.midi.device.alsa;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Transmitter;
import org.tritonus.share.TDebug;
import org.tritonus.lowlevel.alsa.AlsaSeq;
import org.tritonus.lowlevel.alsa.AlsaSeqPortSubscribe;
import org.tritonus.lowlevel.alsa.AlsaSeqEvent;
import org.tritonus.lowlevel.alsa.AlsaSeqQueueStatus;
import org.tritonus.share.midi.TMidiDevice;
import org.tritonus.share.GlobalInfo;
/** A representation of a physical MIDI port based on the ALSA sequencer.
*/
public class AlsaMidiDevice
extends TMidiDevice
implements AlsaMidiIn.AlsaMidiInListener
{
/** ALSA client id of the physical port.
*/
private int m_nPhysicalClient;
/** ALSA port id of the physical port.
*/
private int m_nPhysicalPort;
/** The object interfacing to the ALSA sequencer.
*/
private AlsaSeq m_alsaSeq;
/** The object used for getting timestamps.
*/
private AlsaSeqQueueStatus m_queueStatus;
/** The ALSA port id of the handler.
* This is used by m_alsaSeq.
*/
private int m_nOwnPort;
/** Handler for input from the physical MIDI port.
*/
private AlsaMidiIn m_alsaMidiIn;
/** Handler for output to the physical MIDI port.
*/
private AlsaMidiOut m_alsaMidiOut;
/** ALSA queue number used to timestamp incoming events.
*/
private int m_nTimestampingQueue;
/** The event used for starting and stopping the queue.
*/
private AlsaSeqEvent m_event = new AlsaSeqEvent();
public AlsaMidiDevice(int nClient, int nPort, boolean bUseIn, boolean bUseOut)
{
this(new TMidiDevice.Info("ALSA MIDI port (" + nClient + ":" + nPort + ")",
GlobalInfo.getVendor(),
"ALSA MIDI port (" + nClient + ":" + nPort + ")",
GlobalInfo.getVersion()),
nClient, nPort, bUseIn, bUseOut);
}
protected AlsaMidiDevice(MidiDevice.Info info, int nClient, int nPort, boolean bUseIn, boolean bUseOut)
{
super(info, bUseIn, bUseOut);
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.<init>(): begin"); }
m_nPhysicalClient = nClient;
m_nPhysicalPort = nPort;
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.<init>(): end"); }
}
protected AlsaSeq getAlsaSeq()
{
return m_alsaSeq;
}
protected int getOwnPort()
{
return m_nOwnPort;
}
protected int getPhysicalClient()
{
return m_nPhysicalClient;
}
protected int getPhysicalPort()
{
return m_nPhysicalPort;
}
private AlsaSeqQueueStatus getQueueStatus()
{
return m_queueStatus;
}
protected void openImpl()
{
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.openImpl(): begin"); }
// create an ALSA client...
m_alsaSeq = new AlsaSeq("Tritonus Midi port handler");
// ...and an ALSA port
m_nOwnPort = getAlsaSeq().createPort(
"handler port",
AlsaSeq.SND_SEQ_PORT_CAP_WRITE | AlsaSeq.SND_SEQ_PORT_CAP_SUBS_WRITE | AlsaSeq.SND_SEQ_PORT_CAP_READ | AlsaSeq.SND_SEQ_PORT_CAP_SUBS_READ,
0,
AlsaSeq.SND_SEQ_PORT_TYPE_APPLICATION,
0, 0, 0);
if (getUseTransmitter())
{
/*
* AlsaMidiIn listens to incoming event on the
* MIDI port.
* It calls this.dequeueEvent() if
* it receives an event.
*/
m_nTimestampingQueue = getAlsaSeq().allocQueue();
m_queueStatus = new AlsaSeqQueueStatus();
// TODO: stop queue
startQueue();
m_alsaMidiIn = new AlsaMidiIn(
getAlsaSeq(), getOwnPort(),
getPhysicalClient(), getPhysicalPort(),
getTimestampingQueue(), true,
this);
m_alsaMidiIn.start();
}
if (getUseReceiver())
{
// uses subscribers, immediately
m_alsaMidiOut = new AlsaMidiOut(getAlsaSeq(), getOwnPort());
AlsaSeqPortSubscribe portSubscribe = new AlsaSeqPortSubscribe();
portSubscribe.setSender(getAlsaSeq().getClientId(), getOwnPort());
portSubscribe.setDest(getPhysicalClient(), getPhysicalPort());
getAlsaSeq().subscribePort(portSubscribe);
portSubscribe.free();
}
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.openImpl(): end"); }
}
protected void closeImpl()
{
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.closeImpl(): begin"); }
if (getUseTransmitter())
{
m_alsaMidiIn.interrupt();
m_alsaMidiIn = null;
stopQueue();
// TODO: release timestamping queue
m_queueStatus.free();
m_queueStatus = null;
}
// TODO:
// getAlsaSeq().destroyPort(getOwnPort());
getAlsaSeq().close();
m_alsaSeq = null;
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.closeImpl(): end"); }
}
public long getMicroSecondPosition()
{
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.getMicroSecondPosition(): begin"); }
long lPosition = 0;
if (m_queueStatus != null)
{
getAlsaSeq().getQueueStatus(getTimestampingQueue(), getQueueStatus());
long lNanoSeconds = getQueueStatus().getRealTime();
lPosition = lNanoSeconds / 1000;
}
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.getMicroSecondPosition(): end"); }
return lPosition;
}
private void startQueue()
{
controlQueue(AlsaSeq.SND_SEQ_EVENT_START);
}
private void stopQueue()
{
controlQueue(AlsaSeq.SND_SEQ_EVENT_STOP);
}
private void controlQueue(int nType)
{
m_event.setCommon(nType,
AlsaSeq.SND_SEQ_TIME_STAMP_REAL | AlsaSeq.SND_SEQ_TIME_MODE_REL,
0, AlsaSeq.SND_SEQ_QUEUE_DIRECT, 0L,
0, getOwnPort(),
AlsaSeq.SND_SEQ_CLIENT_SYSTEM, AlsaSeq.SND_SEQ_PORT_SYSTEM_TIMER);
m_event.setQueueControl(getTimestampingQueue(), 0, 0);
getAlsaSeq().eventOutputDirect(m_event);
}
/** Pass MidiMessage from Receivers to physical MIDI port.
*/
protected void receive(MidiMessage message, long lTimeStamp)
{
if (isOpen())
{
m_alsaMidiOut.enqueueMessage(message, lTimeStamp);
}
}
// for AlsaMidiInListener
// passes events read from the device to the Transmitters
public void dequeueEvent(MidiMessage message, long lTimestamp)
{
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.dequeueEvent(): message: " + message); }
if (TDebug.TraceMidiDevice) { TDebug.out("AlsaMidiDevice.dequeueEvent(): tick: " + lTimestamp); }
// send via superclass method
sendImpl(message, lTimestamp);
}
private int getTimestampingQueue()
{
return m_nTimestampingQueue;
}
public Receiver getReceiver()
throws MidiUnavailableException
{
if (! getUseReceiver())
{
throw new MidiUnavailableException("Receivers are not supported by this device");
}
return new AlsaMidiDeviceReceiver();
}
public Transmitter getTransmitter()
throws MidiUnavailableException
{
if (! getUseTransmitter())
{
throw new MidiUnavailableException("Transmitters are not supported by this device");
}
return new AlsaMidiDeviceTransmitter();
}
/////////////////// INNER CLASSES //////////////////////////////////////
private class AlsaMidiDeviceReceiver
extends TReceiver
implements AlsaReceiver
{
public AlsaMidiDeviceReceiver()
{
super();
}
/** Subscribe to the passed port.
* This establishes a subscription in the ALSA sequencer
* so that the device this Receiver belongs to receives
* event from the client:port passed as parameters.
*
* @return true if subscription was established,
* false otherwise
*/
public boolean subscribeTo(int nClient, int nPort)
{
try
{
AlsaSeqPortSubscribe portSubscribe = new AlsaSeqPortSubscribe();
portSubscribe.setSender(nClient, nPort);
portSubscribe.setDest(AlsaMidiDevice.this.getPhysicalClient(), AlsaMidiDevice.this.getPhysicalPort());
AlsaMidiDevice.this.getAlsaSeq().subscribePort(portSubscribe);
portSubscribe.free();
return true;
}
catch (RuntimeException e)
{
if (TDebug.TraceAllExceptions) { TDebug.out(e); }
return false;
}
}
}
private class AlsaMidiDeviceTransmitter
extends TTransmitter
{
private boolean m_bReceiverSubscribed;
public AlsaMidiDeviceTransmitter()
{
super();
m_bReceiverSubscribed = false;
}
public void setReceiver(Receiver receiver)
{
super.setReceiver(receiver);
/*
* Try to establish a subscription of the Receiver
* to the ALSA seqencer client of the device this
* Transmitter belongs to.
*/
if (receiver instanceof AlsaReceiver)
{
m_bReceiverSubscribed = ((AlsaReceiver) receiver).subscribeTo(getPhysicalClient(), getPhysicalPort());
}
}
public void send(MidiMessage message, long lTimeStamp)
{
/*
* Send message via Java methods only if no
* subscription was established. If there is a
* subscription, the message is routed inside of
* the ALSA sequencer.
*/
if (! m_bReceiverSubscribed)
{
super.send(message, lTimeStamp);
}
}
public void close()
{
super.close();
// TODO: remove subscription
}
}
}
/*** AlsaMidiDevice.java ***/