1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
|
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; }
}
|