/* Nord Modular Midi Protocol 3.03 Library Copyright (C) 2003-2006 Marcus Andersson This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package net.sf.nmedit.jnmprotocol; import java.awt.EventQueue; import java.util.List; import java.util.concurrent.locks.ReentrantLock; import javax.sound.midi.Receiver; import javax.sound.midi.Transmitter; import net.sf.nmedit.jnmprotocol.AbstractNmProtocol; import net.sf.nmedit.jnmprotocol.EnqueuedPacket; import net.sf.nmedit.jnmprotocol.MessageHandler; import net.sf.nmedit.jnmprotocol.MidiException; import net.sf.nmedit.jnmprotocol.MidiMessage; import net.sf.nmedit.jnmprotocol.utils.QueueBuffer; import net.sf.nmedit.jpdl.BitStream; /** * Receives and sends javax.sound.midi.MidiMessage. * Incoming javax.sound.midi.MidiMessages are * transformed to net.sf.nmedit.jnmprotocol.MidiMessages. * * This class is thread safe. */ public abstract class AbstractNmProtocol { // empty byte array private static final byte[] NO_BYTES = new byte[0]; // lock for getLock().wait() / getLock().notify() / waitForActivity() calls private final Object lock = new Object(); // lock for the sendQueue private final Object sendLock = new Object(); // lock for the receivedQueue private final Object receiveLock = new Object(); // lock for the event queue private final Object eventsLock = new Object(); // lock for heartbeat() calls private ReentrantLock heartbeatLock = new ReentrantLock(false); // queue containing the outgoing packets private QueueBuffer<EnqueuedPacket> sendQueue = new QueueBuffer<EnqueuedPacket>(); // queue containing the data of incoming javax.sound.midi.MidiMessages private QueueBuffer<byte[]> receivedQueue = new QueueBuffer<byte[]>(); // queue containing incoming net.sf.nmedit.jnmprotocol.MidiMessages private QueueBuffer<MidiMessage> eventQueue = new QueueBuffer<MidiMessage>(); // remembers when activity() was called the last time private volatile long recentActivity = 0; // transmitter (output) private Transmitter transmitter = new ProtocolTransmitter(); // receiver (input) private Receiver receiver = new ProtocolReceiver(this); // message handler to process messages from the event queue private MessageHandler messageHandler; public AbstractNmProtocol() { super(); } /** * Clears all message queues. */ public void reset() { synchronized (sendLock) { sendQueue.clear(); } synchronized (receiveLock) { receivedQueue.clear(); } synchronized (eventsLock) { eventQueue.clear(); } } /** * Sets the message handler. * @param messageHandler the message handler */ public synchronized void setMessageHandler(MessageHandler messageHandler) { this.messageHandler = messageHandler; } /** * Returns the message handler. * @return the message handler */ public synchronized MessageHandler getMessageHandler() { return messageHandler; } /** * Returns the current timeout value. * @return the current timeout value */ protected long getTimeout() { return 0; } /** * Processes all pending messages and dispatches incoming events. * * If heartbeat() is invoked while another thread holds * the lock then heartbeat() returns immediatelly. * * @throws MidiException a midi exception occured */ public final void heartbeat() throws MidiException { // return when heartbeat() is invoked recursively if (heartbeatLock.isHeldByCurrentThread()) return; // the reentrant lock is not necessary but // it avoids that invokations of heartbeat() // from multiple thread block each other unecessarily // see if the lock is held by another thread // if so then the heartbeat implementation is not called if (heartbeatLock.tryLock()) { // no other thread holds the lock try { // call the heartbeat implementation heartbeatImpl(); } finally { // release the lock - event if an exception occured heartbeatLock.unlock(); } } } /** * Processes all pending messages and dispatches incoming events. * * @throws MidiException a midi exception occured */ protected abstract void heartbeatImpl() throws MidiException; public final void waitForActivity() { waitForActivity(0); } /** * Waits at most 'timeout' milliseconds until some activity is observed, * an InterrupedException is thrown due to another reason or * one of the expected reply messages timeout is reached. * * Activity is observed when {@link #activity()} was called. * * @param timeout in milliseconds * @see #activity() */ public final void waitForActivity(long timeout) { // enssure that we do not wait longer than the reply message timeout long msgtimeout = getTimeout(); // reply message timeout is set ... if (msgtimeout>0) { // adjust timeout if necessary if (timeout == 0 || timeout > msgtimeout) timeout = msgtimeout; } // wait try { synchronized (getLock()) { getLock().wait(timeout); } } catch (InterruptedException e) { // caused by activity() or due to another reason } } public final Object getLock() { return lock; } /** * Sets the {@link #getRecentActivity() recent activity time} * and notifies all threads waiting on the {@link #getLock() lock}. */ public void activity() { recentActivity = System.currentTimeMillis(); synchronized (getLock()) { getLock().notify(); getLock().notifyAll(); } } /** * Returns the recent activity time in milliseconds. * The value is set each time {@link #activity()} is invoked. * @return recent activity */ public long getRecentActivity() { return recentActivity; } /** * Removes a message from the send queue. * @throws NoSuchElementException if this send queue is empty. */ protected void removeFromSendQueue() { synchronized (sendLock) { sendQueue.remove(); } } /** * Returns true if the send queue is empty. * @return true if the send queue is empty */ protected boolean isSendQueueEmpty() { synchronized (sendLock) { return sendQueue.isEmpty(); } } /** * Returns the next message in the send queue or null if the sendqueue is empty. * @return the next message in the send queue or null if the sendqueue is empty. */ protected EnqueuedPacket peekSendQueue() { synchronized (sendLock) { return sendQueue.peek(); } } /** * Clears the send queue. */ protected void clearSendQueue() { synchronized(sendLock) { sendQueue.clear(); } } /** * Removes and returns the next message in the received queue. * If the queue was empty then an empty byte array is returned. * @return the next message int the received queue */ protected byte[] getReceivedBytes() { byte[] data; synchronized(receiveLock) { data = receivedQueue.poll(); } return (data != null) ? data : NO_BYTES; } /** * Sends a javax.sound.midi.MidiMessage to the transmitter/device. * @param message the message */ protected void send(javax.sound.midi.MidiMessage message) { transmitter.getReceiver().send(message, -1); } public void send(MidiMessage midiMessage) throws MidiException { List<BitStream> list = midiMessage.getBitStream(); if (list.isEmpty()) return; if (list.size()==1) { BitStream bs = list.get(0); EnqueuedPacket packet = EnqueuedPacket.create(bs.toByteArray(), midiMessage.expectsReply()); synchronized (sendLock) { sendQueue.offer(packet); } } else if (!list.isEmpty()) { QueueBuffer<EnqueuedPacket> packetList = new QueueBuffer<EnqueuedPacket>(); for (BitStream bitStream: list) packetList.add(EnqueuedPacket.create(bitStream.toByteArray(), midiMessage.expectsReply())); synchronized (sendLock) { // O(1) complexity sendQueue.offerAll(packetList); } } else { return; } activity(); } /** * Data of a javax.sound.midi.MidiMessage received by the receiver. * @param data midi message */ protected void received(byte[] data) { synchronized (receiveLock) { receivedQueue.offer(data); } activity(); } /** * Adds an incoming midi message to the event queue. * The messages will be passed to the {@link #getMessageHandler() message handler} * by {@link #dispatchEvents()}. * @param message incoming midi message */ protected void eventQueue_offer(MidiMessage message) { synchronized (eventsLock) { eventQueue.offer(message); } } /** * Returns the transmitter. * @return the transmitter */ public Transmitter getTransmitter() { return transmitter; } /** * Returns the receiver. * @return the receiver */ public Receiver getReceiver() { return receiver; } /** * Removes and returns all events which are currently in the event queue. * If the event queue is empty, then null is returned. * @return events */ private QueueBuffer<MidiMessage> releaseEvents() { synchronized (eventsLock) { if (!eventQueue.isEmpty()) return eventQueue.release(); } return null; } /** * Dispatches the events returned by {@link #releaseEvents()} * in the current thread. */ public void dispatchEventsImmediatelly() { QueueBuffer<MidiMessage> events = releaseEvents(); if (events != null) dispatchEvents(events); } /** * Dispatches the events on the AWT event dispatch thread. */ public void dispatchEvents() { QueueBuffer<MidiMessage> events = releaseEvents(); if (events == null) return; if (EventQueue.isDispatchThread()) { // we are in the AWT event dispatch thread dispatchEvents(events); } else { // post the event to the AWT event dispatch thread EventQueue.invokeLater(new DispatchLater(events)); } } /** * Dispatches the events. * @param events the events */ protected void dispatchEvents(QueueBuffer<MidiMessage> events) { MessageHandler mh = getMessageHandler(); if (mh == null) return; for (MidiMessage message: events) { mh.processMessage(message); } } private class DispatchLater implements Runnable { private QueueBuffer<MidiMessage> events; public DispatchLater(QueueBuffer<MidiMessage> events) { this.events = events; } public void run() { dispatchEvents(events); } } private static class ProtocolReceiver implements Receiver { // private boolean closed = false; private AbstractNmProtocol receiver; public ProtocolReceiver(AbstractNmProtocol receiver) { this.receiver = receiver; } public void send(javax.sound.midi.MidiMessage message, long timeStamp) {/* if (closed) throw new IllegalStateException("receiver closed");*/ receiver.received(message.getMessage()); } public void close() { // closed = true; } } /** * The transmitter */ private static class ProtocolTransmitter implements Transmitter { private Receiver receiver; public synchronized void setReceiver( Receiver receiver ) { this.receiver = receiver; } public synchronized Receiver getReceiver() { return receiver; } public void close() { // no op } } }