mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-10-30 17:23:51 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			540 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			540 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package jportmidi;
 | |
| 
 | |
| /* PortMidi is a general class intended for any Java program using
 | |
|    the PortMidi library. It encapsulates JPortMidiApi with a more
 | |
|    object-oriented interface. A single PortMidi object can manage
 | |
|    up to one input stream and one output stream.
 | |
| 
 | |
|    This class is not safely callable from multiple threads. It
 | |
|    is the client's responsibility to periodically call the Poll
 | |
|    method which checks for midi input and handles it.
 | |
| */
 | |
| 
 | |
| import jportmidi.*;
 | |
| import jportmidi.JPortMidiApi.*;
 | |
| 
 | |
| public class JPortMidi {
 | |
| 
 | |
|     // timecode to send message immediately
 | |
|     public final int NOW = 0;
 | |
| 
 | |
|     // midi codes
 | |
|     public final int MIDI_NOTE_OFF = 0x80;
 | |
|     public final int MIDI_NOTE_ON = 0x90;
 | |
|     public final int CTRL_ALL_OFF = 123;
 | |
|     public final int MIDI_PITCH_BEND = 0xE0;
 | |
|     public final int MIDI_CLOCK = 0xF8;
 | |
|     public final int MIDI_CONTROL = 0xB0;
 | |
|     public final int MIDI_PROGRAM = 0xC0;
 | |
|     public final int MIDI_START = 0xFA;
 | |
|     public final int MIDI_STOP = 0xFC;
 | |
|     public final int MIDI_POLY_TOUCH = 0xA0;
 | |
|     public final int MIDI_TOUCH = 0xD0;
 | |
| 
 | |
|     // error code -- cannot refresh device list while stream is open:
 | |
|     public final int pmStreamOpen = -5000;
 | |
|     public final int pmOutputNotOpen = -4999;
 | |
| 
 | |
|     // access to JPortMidiApi is through a single, global instance
 | |
|     private static JPortMidiApi pm;
 | |
|     // a reference count tracks how many objects have it open
 | |
|     private static int pmRefCount = 0;
 | |
|     private static int openCount = 0;
 | |
| 
 | |
|     public int error; // user can check here for error codes
 | |
|     private PortMidiStream input;
 | |
|     private PortMidiStream output;
 | |
|     private PmEvent buffer;
 | |
|     protected int timestamp; // remember timestamp from incoming messages
 | |
|     protected boolean trace = false; // used to print midi msgs for debugging
 | |
|     
 | |
| 
 | |
|     public JPortMidi() throws JPortMidiException {
 | |
|         if (pmRefCount == 0) {
 | |
|             pm = new JPortMidiApi();
 | |
|             pmRefCount++;
 | |
|             System.out.println("Calling Pm_Initialize");
 | |
|             checkError(pm.Pm_Initialize());
 | |
|             System.out.println("Called Pm_Initialize");
 | |
|         }
 | |
|         buffer = new PmEvent();
 | |
|     }
 | |
|     
 | |
|     public boolean getTrace() { return trace; }
 | |
| 
 | |
|     // set the trace flag and return previous value
 | |
|     public boolean setTrace(boolean flag) {
 | |
|         boolean previous = trace;
 | |
|         trace = flag;
 | |
|         return previous;
 | |
|     }
 | |
| 
 | |
|     // WARNING: you must not call this if any devices are open
 | |
|     public void refreshDeviceLists()
 | |
|             throws JPortMidiException 
 | |
|     {
 | |
|         if (openCount > 0) {
 | |
|             throw new JPortMidiException(pmStreamOpen,
 | |
|                     "RefreshDeviceLists called while stream is open");
 | |
|         }
 | |
|         checkError(pm.Pm_Terminate());
 | |
|         checkError(pm.Pm_Initialize());
 | |
|     }
 | |
| 
 | |
|     // there is no control over when/whether this is called, but it seems
 | |
|     // to be a good idea to close things when this object is collected
 | |
|     public void finalize() {
 | |
|         if (input != null) {
 | |
|             error = pm.Pm_Close(input);
 | |
|         }
 | |
|         if (input != null) {
 | |
|             int rslt = pm.Pm_Close(output);
 | |
|             // we may lose an error code from closing output, but don't
 | |
|             // lose any real error from closing input...
 | |
|             if (error == pm.pmNoError) error = rslt;
 | |
|         }
 | |
|         pmRefCount--;
 | |
|         if (pmRefCount == 0) {
 | |
|             error = pm.Pm_Terminate();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     int checkError(int err) throws JPortMidiException
 | |
|     {
 | |
|         // note that Pm_Read and Pm_Write return positive result values 
 | |
|         // which are not errors, so compare with >=
 | |
|         if (err >= pm.pmNoError) return err;
 | |
|         if (err == pm.pmHostError) {
 | |
|             throw new JPortMidiException(err, pm.Pm_GetHostErrorText());
 | |
|         } else {
 | |
|             throw new JPortMidiException(err, pm.Pm_GetErrorText(err));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // ******** ACCESS TO TIME ***********
 | |
|     
 | |
|     public void timeStart(int resolution) throws JPortMidiException {
 | |
|         checkError(pm.Pt_TimeStart(resolution));
 | |
|     }
 | |
|         
 | |
|     public void timeStop() throws JPortMidiException {
 | |
|         checkError(pm.Pt_TimeStop());
 | |
|     }
 | |
|         
 | |
|     public int timeGet() {
 | |
|         return pm.Pt_Time();
 | |
|     }
 | |
|         
 | |
|     public boolean timeStarted() {
 | |
|         return pm.Pt_TimeStarted();
 | |
|     }
 | |
| 
 | |
|     // ******* QUERY DEVICE INFORMATION *********
 | |
|         
 | |
|     public int countDevices() throws JPortMidiException {
 | |
|         return checkError(pm.Pm_CountDevices());
 | |
|     }
 | |
| 
 | |
|     public int getDefaultInputDeviceID()  throws JPortMidiException {
 | |
|         return checkError(pm.Pm_GetDefaultInputDeviceID());
 | |
|     }
 | |
| 
 | |
|     public int getDefaultOutputDeviceID() throws JPortMidiException {
 | |
|         return checkError(pm.Pm_GetDefaultOutputDeviceID());
 | |
|     }
 | |
| 
 | |
|     public String getDeviceInterf(int i) {
 | |
|         return pm.Pm_GetDeviceInterf(i);
 | |
|     }
 | |
| 
 | |
|     public String getDeviceName(int i) {
 | |
|         return pm.Pm_GetDeviceName(i);
 | |
|     }
 | |
| 
 | |
|     public boolean getDeviceInput(int i) {
 | |
|         return pm.Pm_GetDeviceInput(i);
 | |
|     }
 | |
| 
 | |
|     public boolean getDeviceOutput(int i) {
 | |
|         return pm.Pm_GetDeviceOutput(i);
 | |
|     }
 | |
| 
 | |
|     // ********** MIDI INTERFACE ************
 | |
| 
 | |
|     public boolean isOpenInput() {
 | |
|         return input != null;
 | |
|     }
 | |
| 
 | |
|     public void openInput(int inputDevice, int bufferSize) 
 | |
|             throws JPortMidiException 
 | |
|     {
 | |
|         openInput(inputDevice, "", bufferSize);
 | |
|     }
 | |
| 
 | |
|     public void openInput(int inputDevice, String inputDriverInfo, int bufferSize) 
 | |
|             throws JPortMidiException
 | |
|     {
 | |
|         if (isOpenInput()) pm.Pm_Close(input);
 | |
|         else input = new PortMidiStream();
 | |
|         if (trace) {
 | |
|             System.out.println("openInput " + getDeviceName(inputDevice));
 | |
|         }
 | |
|         checkError(pm.Pm_OpenInput(input, inputDevice, 
 | |
|                                    inputDriverInfo, bufferSize));
 | |
|         // if no exception, then increase count of open streams
 | |
|         openCount++;
 | |
|     }
 | |
| 
 | |
|     public boolean isOpenOutput() {
 | |
|         return output != null;
 | |
|     }
 | |
| 
 | |
|     public void openOutput(int outputDevice, int bufferSize, int latency)
 | |
|             throws JPortMidiException 
 | |
|     {
 | |
|         openOutput(outputDevice, "", bufferSize, latency);
 | |
|     }
 | |
| 
 | |
|     public void openOutput(int outputDevice, String outputDriverInfo,
 | |
|             int bufferSize, int latency) throws JPortMidiException {
 | |
|         if (isOpenOutput()) pm.Pm_Close(output);
 | |
|         else output = new PortMidiStream();
 | |
|         if (trace) {
 | |
|             System.out.println("openOutput " + getDeviceName(outputDevice));
 | |
|         }
 | |
|         checkError(pm.Pm_OpenOutput(output, outputDevice, outputDriverInfo, 
 | |
|                                     bufferSize, latency));
 | |
|         // if no exception, then increase count of open streams
 | |
|         openCount++;
 | |
|     }
 | |
| 
 | |
|     public void setFilter(int filters) throws JPortMidiException {
 | |
|         if (input == null) return; // no effect if input not open
 | |
|         checkError(pm.Pm_SetFilter(input, filters));
 | |
|     }
 | |
| 
 | |
|     public void setChannelMask(int mask) throws JPortMidiException {
 | |
|         if (input == null) return; // no effect if input not open
 | |
|         checkError(pm.Pm_SetChannelMask(input, mask));
 | |
|     }
 | |
| 
 | |
|     public void abort() throws JPortMidiException {
 | |
|         if (output == null) return; // no effect if output not open
 | |
|         checkError(pm.Pm_Abort(output));
 | |
|     }
 | |
| 
 | |
|     // In keeping with the idea that this class represents an input and output,
 | |
|     // there are separate Close methods for input and output streams, avoiding
 | |
|     // the need for clients to ever deal directly with a stream object
 | |
|     public void closeInput() throws JPortMidiException {
 | |
|         if (input == null) return; // no effect if input not open
 | |
|         checkError(pm.Pm_Close(input));
 | |
|         input = null;
 | |
|         openCount--;
 | |
|     }
 | |
| 
 | |
|     public void closeOutput() throws JPortMidiException {
 | |
|         if (output == null) return; // no effect if output not open
 | |
|         checkError(pm.Pm_Close(output));
 | |
|         output = null;
 | |
|         openCount--;
 | |
|     }
 | |
| 
 | |
|     // Poll should be called by client to process input messages (if any)
 | |
|     public void poll() throws JPortMidiException {
 | |
|         if (input == null) return; // does nothing until input is opened
 | |
|         while (true) {
 | |
|             int rslt = pm.Pm_Read(input, buffer);
 | |
|             checkError(rslt);
 | |
|             if (rslt == 0) return; // no more messages
 | |
|             handleMidiIn(buffer);
 | |
|         }
 | |
|     }    
 | |
| 
 | |
|     public void writeShort(int when, int msg) throws JPortMidiException {
 | |
|         if (output == null) 
 | |
|             throw new JPortMidiException(pmOutputNotOpen, 
 | |
|                                          "Output stream not open");
 | |
|         if (trace) {
 | |
|             System.out.println("writeShort: " + Integer.toHexString(msg));
 | |
|         }
 | |
|         checkError(pm.Pm_WriteShort(output, when, msg));
 | |
|     }
 | |
| 
 | |
|     public void writeSysEx(int when, byte msg[]) throws JPortMidiException {
 | |
|         if (output == null) 
 | |
|             throw new JPortMidiException(pmOutputNotOpen, 
 | |
|                                          "Output stream not open");
 | |
|         if (trace) {
 | |
|             System.out.print("writeSysEx: ");
 | |
|             for (int i = 0; i < msg.length; i++) {
 | |
|                 System.out.print(Integer.toHexString(msg[i]));
 | |
|             }
 | |
|             System.out.print("\n");
 | |
|         }
 | |
|         checkError(pm.Pm_WriteSysEx(output, when, msg));
 | |
|     }
 | |
|     
 | |
|     public int midiChanMessage(int chan, int status, int data1, int data2) {
 | |
|         return (((data2 << 16) & 0xFF0000) |
 | |
|                 ((data1 << 8) & 0xFF00) |
 | |
|                 (status & 0xF0) |
 | |
|                 (chan & 0xF));
 | |
|     }
 | |
| 
 | |
|     public int midiMessage(int status, int data1, int data2) {
 | |
|         return ((((data2) << 16) & 0xFF0000) |
 | |
|                 (((data1) << 8) & 0xFF00) |
 | |
|                 ((status) & 0xFF));
 | |
|     }
 | |
| 
 | |
|     public void midiAllOff(int channel) throws JPortMidiException {
 | |
|         midiAllOff(channel, NOW); 
 | |
|     }
 | |
| 
 | |
|     public void midiAllOff(int chan, int when) throws JPortMidiException {
 | |
|         writeShort(when, midiChanMessage(chan, MIDI_CONTROL, CTRL_ALL_OFF, 0));
 | |
|     }
 | |
|         
 | |
|     public void midiPitchBend(int chan, int value) throws JPortMidiException {
 | |
|         midiPitchBend(chan, value, NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiPitchBend(int chan, int value, int when)
 | |
|             throws JPortMidiException {
 | |
|         writeShort(when, 
 | |
|                    midiChanMessage(chan, MIDI_PITCH_BEND, value, value >> 7));
 | |
|     }
 | |
| 
 | |
|     public void midiClock() throws JPortMidiException {
 | |
|         midiClock(NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiClock(int when) throws JPortMidiException {
 | |
|         writeShort(when, midiMessage(MIDI_CLOCK, 0, 0));
 | |
|     }
 | |
| 
 | |
|     public void midiControl(int chan, int control, int value)
 | |
|             throws JPortMidiException {
 | |
|         midiControl(chan, control, value, NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiControl(int chan, int control, int value, int when)
 | |
|             throws JPortMidiException {
 | |
|         writeShort(when, midiChanMessage(chan, MIDI_CONTROL, control, value));
 | |
|     }
 | |
| 
 | |
|     public void midiNote(int chan, int pitch, int vel)
 | |
|             throws JPortMidiException {
 | |
|         midiNote(chan, pitch, vel, NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiNote(int chan, int pitch, int vel, int when)
 | |
|             throws JPortMidiException {
 | |
|         writeShort(when, midiChanMessage(chan, MIDI_NOTE_ON, pitch, vel));
 | |
|     }
 | |
| 
 | |
|     public void midiProgram(int chan, int program)
 | |
|             throws JPortMidiException {
 | |
|         midiProgram(chan, program, NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiProgram(int chan, int program, int when)
 | |
|             throws JPortMidiException {
 | |
|         writeShort(when, midiChanMessage(chan, MIDI_PROGRAM, program, 0));
 | |
|     }
 | |
| 
 | |
|     public void midiStart()
 | |
|             throws JPortMidiException {
 | |
|         midiStart(NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiStart(int when)
 | |
|             throws JPortMidiException {
 | |
|         writeShort(when, midiMessage(MIDI_START, 0, 0));
 | |
|     }
 | |
| 
 | |
|     public void midiStop()
 | |
|             throws JPortMidiException {
 | |
|         midiStop(NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiStop(int when)
 | |
|             throws JPortMidiException {
 | |
|         writeShort(when, midiMessage(MIDI_STOP, 0, 0));
 | |
|     }
 | |
| 
 | |
|     public void midiPolyTouch(int chan, int key, int value)
 | |
|             throws JPortMidiException {
 | |
|         midiPolyTouch(chan, key, value, NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiPolyTouch(int chan, int key, int value, int when)
 | |
|             throws JPortMidiException {
 | |
|         writeShort(when, midiChanMessage(chan, MIDI_POLY_TOUCH, key, value));
 | |
|     }
 | |
| 
 | |
|     public void midiTouch(int chan, int value)
 | |
|             throws JPortMidiException {
 | |
|         midiTouch(chan, value, NOW);
 | |
|     }
 | |
| 
 | |
|     public void midiTouch(int chan, int value, int when)
 | |
|             throws JPortMidiException {
 | |
|         writeShort(when, midiChanMessage(chan, MIDI_TOUCH, value, 0));
 | |
|     }
 | |
| 
 | |
|     // ****** now we implement the message handlers ******
 | |
| 
 | |
|     // an array for incoming sysex messages that can grow. 
 | |
|     // The downside is that after getting a message, we 
 | |
| 
 | |
|     private byte sysexBuffer[] = null;
 | |
|     private int sysexBufferIndex = 0;
 | |
| 
 | |
|     void sysexBufferReset() {
 | |
|         sysexBufferIndex = 0;
 | |
|         if (sysexBuffer == null) sysexBuffer = new byte[256];
 | |
|     }
 | |
| 
 | |
|     void sysexBufferCheck() {
 | |
|         if (sysexBuffer.length < sysexBufferIndex + 4) {
 | |
|             byte bigger[] = new byte[sysexBuffer.length * 2];
 | |
|             for (int i = 0; i < sysexBufferIndex; i++) {
 | |
|                 bigger[i] = sysexBuffer[i];
 | |
|             }
 | |
|             sysexBuffer = bigger;
 | |
|         }
 | |
|         // now we have space to write some bytes
 | |
|     }
 | |
| 
 | |
|     // call this to insert Sysex and EOX status bytes
 | |
|     // call sysexBufferAppendBytes to insert anything else
 | |
|     void sysexBufferAppendStatus(byte status) {
 | |
|         sysexBuffer[sysexBufferIndex++] = status;
 | |
|     }
 | |
| 
 | |
|     void sysexBufferAppendBytes(int msg, int len) {
 | |
|         for (int i = 0; i < len; i++) {
 | |
|             byte b = (byte) msg;
 | |
|             if ((msg & 0x80) != 0) {
 | |
|                 if (b == 0xF7) { // end of sysex
 | |
|                     sysexBufferAppendStatus(b);
 | |
|                     sysex(sysexBuffer, sysexBufferIndex);
 | |
|                     return;
 | |
|                 }
 | |
|                 // recursively handle embedded real-time messages
 | |
|                 PmEvent buffer = new PmEvent();
 | |
|                 buffer.timestamp = timestamp;
 | |
|                 buffer.message = b;
 | |
|                 handleMidiIn(buffer);
 | |
|             } else {
 | |
|                 sysexBuffer[sysexBufferIndex++] = b;
 | |
|             }
 | |
|             msg = msg >> 8;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void sysexBegin(int msg) {
 | |
|         sysexBufferReset(); // start from 0, we have at least 256 bytes now
 | |
|         sysexBufferAppendStatus((byte) (msg & 0xFF)); // first byte is special
 | |
|         sysexBufferAppendBytes(msg >> 8, 3); // process remaining bytes
 | |
|     }
 | |
| 
 | |
|     public void handleMidiIn(PmEvent buffer)
 | |
|     {
 | |
|         if (trace) {
 | |
|             System.out.println("handleMidiIn: " + 
 | |
|                                Integer.toHexString(buffer.message));
 | |
|         }
 | |
|         // rather than pass timestamps to every handler, where typically 
 | |
|         // timestamps are ignored, just save the timestamp as a member
 | |
|         // variable where methods can access it if they want it
 | |
|         timestamp = buffer.timestamp;
 | |
|         int status = buffer.message & 0xFF;
 | |
|         if (status < 0x80) {
 | |
|             sysexBufferCheck(); // make enough space
 | |
|             sysexBufferAppendBytes(buffer.message, 4); // process 4 bytes
 | |
|             return;
 | |
|         }
 | |
|         int command = status & 0xF0;
 | |
|         int channel = status & 0x0F;
 | |
|         int data1 = (buffer.message >> 8) & 0xFF;
 | |
|         int data2 = (buffer.message >> 16) & 0xFF;
 | |
|         switch (command) {
 | |
|         case MIDI_NOTE_OFF:
 | |
|             noteOff(channel, data1, data2); break;
 | |
|         case MIDI_NOTE_ON:
 | |
|             if (data2 > 0) {
 | |
|                 noteOn(channel, data1, data2); break;
 | |
|             } else {
 | |
|                 noteOff(channel, data1);
 | |
|             }
 | |
|             break;
 | |
|         case MIDI_CONTROL:
 | |
|             control(channel, data1, data2); break;
 | |
|         case MIDI_POLY_TOUCH:
 | |
|             polyTouch(channel, data1, data2); break;
 | |
|         case MIDI_TOUCH:
 | |
|             touch(channel, data1); break;
 | |
|         case MIDI_PITCH_BEND:
 | |
|             pitchBend(channel, (data1 + (data2 << 7)) - 8192); break;
 | |
|         case MIDI_PROGRAM:
 | |
|             program(channel, data1); break;
 | |
|         case 0xF0:
 | |
|             switch (channel) {
 | |
|             case 0: sysexBegin(buffer.message); break;
 | |
|             case 1: mtcQuarterFrame(data1);
 | |
|             case 2: songPosition(data1 + (data2 << 7)); break;
 | |
|             case 3: songSelect(data1); break;
 | |
|             case 4: /* unused */ break;
 | |
|             case 5: /* unused */ break;
 | |
|             case 6: tuneRequest(); break;
 | |
|             case 7: sysexBufferAppendBytes(buffer.message, buffer.message); break;
 | |
|             case 8: clock(); break;
 | |
|             case 9: tick(); break;
 | |
|             case 0xA: clockStart(); break;
 | |
|             case 0xB: clockContinue(); break;
 | |
|             case 0xC: clockStop(); break;
 | |
|             case 0xD: /* unused */ break;
 | |
|             case 0xE: activeSense(); break;
 | |
|             case 0xF: reset(); break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|           
 | |
|     // the value ranges from +8181 to -8192. The interpretation is 
 | |
|     // synthesizer dependent. Often the range is +/- one whole step
 | |
|     // (two semitones), but the range is usually adjustable within
 | |
|     // the synthesizer.
 | |
|     void pitchBend(int channel, int value) { return; }
 | |
|     void control(int channel, int control, int value) { return; }
 | |
|     void noteOn(int channel, int pitch, int velocity) { return; }
 | |
|     // you can handle velocity in note-off if you want, but the default
 | |
|     // is to drop the velocity and call the simpler NoteOff handler
 | |
|     void noteOff(int channel, int pitch, int velocity) { 
 | |
|         noteOff(channel, pitch);
 | |
|     }
 | |
|     // if the subclass wants to implement NoteOff with velocity, it
 | |
|     // should override the following to make sure all NoteOffs are handled
 | |
|     void noteOff(int channel, int pitch) { return; }
 | |
|     void program(int channel, int program) { return; }
 | |
|     // the byte array may be bigger than the message, length tells how
 | |
|     // many bytes in the array are part of the message
 | |
|     void sysex(byte[] msg, int length) { return; }
 | |
|     void polyTouch(int channel, int key, int value) { return; }
 | |
|     void touch(int channel, int value) { return; }
 | |
|     void mtcQuarterFrame(int value) { return; }
 | |
|     // the value is a 14-bit integer representing 16th notes
 | |
|     void songPosition(int value) { return; }
 | |
|     void songSelect(int value) { return; }
 | |
|     void tuneRequest() { return; }
 | |
|     void clock() { return; } // represents 1/24th of a quarter note
 | |
|     void tick() { return; } // represents 10ms
 | |
|     void clockStart() { return; }
 | |
|     void clockStop() { return; }
 | |
|     void clockContinue() { return; }
 | |
|     void activeSense() { return; }
 | |
|     void reset() { return; }
 | |
| }
 |