subsequence.midi_utils

MIDI device plumbing — discovering, opening, and registering hardware ports.

Provides interactive/automatic output and input device selection, the multi-device registry used by the sequencer, and the bank_select() helper for addressing synth banks beyond the first 128 programs.

Module Contents

class subsequence.midi_utils.MidiDeviceRegistry[source]

Ordered registry of named MIDI ports (output or input).

Devices are stored in insertion order. Index 0 is always the first (or only) device — the default for all APIs that do not specify a device. Devices can be looked up by integer index or by name string. None always resolves to index 0.

The registry is intended to be append-only once playback has started. All registered port objects must already be open.

Create an empty registry; populate it with add().

add(name: str, port: Any, latency_ms: float = 0.0) int[source]

Register a port under name. Returns the assigned integer index.

latency_ms is the device’s physical output latency (non-negative); see set_latency().

close_all() None[source]

Close every registered port and clear the registry.

get(device: DeviceId = None) Any | None[source]

Return the port for device, or None if the registry is empty.

None → index 0. int → direct index. str → name lookup. Returns None if the device cannot be resolved (empty registry, out-of-range index, unknown name).

index_of(device: DeviceId = None) int[source]

Resolve device to an integer index. Returns 0 for None. Returns -1 if the name is unknown or the index is out of range.

latency_of(device: DeviceId = None) float[source]

Return the latency (ms) for device, or 0.0 if it cannot be resolved.

Defensive on the hot dispatch path: an unknown device yields 0.0 rather than raising, so a stray event can never crash the send loop.

max_latency() float[source]

Return the largest latency across all registered devices (0.0 if empty).

replace(index: int, port: Any) None[source]

Replace the port object at index without changing the name or index mapping.

Used by the backward-compat midi_out/midi_in setters to allow test code to inject a fake port after the registry has been populated. Raises IndexError if index is out of range.

set_latency(device: DeviceId, latency_ms: float) None[source]

Set the physical output latency (milliseconds) for device.

latency_ms must be non-negative — a device cannot sound before it is triggered, so a negative output latency is meaningless. Raises ValueError for a negative value or an unknown device.

subsequence.midi_utils.bank_select(bank: int) Tuple[int, int][source]

Convert a 14-bit MIDI bank number to (MSB, LSB) for use with p.program_change().

MIDI bank select uses two control-change messages: CC 0 (Bank MSB) and CC 32 (Bank LSB). Together they encode a 14-bit bank number in the range 0–16,383:

MSB = bank // 128 (upper 7 bits, sent on CC 0) LSB = bank % 128 (lower 7 bits, sent on CC 32)

Parameters:

bank – Integer bank number, 0–16,383. Values outside this range are clamped.

Returns:

(msb, lsb) tuple, each value in 0–127.

Example

msb, lsb = subsequence.bank_select(128)   # → (1, 0)
p.program_change(48, bank_msb=msb, bank_lsb=lsb)
subsequence.midi_utils.select_input_device(device_name: str | None = None, callback: Callable | None = None) Tuple[str | None, Any | None][source]

Select and open a MIDI input device.

If device_name is provided, attempts to open exactly that device. If device_name is None, returns None without prompting (input is optional/advanced). To enforce input, the caller should check the return value.

A named device that is not present raises ValueError rather than falling back to another input: MIDI input drives clock-follow and live note capture, so silently listening to the wrong device would desynchronise or mis-record a performance.

Returns:

A tuple of (device_name, midi_in_object), or (None, None) when no name was given or the device failed to open.

Raises:

ValueError – If device_name is not among the available inputs.

subsequence.midi_utils.select_output_device(device_name: str | None = None) Tuple[str | None, Any | None][source]

Select and open a MIDI output device.

If device_name is provided, attempts to open that specific device. If device_name is None, auto-discovers available devices:

  • If exactly one device exists, it is selected automatically.

  • If multiple devices exist, prompts the user to choose one from the console.

  • If no devices exist, logs an error and returns None.

Returns:

A tuple of (device_name, midi_out_object) or (None, None) on failure.