1 package jportmidi;
2 
3 /* PortMidi is a general class intended for any Java program using
4    the PortMidi library. It encapsulates JPortMidiApi with a more
5    object-oriented interface. A single PortMidi object can manage
6    up to one input stream and one output stream.
7 
8    This class is not safely callable from multiple threads. It
9    is the client's responsibility to periodically call the Poll
10    method which checks for midi input and handles it.
11 */
12 
13 import jportmidi.*;
14 import jportmidi.JPortMidiApi.*;
15 
16 public class JPortMidi {
17 
18     // timecode to send message immediately
19     public final int NOW = 0;
20 
21     // midi codes
22     public final int MIDI_NOTE_OFF = 0x80;
23     public final int MIDI_NOTE_ON = 0x90;
24     public final int CTRL_ALL_OFF = 123;
25     public final int MIDI_PITCH_BEND = 0xE0;
26     public final int MIDI_CLOCK = 0xF8;
27     public final int MIDI_CONTROL = 0xB0;
28     public final int MIDI_PROGRAM = 0xC0;
29     public final int MIDI_START = 0xFA;
30     public final int MIDI_STOP = 0xFC;
31     public final int MIDI_POLY_TOUCH = 0xA0;
32     public final int MIDI_TOUCH = 0xD0;
33 
34     // error code -- cannot refresh device list while stream is open:
35     public final int pmStreamOpen = -5000;
36     public final int pmOutputNotOpen = -4999;
37 
38     // access to JPortMidiApi is through a single, global instance
39     private static JPortMidiApi pm;
40     // a reference count tracks how many objects have it open
41     private static int pmRefCount = 0;
42     private static int openCount = 0;
43 
44     public int error; // user can check here for error codes
45     private PortMidiStream input;
46     private PortMidiStream output;
47     private PmEvent buffer;
48     protected int timestamp; // remember timestamp from incoming messages
49     protected boolean trace = false; // used to print midi msgs for debugging
50 
51 
JPortMidi()52     public JPortMidi() throws JPortMidiException {
53         if (pmRefCount == 0) {
54             pm = new JPortMidiApi();
55             pmRefCount++;
56             System.out.println("Calling Pm_Initialize");
57             checkError(pm.Pm_Initialize());
58             System.out.println("Called Pm_Initialize");
59         }
60         buffer = new PmEvent();
61     }
62 
getTrace()63     public boolean getTrace() { return trace; }
64 
65     // set the trace flag and return previous value
setTrace(boolean flag)66     public boolean setTrace(boolean flag) {
67         boolean previous = trace;
68         trace = flag;
69         return previous;
70     }
71 
72     // WARNING: you must not call this if any devices are open
refreshDeviceLists()73     public void refreshDeviceLists()
74             throws JPortMidiException
75     {
76         if (openCount > 0) {
77             throw new JPortMidiException(pmStreamOpen,
78                     "RefreshDeviceLists called while stream is open");
79         }
80         checkError(pm.Pm_Terminate());
81         checkError(pm.Pm_Initialize());
82     }
83 
84     // there is no control over when/whether this is called, but it seems
85     // to be a good idea to close things when this object is collected
finalize()86     public void finalize() {
87         if (input != null) {
88             error = pm.Pm_Close(input);
89         }
90         if (input != null) {
91             int rslt = pm.Pm_Close(output);
92             // we may lose an error code from closing output, but don't
93             // lose any real error from closing input...
94             if (error == pm.pmNoError) error = rslt;
95         }
96         pmRefCount--;
97         if (pmRefCount == 0) {
98             error = pm.Pm_Terminate();
99         }
100     }
101 
checkError(int err)102     int checkError(int err) throws JPortMidiException
103     {
104         // note that Pm_Read and Pm_Write return positive result values
105         // which are not errors, so compare with >=
106         if (err >= pm.pmNoError) return err;
107         if (err == pm.pmHostError) {
108             throw new JPortMidiException(err, pm.Pm_GetHostErrorText());
109         } else {
110             throw new JPortMidiException(err, pm.Pm_GetErrorText(err));
111         }
112     }
113 
114     // ******** ACCESS TO TIME ***********
115 
timeStart(int resolution)116     public void timeStart(int resolution) throws JPortMidiException {
117         checkError(pm.Pt_TimeStart(resolution));
118     }
119 
timeStop()120     public void timeStop() throws JPortMidiException {
121         checkError(pm.Pt_TimeStop());
122     }
123 
timeGet()124     public int timeGet() {
125         return pm.Pt_Time();
126     }
127 
timeStarted()128     public boolean timeStarted() {
129         return pm.Pt_TimeStarted();
130     }
131 
132     // ******* QUERY DEVICE INFORMATION *********
133 
countDevices()134     public int countDevices() throws JPortMidiException {
135         return checkError(pm.Pm_CountDevices());
136     }
137 
getDefaultInputDeviceID()138     public int getDefaultInputDeviceID()  throws JPortMidiException {
139         return checkError(pm.Pm_GetDefaultInputDeviceID());
140     }
141 
getDefaultOutputDeviceID()142     public int getDefaultOutputDeviceID() throws JPortMidiException {
143         return checkError(pm.Pm_GetDefaultOutputDeviceID());
144     }
145 
getDeviceInterf(int i)146     public String getDeviceInterf(int i) {
147         return pm.Pm_GetDeviceInterf(i);
148     }
149 
getDeviceName(int i)150     public String getDeviceName(int i) {
151         return pm.Pm_GetDeviceName(i);
152     }
153 
getDeviceInput(int i)154     public boolean getDeviceInput(int i) {
155         return pm.Pm_GetDeviceInput(i);
156     }
157 
getDeviceOutput(int i)158     public boolean getDeviceOutput(int i) {
159         return pm.Pm_GetDeviceOutput(i);
160     }
161 
162     // ********** MIDI INTERFACE ************
163 
isOpenInput()164     public boolean isOpenInput() {
165         return input != null;
166     }
167 
openInput(int inputDevice, int bufferSize)168     public void openInput(int inputDevice, int bufferSize)
169             throws JPortMidiException
170     {
171         openInput(inputDevice, "", bufferSize);
172     }
173 
openInput(int inputDevice, String inputDriverInfo, int bufferSize)174     public void openInput(int inputDevice, String inputDriverInfo, int bufferSize)
175             throws JPortMidiException
176     {
177         if (isOpenInput()) pm.Pm_Close(input);
178         else input = new PortMidiStream();
179         if (trace) {
180             System.out.println("openInput " + getDeviceName(inputDevice));
181         }
182         checkError(pm.Pm_OpenInput(input, inputDevice,
183                                    inputDriverInfo, bufferSize));
184         // if no exception, then increase count of open streams
185         openCount++;
186     }
187 
isOpenOutput()188     public boolean isOpenOutput() {
189         return output != null;
190     }
191 
openOutput(int outputDevice, int bufferSize, int latency)192     public void openOutput(int outputDevice, int bufferSize, int latency)
193             throws JPortMidiException
194     {
195         openOutput(outputDevice, "", bufferSize, latency);
196     }
197 
openOutput(int outputDevice, String outputDriverInfo, int bufferSize, int latency)198     public void openOutput(int outputDevice, String outputDriverInfo,
199             int bufferSize, int latency) throws JPortMidiException {
200         if (isOpenOutput()) pm.Pm_Close(output);
201         else output = new PortMidiStream();
202         if (trace) {
203             System.out.println("openOutput " + getDeviceName(outputDevice));
204         }
205         checkError(pm.Pm_OpenOutput(output, outputDevice, outputDriverInfo,
206                                     bufferSize, latency));
207         // if no exception, then increase count of open streams
208         openCount++;
209     }
210 
setFilter(int filters)211     public void setFilter(int filters) throws JPortMidiException {
212         if (input == null) return; // no effect if input not open
213         checkError(pm.Pm_SetFilter(input, filters));
214     }
215 
setChannelMask(int mask)216     public void setChannelMask(int mask) throws JPortMidiException {
217         if (input == null) return; // no effect if input not open
218         checkError(pm.Pm_SetChannelMask(input, mask));
219     }
220 
abort()221     public void abort() throws JPortMidiException {
222         if (output == null) return; // no effect if output not open
223         checkError(pm.Pm_Abort(output));
224     }
225 
226     // In keeping with the idea that this class represents an input and output,
227     // there are separate Close methods for input and output streams, avoiding
228     // the need for clients to ever deal directly with a stream object
closeInput()229     public void closeInput() throws JPortMidiException {
230         if (input == null) return; // no effect if input not open
231         checkError(pm.Pm_Close(input));
232         input = null;
233         openCount--;
234     }
235 
closeOutput()236     public void closeOutput() throws JPortMidiException {
237         if (output == null) return; // no effect if output not open
238         checkError(pm.Pm_Close(output));
239         output = null;
240         openCount--;
241     }
242 
243     // Poll should be called by client to process input messages (if any)
poll()244     public void poll() throws JPortMidiException {
245         if (input == null) return; // does nothing until input is opened
246         while (true) {
247             int rslt = pm.Pm_Read(input, buffer);
248             checkError(rslt);
249             if (rslt == 0) return; // no more messages
250             handleMidiIn(buffer);
251         }
252     }
253 
writeShort(int when, int msg)254     public void writeShort(int when, int msg) throws JPortMidiException {
255         if (output == null)
256             throw new JPortMidiException(pmOutputNotOpen,
257                                          "Output stream not open");
258         if (trace) {
259             System.out.println("writeShort: " + Integer.toHexString(msg));
260         }
261         checkError(pm.Pm_WriteShort(output, when, msg));
262     }
263 
writeSysEx(int when, byte msg[])264     public void writeSysEx(int when, byte msg[]) throws JPortMidiException {
265         if (output == null)
266             throw new JPortMidiException(pmOutputNotOpen,
267                                          "Output stream not open");
268         if (trace) {
269             System.out.print("writeSysEx: ");
270             for (int i = 0; i < msg.length; i++) {
271                 System.out.print(Integer.toHexString(msg[i]));
272             }
273             System.out.print("\n");
274         }
275         checkError(pm.Pm_WriteSysEx(output, when, msg));
276     }
277 
midiChanMessage(int chan, int status, int data1, int data2)278     public int midiChanMessage(int chan, int status, int data1, int data2) {
279         return (((data2 << 16) & 0xFF0000) |
280                 ((data1 << 8) & 0xFF00) |
281                 (status & 0xF0) |
282                 (chan & 0xF));
283     }
284 
midiMessage(int status, int data1, int data2)285     public int midiMessage(int status, int data1, int data2) {
286         return ((((data2) << 16) & 0xFF0000) |
287                 (((data1) << 8) & 0xFF00) |
288                 ((status) & 0xFF));
289     }
290 
midiAllOff(int channel)291     public void midiAllOff(int channel) throws JPortMidiException {
292         midiAllOff(channel, NOW);
293     }
294 
midiAllOff(int chan, int when)295     public void midiAllOff(int chan, int when) throws JPortMidiException {
296         writeShort(when, midiChanMessage(chan, MIDI_CONTROL, CTRL_ALL_OFF, 0));
297     }
298 
midiPitchBend(int chan, int value)299     public void midiPitchBend(int chan, int value) throws JPortMidiException {
300         midiPitchBend(chan, value, NOW);
301     }
302 
midiPitchBend(int chan, int value, int when)303     public void midiPitchBend(int chan, int value, int when)
304             throws JPortMidiException {
305         writeShort(when,
306                    midiChanMessage(chan, MIDI_PITCH_BEND, value, value >> 7));
307     }
308 
midiClock()309     public void midiClock() throws JPortMidiException {
310         midiClock(NOW);
311     }
312 
midiClock(int when)313     public void midiClock(int when) throws JPortMidiException {
314         writeShort(when, midiMessage(MIDI_CLOCK, 0, 0));
315     }
316 
midiControl(int chan, int control, int value)317     public void midiControl(int chan, int control, int value)
318             throws JPortMidiException {
319         midiControl(chan, control, value, NOW);
320     }
321 
midiControl(int chan, int control, int value, int when)322     public void midiControl(int chan, int control, int value, int when)
323             throws JPortMidiException {
324         writeShort(when, midiChanMessage(chan, MIDI_CONTROL, control, value));
325     }
326 
midiNote(int chan, int pitch, int vel)327     public void midiNote(int chan, int pitch, int vel)
328             throws JPortMidiException {
329         midiNote(chan, pitch, vel, NOW);
330     }
331 
midiNote(int chan, int pitch, int vel, int when)332     public void midiNote(int chan, int pitch, int vel, int when)
333             throws JPortMidiException {
334         writeShort(when, midiChanMessage(chan, MIDI_NOTE_ON, pitch, vel));
335     }
336 
midiProgram(int chan, int program)337     public void midiProgram(int chan, int program)
338             throws JPortMidiException {
339         midiProgram(chan, program, NOW);
340     }
341 
midiProgram(int chan, int program, int when)342     public void midiProgram(int chan, int program, int when)
343             throws JPortMidiException {
344         writeShort(when, midiChanMessage(chan, MIDI_PROGRAM, program, 0));
345     }
346 
midiStart()347     public void midiStart()
348             throws JPortMidiException {
349         midiStart(NOW);
350     }
351 
midiStart(int when)352     public void midiStart(int when)
353             throws JPortMidiException {
354         writeShort(when, midiMessage(MIDI_START, 0, 0));
355     }
356 
midiStop()357     public void midiStop()
358             throws JPortMidiException {
359         midiStop(NOW);
360     }
361 
midiStop(int when)362     public void midiStop(int when)
363             throws JPortMidiException {
364         writeShort(when, midiMessage(MIDI_STOP, 0, 0));
365     }
366 
midiPolyTouch(int chan, int key, int value)367     public void midiPolyTouch(int chan, int key, int value)
368             throws JPortMidiException {
369         midiPolyTouch(chan, key, value, NOW);
370     }
371 
midiPolyTouch(int chan, int key, int value, int when)372     public void midiPolyTouch(int chan, int key, int value, int when)
373             throws JPortMidiException {
374         writeShort(when, midiChanMessage(chan, MIDI_POLY_TOUCH, key, value));
375     }
376 
midiTouch(int chan, int value)377     public void midiTouch(int chan, int value)
378             throws JPortMidiException {
379         midiTouch(chan, value, NOW);
380     }
381 
midiTouch(int chan, int value, int when)382     public void midiTouch(int chan, int value, int when)
383             throws JPortMidiException {
384         writeShort(when, midiChanMessage(chan, MIDI_TOUCH, value, 0));
385     }
386 
387     // ****** now we implement the message handlers ******
388 
389     // an array for incoming sysex messages that can grow.
390     // The downside is that after getting a message, we
391 
392     private byte sysexBuffer[] = null;
393     private int sysexBufferIndex = 0;
394 
sysexBufferReset()395     void sysexBufferReset() {
396         sysexBufferIndex = 0;
397         if (sysexBuffer == null) sysexBuffer = new byte[256];
398     }
399 
sysexBufferCheck()400     void sysexBufferCheck() {
401         if (sysexBuffer.length < sysexBufferIndex + 4) {
402             byte bigger[] = new byte[sysexBuffer.length * 2];
403             for (int i = 0; i < sysexBufferIndex; i++) {
404                 bigger[i] = sysexBuffer[i];
405             }
406             sysexBuffer = bigger;
407         }
408         // now we have space to write some bytes
409     }
410 
411     // call this to insert Sysex and EOX status bytes
412     // call sysexBufferAppendBytes to insert anything else
sysexBufferAppendStatus(byte status)413     void sysexBufferAppendStatus(byte status) {
414         sysexBuffer[sysexBufferIndex++] = status;
415     }
416 
sysexBufferAppendBytes(int msg, int len)417     void sysexBufferAppendBytes(int msg, int len) {
418         for (int i = 0; i < len; i++) {
419             byte b = (byte) msg;
420             if ((msg & 0x80) != 0) {
421                 if (b == 0xF7) { // end of sysex
422                     sysexBufferAppendStatus(b);
423                     sysex(sysexBuffer, sysexBufferIndex);
424                     return;
425                 }
426                 // recursively handle embedded real-time messages
427                 PmEvent buffer = new PmEvent();
428                 buffer.timestamp = timestamp;
429                 buffer.message = b;
430                 handleMidiIn(buffer);
431             } else {
432                 sysexBuffer[sysexBufferIndex++] = b;
433             }
434             msg = msg >> 8;
435         }
436     }
437 
sysexBegin(int msg)438     void sysexBegin(int msg) {
439         sysexBufferReset(); // start from 0, we have at least 256 bytes now
440         sysexBufferAppendStatus((byte) (msg & 0xFF)); // first byte is special
441         sysexBufferAppendBytes(msg >> 8, 3); // process remaining bytes
442     }
443 
handleMidiIn(PmEvent buffer)444     public void handleMidiIn(PmEvent buffer)
445     {
446         if (trace) {
447             System.out.println("handleMidiIn: " +
448                                Integer.toHexString(buffer.message));
449         }
450         // rather than pass timestamps to every handler, where typically
451         // timestamps are ignored, just save the timestamp as a member
452         // variable where methods can access it if they want it
453         timestamp = buffer.timestamp;
454         int status = buffer.message & 0xFF;
455         if (status < 0x80) {
456             sysexBufferCheck(); // make enough space
457             sysexBufferAppendBytes(buffer.message, 4); // process 4 bytes
458             return;
459         }
460         int command = status & 0xF0;
461         int channel = status & 0x0F;
462         int data1 = (buffer.message >> 8) & 0xFF;
463         int data2 = (buffer.message >> 16) & 0xFF;
464         switch (command) {
465         case MIDI_NOTE_OFF:
466             noteOff(channel, data1, data2); break;
467         case MIDI_NOTE_ON:
468             if (data2 > 0) {
469                 noteOn(channel, data1, data2); break;
470             } else {
471                 noteOff(channel, data1);
472             }
473             break;
474         case MIDI_CONTROL:
475             control(channel, data1, data2); break;
476         case MIDI_POLY_TOUCH:
477             polyTouch(channel, data1, data2); break;
478         case MIDI_TOUCH:
479             touch(channel, data1); break;
480         case MIDI_PITCH_BEND:
481             pitchBend(channel, (data1 + (data2 << 7)) - 8192); break;
482         case MIDI_PROGRAM:
483             program(channel, data1); break;
484         case 0xF0:
485             switch (channel) {
486             case 0: sysexBegin(buffer.message); break;
487             case 1: mtcQuarterFrame(data1);
488             case 2: songPosition(data1 + (data2 << 7)); break;
489             case 3: songSelect(data1); break;
490             case 4: /* unused */ break;
491             case 5: /* unused */ break;
492             case 6: tuneRequest(); break;
493             case 7: sysexBufferAppendBytes(buffer.message, buffer.message); break;
494             case 8: clock(); break;
495             case 9: tick(); break;
496             case 0xA: clockStart(); break;
497             case 0xB: clockContinue(); break;
498             case 0xC: clockStop(); break;
499             case 0xD: /* unused */ break;
500             case 0xE: activeSense(); break;
501             case 0xF: reset(); break;
502             }
503         }
504     }
505 
506     // the value ranges from +8181 to -8192. The interpretation is
507     // synthesizer dependent. Often the range is +/- one whole step
508     // (two semitones), but the range is usually adjustable within
509     // the synthesizer.
pitchBend(int channel, int value)510     void pitchBend(int channel, int value) { return; }
control(int channel, int control, int value)511     void control(int channel, int control, int value) { return; }
noteOn(int channel, int pitch, int velocity)512     void noteOn(int channel, int pitch, int velocity) { return; }
513     // you can handle velocity in note-off if you want, but the default
514     // is to drop the velocity and call the simpler NoteOff handler
noteOff(int channel, int pitch, int velocity)515     void noteOff(int channel, int pitch, int velocity) {
516         noteOff(channel, pitch);
517     }
518     // if the subclass wants to implement NoteOff with velocity, it
519     // should override the following to make sure all NoteOffs are handled
noteOff(int channel, int pitch)520     void noteOff(int channel, int pitch) { return; }
program(int channel, int program)521     void program(int channel, int program) { return; }
522     // the byte array may be bigger than the message, length tells how
523     // many bytes in the array are part of the message
sysex(byte[] msg, int length)524     void sysex(byte[] msg, int length) { return; }
polyTouch(int channel, int key, int value)525     void polyTouch(int channel, int key, int value) { return; }
touch(int channel, int value)526     void touch(int channel, int value) { return; }
mtcQuarterFrame(int value)527     void mtcQuarterFrame(int value) { return; }
528     // the value is a 14-bit integer representing 16th notes
songPosition(int value)529     void songPosition(int value) { return; }
songSelect(int value)530     void songSelect(int value) { return; }
tuneRequest()531     void tuneRequest() { return; }
clock()532     void clock() { return; } // represents 1/24th of a quarter note
tick()533     void tick() { return; } // represents 10ms
clockStart()534     void clockStart() { return; }
clockStop()535     void clockStop() { return; }
clockContinue()536     void clockContinue() { return; }
activeSense()537     void activeSense() { return; }
reset()538     void reset() { return; }
539 }
540