Skip to content

Commit 1b9e8d6

Browse files
committed
Merge pull request #1031 from julbouln/master
Add SysEx to USBMIDI device
2 parents 2198e68 + 8dab8c4 commit 1b9e8d6

File tree

5 files changed

+257
-10
lines changed

5 files changed

+257
-10
lines changed

libraries/USBDevice/USBMIDI/MIDIMessage.h

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
#include "mbed.h"
2323

24+
#define MAX_MIDI_MESSAGE_SIZE 256 // Max message size. SysEx can be up to 65536 but 256 should be fine for most usage
25+
2426
// MIDI Message Format
2527
//
2628
// [ msg(4) | channel(4) ] [ 0 | n(7) ] [ 0 | m(7) ]
@@ -49,6 +51,16 @@ class MIDIMessage {
4951
data[i] = buf[i];
5052
}
5153

54+
// New constructor, buf is a true MIDI message (not USBMidi message) and buf_len true message length.
55+
MIDIMessage(uint8_t *buf, int buf_len) {
56+
length=buf_len+1;
57+
// first byte keeped for retro-compatibility
58+
data[0]=0;
59+
60+
for (int i = 0; i < buf_len; i++)
61+
data[i+1] = buf[i];
62+
}
63+
5264
// create messages
5365

5466
/** Create a NoteOff message
@@ -162,6 +174,16 @@ class MIDIMessage {
162174
return ControlChange(123, 0, channel);
163175
}
164176

177+
/** Create a SysEx message
178+
* @param data SysEx data (including 0xF0 .. 0xF7)
179+
* @param len SysEx data length
180+
* @returns A MIDIMessage
181+
*/
182+
static MIDIMessage SysEx(uint8_t *data, int len) {
183+
MIDIMessage msg=MIDIMessage(data,len);
184+
return msg;
185+
}
186+
165187
// decode messages
166188

167189
/** MIDI Message Types */
@@ -174,7 +196,8 @@ class MIDIMessage {
174196
ProgramChangeType,
175197
ChannelAftertouchType,
176198
PitchWheelType,
177-
AllNotesOffType
199+
AllNotesOffType,
200+
SysExType
178201
};
179202

