Skip to content

Commit 74fd7f9

Browse files
authored
Merge pull request #2 from todbot/main
Add timeout parameter, add Feather RP2040 with USB Type A Host example
2 parents fa7182a + 401eb87 commit 74fd7f9

File tree

7 files changed

+130
-24
lines changed

7 files changed

+130
-24
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
#
33
# SPDX-License-Identifier: Unlicense
44

5+
default_language_version:
6+
# force all unspecified python hooks to run python3.11
7+
python: python3.11
8+
59
repos:
610
- repo: https://github.com/python/black
711
rev: 23.3.0

README.rst

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,6 @@ or individual libraries can be installed using
3737
`circup <https://github.com/adafruit/circup>`_.
3838

3939

40-
41-
.. todo:: Describe the Adafruit product this library works with. For PCBs, you can also add the
42-
image from the assets folder in the PCB's GitHub repo.
43-
44-
`Purchase one from the Adafruit shop <http://www.adafruit.com/products/>`_
45-
4640
Installing from PyPI
4741
=====================
4842

@@ -95,8 +89,23 @@ Or the following command to update an existing version:
9589
Usage Example
9690
=============
9791

98-
.. todo:: Add a quick, simple example. It and other examples should live in the
99-
examples folder and be included in docs/examples.rst.
92+
.. code-block:: python
93+
94+
import adafruit_midi
95+
import adafruit_usb_host_midi
96+
97+
print("Looking for midi device")
98+
raw_midi = None
99+
while raw_midi is None:
100+
for device in usb.core.find(find_all=True):
101+
try:
102+
raw_midi = adafruit_usb_host_midi.MIDI(device)
103+
print("Found", hex(device.idVendor), hex(device.idProduct))
104+
except ValueError:
105+
continue
106+
107+
midi_device = adafruit_midi.MIDI(midi_in=raw_midi, in_channel=0)
108+
100109
101110
Documentation
102111
=============

