/* * AlsaMidiIn.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.InvalidMidiDataException; import javax.sound.midi.MetaMessage; import javax.sound.midi.MidiEvent; import javax.sound.midi.MidiMessage; import javax.sound.midi.ShortMessage; import javax.sound.midi.SysexMessage; import org.tritonus.share.TDebug; import org.tritonus.lowlevel.alsa.AlsaSeq; import org.tritonus.lowlevel.alsa.AlsaSeqEvent; import org.tritonus.lowlevel.alsa.AlsaSeqPortSubscribe; /** Handles input from an ALSA port. */ public class AlsaMidiIn extends Thread { /** ALSA client used to receive events. */ private AlsaSeq m_alsaSeq; /** ALSA port number (belonging to the client represented be m_alsaSeq) used to receive events. */ private int m_nDestPort; /** ALSA client number to subscribe to to receive events. */ private int m_nSourceClient; /** ALSA port number (belonging to m_nSourceClient) to subscribe to to receive events. */ private int m_nSourcePort; private AlsaMidiInListener m_listener; private AlsaSeqEvent m_event = new AlsaSeqEvent(); // used to query event for detailed information private int[] m_anValues = new int[5]; private long[] m_alValues = new long[1]; /** Receives events without timestamping them. Does establish a subscription where events are routed directely (not getting a timestamp). @param alsaSeq The client that should be used to receive events. @param nDestPort The port number that should be used to receive events. This port has to exist on the client represented by alsaSeq. @param nSourceClient The client number that should be listened to. This and nSourcePort must exist prior to calling this constructor. The port has to allow read subscriptions. @param nSourcePort The port number that should be listened to. This and nSourceClient must exist prior to calling this constructor. The port has to allow read subscriptions. @param listener The listener that should receive the MidiMessage objects created here from received events. */ public AlsaMidiIn(AlsaSeq alsaSeq, int nDestPort, int nSourceClient, int nSourcePort, AlsaMidiInListener listener) { this(alsaSeq, nDestPort, nSourceClient, nSourcePort, -1, false, // signals: do not do timestamping listener); } /** Does establish a subscription where events are routed through a queue to get a timestamp. */ public AlsaMidiIn(AlsaSeq alsaSeq, int nDestPort, int nSourceClient, int nSourcePort, int nTimestampingQueue, boolean bRealtime, AlsaMidiInListener listener) { m_nSourceClient = nSourceClient; m_nSourcePort = nSourcePort; m_listener = listener; m_alsaSeq = alsaSeq; m_nDestPort = nDestPort; if (nTimestampingQueue >= 0) { AlsaSeqPortSubscribe portSubscribe = new AlsaSeqPortSubscribe(); portSubscribe.setSender(nSourceClient, nSourcePort); portSubscribe.setDest(getAlsaSeq().getClientId(), nDestPort); portSubscribe.setQueue(nTimestampingQueue); portSubscribe.setExclusive(false); portSubscribe.setTimeUpdate(true); portSubscribe.setTimeReal(bRealtime); getAlsaSeq().subscribePort(portSubscribe); portSubscribe.free(); } else { AlsaSeqPortSubscribe portSubscribe = new AlsaSeqPortSubscribe(); portSubscribe.setSender(nSourceClient, nSourcePort); portSubscribe.setDest(getAlsaSeq().getClientId(), nDestPort); getAlsaSeq().subscribePort(portSubscribe); portSubscribe.free(); } setDaemon(true); } private AlsaSeq getAlsaSeq() { return m_alsaSeq; } /** The working part of the class. Here, the thread repeats in blocking in a call to getEvent() and calling the listener's dequeueEvent() method. */ public void run() { // TODO: recheck interupt mechanism while (!interrupted()) { MidiEvent event = getEvent(); if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.run(): got event: " + event); } if (event != null) { MidiMessage message = event.getMessage(); long lTimestamp = event.getTick(); if (message instanceof MetaMessage) { MetaMessage me = (MetaMessage) message; if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.run(): MetaMessage.getData().length: " + me.getData().length); } } m_listener.dequeueEvent(message, lTimestamp); } else { if (TDebug.TraceAlsaMidiIn || TDebug.TraceAllWarnings) { TDebug.out("AlsaMidiIn.run(): received null from getEvent()"); } } } } private MidiEvent getEvent() { if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): before eventInput()"); } while (true) { int nReturn = getAlsaSeq().eventInput(m_event); if (nReturn >= 0) { break; } /* * Sleep for 1 ms to enable scheduling. */ if (TDebug.TraceAlsaMidiIn || TDebug.TraceAllWarnings) { TDebug.out("AlsaMidiIn.getEvent(): sleeping because got no event"); } try { Thread.sleep(1); } catch (InterruptedException e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } } } MidiMessage message = null; int nType = m_event.getType(); switch (nType) { case AlsaSeq.SND_SEQ_EVENT_NOTEON: case AlsaSeq.SND_SEQ_EVENT_NOTEOFF: case AlsaSeq.SND_SEQ_EVENT_KEYPRESS: { if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): note/aftertouch event"); } m_event.getNote(m_anValues); ShortMessage shortMessage = new ShortMessage(); int nCommand = -1; switch (nType) { case AlsaSeq.SND_SEQ_EVENT_NOTEON: nCommand = ShortMessage.NOTE_ON; break; case AlsaSeq.SND_SEQ_EVENT_NOTEOFF: nCommand = ShortMessage.NOTE_OFF; break; case AlsaSeq.SND_SEQ_EVENT_KEYPRESS: nCommand = ShortMessage.POLY_PRESSURE; break; } int nChannel = m_anValues[0] & 0xF; int nKey = m_anValues[1] & 0x7F; int nVelocity = m_anValues[2] & 0x7F; try { shortMessage.setMessage(nCommand, nChannel, nKey, nVelocity); } catch (InvalidMidiDataException e) { if (TDebug.TraceAlsaMidiIn || TDebug.TraceAllExceptions) { TDebug.out(e); } } message = shortMessage; break; } // all event types that use snd_seq_ev_ctrl_t // TODO: more case AlsaSeq.SND_SEQ_EVENT_CONTROLLER: case AlsaSeq.SND_SEQ_EVENT_PGMCHANGE: case AlsaSeq.SND_SEQ_EVENT_CHANPRESS: case AlsaSeq.SND_SEQ_EVENT_PITCHBEND: case AlsaSeq.SND_SEQ_EVENT_QFRAME: case AlsaSeq.SND_SEQ_EVENT_SONGPOS: case AlsaSeq.SND_SEQ_EVENT_SONGSEL: { m_event.getControl(m_anValues); int nCommand = -1; int nChannel = m_anValues[0] & 0xF; int nData1 = -1; int nData2 = -1; switch (nType) { case AlsaSeq.SND_SEQ_EVENT_CONTROLLER: if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): controller event"); } nCommand = ShortMessage.CONTROL_CHANGE; nData1 = m_anValues[1] & 0x7F; nData2 = m_anValues[2] & 0x7F; break; case AlsaSeq.SND_SEQ_EVENT_PGMCHANGE: if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): program change event"); } nCommand = ShortMessage.PROGRAM_CHANGE; nData1 = m_anValues[2] & 0x7F; nData2 = 0; break; case AlsaSeq.SND_SEQ_EVENT_CHANPRESS: if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): channel pressure event"); } nCommand = ShortMessage.CHANNEL_PRESSURE; nData1 = m_anValues[2] & 0x7F; nData2 = 0; break; case AlsaSeq.SND_SEQ_EVENT_PITCHBEND: if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): pitchbend event"); } nCommand = ShortMessage.PITCH_BEND; nData1 = m_anValues[2] & 0x7F; nData2 = (m_anValues[2] >> 7) & 0x7F; break; case AlsaSeq.SND_SEQ_EVENT_QFRAME: if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): MTC event"); } nCommand = ShortMessage.MIDI_TIME_CODE; nData1 = m_anValues[2] & 0x7F; nData2 = 0; break; case AlsaSeq.SND_SEQ_EVENT_SONGPOS: if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): song position event"); } nCommand = ShortMessage.SONG_POSITION_POINTER; nData1 = m_anValues[2] & 0x7F; nData2 = (m_anValues[2] >> 7) & 0x7F; break; case AlsaSeq.SND_SEQ_EVENT_SONGSEL: if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): song select event"); } nCommand = ShortMessage.SONG_SELECT; nData1 = m_anValues[2] & 0x7F; nData2 = 0; break; } ShortMessage shortMessage = new ShortMessage(); try { shortMessage.setMessage(nCommand, nChannel, nData1, nData2); } catch (InvalidMidiDataException e) { if (TDebug.TraceAlsaMidiIn || TDebug.TraceAllExceptions) { TDebug.out(e); } } message = shortMessage; } break; // status-only events case AlsaSeq.SND_SEQ_EVENT_TUNE_REQUEST: case AlsaSeq.SND_SEQ_EVENT_CLOCK: case AlsaSeq.SND_SEQ_EVENT_START: case AlsaSeq.SND_SEQ_EVENT_CONTINUE: case AlsaSeq.SND_SEQ_EVENT_STOP: case AlsaSeq.SND_SEQ_EVENT_SENSING: case AlsaSeq.SND_SEQ_EVENT_RESET: { int nStatus = -1; switch (nType) { case AlsaSeq.SND_SEQ_EVENT_TUNE_REQUEST: nStatus = ShortMessage.TUNE_REQUEST; break; case AlsaSeq.SND_SEQ_EVENT_CLOCK: if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): clock event"); } nStatus = ShortMessage.TIMING_CLOCK; break; case AlsaSeq.SND_SEQ_EVENT_START: nStatus = ShortMessage.START; break; case AlsaSeq.SND_SEQ_EVENT_CONTINUE: nStatus = ShortMessage.CONTINUE; break; case AlsaSeq.SND_SEQ_EVENT_STOP: nStatus = ShortMessage.STOP; break; case AlsaSeq.SND_SEQ_EVENT_SENSING: nStatus = ShortMessage.ACTIVE_SENSING; break; case AlsaSeq.SND_SEQ_EVENT_RESET: nStatus = ShortMessage.SYSTEM_RESET; break; } ShortMessage shortMessage = new ShortMessage(); try { shortMessage.setMessage(nStatus); } catch (InvalidMidiDataException e) { if (TDebug.TraceAlsaMidiIn || TDebug.TraceAllExceptions) { TDebug.out(e); } } message = shortMessage; break; } case AlsaSeq.SND_SEQ_EVENT_USR_VAR4: { if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): meta event"); } MetaMessage metaMessage = new MetaMessage(); byte[] abTransferData = m_event.getVar(); int nMetaType = abTransferData[0]; byte[] abData = new byte[abTransferData.length - 1]; System.arraycopy(abTransferData, 1, abData, 0, abTransferData.length - 1); try { metaMessage.setMessage(nMetaType, abData, abData.length); } catch (InvalidMidiDataException e) { if (TDebug.TraceAlsaMidiIn || TDebug.TraceAllExceptions) { TDebug.out(e); } } message = metaMessage; break; } case AlsaSeq.SND_SEQ_EVENT_SYSEX: { if (TDebug.TraceAlsaMidiIn) { TDebug.out("AlsaMidiIn.getEvent(): sysex event"); } SysexMessage sysexMessage = new SysexMessage(); byte[] abData = m_event.getVar(); try { sysexMessage.setMessage(abData, abData.length); } catch (InvalidMidiDataException e) { if (TDebug.TraceAlsaMidiIn || TDebug.TraceAllExceptions) { TDebug.out(e); } } message = sysexMessage; break; } default: if (TDebug.TraceAlsaMidiIn || TDebug.TraceAllWarnings) { TDebug.out("AlsaMidiIn.getEvent(): unknown event"); } } if (message != null) { /* If the timestamp is in ticks, ticks in the MidiEvent gets this value. Otherwise, if the timestamp is in realtime (ns), we put us in the tick value. */ long lTimestamp = m_event.getTimestamp(); if ((m_event.getFlags() & AlsaSeq.SND_SEQ_TIME_STAMP_MASK) == AlsaSeq.SND_SEQ_TIME_STAMP_REAL) { // ns -> us lTimestamp /= 1000; } MidiEvent event = new MidiEvent(message, lTimestamp); return event; } else { return null; } } /** */ public static interface AlsaMidiInListener { public void dequeueEvent(MidiMessage message, long lTimestamp); } } /*** AlsaMidiIn.java ***/