/* * TMidiDevice.java * * This file is part of Tritonus: http://www.tritonus.org/ */ /* * Copyright (c) 1999 - 2006 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.share.midi; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.MetaMessage; 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; /** Base class for MidiDevice implementations. * The goal of this class is to supply the common functionality for * classes that implement the interface MidiDevice. */ public abstract class TMidiDevice implements MidiDevice { /** The Info object for a certain instance of MidiDevice. */ private MidiDevice.Info m_info; /** A flag to store whether the device is "open". */ private boolean m_bDeviceOpen; /** Whether to handle input from the physical port and to allow Transmitters. */ private boolean m_bUseTransmitter; /** Whether to handle output to the physical port and to allow Receivers. */ private boolean m_bUseReceiver; /** The list of Receiver objects that belong to this * MidiDevice. * * @see #addReceiver * @see #removeReceiver */ private List<Receiver> m_receivers; /** The list of Transmitter objects that belong to this * MidiDevice. * * @see #addTransmitter * @see #removeTransmitter */ private List<Transmitter> m_transmitters; /** Initialize this class. * This sets the info from the passed one, sets the open status * to false, the number of Receivers to zero and the collection * of Transmitters to be empty. * * @param info The info object that describes this instance. */ public TMidiDevice(MidiDevice.Info info) { this(info, true, true); } /** Initialize this class. * This sets the info from the passed one, sets the open status * to false, the number of Receivers to zero and the collection * of Transmitters to be empty. * * @param info The info object that describes this instance. */ public TMidiDevice(MidiDevice.Info info, boolean bUseTransmitter, boolean bUseReceiver) { m_info = info; m_bUseTransmitter = bUseTransmitter; m_bUseReceiver = bUseReceiver; m_bDeviceOpen = false; m_receivers = new ArrayList<Receiver>(); m_transmitters = new ArrayList<Transmitter>(); } /** Retrieves a description of this instance. * This returns the info object passed to the constructor. * * @return the description * * @see #TMidiDevice */ public MidiDevice.Info getDeviceInfo() { return m_info; } public synchronized void open() throws MidiUnavailableException { if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.open(): begin"); } if (! isOpen()) { openImpl(); /* If openImpl() throws a MidiUnavailableException, m_bDeviceOpen * remains false. */ m_bDeviceOpen = true; } if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.open(): end"); } } /** * Subclasses have to override this method to be notified of * opening. */ protected void openImpl() throws MidiUnavailableException { if (TDebug.TraceMidiDevice) TDebug.out("TMidiDevice.openImpl(): begin"); if (TDebug.TraceMidiDevice) TDebug.out("TMidiDevice.openImpl(): end"); } public synchronized void close() { if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.close(): begin"); } if (isOpen()) { closeImpl(); // TODO: close all Receivers and Transmitters m_bDeviceOpen = false; } if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.close(): end"); } } /** * Subclasses have to override this method to be notified of * closeing. */ protected void closeImpl() { if (TDebug.TraceMidiDevice) TDebug.out("TMidiDevice.closeImpl(): begin"); if (TDebug.TraceMidiDevice) TDebug.out("TMidiDevice.closeImpl(): end"); } public boolean isOpen() { return m_bDeviceOpen; } /** Returns whether to handle input. If this is true, retrieving Transmitters is possible and input from the physical port is passed to them. @see #getUseOut */ protected boolean getUseTransmitter() { return m_bUseTransmitter; } /** Returns whether to handle output. If this is true, retrieving Receivers is possible and output to them is passed to the physical port. @see #getUseTransmitter */ protected boolean getUseReceiver() { return m_bUseReceiver; } /** Returns the device time in microseconds. This is a default implementation, telling the application program that the device doesn't track time. If a device wants to give timing information, it has to override this method. */ public long getMicrosecondPosition() { return -1; } public int getMaxReceivers() { int nMaxReceivers = 0; if (getUseReceiver()) { /* * The value -1 means unlimited. */ nMaxReceivers = -1; } return nMaxReceivers; } public int getMaxTransmitters() { int nMaxTransmitters = 0; if (getUseTransmitter()) { /* * The value -1 means unlimited. */ nMaxTransmitters = -1; } return nMaxTransmitters; } /** Creates a new Receiver object associated with this instance. * In this implementation, an unlimited number of Receivers * per MidiDevice can be created. */ public Receiver getReceiver() throws MidiUnavailableException { if (! getUseReceiver()) { throw new MidiUnavailableException("Receivers are not supported by this device"); } return new TReceiver(); } /** Creates a new Transmitter object associated with this instance. * In this implementation, an unlimited number of Transmitters * per MidiDevice can be created. */ public Transmitter getTransmitter() throws MidiUnavailableException { if (! getUseTransmitter()) { throw new MidiUnavailableException("Transmitters are not supported by this device"); } return new TTransmitter(); } public List<Receiver> getReceivers() { return Collections.unmodifiableList(m_receivers); } public List<Transmitter> getTransmitters() { return Collections.unmodifiableList(m_transmitters); } /* * Intended for overriding by subclasses to receive messages. * This method is called by TMidiDevice.Receiver object on * receipt of a MidiMessage. */ protected void receive(MidiMessage message, long lTimeStamp) { if (TDebug.TraceMidiDevice) { TDebug.out("### [should be overridden] TMidiDevice.receive(): message " + message); } } protected void addReceiver(Receiver receiver) { synchronized (m_receivers) { m_receivers.add(receiver); } } protected void removeReceiver(Receiver receiver) { synchronized (m_receivers) { m_receivers.remove(receiver); } } protected void addTransmitter(Transmitter transmitter) { synchronized (m_transmitters) { m_transmitters.add(transmitter); } } protected void removeTransmitter(Transmitter transmitter) { synchronized (m_transmitters) { m_transmitters.remove(transmitter); } } /** Send a MidiMessage to all Transmitters. * This method should be called by subclasses when they get a * message from a physical MIDI port. */ protected void sendImpl(MidiMessage message, long lTimeStamp) { if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.sendImpl(): begin"); } Iterator transmitters = m_transmitters.iterator(); while (transmitters.hasNext()) { TTransmitter transmitter = (TTransmitter) transmitters.next(); /* due to a bug in the Sun jdk1.3, we cannot use clone() for MetaMessages. So we have to do the equivalent ourselves. */ // MidiMessage copiedMessage = (MidiMessage) message.clone(); MidiMessage copiedMessage = null; if (message instanceof MetaMessage) { MetaMessage origMessage = (MetaMessage) message; MetaMessage metaMessage = new MetaMessage(); try { metaMessage.setMessage(origMessage.getType(), origMessage.getData(), origMessage.getData().length); } catch (InvalidMidiDataException e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } } copiedMessage = metaMessage; } else { copiedMessage = (MidiMessage) message.clone(); } if (message instanceof MetaMessage) { if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.sendImpl(): MetaMessage.getData().length (original): " + ((MetaMessage) message).getData().length); } if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.sendImpl(): MetaMessage.getData().length (cloned): " + ((MetaMessage) copiedMessage).getData().length); } } transmitter.send(copiedMessage, lTimeStamp); } if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.sendImpl(): end"); } } /////////////////// INNER CLASSES ////////////////////////////////////// /** Receiver proxy class. * This class' objects are handed out on calls to * TMidiDevice.getReceiver(). */ public class TReceiver implements Receiver { private boolean m_bOpen; public TReceiver() { TMidiDevice.this.addReceiver(this); m_bOpen = true; } protected boolean isOpen() { return m_bOpen; } /** Receive a MidiMessage. * */ public void send(MidiMessage message, long lTimeStamp) { if (TDebug.TraceMidiDevice) { TDebug.out("TMidiDevice.TReceiver.send(): message " + message); } if (m_bOpen) { TMidiDevice.this.receive(message, lTimeStamp); } else { throw new IllegalStateException("receiver is not open"); } } /** Closes the receiver. * After a receiver has been closed, it does no longer * propagate MidiMessages to its associated MidiDevice. */ public void close() { TMidiDevice.this.removeReceiver(this); m_bOpen = false; } } public class TTransmitter implements Transmitter { private boolean m_bOpen; private Receiver m_receiver; public TTransmitter() { m_bOpen = true; TMidiDevice.this.addTransmitter(this); } public void setReceiver(Receiver receiver) { synchronized (this) { m_receiver = receiver; } } public Receiver getReceiver() { return m_receiver; } public void send(MidiMessage message, long lTimeStamp) { if (getReceiver() != null && m_bOpen) { getReceiver().send(message, lTimeStamp); } } /** Closes the transmitter. * After a transmitter has been closed, it no longer * passes MidiMessages to a Receiver previously set for * it. */ public void close() { TMidiDevice.this.removeTransmitter(this); m_bOpen = false; /* Previously, this method just set m_receiver to null instead of maintaining an open flag. This allows to exploit the behaviour of calling close(), the setReceiver() again, and the Transmitter is "reopened". TODO: write a test case for this scenario. */ } } /* * This is needed only because MidiDevice.Info's * constructor is protected (in the Sun jdk1.3). */ public static class Info extends MidiDevice.Info { public Info(String a, String b, String c, String d) { super(a, b, c, d); } } } /*** TMidiDevice.java ***/