| /* |
| * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code 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 General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.sun.media.sound; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import javax.sound.midi.InvalidMidiDataException; |
| import javax.sound.midi.MidiDevice; |
| import javax.sound.midi.MidiDeviceReceiver; |
| import javax.sound.midi.MidiDeviceTransmitter; |
| import javax.sound.midi.MidiMessage; |
| import javax.sound.midi.MidiUnavailableException; |
| import javax.sound.midi.Receiver; |
| import javax.sound.midi.Transmitter; |
| |
| |
| /** |
| * Abstract AbstractMidiDevice class representing functionality shared by |
| * MidiInDevice and MidiOutDevice objects. |
| * |
| * @author David Rivas |
| * @author Kara Kytle |
| * @author Matthias Pfisterer |
| * @author Florian Bomers |
| */ |
| abstract class AbstractMidiDevice implements MidiDevice, ReferenceCountingDevice { |
| |
| private static final boolean TRACE_TRANSMITTER = false; |
| |
| private ArrayList<Receiver> receiverList; |
| |
| private TransmitterList transmitterList; |
| |
| // lock to protect receiverList and transmitterList |
| // from simultaneous creation and destruction |
| // reduces possibility of deadlock, compared to |
| // synchronizing to the class instance |
| private final Object traRecLock = new Object(); |
| |
| // DEVICE ATTRIBUTES |
| |
| private final MidiDevice.Info info; |
| |
| // DEVICE STATE |
| |
| private volatile boolean open; |
| private int openRefCount; |
| |
| /** List of Receivers and Transmitters that opened the device implicitely. |
| */ |
| private List<Object> openKeepingObjects; |
| |
| /** |
| * This is the device handle returned from native code. |
| */ |
| protected volatile long id; |
| |
| /** |
| * Constructs an AbstractMidiDevice with the specified info object. |
| * @param info the description of the device |
| */ |
| /* |
| * The initial mode and only supported mode default to OMNI_ON_POLY. |
| */ |
| protected AbstractMidiDevice(MidiDevice.Info info) { |
| |
| if(Printer.trace) Printer.trace(">> AbstractMidiDevice CONSTRUCTOR"); |
| |
| this.info = info; |
| openRefCount = 0; |
| |
| if(Printer.trace) Printer.trace("<< AbstractMidiDevice CONSTRUCTOR completed"); |
| } |
| |
| // MIDI DEVICE METHODS |
| |
| @Override |
| public final MidiDevice.Info getDeviceInfo() { |
| return info; |
| } |
| |
| /** Open the device from an application program. |
| * Setting the open reference count to -1 here prevents Transmitters and Receivers that |
| * opened the device implicitly from closing it. The only way to close the device after |
| * this call is a call to close(). |
| */ |
| @Override |
| public final void open() throws MidiUnavailableException { |
| if (Printer.trace) Printer.trace("> AbstractMidiDevice: open()"); |
| synchronized(this) { |
| openRefCount = -1; |
| doOpen(); |
| } |
| if (Printer.trace) Printer.trace("< AbstractMidiDevice: open() completed"); |
| } |
| |
| /** Open the device implicitly. |
| * This method is intended to be used by AbstractReceiver |
| * and BasicTransmitter. Actually, it is called by getReceiverReferenceCounting() and |
| * getTransmitterReferenceCounting(). These, in turn, are called by MidiSytem on calls to |
| * getReceiver() and getTransmitter(). The former methods should pass the Receiver or |
| * Transmitter just created as the object parameter to this method. Storing references to |
| * these objects is necessary to be able to decide later (when it comes to closing) if |
| * R/T's are ones that opened the device implicitly. |
| * |
| * @object The Receiver or Transmitter instance that triggered this implicit open. |
| */ |
| private void openInternal(Object object) throws MidiUnavailableException { |
| if (Printer.trace) Printer.trace("> AbstractMidiDevice: openInternal()"); |
| synchronized(this) { |
| if (openRefCount != -1) { |
| openRefCount++; |
| getOpenKeepingObjects().add(object); |
| } |
| // double calls to doOpens() will be catched by the open flag. |
| doOpen(); |
| } |
| if (Printer.trace) Printer.trace("< AbstractMidiDevice: openInternal() completed"); |
| } |
| |
| private void doOpen() throws MidiUnavailableException { |
| if (Printer.trace) Printer.trace("> AbstractMidiDevice: doOpen()"); |
| synchronized(this) { |
| if (! isOpen()) { |
| implOpen(); |
| open = true; |
| } |
| } |
| if (Printer.trace) Printer.trace("< AbstractMidiDevice: doOpen() completed"); |
| } |
| |
| @Override |
| public final void close() { |
| if (Printer.trace) Printer.trace("> AbstractMidiDevice: close()"); |
| synchronized (this) { |
| doClose(); |
| openRefCount = 0; |
| } |
| if (Printer.trace) Printer.trace("< AbstractMidiDevice: close() completed"); |
| } |
| |
| /** Close the device for an object that implicitely opened it. |
| * This method is intended to be used by Transmitter.close() and Receiver.close(). |
| * Those methods should pass this for the object parameter. Since Transmitters or Receivers |
| * do not know if their device has been opened implicitely because of them, they call this |
| * method in any case. This method now is able to seperate Receivers/Transmitters that opened |
| * the device implicitely from those that didn't by looking up the R/T in the |
| * openKeepingObjects list. Only if the R/T is contained there, the reference count is |
| * reduced. |
| * |
| * @param object The object that might have been opening the device implicitely (for now, |
| * this may be a Transmitter or receiver). |
| */ |
| public final void closeInternal(Object object) { |
| if (Printer.trace) Printer.trace("> AbstractMidiDevice: closeInternal()"); |
| synchronized(this) { |
| if (getOpenKeepingObjects().remove(object)) { |
| if (openRefCount > 0) { |
| openRefCount--; |
| if (openRefCount == 0) { |
| doClose(); |
| } |
| } |
| } |
| } |
| if (Printer.trace) Printer.trace("< AbstractMidiDevice: closeInternal() completed"); |
| } |
| |
| public final void doClose() { |
| if (Printer.trace) Printer.trace("> AbstractMidiDevice: doClose()"); |
| synchronized(this) { |
| if (isOpen()) { |
| implClose(); |
| open = false; |
| } |
| } |
| if (Printer.trace) Printer.trace("< AbstractMidiDevice: doClose() completed"); |
| } |
| |
| @Override |
| public final boolean isOpen() { |
| return open; |
| } |
| |
| protected void implClose() { |
| synchronized (traRecLock) { |
| if (receiverList != null) { |
| // close all receivers |
| for(int i = 0; i < receiverList.size(); i++) { |
| receiverList.get(i).close(); |
| } |
| receiverList.clear(); |
| } |
| if (transmitterList != null) { |
| // close all transmitters |
| transmitterList.close(); |
| } |
| } |
| } |
| |
| /** |
| * This implementation always returns -1. |
| * Devices that actually provide this should over-ride |
| * this method. |
| */ |
| @Override |
| public long getMicrosecondPosition() { |
| return -1; |
| } |
| |
| /** Return the maximum number of Receivers supported by this device. |
| Depending on the return value of hasReceivers(), this method returns either 0 or -1. |
| Subclasses should rather override hasReceivers() than override this method. |
| */ |
| @Override |
| public final int getMaxReceivers() { |
| if (hasReceivers()) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| /** Return the maximum number of Transmitters supported by this device. |
| Depending on the return value of hasTransmitters(), this method returns either 0 or -1. |
| Subclasses should override hasTransmitters(). |
| */ |
| @Override |
| public final int getMaxTransmitters() { |
| if (hasTransmitters()) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| /** Retrieve a Receiver for this device. |
| This method returns the value returned by createReceiver(), if it doesn't throw |
| an exception. Subclasses should rather override createReceiver() than override |
| this method. |
| If createReceiver returns a Receiver, it is added to the internal list |
| of Receivers (see getReceiversList) |
| */ |
| @Override |
| public final Receiver getReceiver() throws MidiUnavailableException { |
| Receiver receiver; |
| synchronized (traRecLock) { |
| receiver = createReceiver(); // may throw MidiUnavailableException |
| getReceiverList().add(receiver); |
| } |
| return receiver; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") // Cast of result of clone |
| public final List<Receiver> getReceivers() { |
| List<Receiver> recs; |
| synchronized (traRecLock) { |
| if (receiverList == null) { |
| recs = Collections.unmodifiableList(new ArrayList<Receiver>(0)); |
| } else { |
| recs = Collections.unmodifiableList |
| ((List<Receiver>) (receiverList.clone())); |
| } |
| } |
| return recs; |
| } |
| |
| /** |
| * This implementation uses createTransmitter, which may throw an exception. |
| * If a transmitter is returned in createTransmitter, it is added to the internal |
| * TransmitterList |
| */ |
| @Override |
| public final Transmitter getTransmitter() throws MidiUnavailableException { |
| Transmitter transmitter; |
| synchronized (traRecLock) { |
| transmitter = createTransmitter(); // may throw MidiUnavailableException |
| getTransmitterList().add(transmitter); |
| } |
| return transmitter; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") // Cast of result of clone |
| public final List<Transmitter> getTransmitters() { |
| List<Transmitter> tras; |
| synchronized (traRecLock) { |
| if (transmitterList == null |
| || transmitterList.transmitters.size() == 0) { |
| tras = Collections.unmodifiableList(new ArrayList<Transmitter>(0)); |
| } else { |
| tras = Collections.unmodifiableList((List<Transmitter>) (transmitterList.transmitters.clone())); |
| } |
| } |
| return tras; |
| } |
| |
| final long getId() { |
| return id; |
| } |
| |
| // REFERENCE COUNTING |
| |
| /** Retrieve a Receiver and open the device implicitly. |
| This method is called by MidiSystem.getReceiver(). |
| */ |
| @Override |
| public final Receiver getReceiverReferenceCounting() |
| throws MidiUnavailableException { |
| /* Keep this order of commands! If getReceiver() throws an exception, |
| openInternal() should not be called! |
| */ |
| Receiver receiver; |
| synchronized (traRecLock) { |
| receiver = getReceiver(); |
| AbstractMidiDevice.this.openInternal(receiver); |
| } |
| return receiver; |
| } |
| |
| /** Retrieve a Transmitter and open the device implicitly. |
| This method is called by MidiSystem.getTransmitter(). |
| */ |
| @Override |
| public final Transmitter getTransmitterReferenceCounting() |
| throws MidiUnavailableException { |
| /* Keep this order of commands! If getTransmitter() throws an exception, |
| openInternal() should not be called! |
| */ |
| Transmitter transmitter; |
| synchronized (traRecLock) { |
| transmitter = getTransmitter(); |
| AbstractMidiDevice.this.openInternal(transmitter); |
| } |
| return transmitter; |
| } |
| |
| /** Return the list of objects that have opened the device implicitely. |
| */ |
| private synchronized List<Object> getOpenKeepingObjects() { |
| if (openKeepingObjects == null) { |
| openKeepingObjects = new ArrayList<>(); |
| } |
| return openKeepingObjects; |
| } |
| |
| // RECEIVER HANDLING METHODS |
| |
| /** Return the internal list of Receivers, possibly creating it first. |
| */ |
| private List<Receiver> getReceiverList() { |
| synchronized (traRecLock) { |
| if (receiverList == null) { |
| receiverList = new ArrayList<>(); |
| } |
| } |
| return receiverList; |
| } |
| |
| /** Returns if this device supports Receivers. |
| Subclasses that use Receivers should override this method to |
| return true. They also should override createReceiver(). |
| |
| @return true, if the device supports Receivers, false otherwise. |
| */ |
| protected boolean hasReceivers() { |
| return false; |
| } |
| |
| /** Create a Receiver object. |
| throwing an exception here means that Receivers aren't enabled. |
| Subclasses that use Receivers should override this method with |
| one that returns objects implementing Receiver. |
| Classes overriding this method should also override hasReceivers() |
| to return true. |
| */ |
| protected Receiver createReceiver() throws MidiUnavailableException { |
| throw new MidiUnavailableException("MIDI IN receiver not available"); |
| } |
| |
| // TRANSMITTER HANDLING |
| |
| /** Return the internal list of Transmitters, possibly creating it first. |
| */ |
| final TransmitterList getTransmitterList() { |
| synchronized (traRecLock) { |
| if (transmitterList == null) { |
| transmitterList = new TransmitterList(); |
| } |
| } |
| return transmitterList; |
| } |
| |
| /** Returns if this device supports Transmitters. |
| Subclasses that use Transmitters should override this method to |
| return true. They also should override createTransmitter(). |
| |
| @return true, if the device supports Transmitters, false otherwise. |
| */ |
| protected boolean hasTransmitters() { |
| return false; |
| } |
| |
| /** Create a Transmitter object. |
| throwing an exception here means that Transmitters aren't enabled. |
| Subclasses that use Transmitters should override this method with |
| one that returns objects implementing Transmitters. |
| Classes overriding this method should also override hasTransmitters() |
| to return true. |
| */ |
| protected Transmitter createTransmitter() throws MidiUnavailableException { |
| throw new MidiUnavailableException("MIDI OUT transmitter not available"); |
| } |
| |
| protected abstract void implOpen() throws MidiUnavailableException; |
| |
| /** |
| * close this device if discarded by the garbage collector. |
| */ |
| @Override |
| @SuppressWarnings("deprecation") |
| protected final void finalize() { |
| close(); |
| } |
| |
| /** Base class for Receivers. |
| Subclasses that use Receivers must use this base class, since it |
| contains magic necessary to manage implicit closing the device. |
| This is necessary for Receivers retrieved via MidiSystem.getReceiver() |
| (which opens the device implicitely). |
| */ |
| abstract class AbstractReceiver implements MidiDeviceReceiver { |
| private volatile boolean open = true; |
| |
| |
| /** Deliver a MidiMessage. |
| This method contains magic related to the closed state of a |
| Receiver. Therefore, subclasses should not override this method. |
| Instead, they should implement implSend(). |
| */ |
| @Override |
| public final synchronized void send(final MidiMessage message, |
| final long timeStamp) { |
| if (!open) { |
| throw new IllegalStateException("Receiver is not open"); |
| } |
| implSend(message, timeStamp); |
| } |
| |
| abstract void implSend(MidiMessage message, long timeStamp); |
| |
| /** Close the Receiver. |
| * Here, the call to the magic method closeInternal() takes place. |
| * Therefore, subclasses that override this method must call |
| * 'super.close()'. |
| */ |
| @Override |
| public final void close() { |
| open = false; |
| synchronized (AbstractMidiDevice.this.traRecLock) { |
| AbstractMidiDevice.this.getReceiverList().remove(this); |
| } |
| AbstractMidiDevice.this.closeInternal(this); |
| } |
| |
| @Override |
| public final MidiDevice getMidiDevice() { |
| return AbstractMidiDevice.this; |
| } |
| |
| final boolean isOpen() { |
| return open; |
| } |
| |
| //$$fb is that a good idea? |
| //protected void finalize() { |
| // close(); |
| //} |
| |
| } // class AbstractReceiver |
| |
| |
| /** |
| * Transmitter base class. |
| * This class especially makes sure the device is closed if it |
| * has been opened implicitly by a call to MidiSystem.getTransmitter(). |
| * The logic of doing so is actually in closeInternal(). |
| * |
| * Also, it has some optimizations regarding sending to the Receivers, |
| * for known Receivers, and managing itself in the TransmitterList. |
| */ |
| class BasicTransmitter implements MidiDeviceTransmitter { |
| |
| private Receiver receiver = null; |
| TransmitterList tlist = null; |
| |
| protected BasicTransmitter() { |
| } |
| |
| private void setTransmitterList(TransmitterList tlist) { |
| this.tlist = tlist; |
| } |
| |
| @Override |
| public final void setReceiver(Receiver receiver) { |
| if (tlist != null && this.receiver != receiver) { |
| if (Printer.debug) Printer.debug("Transmitter "+toString()+": set receiver "+receiver); |
| tlist.receiverChanged(this, this.receiver, receiver); |
| this.receiver = receiver; |
| } |
| } |
| |
| @Override |
| public final Receiver getReceiver() { |
| return receiver; |
| } |
| |
| /** Close the Transmitter. |
| * Here, the call to the magic method closeInternal() takes place. |
| * Therefore, subclasses that override this method must call |
| * 'super.close()'. |
| */ |
| @Override |
| public final void close() { |
| AbstractMidiDevice.this.closeInternal(this); |
| if (tlist != null) { |
| tlist.receiverChanged(this, this.receiver, null); |
| tlist.remove(this); |
| tlist = null; |
| } |
| } |
| |
| @Override |
| public final MidiDevice getMidiDevice() { |
| return AbstractMidiDevice.this; |
| } |
| |
| } // class BasicTransmitter |
| |
| /** |
| * a class to manage a list of transmitters. |
| */ |
| final class TransmitterList { |
| |
| private final ArrayList<Transmitter> transmitters = new ArrayList<>(); |
| private MidiOutDevice.MidiOutReceiver midiOutReceiver; |
| |
| // how many transmitters must be present for optimized |
| // handling |
| private int optimizedReceiverCount = 0; |
| |
| |
| private void add(Transmitter t) { |
| synchronized(transmitters) { |
| transmitters.add(t); |
| } |
| if (t instanceof BasicTransmitter) { |
| ((BasicTransmitter) t).setTransmitterList(this); |
| } |
| if (Printer.debug) Printer.debug("--added transmitter "+t); |
| } |
| |
| private void remove(Transmitter t) { |
| synchronized(transmitters) { |
| int index = transmitters.indexOf(t); |
| if (index >= 0) { |
| transmitters.remove(index); |
| if (Printer.debug) Printer.debug("--removed transmitter "+t); |
| } |
| } |
| } |
| |
| private void receiverChanged(BasicTransmitter t, |
| Receiver oldR, |
| Receiver newR) { |
| synchronized(transmitters) { |
| // some optimization |
| if (midiOutReceiver == oldR) { |
| midiOutReceiver = null; |
| } |
| if (newR != null) { |
| if ((newR instanceof MidiOutDevice.MidiOutReceiver) |
| && (midiOutReceiver == null)) { |
| midiOutReceiver = ((MidiOutDevice.MidiOutReceiver) newR); |
| } |
| } |
| optimizedReceiverCount = |
| ((midiOutReceiver!=null)?1:0); |
| } |
| // more potential for optimization here |
| } |
| |
| |
| /** closes all transmitters and empties the list */ |
| void close() { |
| synchronized (transmitters) { |
| for(int i = 0; i < transmitters.size(); i++) { |
| transmitters.get(i).close(); |
| } |
| transmitters.clear(); |
| } |
| if (Printer.trace) Printer.trace("TransmitterList.close() succeeded"); |
| } |
| |
| |
| |
| /** |
| * Send this message to all receivers |
| * status = packedMessage & 0xFF |
| * data1 = (packedMessage & 0xFF00) >> 8; |
| * data1 = (packedMessage & 0xFF0000) >> 16; |
| */ |
| void sendMessage(int packedMessage, long timeStamp) { |
| try { |
| synchronized(transmitters) { |
| int size = transmitters.size(); |
| if (optimizedReceiverCount == size) { |
| if (midiOutReceiver != null) { |
| if (TRACE_TRANSMITTER) Printer.println("Sending packed message to MidiOutReceiver"); |
| midiOutReceiver.sendPackedMidiMessage(packedMessage, timeStamp); |
| } |
| } else { |
| if (TRACE_TRANSMITTER) Printer.println("Sending packed message to "+size+" transmitter's receivers"); |
| for (int i = 0; i < size; i++) { |
| Receiver receiver = transmitters.get(i).getReceiver(); |
| if (receiver != null) { |
| if (optimizedReceiverCount > 0) { |
| if (receiver instanceof MidiOutDevice.MidiOutReceiver) { |
| ((MidiOutDevice.MidiOutReceiver) receiver).sendPackedMidiMessage(packedMessage, timeStamp); |
| } else { |
| receiver.send(new FastShortMessage(packedMessage), timeStamp); |
| } |
| } else { |
| receiver.send(new FastShortMessage(packedMessage), timeStamp); |
| } |
| } |
| } |
| } |
| } |
| } catch (InvalidMidiDataException e) { |
| // this happens when invalid data comes over the wire. Ignore it. |
| } |
| } |
| |
| void sendMessage(byte[] data, long timeStamp) { |
| try { |
| synchronized(transmitters) { |
| int size = transmitters.size(); |
| if (TRACE_TRANSMITTER) Printer.println("Sending long message to "+size+" transmitter's receivers"); |
| for (int i = 0; i < size; i++) { |
| Receiver receiver = transmitters.get(i).getReceiver(); |
| if (receiver != null) { |
| //$$fb 2002-04-02: SysexMessages are mutable, so |
| // an application could change the contents of this object, |
| // or try to use the object later. So we can't get around object creation |
| // But the array need not be unique for each FastSysexMessage object, |
| // because it cannot be modified. |
| receiver.send(new FastSysexMessage(data), timeStamp); |
| } |
| } |
| } |
| } catch (InvalidMidiDataException e) { |
| // this happens when invalid data comes over the wire. Ignore it. |
| return; |
| } |
| } |
| |
| /** |
| * Send this message to all transmitters. |
| */ |
| void sendMessage(MidiMessage message, long timeStamp) { |
| if (message instanceof FastShortMessage) { |
| sendMessage(((FastShortMessage) message).getPackedMsg(), timeStamp); |
| return; |
| } |
| synchronized(transmitters) { |
| int size = transmitters.size(); |
| if (optimizedReceiverCount == size) { |
| if (midiOutReceiver != null) { |
| if (TRACE_TRANSMITTER) Printer.println("Sending MIDI message to MidiOutReceiver"); |
| midiOutReceiver.send(message, timeStamp); |
| } |
| } else { |
| if (TRACE_TRANSMITTER) Printer.println("Sending MIDI message to "+size+" transmitter's receivers"); |
| for (int i = 0; i < size; i++) { |
| Receiver receiver = transmitters.get(i).getReceiver(); |
| if (receiver != null) { |
| //$$fb 2002-04-02: ShortMessages are mutable, so |
| // an application could change the contents of this object, |
| // or try to use the object later. |
| // We violate this spec here, to avoid costly (and gc-intensive) |
| // object creation for potentially hundred of messages per second. |
| // The spec should be changed to allow Immutable MidiMessages |
| // (i.e. throws InvalidStateException or so in setMessage) |
| receiver.send(message, timeStamp); |
| } |
| } |
| } |
| } |
| } |
| } // TransmitterList |
| } |