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