180203
/** Read the message type
@@ -196,6 +219,7 @@ class MIDIMessage {
196219
case 0xC: return ProgramChangeType;
197220
case 0xD: return ChannelAftertouchType;
198221
case 0xE: return PitchWheelType;
222+
case 0xF: return SysExType;
199223
default: return ErrorType;
200224
}
201225
}
@@ -245,7 +269,8 @@ class MIDIMessage {
245269
return p - 8192; // 0 - 16383, 8192 is center
246270
}
247271

248-
uint8_t data[4];
272+
uint8_t data[MAX_MIDI_MESSAGE_SIZE+1];
273+
uint8_t length=4;
249274
};
250275

251276
#endif

libraries/USBDevice/USBMIDI/USBMIDI.cpp

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,118 @@ USBMIDI::USBMIDI(uint16_t vendor_id, uint16_t product_id, uint16_t product_relea
2525
USBDevice::connect();
2626
}
2727

28+
// write plain MIDIMessage that will be converted to USBMidi event packet
2829
void USBMIDI::write(MIDIMessage m) {
29-
USBDevice::write(EPBULK_IN, m.data, 4, MAX_PACKET_SIZE_EPBULK);
30+
// first byte keeped for retro-compatibility
31+
for(int p=1; p < m.length; p+=3) {
32+
uint8_t buf[4];
33+
// Midi message to USBMidi event packet
34+
buf[0]=m.data[1] >> 4;
35+
// SysEx
36+
if(buf[0] == 0xF) {
37+
if((m.length - p) > 3) {
38+
// SysEx start or continue
39+
buf[0]=0x4;
40+
} else {
41+
switch(m.length - p) {
42+
case 1:
43+
// SysEx end with one byte
44+
buf[0]=0x5;
45+
break;
46+
case 2:
47+
// SysEx end with two bytes
48+
buf[0]=0x6;
49+
break;
50+
case 3:
51+
// SysEx end with three bytes
52+
buf[0]=0x7;
53+
break;
54+
}
55+
}
56+
}
57+
buf[1]=m.data[p];
58+
59+
if(p+1 < m.length)
60+
buf[2]=m.data[p+1];
61+
else
62+
buf[2]=0;
63+
64+
if(p+2 < m.length)
65+
buf[3]=m.data[p+2];
66+
else
67+
buf[3]=0;
68+
69+
USBDevice::write(EPBULK_IN, buf, 4, MAX_PACKET_SIZE_EPBULK);
70+
}
3071
}
3172

3273

3374
void USBMIDI::attach(void (*fptr)(MIDIMessage)) {
3475
midi_evt = fptr;
3576
}
3677

37-
3878
bool USBMIDI::EPBULK_OUT_callback() {
3979
uint8_t buf[64];
4080
uint32_t len;
4181
readEP(EPBULK_OUT, buf, &len, 64);
4282

4383
if (midi_evt != NULL) {
44-
for (uint32_t i=0; i<len; i+=4) {
45-
midi_evt(MIDIMessage(buf+i));
46-
}
84+
for (uint32_t i=0; i<len; i+=4) {
85+
uint8_t data_read;
86+
data_end=true;
87+
switch(buf[i]) {
88+
case 0x2:
89+
// Two-bytes System Common Message - undefined in USBMidi 1.0
90+
data_read=2;
91+
break;
92+
case 0x4:
93+
// SysEx start or continue
94+
data_end=false;
95+
data_read=3;
96+
break;
97+
case 0x5:
98+
// Single-byte System Common Message or SysEx end with one byte
99+
data_read=1;
100+
break;
101+
case 0x6:
102+
// SysEx end with two bytes
103+
data_read=2;
104+
break;
105+
case 0xC:
106+
// Program change
107+
data_read=2;
108+
break;
109+
case 0xD:
110+
// Channel pressure
111+
data_read=2;
112+
break;
113+
case 0xF:
114+
// Single byte
115+
data_read=1;
116+
break;
117+
default:
118+
// Others three-bytes messages
119+
data_read=3;
120+
break;
121+
}
122+
123+
for(uint8_t j=1;j<data_read+1;j++) {
124+
data[cur_data]=buf[i+j];
125+
cur_data++;
126+
}
127+
128+
if(data_end) {
129+
midi_evt(MIDIMessage(data,cur_data));
130+
cur_data=0;
131+
}
132+
}
47133
}
48134

49135
// We reactivate the endpoint to receive next characters
50136
readStart(EPBULK_OUT, MAX_PACKET_SIZE_EPBULK);
51137
return true;
52138
}
53139

54-
55-
56140
// Called in ISR context
57141
// Set configuration. Return false if the
58142
// configuration is not supported.

libraries/USBDevice/USBMIDI/USBMIDI.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,11 @@ class USBMIDI: public USBDevice {
102102
virtual uint8_t * configurationDesc();
103103

104104
private:
105+
uint8_t data[MAX_MIDI_MESSAGE_SIZE+1];
106+
uint8_t cur_data=0;
107+
bool data_end = true;
108+
105109
void (*midi_evt)(MIDIMessage);
106-
107110
};
108111

109112
#endif

libraries/tests/usb/device/midi/main.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,70 @@
44
USBMIDI midi;
55
Serial pc(USBTX, USBRX);
66

7+
// MIDI IN
8+
void transmitMessage(MIDIMessage msg) {
9+
switch (msg.type()) {
10+
case MIDIMessage::NoteOnType:
11+
wait(0.1);
12+
midi.write(MIDIMessage::NoteOn(msg.key()));
13+
break;
14+
case MIDIMessage::NoteOffType:
15+
wait(0.1);
16+
midi.write(MIDIMessage::NoteOff(msg.key()));
17+
break;
18+
case MIDIMessage::ProgramChangeType:
19+
wait(0.1);
20+
midi.write(MIDIMessage::ProgramChange(msg.program()));
21+
break;
22+
case MIDIMessage::SysExType:
23+
wait(0.1);
24+
unsigned char tmp[64];
25+
for(int i=0;i<msg.length-1;i++) {
26+
tmp[i]=msg.data[i+1];
27+
}
28+
midi.write(MIDIMessage::SysEx(tmp,msg.length-1));
29+
break;
30+
default:
31+
break;
32+
}
33+
}
34+
735
int main(void)
836
{
37+
wait(5);
38+
// MIDI OUT
39+
40+
// set piano
41+
midi.write(MIDIMessage::ProgramChange(1));
42+
wait(0.1);
43+
44+
// play A
45+
midi.write(MIDIMessage::NoteOn(21));
46+
wait(0.1);
47+
midi.write(MIDIMessage::NoteOff(21));
48+
wait(0.1);
49+
50+
// GM reset
51+
unsigned char gm_reset[]={0xF0,0x7E,0x7F,0x09,0x01,0xF7};
52+
midi.write(MIDIMessage::SysEx(gm_reset,6));
53+
wait(0.1);
54+
55+
// GM Master volume max
56+
unsigned char gm_master_vol_max[]={0xF0,0x7F,0x7F,0x04,0x01,0x7F,0x7F,0xF7};
57+
midi.write(MIDIMessage::SysEx(gm_master_vol_max,8));
58+
wait(0.1);
59+
60+
// GS reset
61+
unsigned char gs_reset[]={0xF0,0x41,0x10,0x42,0x12,0x40,0x00,0x7F,0x00,0x41,0xF7};
62+
midi.write(MIDIMessage::SysEx(gs_reset,11));
63+
wait(0.1);
64+
65+
// GS Master volume max
66+
unsigned char gs_master_vol_max[]={0xF0,0x41,0x10,0x42,0x12,0x40,0x00,0x04,0x7F,0x3D,0xF7};
67+
midi.write(MIDIMessage::SysEx(gs_master_vol_max,11));
68+
wait(0.1);
69+
70+
midi.attach(transmitMessage);
71+
972
while(1);
1073
}

workspace_tools/host_tests/midi.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from __future__ import print_function
2+
import sys
3+
import re
4+
import time
5+
import mido
6+
from mido import Message
7+
8+
9+
def test_midi_in(port):
10+
expected_messages_count=0
11+
while expected_messages_count < 7:
12+
for message in port.iter_pending():
13+
if message.type in ('note_on', 'note_off', 'program_change', 'sysex'):
14+
yield message
15+
expected_messages_count+=1
16+
time.sleep(0.1)
17+
18+
def test_midi_loopback(input_port):
19+
expected_messages_count=0
20+
while expected_messages_count < 1:
21+
for message in input_port.iter_pending():
22+
print('Test MIDI OUT loopback received {}'.format(message.hex()))
23+
expected_messages_count+=1
24+
25+
def test_midi_out_loopback(output_port,input_port):
26+
print("Test MIDI OUT loopback")
27+
output_port.send(Message('program_change', program=1))
28+
test_midi_loopback(input_port)
29+
30+
output_port.send(Message('note_on', note=21))
31+
test_midi_loopback(input_port)
32+
33+
output_port.send(Message('note_off', note=21))
34+
test_midi_loopback(input_port)
35+
36+
output_port.send(Message('sysex', data=[0x7E,0x7F,0x09,0x01]))
37+
test_midi_loopback(input_port)
38+
39+
output_port.send(Message('sysex', data=[0x7F,0x7F,0x04,0x01,0x7F,0x7F]))
40+
test_midi_loopback(input_port)
41+
42+
output_port.send(Message('sysex', data=[0x41,0x10,0x42,0x12,0x40,0x00,0x7F,0x00,0x41]))
43+
test_midi_loopback(input_port)
44+
45+
output_port.send(Message('sysex', data=[0x41,0x10,0x42,0x12,0x40,0x00,0x04,0x7F,0x3D]))
46+
test_midi_loopback(input_port)
47+
48+
portname=""
49+
50+
while portname=="":
51+
print("Wait for MIDI IN plug ...")
52+
for name in mido.get_input_names():
53+
matchObj = re.match( r'Mbed', name)
54+
55+
if matchObj:
56+
portname=name
57+
time.sleep( 1 )
58+
59+
try:
60+
input_port = mido.open_input(portname)
61+
output_port = mido.open_output(portname)
62+
63+
print('Using {}'.format(input_port))
64+
65+
print("Test MIDI IN")
66+
67+
for message in test_midi_in(input_port):
68+
print('Test MIDI IN received {}'.format(message.hex()))
69+
70+
test_midi_out_loopback(output_port,input_port)
71+
except KeyboardInterrupt:
72+
pass

0 commit comments

Comments
 (0)