Skip to content

Add timeout parameter, add Feather RP2040 with USB Type A Host example #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading