Skip to content

Commit

Permalink
Merge pull request #2 from todbot/main
Browse files Browse the repository at this point in the history
Add timeout parameter, add Feather RP2040 with USB Type A Host example
  • Loading branch information
tannewt authored May 20, 2024
2 parents fa7182a + 401eb87 commit 74fd7f9
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 24 deletions.
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
#
# SPDX-License-Identifier: Unlicense

default_language_version:
# force all unspecified python hooks to run python3.11
python: python3.11

repos:
- repo: https://github.com/python/black
rev: 23.3.0
Expand Down
25 changes: 17 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ or individual libraries can be installed using
`circup <https://github.com/adafruit/circup>`_.



.. todo:: Describe the Adafruit product this library works with. For PCBs, you can also add the
image from the assets folder in the PCB's GitHub repo.

`Purchase one from the Adafruit shop <http://www.adafruit.com/products/>`_

Installing from PyPI
=====================

Expand Down Expand Up @@ -95,8 +89,23 @@ Or the following command to update an existing version:
Usage Example
=============

.. todo:: Add a quick, simple example. It and other examples should live in the
examples folder and be included in docs/examples.rst.
.. code-block:: python
import adafruit_midi
import adafruit_usb_host_midi
print("Looking for midi device")
raw_midi = None
while raw_midi is None:
for device in usb.core.find(find_all=True):
try:
raw_midi = adafruit_usb_host_midi.MIDI(device)
print("Found", hex(device.idVendor), hex(device.idProduct))
except ValueError:
continue
midi_device = adafruit_midi.MIDI(midi_in=raw_midi, in_channel=0)
Documentation
=============
Expand Down
64 changes: 59 additions & 5 deletions adafruit_usb_host_midi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Author(s): Scott Shawcroft
"""

import usb.core
import adafruit_usb_host_descriptors

__version__ = "0.0.0+auto.0"
Expand All @@ -19,12 +20,24 @@

DIR_IN = 0x80


class MIDI:
def __init__(self, device):
"""
Stream-like MIDI device for use with ``adafruit_midi`` and similar upstream
MIDI parser libraries.
:param device: a ``usb.core.Device`` object which implements
``read(endpoint, buffer)`` and ``write(endpoint,buffer)``
:param float timeout: timeout in seconds to wait for read or write operation
to succeeds. Default to None, i.e. reads and writes will block.
"""

def __init__(self, device, timeout=None):
self.interface_number = 0
self.in_ep = 0
self.out_ep = 0
self.device = device
self.timeout_ms = round(timeout * 1000) if timeout else 0

self.buf = bytearray(64)
self.start = 0
Expand All @@ -40,14 +53,16 @@ def __init__(self, device):
descriptor_len = config_descriptor[i]
descriptor_type = config_descriptor[i + 1]
if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION:
# pylint: disable=unused-variable
config_value = config_descriptor[i + 5]
# pylint: enable=unused-variable
elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE:
interface_number = config_descriptor[i + 2]
interface_class = config_descriptor[i + 5]
interface_subclass = config_descriptor[i + 6]
midi_interface = interface_class == 0x1 and interface_subclass == 0x3
if midi_interface:
self.interface_number= interface_number
self.interface_number = interface_number

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

def read(self, size):
"""
Read bytes. If ``nbytes`` is specified then read at most that many
bytes. Otherwise, read everything that arrives until the connection
times out. Providing the number of bytes expected is highly recommended
because it will be faster. If no bytes are read, return ``None``.
.. note:: When no bytes are read due to a timeout, this function returns ``None``.
This matches the behavior of `io.RawIOBase.read` in Python 3, but
differs from pyserial which returns ``b''`` in that situation.
:return: Data read
:rtype: bytes or None
"""

if self._remaining == 0:
self._remaining = self.device.read(self.in_ep, self.buf) - 1
self.start = 1
try:
n = self.device.read(self.in_ep, self.buf, self.timeout_ms)
self._remaining = n - 1
self.start = 1
except usb.core.USBTimeoutError:
pass
size = min(size, self._remaining)
b = self.buf[self.start:self.start + size]
b = self.buf[self.start : self.start + size]
self.start += size
self._remaining -= size
return b

def readinto(self, buf):
"""Read bytes into the ``buf``. Read at most ``len(buf)`` bytes.
:return: number of bytes read and stored into ``buf``
:rtype: int or None (on a non-blocking error)
"""
b = self.read(len(buf))
n = len(b)
if n:
buf[:] = b
return n

def __repr__(self):
# also idProduct/idVendor for vid/pid
return (
"MIDI Device "
+ str(self.device.manufacturer)
+ "/"
+ str(self.device.product)
)
6 changes: 0 additions & 6 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,9 @@ Table of Contents
.. toctree::
:caption: Tutorials

.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave
the toctree above for use later.

.. toctree::
:caption: Related Products

.. todo:: Add any product links here. If there are none, then simply delete this todo and leave
the toctree above for use later.

.. toctree::
:caption: Other Links

Expand Down
14 changes: 9 additions & 5 deletions examples/usb_host_midi_simpletest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import audiobusio
import board
import synthio
import adafruit_midi
import adafruit_usb_host_midi
import usb.core
import wm8960
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
import wm8960
import adafruit_usb_host_midi

print("Looking for midi device")
raw_midi = None
Expand All @@ -26,7 +26,9 @@
# This setup is for the headphone output on the iMX RT 1060 EVK.
dac = wm8960.WM8960(board.I2C())
dac.start_i2s_out()
audio = audiobusio.I2SOut(board.AUDIO_BCLK, board.AUDIO_SYNC, board.AUDIO_TXD, main_clock=board.AUDIO_MCLK)
audio = audiobusio.I2SOut(
board.AUDIO_BCLK, board.AUDIO_SYNC, board.AUDIO_TXD, main_clock=board.AUDIO_MCLK
)
synth = synthio.Synthesizer(sample_rate=44100)
audio.play(synth)

Expand All @@ -41,7 +43,9 @@
print("noteOn: ", msg.note, "vel:", msg.velocity)
synth.press(note)
pressed[msg.note] = note
elif (isinstance(msg,NoteOff) or (isinstance(msg,NoteOn) and msg.velocity==0)) and msg.note in pressed:
elif (
isinstance(msg, NoteOff) or (isinstance(msg, NoteOn) and msg.velocity == 0)
) and msg.note in pressed:
print("noteOff:", msg.note, "vel:", msg.velocity)
note = pressed[msg.note]
synth.release(note)
Expand Down
39 changes: 39 additions & 0 deletions examples/usb_host_midi_simpletest_rp2040usbfeather.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: Copyright (c) 2023 Scott Shawcroft for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense
# pylint: disable=unused-import

import board
import busio
import usb.core
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
from adafruit_midi.control_change import ControlChange
from adafruit_midi.pitch_bend import PitchBend
import adafruit_usb_host_midi

print("Looking for midi device")
raw_midi = None
while raw_midi is None:
for device in usb.core.find(find_all=True):
try:
raw_midi = adafruit_usb_host_midi.MIDI(device)
print("Found", hex(device.idVendor), hex(device.idProduct))
except ValueError:
continue

# This setup is to use TX pin on Feather RP2040 with USB Type A Host as MIDI out
# You must wire up the needed resistors and jack yourself
# This will forward all MIDI messages from the device to hardware uart MIDI
uart = busio.UART(rx=board.RX, tx=board.TX, baudrate=31250, timeout=0.001)

midi_device = adafruit_midi.MIDI(midi_in=raw_midi, in_channel=0)
midi_uart = adafruit_midi.MIDI(midi_out=uart, midi_in=uart)


while True:
msg = midi_device.receive()
if msg:
print("midi msg:", msg)
midi_uart.send(msg)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
# SPDX-License-Identifier: MIT

Adafruit-Blinka
adafruit-circuitpython-usb-host-descriptors
pyusb

0 comments on commit 74fd7f9

Please sign in to comment.