Skip to content

Commit ec3a0c4

Browse files
authored
Give up com.noisepages.nettoyeur:midi and reimplement Midi (#129)
* Give up com.noisepages.nettoyeur:midi Reimplement Midi adapters using Android Midi API, reusing Peter's (from/to)WireConverters coming from: https://github.com/nettoyeurny/btmidi/blob/master/AndroidMidi/src/com/noisepages/nettoyeur/midi/ * Midi adapters: handle multiple devices, and javadoc-ument code
1 parent 5048479 commit ec3a0c4

File tree

3 files changed

+221
-79
lines changed

3 files changed

+221
-79
lines changed

PdCore/build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ archivesBaseName = 'pd-core'
99
version = rootProject.version
1010

1111
dependencies {
12-
api 'com.noisepages.nettoyeur:midi:1.0.0-rc1'
13-
implementation 'com.noisepages.nettoyeur:midi:1.0.0-rc1'
1412
implementation 'androidx.legacy:legacy-support-v4:' + rootProject.androidxLegacySupportVersion
1513
}
1614

PdCore/src/main/java/org/puredata/android/midi/MidiToPdAdapter.java

Lines changed: 101 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,61 +8,120 @@
88
package org.puredata.android.midi;
99

1010
import org.puredata.core.PdBase;
11-
12-
import com.noisepages.nettoyeur.midi.MidiReceiver;
11+
import android.media.midi.MidiReceiver;
1312

1413
/**
1514
* Adapter class for connecting output from AndroidMidi to MIDI input for Pd.
1615
*
1716
* @author Peter Brinkmann ([email protected])
17+
* @author Antoine Rousseau ([email protected])
1818
*/
19-
public class MidiToPdAdapter implements MidiReceiver {
20-
21-
@Override
22-
public void onRawByte(byte value) {
23-
PdBase.sendMidiByte(0, value);
24-
}
25-
26-
@Override
27-
public void onProgramChange(int channel, int program) {
28-
PdBase.sendProgramChange(channel, program);
29-
}
30-
31-
@Override
32-
public void onPolyAftertouch(int channel, int key, int velocity) {
33-
PdBase.sendPolyAftertouch(channel, key, velocity);
34-
}
35-
36-
@Override
37-
public void onPitchBend(int channel, int value) {
38-
PdBase.sendPitchBend(channel, value);
39-
}
40-
41-
@Override
42-
public void onNoteOn(int channel, int key, int velocity) {
43-
PdBase.sendNoteOn(channel, key, velocity);
19+
public class MidiToPdAdapter extends MidiReceiver {
20+
private final int port;
21+
private static enum State {
22+
NOTE_OFF, NOTE_ON, POLY_TOUCH, CONTROL_CHANGE, PROGRAM_CHANGE, AFTERTOUCH, PITCH_BEND, NONE
4423
}
24+
private State midiState = State.NONE;
25+
private int channel;
26+
private int firstByte;
4527

28+
/**
29+
* Create an adapter for a specific port, to connect to a MidiOutputPort
30+
* @param port starting at 0; Midi messages sent to Pd will have the channel increased by (16 * port).
31+
* <br><br>
32+
* Example code:
33+
* <pre>{@code
34+
MidiManager midiManager = (MidiManager) getSystemService(MIDI_SERVICE);
35+
final MidiDeviceInfo[] infos = midiManager.getDevices();
36+
if (infos.length == 0) return;
37+
final MidiDeviceInfo info = infos[0]; // Select the first available device
38+
midiManager.openDevice(info, new MidiManager.OnDeviceOpenedListener() {
4639
@Override
47-
public void onNoteOff(int channel, int key, int velocity) {
48-
PdBase.sendNoteOn(channel, key, 0);
40+
public void onDeviceOpened(MidiDevice device) {
41+
if (device == null) return;
42+
for (MidiDeviceInfo.PortInfo portInfo : device.getInfo().getPorts()) {
43+
if (portInfo.getType() == MidiDeviceInfo.PortInfo.TYPE_OUTPUT) {
44+
MidiOutputPort outputPort = device.openOutputPort(portInfo.getPortNumber());
45+
if (outputPort != null) {
46+
outputPort.connect(new MidiToPdAdapter(0)); // Map the device to Pd Midi port 0 (channel 0-15)
47+
break; // Only connect to the first available output port
48+
}
49+
}
50+
}
4951
}
50-
51-
@Override
52-
public void onControlChange(int channel, int controller, int value) {
53-
PdBase.sendControlChange(channel, controller, value);
52+
}
53+
* }</pre>
54+
*/
55+
public MidiToPdAdapter(int port) {
56+
this.port = port;
5457
}
5558

5659
@Override
57-
public void onAftertouch(int channel, int velocity) {
58-
PdBase.sendAftertouch(channel, velocity);
60+
public void onSend(byte[] msg, int offset, int count, long timestamp) {
61+
while(count-- != 0) processByte(msg[offset++]);
5962
}
6063

61-
@Override
62-
public boolean beginBlock() {
63-
return false;
64+
private void processByte(int b) {
65+
if (b < 0) {
66+
midiState = State.values()[(b >> 4) & 0x07];
67+
if (midiState != State.NONE) {
68+
channel = b & 0x0f + 16 * port;
69+
firstByte = -1;
70+
} else {
71+
PdBase.sendMidiByte(0, b);
72+
}
73+
} else {
74+
switch (midiState) {
75+
case NOTE_OFF:
76+
if (firstByte < 0) {
77+
firstByte = b;
78+
} else {
79+
PdBase.sendNoteOn(channel, firstByte, 0);
80+
firstByte = -1;
81+
}
82+
break;
83+
case NOTE_ON:
84+
if (firstByte < 0) {
85+
firstByte = b;
86+
} else {
87+
PdBase.sendNoteOn(channel, firstByte, b);
88+
firstByte = -1;
89+
}
90+
break;
91+
case POLY_TOUCH:
92+
if (firstByte < 0) {
93+
firstByte = b;
94+
} else {
95+
PdBase.sendPolyAftertouch(channel, firstByte, b);
96+
firstByte = -1;
97+
}
98+
break;
99+
case CONTROL_CHANGE:
100+
if (firstByte < 0) {
101+
firstByte = b;
102+
} else {
103+
PdBase.sendControlChange(channel, firstByte, b);
104+
firstByte = -1;
105+
}
106+
break;
107+
case PROGRAM_CHANGE:
108+
PdBase.sendProgramChange(channel, b);
109+
break;
110+
case AFTERTOUCH:
111+
PdBase.sendAftertouch(channel, b);
112+
break;
113+
case PITCH_BEND:
114+
if (firstByte < 0) {
115+
firstByte = b;
116+
} else {
117+
PdBase.sendPitchBend(channel, ((b << 7) | firstByte) - 8192);
118+
firstByte = -1;
119+
}
120+
break;
121+
default /* State.NONE */:
122+
PdBase.sendMidiByte(0, b);
123+
break;
124+
}
125+
}
64126
}
65-
66-
@Override
67-
public void endBlock() {}
68-
}
127+
}

PdCore/src/main/java/org/puredata/android/midi/PdToMidiAdapter.java

Lines changed: 120 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,70 +8,155 @@
88
package org.puredata.android.midi;
99

1010
import org.puredata.core.PdMidiReceiver;
11-
12-
import com.noisepages.nettoyeur.midi.MidiReceiver;
11+
import android.media.midi.MidiInputPort;
12+
import java.util.Map;
13+
import java.util.HashMap;
14+
import java.util.Map.Entry;
1315

1416
/**
1517
* Adapter class for connecting MIDI output from Pd to input for AndroidMidi.
1618
*
1719
* @author Peter Brinkmann ([email protected])
20+
* @author Antoine Rousseau ([email protected])
1821
*/
1922
public class PdToMidiAdapter implements PdMidiReceiver {
23+
private Map<Integer, MidiInputPort> inputPorts = new HashMap<Integer, MidiInputPort>();
2024

21-
private final MidiReceiver receiver;
22-
23-
/**
24-
* Constructor. Note that instances of this class still need to be installed with
25-
* PdBase.setMidiReceiver.
26-
*
27-
* @param receiver to forward MIDI messages to
28-
*/
29-
public PdToMidiAdapter(MidiReceiver receiver) {
30-
this.receiver = receiver;
31-
}
32-
25+
/**
26+
* Send Midi messages from Pd to a Midi device
27+
* @param inputPort input port of the Midi ouput device, as returned by MidiDevice.openInputPort()
28+
* @param pdPort starting at 0; Midi messages received from Pd whose channel is between
29+
* (16 * pdPort) and (16 * pdPort + 15) will be sent to the device with the channel reduced by (16 * pdPort)
30+
* <br><br>
31+
* Example code:
32+
* <pre>{@code
33+
MidiManager midiManager = (MidiManager) getSystemService(MIDI_SERVICE);
34+
final MidiDeviceInfo[] infos = midiManager.getDevices();
35+
if (infos.length == 0) return;
36+
final MidiDeviceInfo info = infos[0]; // Select the first available device
37+
midiManager.openDevice(info, new MidiManager.OnDeviceOpenedListener() {
3338
@Override
34-
public void receiveProgramChange(int channel, int value) {
35-
receiver.onProgramChange(channel, value);
39+
public void onDeviceOpened(MidiDevice device) {
40+
if (device == null) return;
41+
for (MidiDeviceInfo.PortInfo portInfo : device.getInfo().getPorts()) {
42+
if (portInfo.getType() == MidiDeviceInfo.PortInfo.TYPE_INPUT) {
43+
MidiInputPort inputPort = device.openInputPort(portInfo.getPortNumber());
44+
if (inputPort != null) {
45+
pdToMidiAdapter.open(inputPort, 0); // Map the device to Pd Midi port 0 (channel 0-15)
46+
break; // Only connect to the first available input port
47+
}
48+
}
49+
}
3650
}
37-
38-
@Override
39-
public void receivePolyAftertouch(int channel, int pitch, int value) {
40-
receiver.onPolyAftertouch(channel, pitch, value);
51+
}
52+
* }</pre>
53+
*/
54+
public void open(MidiInputPort inputPort, int pdPort) {
55+
close(inputPort);
56+
close(pdPort);
57+
inputPorts.put(pdPort, inputPort);
4158
}
42-
43-
@Override
44-
public void receivePitchBend(int channel, int value) {
45-
receiver.onPitchBend(channel, value);
59+
60+
/**
61+
* Close the connection to a device port
62+
* @param inputPort input port of the Midi ouput device, that needs to be closed
63+
*/
64+
public void close(MidiInputPort inputPort) {
65+
if(! inputPorts.containsValue(inputPort)) return;
66+
for (Entry<Integer, MidiInputPort> entry : inputPorts.entrySet()) {
67+
if (entry.getValue().equals(inputPort)) {
68+
close(entry.getKey());
69+
}
70+
}
71+
}
72+
73+
/**
74+
* Close the connection from a Pd Midi port
75+
* @param pdPort starting at 0; Midi messages coming from Pd for this port will be ignored, and the associated MidiInputPort will be closed
76+
*/
77+
public void close(int pdPort) {
78+
MidiInputPort inputPort = inputPorts.get(pdPort);
79+
if(inputPort != null) {
80+
try {
81+
inputPort.close();
82+
} catch(Exception e) {}
83+
inputPorts.remove(pdPort);
84+
}
4685
}
47-
86+
87+
/** @hidden to javadoc*/
4888
@Override
4989
public void receiveNoteOn(int channel, int pitch, int velocity) {
50-
receiver.onNoteOn(channel, pitch, velocity);
90+
write(0x90, channel, pitch, velocity);
5191
}
52-
92+
93+
/** @hidden to javadoc*/
5394
@Override
54-
public void receiveMidiByte(int port, int value) {
55-
receiver.onRawByte((byte) value);
95+
public void receivePolyAftertouch(int channel, int pitch, int value) {
96+
write(0xa0, channel, pitch, value);
5697
}
57-
98+
99+
/** @hidden to javadoc*/
58100
@Override
59101
public void receiveControlChange(int channel, int controller, int value) {
60-
receiver.onControlChange(channel, controller, value);
102+
write(0xb0, channel, controller, value);
61103
}
62-
104+
105+
/** @hidden to javadoc*/
106+
@Override
107+
public void receiveProgramChange(int channel, int program) {
108+
write(0xc0, channel, program);
109+
}
110+
111+
/** @hidden to javadoc*/
63112
@Override
64113
public void receiveAftertouch(int channel, int value) {
65-
receiver.onAftertouch(channel, value);
114+
write(0xd0, channel, value);
115+
}
116+
117+
/** @hidden to javadoc*/
118+
@Override
119+
public void receivePitchBend(int channel, int value) {
120+
value += 8192;
121+
write(0xe0, channel, (value & 0x7f), (value >> 7));
122+
}
123+
124+
/** @hidden to javadoc*/
125+
@Override
126+
public void receiveMidiByte(int port, int value) {
127+
final byte[] message = {(byte) value};
128+
writeMessage(port, message);
129+
}
130+
131+
private static byte firstByte(int msg, int ch) {
132+
return (byte) (msg | (ch & 0x0f));
133+
}
134+
135+
private void write(int msg, int ch, int a) {
136+
final byte[] message = {firstByte(msg, ch), (byte) a};
137+
writeMessage(ch, message);
138+
}
139+
140+
private void write(int msg, int ch, int a, int b) {
141+
final byte[] message = {firstByte(msg, ch), (byte) a, (byte) b};
142+
writeMessage(ch, message);
143+
}
144+
145+
private void writeMessage(int channel, byte[] message) {
146+
MidiInputPort inputPort = inputPorts.get(channel / 16);
147+
if(inputPort != null) try {
148+
inputPort.send(message, 0, message.length);
149+
} catch(Exception e) {}
66150
}
67151

152+
/** @hidden to javadoc*/
68153
@Override
69154
public boolean beginBlock() {
70-
return receiver.beginBlock();
155+
return false;
71156
}
72157

158+
/** @hidden to javadoc*/
73159
@Override
74160
public void endBlock() {
75-
receiver.endBlock();
76161
}
77162
}

0 commit comments

Comments
 (0)