adafruit_usb_host_midi.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* Author(s): Scott Shawcroft
1212
"""
1313

14+
import usb.core
1415
import adafruit_usb_host_descriptors
1516

1617
__version__ = "0.0.0+auto.0"
@@ -19,12 +20,24 @@
1920

2021
DIR_IN = 0x80
2122

23+
2224
class MIDI:
23-
def __init__(self, device):
25+
"""
26+
Stream-like MIDI device for use with ``adafruit_midi`` and similar upstream
27+
MIDI parser libraries.
28+
29+
:param device: a ``usb.core.Device`` object which implements
30+
``read(endpoint, buffer)`` and ``write(endpoint,buffer)``
31+
:param float timeout: timeout in seconds to wait for read or write operation
32+
to succeeds. Default to None, i.e. reads and writes will block.
33+
"""
34+
35+
def __init__(self, device, timeout=None):
2436
self.interface_number = 0
2537
self.in_ep = 0
2638
self.out_ep = 0
2739
self.device = device
40+
self.timeout_ms = round(timeout * 1000) if timeout else 0
2841

2942
self.buf = bytearray(64)
3043
self.start = 0
@@ -40,14 +53,16 @@ def __init__(self, device):
4053
descriptor_len = config_descriptor[i]
4154
descriptor_type = config_descriptor[i + 1]
4255
if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION:
56+
# pylint: disable=unused-variable
4357
config_value = config_descriptor[i + 5]
58+
# pylint: enable=unused-variable
4459
elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE:
4560
interface_number = config_descriptor[i + 2]
4661
interface_class = config_descriptor[i + 5]
4762
interface_subclass = config_descriptor[i + 6]
4863
midi_interface = interface_class == 0x1 and interface_subclass == 0x3
4964
if midi_interface:
50-
self.interface_number= interface_number
65+
self.interface_number = interface_number
5166

5267
elif descriptor_type == adafruit_usb_host_descriptors.DESC_ENDPOINT:
5368
endpoint_address = config_descriptor[i + 2]
@@ -63,11 +78,50 @@ def __init__(self, device):
6378
device.detach_kernel_driver(self.interface_number)
6479

6580
def read(self, size):
81+
"""
82+
Read bytes. If ``nbytes`` is specified then read at most that many
83+
bytes. Otherwise, read everything that arrives until the connection
84+
times out. Providing the number of bytes expected is highly recommended
85+
because it will be faster. If no bytes are read, return ``None``.
86+
87+
.. note:: When no bytes are read due to a timeout, this function returns ``None``.
88+
This matches the behavior of `io.RawIOBase.read` in Python 3, but
89+
differs from pyserial which returns ``b''`` in that situation.
90+
91+
:return: Data read
92+
:rtype: bytes or None
93+
"""
94+
6695
if self._remaining == 0:
67-
self._remaining = self.device.read(self.in_ep, self.buf) - 1
68-
self.start = 1
96+
try:
97+
n = self.device.read(self.in_ep, self.buf, self.timeout_ms)
98+
self._remaining = n - 1
99+
self.start = 1
100+
except usb.core.USBTimeoutError:
101+
pass
69102
size = min(size, self._remaining)
70-
b = self.buf[self.start:self.start + size]
103+
b = self.buf[self.start : self.start + size]
71104
self.start += size
72105
self._remaining -= size
73106
return b
107+
108+
def readinto(self, buf):
109+
"""Read bytes into the ``buf``. Read at most ``len(buf)`` bytes.
110+
111+
:return: number of bytes read and stored into ``buf``
112+
:rtype: int or None (on a non-blocking error)
113+
"""
114+
b = self.read(len(buf))
115+
n = len(b)
116+
if n:
117+
buf[:] = b
118+
return n
119+
120+
def __repr__(self):
121+
# also idProduct/idVendor for vid/pid
122+
return (
123+
"MIDI Device "
124+
+ str(self.device.manufacturer)
125+
+ "/"
126+
+ str(self.device.product)
127+
)

docs/index.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,9 @@ Table of Contents
2424
.. toctree::
2525
:caption: Tutorials
2626

27-
.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave
28-
the toctree above for use later.
29-
3027
.. toctree::
3128
:caption: Related Products
3229

33-
.. todo:: Add any product links here. If there are none, then simply delete this todo and leave
34-
the toctree above for use later.
35-
3630
.. toctree::
3731
:caption: Other Links
3832

examples/usb_host_midi_simpletest.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import audiobusio
66
import board
77
import synthio
8-
import adafruit_midi
9-
import adafruit_usb_host_midi
108
import usb.core
9+
import wm8960
10+
import adafruit_midi
1111
from adafruit_midi.note_on import NoteOn
1212
from adafruit_midi.note_off import NoteOff
13-
import wm8960
13+
import adafruit_usb_host_midi
1414

1515
print("Looking for midi device")
1616
raw_midi = None
@@ -26,7 +26,9 @@
2626
# This setup is for the headphone output on the iMX RT 1060 EVK.
2727
dac = wm8960.WM8960(board.I2C())
2828
dac.start_i2s_out()
29-
audio = audiobusio.I2SOut(board.AUDIO_BCLK, board.AUDIO_SYNC, board.AUDIO_TXD, main_clock=board.AUDIO_MCLK)
29+
audio = audiobusio.I2SOut(
30+
board.AUDIO_BCLK, board.AUDIO_SYNC, board.AUDIO_TXD, main_clock=board.AUDIO_MCLK
31+
)
3032
synth = synthio.Synthesizer(sample_rate=44100)
3133
audio.play(synth)
3234

@@ -41,7 +43,9 @@
4143
print("noteOn: ", msg.note, "vel:", msg.velocity)
4244
synth.press(note)
4345
pressed[msg.note] = note
44-
elif (isinstance(msg,NoteOff) or (isinstance(msg,NoteOn) and msg.velocity==0)) and msg.note in pressed:
46+
elif (
47+
isinstance(msg, NoteOff) or (isinstance(msg, NoteOn) and msg.velocity == 0)
48+
) and msg.note in pressed:
4549
print("noteOff:", msg.note, "vel:", msg.velocity)
4650
note = pressed[msg.note]
4751
synth.release(note)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2023 Scott Shawcroft for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
# pylint: disable=unused-import
5+
6+
import board
7+
import busio
8+
import usb.core
9+
import adafruit_midi
10+
from adafruit_midi.note_on import NoteOn
11+
from adafruit_midi.note_off import NoteOff
12+
from adafruit_midi.control_change import ControlChange
13+
from adafruit_midi.pitch_bend import PitchBend
14+
import adafruit_usb_host_midi
15+
16+
print("Looking for midi device")
17+
raw_midi = None
18+
while raw_midi is None:
19+
for device in usb.core.find(find_all=True):
20+
try:
21+
raw_midi = adafruit_usb_host_midi.MIDI(device)
22+
print("Found", hex(device.idVendor), hex(device.idProduct))
23+
except ValueError:
24+
continue
25+
26+
# This setup is to use TX pin on Feather RP2040 with USB Type A Host as MIDI out
27+
# You must wire up the needed resistors and jack yourself
28+
# This will forward all MIDI messages from the device to hardware uart MIDI
29+
uart = busio.UART(rx=board.RX, tx=board.TX, baudrate=31250, timeout=0.001)
30+
31+
midi_device = adafruit_midi.MIDI(midi_in=raw_midi, in_channel=0)
32+
midi_uart = adafruit_midi.MIDI(midi_out=uart, midi_in=uart)
33+
34+
35+
while True:
36+
msg = midi_device.receive()
37+
if msg:
38+
print("midi msg:", msg)
39+
midi_uart.send(msg)

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
# SPDX-License-Identifier: MIT
55

66
Adafruit-Blinka
7+
adafruit-circuitpython-usb-host-descriptors
8+
pyusb

0 commit comments

Comments
 (0)