Part of the multi-midi documentation
© 2025 Harm Lammers
For questions or contributions open an issue or start a discussion on the GitHub repository.
This document provides a detailed description of the most important interface classes and functions in the multi-midi library.
Table of Contents
Asynchronous MIDI I/O manager for multi-port UART, PIO and USB MIDI.
This class has been defined with the @singleton, so only a single MidiManager class can exist and initiating it again return that first instance.
Class Attributes
out_ports | list[OutPortUART|OutPortPIO|OutPortUSB] | List with all single OUT port handler instances. |
Class Methods
MidiManager.set_usb_strings()
Set USB device’s manufacturer name, product name and/or serial number.
Method Arguments
manufacturer_str | str|bytes|bytearray|None, optional | Manufacturer name to be assigned to the USB Device Defaults to None (copy manufacturer name from MicroPython’s built-in driver) |
product_str | str|bytes|bytearray|None, optional | Product name to be assigned to the USB Device Defaults to None (copy product name from MicroPython’s built-in driver) |
serial_str | str|bytes|bytearray|None, optional | Unique serial number to be assigned to the USB Device Defaults to None (copy serial number from MicroPython’s built-in driver) |
Tip
Use machine.unique_id() to get a byte string with a unique identifier of a board/SoC to use as serial_str.
MidiManager.usb_is_active()
asyncio awaitable which releases once the USB host activated the connection or returns immediately if USB is not initiated.
MidiManager.assign_callbacks()
Assign callback functions to be called when receiving MIDI messages.
Method Arguments
cb_rt | Callable, optional | Callback for MIDI Real-Time messages Signature: f(port_id: int, byte: int) -> None.Defaults to None |
cb_sys_ex | Callable, optional | Callback for MIDI SysEx messages. Signature: f(port_id: int, buf: bytearray, num_bytes: int) -> NoneDefaults to None |
cb_data | Callable, optional | Callback for regular MIDI messages (all except Real-Time and SysEx). Signature: f(port_id: int, byte_0: int, byte_1: int = 0, byte_2: int = 0) -> NoneDefaults to None |
MidiManager.add_uart_in()
Set up hardware MIDI IN port based on a standard set of RP2040/RP2350 UART pins, which gets the next available IN port ID assigned.
Only call this function once for a given uart_id number.
Method Arguments
uart_id | int | UART to be used (0: UART0, 1: UART1). |
tx_pin | int|None, optional | GPIO number to be used for transmitting UART Defaults to None (0 for UART0, 4 for UART1) |
rx_pin | int|None, optional | GPIO number to be used for receiving UART Defaults to None (1 for UART0, 5 for UART1) |
Method Returns
int | Assigned IN port ID (attribute in_ports index number). |
Note
- UART TX and RX come in pairs, so it is set up the first time an UART ID is used for either adding an IN port or an OUT port.
- If you’d like to adjust the TX and RX pins of a UART, you should do so the first time you assign an IN or OUT port to that UART.
- UART0 can only be mapped to GPIO 0/1, 12/13 and 16/17; UART1 can only be mapped to GPIO 4/5 and 8/9.
MidiManager.add_pio_in()
Set up hardware MIDI IN port based on a RP2040/RP2350 PIO state machine, which gets the next available IN port ID assigned.
Only call this function once for a given pio_id number.
Method Arguments
pio_id | int | PIO state machine ID to be used (RP2040: 0 to 7, RP2350: 0 to 11). |
pin | int | GPIO number to be used. |
Method Returns
int | Assigned IN port ID (attribute in_ports index number). |
MidiManager.add_usb_in()
Set up USB MIDI IN port based on a ‘virtual cable’, which gets the next available IN port ID assigned.
Method Arguments
cable | int | USB virtual IN cable number to be assigned. |
port_name | str|bytes|bytearray|None, optional | Names to be shown by the host. The same name is used for IN and OUT ports with the same virtual cable number (overwrites previously defined name, if not None).Defaults to None |
external_jack | bool|None, optional | Whether to set up an ‘external jack’ interface This setting applies to both IN and OUT ports with the same virtual cable number (overwrites previous setting). Defaults to None (becomes True if never set) |
Method Returns
int | Assigned IN port ID (attribute in_ports index number). |
Note
- Avoid skipping cable numbers because the missing ones will be set up as well and show up on the host.
- The Port name needs to be at least 2 characters long (otherwise a Windows host would fail to recognize the device) and is not shown on a Windows host.
MidiManager.add_uart_out()
Set up hardware MIDI OUT port based on a standard set of RP2040/RP2350 UART pins, which gets the next available OUT port ID assigned.
Only call this function once for a given uart_id number.
Method Arguments
uart_id | int | UART to be used (0: UART0, 1: UART1). |
tx_pin | int|None, optional | GPIO number to be used for transmitting UART. Defaults to None (0 for UART0, 4 for UART1) |
rx_pin | int|None, optional | GPIO number to be used for receiving UART. Defaults to None (1 for UART0, 5 for UART1) |
running_status | bool, optional | Set whether to apply running status when sending out MIDI data. Defaults to True |
Method Returns
int | Assigned OUT port ID (attribute out_ports index number). |
Note
- UART TX and RX come in pairs, so it is set up the first time an UART ID is used for either adding an IN port or an OUT port.
- If you’d like to adjust the TX and RX pins of a UART, you should do so the first time you assign an IN or OUT port to that UART.
- UART0 can only be mapped to GPIO 0/1, 12/13 and 16/17; UART1 can only be mapped to GPIO 4/5 and 8/9.
MidiManager.add_pio_out()
Set up hardware MIDI OUT port based on a RP2040/RP2350 PIO state machine, which gets the next available OUT port ID assigned.
Only call this function once for a given pio_id number.
Method Arguments
pio_id | int | PIO state machine ID to be used (RP2040: 0 to 7, RP2350: 0 to 11). |
pin | int | GPIO number to be used. |
running_status | bool, optional | Set whether to apply running status when sending out MIDI data. Defaults to True |
Method Returns
int | Assigned OUT port ID (attribute out_ports index number). |
MidiManager.add_usb_out()
Set up USB MIDI OUT port based on a ‘virtual cable’, which gets the next available OUT port ID assigned.
Method Arguments
cable | int | USB virtual OUT cable number to be assigned. |
port_name | str|bytes|bytearray|None, optional | Names to be shown by the host. The same name is used for IN and OUT ports with the same virtual cable number (overwrites previously defined name, if not None).Defaults to None |
external_jack | bool|None, optional | Whether to set up an ‘external jack’ interface. This setting applies to both IN and OUT ports with the same virtual cable number (overwrites previous setting) Defaults to None (becomes True if never set) |
Method Returns
int | Assigned OUT port ID (attribute out_ports index number). |
Note
- Avoid skipping cable numbers because the missing ones will be set up as well and show up on the host.
- The Port name needs to be at least 2 characters long (otherwise a Windows host would fail to recognize the device) and is not shown on a Windows host.
MidiManager.set_midi_real_time_routing()
Set up fast-track routing of MIDI Real-Time messages to the destination MIDI OUT ports when received from the source MIDI IN port.
Method Arguments
source | int | Port ID of the MIDI IN port to be used as source for MIDI Real-Time routing. |
destinations | list|tuple | Port IDs of the MIDI OUT ports to be used as destination for MIDI Real-Time routing. |
MidiManager.run()
asyncio awaitable to start the midi manager: start the USB driver (if USB MIDI ports were defined) and schedule tasks to run each set up MIDI ports.
MidiManager.deinit()
Deinitialize all processes and tasks related to set up MIDI ports (including the USB Midi singleton if applicable).
Single port handlers for hardware UART, PIO UART or USB MIDI virtual cable based MIDI IN port.
Use MidiPort.add_*_in() to set up (do not instance directly).
Class Methods
InPort*.set_type_filter()
Set whether to block a MIDI message type.
Method Arguments
msg_type | int | MIDI message type (or status byte) to be (un)blocked. For Channel Voice or Channel Common type message the channel part of a status byte is ignored. |
block | bool | True to block, False to unblock. |
InPort*.set_all_type_filters()
Set for all MIDI message types whether to be blocked.
Method Arguments
block | bool | True to block, False to unblock. |
InPort*.set_channel_filter()
Set whether to block a MIDI channel.
Method Arguments
channel | int | MIDI channel (status byte) to be (un)blocked If a status byte is provided, only its least significant nibble is taken. |
block | bool | True to block, False to unblock. |
InPort*.set_all_channel_filters()
Set for all MIDI channels whether to be blocked.
Method Arguments
block | bool | True to block, False to unblock. |
InPort*.set_cc_filter()
Set whether to block a MIDI Control Change (CC) number.
Method Arguments
cc_number | int | MIDI Control Change number to be (un)blocked. |
block | bool | True to block, False to unblock. |
InPort*.set_all_cc_filters()
Set for all MIDI Control Change (CC) numbers whether to be blocked.
Method Arguments
block | bool | True to block, False to unblock. |
Single port handler for hardware UART, PIO UART or USB MIDI virtual cable based MIDI OUT port
Use MidiPort.add_*_out() to set up (do not instance directly)
Class Methods
OutPort*.write_real_time()
Queue a MIDI Real-Time message to be sent to MIDI OUT port, which will be sent as quick as possible.
Method Arguments
byte | int | Single-byte MIDI Real-Time message to be sent. |
OutPort*.write_sysex()
Queue a block of MIDI SysEx data to be sent to MIDI OUT port.
Method Arguments
sysex_bytes | bytearray|bytes|memoryview | SysEx data buffer from which to be sent. |
num_bytes | int | Number of bytes to be sent. |
OutPort*.write_data()
Queue a MIDI message to be sent to MIDI OUT port.
Do not use for System Real-Time and SysEx messages.
Method Arguments
byte_0 | int | First byte of the MIDI message to be sent. |
byte_1 | int, optional | Second byte of the MIDI message to be sent. Defaults to 0 |
byte_2 | int, optional | Third byte of the MIDI message to be sent. Defaults to 0 |
MIDI filter supporting message type, channel and CC number filtering.
MidiFilter instances could be used to filter outgoing MIDI messages before sending them to a MIDI OUT port. The classes InPortUART, InPortPIO and InPortUSB inherit this class to offer the option to filter incoming MIDI messages.
Class Methods
MidiFilter.set_type_filter()
Set whether to block a MIDI message type.
Method Arguments
msg_type | int | MIDI message type (or status byte) to be (un)blocked. For Channel Voice or Channel Common type message the channel part of a status byte is ignored. |
block | bool | True to block, False to unblock. |
MidiFilter.set_all_type_filters()
Set for all MIDI message types whether to be blocked.
Method Arguments
block | bool | True to block, False to unblock. |
MidiFilter.set_channel_filter()
Set whether to block a MIDI channel.
Method Arguments
channel | int | MIDI channel (status byte) to be (un)blocked If a status byte is provided, only its least significant nibble is taken. |
block | bool | True to block, False to unblock. |
MidiFilter.set_all_channel_filters()
Set for all MIDI channels whether to be blocked.
Method Arguments
block | bool | True to block, False to unblock<./td> |
MidiFilter.set_cc_filter()
Set whether to block a MIDI Control Change (CC) number.
Method Arguments
cc_number | int | MIDI Control Change number to be (un)blocked. |
block | bool | True to block, False to unblock. |
MidiFilter.set_all_cc_filters()
Set for all MIDI Control Change (CC) numbers whether to be blocked.
Method Arguments
block | bool | True to block, False to unblock. |
MidiFilter.filter()
Determine whether to block a MIDI message.
Method Arguments
msg | bytearray|bytes|memoryview | At least the first 2-bytes of the MIDI message to be assessed. |
Method Returns
bool | True if the MIDI message is to be blocked, otherwise False. |
Decorator function which restricts a class to a single instance: it creates the instance only on first-time instantiation and returns that instance on each consecutive instantiation.
asyncio compatible logger which prints the message and can be configured to write to file and/or monitor.
This class has been defined with the @singleton, so only a single Log class can exist and initiating it again return that first instance.
The dependency on the display module is optional: the Log class also works if display.py is missing, but only for logging to file. For logging to display this module is set up to work with the Cybo-Drummer hardware (https://github.com/HLammers/cybo-drummer), which uses an ILI9225 display, connected as defined in the constant definitions below. To make this work with another screen the _DisplayMonitor class needs to be rewritten to use different display drivers.
Class Arguments
to_file | bool, optional | Write logs to log file if True.Defaults to False |
to_monitor | bool, optional | Write logs to display if True.Defaults to True (which is overruled if the display import failed) |
file_name | str, optional | File name for log file. Defaults to /log.txt |
Class Methods
Log.write()
Write message to logger buffer to be logged synchronously.
Method Arguments
msg | str|bytes|bytearray | Message to write to logger. |
Log.sync_write()
Write message to logger synchronously (flushes to screen immediately and blocks until completed).
Method Arguments
msg | str|bytes|bytearray | Message to write to logger. |
Log.run()
asyncio task to read from log buffer and print, write to file and/or show on display whenever data is available.
Log.deinit()
Stop second core context, switch off display and cancel display monitor task.
Dummy stream reader for testing purposes, simulating data coming from a hardware MIDI IN port.
Designed to work as replacement for a micropython.RingIO, asyncio.StreamReader or other stream protocol compatible object from which data is read using a readinto() method.
Class Methods
DummyStreamReaderUART.readinto()
Stream protocol compatible method to read test data into buffer.
Method Arguments
buf | bytearray | Buffer to read into. |
Method Returns
int | number of bytes read. |
Dummy data generator simulating data coming from a USB MIDI IN port.
Class Methods
DummyReaderUSB.next_packet()
Returns next 4-byte packet of test data.
Method Returns
bytearray(4) | USB MIDI packet. |
Dummy data generator supplying 3-byte MIDI messages with an option to also generate SysEx data blocks.
Class Arguments
include_sysex | bool, optional | Generates also SysEx blocks if set to True.Defaults to False |
Class Methods
DummyBufferStream.next_message()
Returns next 3-byte MIDI message (1-byte and 2-byte messages are appended with zero value bytes) or SysEx block (if enabled).
Method Returns
bytearray | 3-byte USB MIDI message or variable-length SysEx block (if enabled). |
MIDI monitor which uses the Log singleton class to log MIDI messages.
This class has been defined with the @singleton, so only a single MidiMonitor class can exist and initiating it again return that first instance.
Class Arguments
long | bool | If True include HEX representation of the message.Defaults to True |
Class Methods
MidiMonitor.write()
Generate MIDI monitor string and send to logger.
Do not use for SysEx data bytes.
Method Arguments
port_id | int | Port number. |
byte_0 | int | First byte of the MIDI message. |
byte_1 | int | Second byte of the MIDI message. |
byte_2 | int | Third byte of the MIDI message. |
Context manager to asynchronously run functions or methods on the second core (using _threads).
This class has been defined with the @singleton, so only a single Context class can exist and initiating it again return that first instance.
Class Arguments
queue_size | int, optional | Size of the deque buffer to store tasks to be queued to be run on the second core. Defaults to 10 |
Class Methods
Context.assign()
asyncio awaitable to queue function to be run on the second core.
Releases once the function completed running.
Method Arguments
func | Callable | Function to be queued. |
*args | Positional arguments to pass to func. | |
**kwargs | Keyword arguments to pass to func. |
Method Returns
Any | Return value(s) from func. |
Context.deinit()
Stop second core context.
Decorator function to time a function and log the result using the Log class (if available).
Gives the average of each time the function was called.
FrameBuffer based asynchronous display driver implementation for ILI9225 on RP2040/RP2350 based boards (using DMA).
This display driver doesn’t include an option to change the screen orientation, because it is optimized to be used by Cybo-Drummer (https://github.com/HLammers/cybo-drummer), which uses the display in a landscape orientation.
This class inherits all framebuf.FrameBuffer methods.
Class Arguments
spi_id | int | ID of the SPI controller to be used (0: SPI0, 1: SPI1). |
baudrate | int | SCK clock rage (Hz). |
rs_pin | machine.Pin | Pin object for the RS (register selection) pin. |
rst_pin | machine.Pin | Pin object for the RST (reset) pin. |
led_pin | machine.Pin | Pin object for the LED (backlight) pin. |
sck | machine.Pin|None, optional | Pin object for the CLK (serial clock) pin. Defaults to None (18 for SPI0, 10 for SPI1) |
mosi | machine.Pin|None, optional | Pin object for the MOSI (master output slave input) / SDI pin. Defaults to None (19 for SPI0, 11 for SPI1) |
miso | machine.Pin|None, optional | Pin object for the MISO (master input slave output) pin. Defaults to None (16 for SPI0, 8 for SPI1) |
rp2350 | bool | True to set up for RP2350 based board, False for RP2040 based board.Defaults to True (RP2350) |
Note
- This display driver assumes that the CS (chip select) pin is hard-wired to +3.3V.
- SPI0 can only be mapped to GPIO 2/3/0, 6/7/4 or 18/19/16; SPI1 can only be mapped to GPIO 10/11/8 or 14/15/12.
Class Methods
Display.on()
Turn display on (including backlight).
Display.off()
Turn display off (including backlight).
Display.async_show()
asyncio awaitable to flush display buffer to screen asynchronously.
Display.show()
Flush display buffer to screen (blocks until completed).
Display.deinit()
Turn screen off, close DMA and deinitialize SPI controller.