Skip to content

Commit e2a3e45

Browse files
committed
usbd: Add midi interface definition from @paulhamsh.
Taken from https://github.com/paulhamsh/Micropython-Midi-Device as of commit 2678d13.
1 parent f5b8489 commit e2a3e45

File tree

1 file changed

+265
-0
lines changed

1 file changed

+265
-0
lines changed

micropython/usbd/midi.py

+265
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# MicroPython USB MIDI module
2+
# MIT license; Copyright (c) 2022 Angus Gratton, Paul Hamshere
3+
from device import USBInterface
4+
from micropython import const
5+
import ustruct
6+
7+
dat = bytearray(64)
8+
9+
_INTERFACE_CLASS_IGNORE = 0x01
10+
_INTERFACE_SUBCLASS_IGNORE = 0x01
11+
_PROTOCOL_IGNORE = 0x00
12+
13+
14+
class RingBuf:
15+
def __init__(self, size):
16+
self.data = bytearray(size)
17+
self.size = size
18+
self.index_put = 0
19+
self.index_get = 0
20+
21+
def put(self, value):
22+
next_index = (self.index_put + 1) % self.size
23+
# check for overflow
24+
if self.index_get != next_index:
25+
self.data[self.index_put] = value
26+
self.index_put = next_index
27+
return value
28+
else:
29+
return None
30+
31+
def get(self):
32+
if self.index_get == self.index_put:
33+
return None ## buffer empty
34+
else:
35+
value = self.data[self.index_get]
36+
self.index_get = (self.index_get + 1) % self.size
37+
return value
38+
39+
def is_empty(self):
40+
return (self.index_get == self. index_put)
41+
42+
43+
class AudioInterface(USBInterface):
44+
""" Abstract base class to implement a USB MIDI device in Python. """
45+
def __init__(self):
46+
super().__init__(
47+
_INTERFACE_CLASS_IGNORE, _INTERFACE_SUBCLASS_IGNORE, _PROTOCOL_IGNORE, 0x00
48+
)
49+
50+
51+
def get_itf_descriptor(self, num_eps, itf_idx, str_idx):
52+
"""Return the MIDI USB interface descriptors.
53+
"""
54+
55+
ms_interface = ustruct.pack(
56+
"<BBBBBBBBB",
57+
9, # bLength
58+
0x04, # bDescriptorType INTERFACE
59+
itf_idx, # bInterfaceNumber
60+
0x00, # bAlternateSetting
61+
0x00, # bNumEndpoints
62+
0x01, # bInterfaceClass AUDIO
63+
0x01, # bInterfaceSubclass AUDIO_CONTROL
64+
0x00, # bInterfaceProtocol
65+
0x00 # iInterface
66+
)
67+
cs_ms_interface = ustruct.pack(
68+
"<BBBHHBB",
69+
9, # bLength
70+
0x24, # bDescriptorType CS_INTERFACE
71+
0x01, # bDescriptorSubtype MS_HEADER
72+
0x0100, # BcdADC
73+
0x0009, # wTotalLength
74+
0x01, # bInCollection,
75+
0x01 # baInterfaceNr
76+
)
77+
78+
iface = ms_interface + cs_ms_interface
79+
return (iface, [])
80+
#return (b"", [])
81+
82+
83+
class MIDIInterface(USBInterface):
84+
""" Base class to implement a USB MIDI device in Python. """
85+
def __init__(self):
86+
super().__init__(
87+
_INTERFACE_CLASS_IGNORE, _INTERFACE_SUBCLASS_IGNORE, _PROTOCOL_IGNORE, 0x00
88+
)
89+
self.ep_out = 0x03 # set during enumeration
90+
self.ep_in = 0x83
91+
self.got_data = False
92+
self.rx_data = bytearray(64)
93+
self.rb = RingBuf(128)
94+
95+
96+
def send_data(self, tx_data):
97+
""" Helper function to send data. """
98+
self.submit_xfer(0x83, tx_data)
99+
100+
def midi_received(self):
101+
return not self.rb.is_empty()
102+
103+
def get_rb(self):
104+
return self.rb.get()
105+
106+
def receive_data_callback(self, ep_addr, result, xferred_bytes):
107+
for i in range (0, xferred_bytes):
108+
self.rb.put(self.rx_data[i])
109+
self.submit_xfer(0x03, self.rx_data, self.receive_data_callback)
110+
111+
def start_receive_data(self):
112+
self.submit_xfer(0x03, self.rx_data, self.receive_data_callback) # self.receive_data_callback)
113+
114+
def get_itf_descriptor(self, num_eps, itf_idx, str_idx):
115+
"""Return the MIDI USB interface descriptors. """
116+
117+
ms_interface = ustruct.pack(
118+
"<BBBBBBBBB",
119+
9, # bLength
120+
0x04, # bDescriptorType INTERFACE
121+
itf_idx, # bInterfaceNumber
122+
0x00, # bAlternateSetting
123+
0x02, # bNumEndpoints
124+
0x01, # bInterfaceClass AUDIO
125+
0x03, # bInterfaceSubclass MIDISTREAMING
126+
0x00, # bInterfaceProtocol
127+
0x00 # iInterface
128+
)
129+
cs_ms_interface = ustruct.pack(
130+
"<BBBHH",
131+
7, # bLength
132+
0x24, # bDescriptorType CS_INTERFACE
133+
0x01, # bDescriptorSubtype MS_HEADER
134+
0x0100, # BcdADC
135+
0x0041 # wTotalLength
136+
)
137+
138+
jack1 = ustruct.pack(
139+
"<BBBBBB",
140+
6, # bLength
141+
0x24, # bDescriptorType CS_INTERFACE
142+
0x02, # bDescriptorSubtype MIDI_IN_JACK
143+
0x01, # bJackType EMBEDDED
144+
0x01, # bJackID
145+
0x00 # iJack
146+
)
147+
jack2 = ustruct.pack(
148+
"<BBBBBB",
149+
6, # bLength
150+
0x24, # bDescriptorType CS_INTERFACE
151+
0x02, # bDescriptorSubtype MIDI_IN_JACK
152+
0x02, # bJackType EXTERNAL
153+
0x02, # bJackID
154+
0x00 # iJack
155+
)
156+
jack3 = ustruct.pack(
157+
"<BBBBBBBBB",
158+
9, # bLength
159+
0x24, # bDescriptorType CS_INTERFACE
160+
0x03, # bDescriptorSubtype MIDI_OUT_JACK
161+
0x01, # bJackType EMBEDDED
162+
0x03, # bJackID
163+
0x01, # bNrInputPins
164+
0x02, # BaSourceID
165+
0x01, # BaSourcePin
166+
0x00 # iJack
167+
)
168+
jack4 = ustruct.pack(
169+
"<BBBBBBBBB",
170+
9, # bLength
171+
0x24, # bDescriptorType CS_INTERFACE
172+
0x03, # bDescriptorSubtype MIDI_OUT_JACK
173+
0x02, # bJackType EXTERNAL
174+
0x04, # bJackID
175+
0x01, # bNrInputPins
176+
0x01, # BaSourceID
177+
0x01, # BaSourcePin
178+
0x00 # iJack
179+
)
180+
iface = ms_interface + cs_ms_interface + jack1 + jack2 + jack3 + jack4
181+
return (iface, [])
182+
183+
def get_endpoint_descriptors(self, ep_addr, str_idx):
184+
"""Return the MIDI USB endpoint descriptors.
185+
"""
186+
187+
epA = ustruct.pack(
188+
"<BBBBHBBB",
189+
9, # bLength
190+
0x05, # bDescriptorType ENDPOINT
191+
0x03, # bEndpointAddress
192+
0x02, # bmAttributes
193+
0x0040, # wMaxPacketSize
194+
0x00, # bInterval
195+
0x00, # bRefresh
196+
0x00 # bSyncAddress
197+
)
198+
cs_epA = ustruct.pack(
199+
"<BBBBB",
200+
5, # bLength
201+
0x25, # bDescriptorType CS_ENDPOINT
202+
0x01, # bDescriptorSubtype MS_GENERAL
203+
0x01, # bNumEmbMIDIJack
204+
0x01 # BaAssocJackID
205+
)
206+
epB = ustruct.pack(
207+
"<BBBBHBBB",
208+
9, # bLength
209+
0x05, # bDescriptorType ENDPOINT
210+
0x83, # bEndpointAddress
211+
0x02, # bmAttributes
212+
0x0040, # wMaxPacketSize
213+
0x00, # bInterval
214+
0x00, # bRefresh
215+
0x00 # bSyncAddress
216+
)
217+
cs_epB = ustruct.pack(
218+
"<BBBBB",
219+
5, # bLength
220+
0x25, # bDescriptorType CS_ENDPOINT
221+
0x01, # bDescriptorSubtype MS_GENERAL
222+
0x01, # bNumEmbMIDIJack
223+
0x03 # BaAssocJackID
224+
)
225+
226+
desc = epA + cs_epA + epB + cs_epB
227+
ep_addr = [0x03, 0x83]
228+
return (desc, [], ep_addr)
229+
230+
231+
class AudioUSB(AudioInterface):
232+
""" Empty USB Audio interface """
233+
def __init__(self):
234+
super().__init__()
235+
236+
class MidiUSB(MIDIInterface):
237+
""" Very basic synchronous USB MIDI interface """
238+
def __init__(self):
239+
super().__init__()
240+
241+
def note_on(self, channel, pitch, vel):
242+
obuf = ustruct.pack("<BBBB", 0x09, 0x90 | channel, pitch, vel)
243+
super().send_data(obuf)
244+
245+
def note_off(self, channel, pitch, vel):
246+
obuf = ustruct.pack("<BBBB", 0x08, 0x80 | channel, pitch, vel)
247+
super().send_data(obuf)
248+
249+
def start(self):
250+
super().start_receive_data()
251+
252+
def midi_received(self):
253+
return super().midi_received()
254+
255+
def get_midi(self):
256+
if super().midi_received():
257+
cin = super().get_rb()
258+
cmd = super().get_rb()
259+
val1 = super().get_rb()
260+
val2 = super().get_rb()
261+
return (cin, cmd, val1, val2)
262+
else:
263+
return (None, None, None, None)
264+
265+

0 commit comments

Comments
 (0)