diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-07 06:50:04 +0200 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-07 06:50:04 +0200 |
| commit | 988f5d2b5343850e19ad1512cefe6c53953aa02e (patch) | |
| tree | 1ff29934294e73b2575488c06df91866ce251dbe /portmidi/pm_java/jportmidi/JPortMidi.java | |
| parent | 9b5839c58a2e1df8bddf6b98805998508ea417d5 (diff) | |
| download | ttdaw-988f5d2b5343850e19ad1512cefe6c53953aa02e.tar.gz | |
Added bunch of examples
Diffstat (limited to 'portmidi/pm_java/jportmidi/JPortMidi.java')
| -rw-r--r-- | portmidi/pm_java/jportmidi/JPortMidi.java | 541 |
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 @@ +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"); + } + if (trace) System.out.println("Pm_Terminate"); + checkError(pm.Pm_Terminate()); + if (trace) System.out.println("Pm_Initialize"); + 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; } +} |
