Skip to content

MIDI

MIDI I/O via embedded RtMidi: message types, MidiIn, MidiOut, and the MIDI clock bridge. See the MIDI Guide for usage.

nanosynth.midi

MIDI input and output via embedded RtMidi.

Provides parsed MIDI message types, MidiIn and MidiOut ports, and MIDI clock send (MidiClockOut) and receive (MidiClockIn) bridged to a pattern :class:~nanosynth.patterns.Clock.

This module requires the _midi C extension (built by default with NANOSYNTH_EMBED_MIDI=ON). If unavailable, importing this module raises ImportError.

Basic usage::

from nanosynth.midi import MidiIn, NoteOn

midi = MidiIn(port=0)
midi.on_note_on(lambda msg: print(f"Note {msg.note} vel {msg.velocity}"))
# ...
midi.close()

# Or as context manager:
with MidiIn(port=0) as midi:
    midi.on_cc(lambda msg: print(f"CC {msg.control} = {msg.value}"))
    input("Press Enter to quit...")

NoteOn dataclass

MIDI Note On message.

NoteOff dataclass

MIDI Note Off message.

ControlChange dataclass

MIDI Control Change (CC) message.

PitchBend dataclass

MIDI Pitch Bend message.

Value range is 0--16383, with 8192 as center (no bend).

ProgramChange dataclass

MIDI Program Change message.

Aftertouch dataclass

MIDI Channel Pressure (channel aftertouch) message.

PolyAftertouch dataclass

MIDI Polyphonic Key Pressure (per-note aftertouch) message.

SongPosition dataclass

MIDI Song Position Pointer, in sixteenth notes (0--16383).

TimingClock dataclass

MIDI Timing Clock tick (24 per quarter note).

Start dataclass

MIDI Start (transport).

Continue dataclass

MIDI Continue (transport).

Stop dataclass

MIDI Stop (transport).

MidiIn

MIDI input port.

Args: port: Port to open. None opens a virtual port, int opens by index, str matches by name. receive_clock: Deliver Timing Clock ticks (TimingClock), which RtMidi drops by default. Transport messages arrive either way.

Raises: ImportError: If the _midi C extension is not available. RuntimeError: If the requested port cannot be opened.

close

close() -> None

Close the MIDI input port.

on

on(
    msg_type: type[M], callback: Callable[[M], None]
) -> None

Register a handler for one message type, e.g. on(ProgramChange, f).

off

off(
    msg_type: type[M], callback: Callable[[M], None]
) -> None

Remove a handler registered with :meth:on. Unknown handlers are ignored.

on_note_on

on_note_on(callback: Callable[[NoteOn], None]) -> None

Register a handler for Note On messages.

on_note_off

on_note_off(callback: Callable[[NoteOff], None]) -> None

Register a handler for Note Off messages.

on_cc

on_cc(callback: Callable[[ControlChange], None]) -> None

Register a handler for Control Change messages.

on_pitch_bend

on_pitch_bend(
    callback: Callable[[PitchBend], None],
) -> None

Register a handler for Pitch Bend messages.

off_note_on

off_note_on(callback: Callable[[NoteOn], None]) -> None

Remove a Note On handler.

off_note_off

off_note_off(callback: Callable[[NoteOff], None]) -> None

Remove a Note Off handler.

off_cc

off_cc(callback: Callable[[ControlChange], None]) -> None

Remove a Control Change handler.

off_pitch_bend

off_pitch_bend(
    callback: Callable[[PitchBend], None],
) -> None

Remove a Pitch Bend handler.

list_ports staticmethod

list_ports() -> list[str]

Return a list of available MIDI input port names.

MidiOut

MIDI output port.

Args: port: Port to open. None opens a virtual port, int opens by index, str matches by name.

Example::

with MidiOut("Synth") as out:
    out.send(ProgramChange(channel=0, program=5))
    out.send(NoteOn(channel=0, note=60, velocity=100))

send

send(msg: MidiMessage) -> None

Encode and send one message.

send_bytes

send_bytes(data: bytes) -> None

Send one raw MIDI message (e.g. SysEx) unvalidated.

close

close() -> None

Close the MIDI output port.

list_ports staticmethod

list_ports() -> list[str]

Return a list of available MIDI output port names.

MidiClockOut

Send MIDI Timing Clock and transport, following a pattern Clock.

Ticks run on a daemon thread at PPQN per beat of clock.bpm; tempo changes apply from the next tick. :meth:start sends Start and begins ticking on the clock's next beat; :meth:stop sends Stop.

Args: midi_out: Destination port. clock: Tempo source.

running property

running: bool

Whether the tick thread is active.

start

start() -> None

Send Start and begin ticking on the clock's next beat.

stop

stop() -> None

Stop ticking and send Stop.

MidiClockIn

Slave a pattern Clock to incoming MIDI Timing Clock.

Sets clock.bpm from the mean tick interval over the last beat, and re-anchors the clock's beat grid on each received beat so quantized players stay in phase with the master. Start resets the grid to beat 0; SongPosition moves it.

The MidiIn must be opened with receive_clock=True.

Args: midi_in: Source port. clock: Clock to drive.

beats property

beats: float

Beats received since the last Start or SongPosition.

close

close() -> None

Stop following the incoming clock.

midi_note_map

midi_note_map(
    midi_in: MidiIn,
    server: Server,
    synthdef_name: str,
    **fixed_params: float,
) -> Callable[[], None]

Map MIDI note-on to synth creation and note-off to gate release.

Creates a synth for each note-on with freq derived from the MIDI note number. On note-off, sends gate=0 to the corresponding synth.

Args: midi_in: The MidiIn instance to listen on. server: The Server to create synths on. synthdef_name: Name of the SynthDef to instantiate. **fixed_params: Additional fixed parameters for all synths.

Returns: A cleanup function that removes the handlers.

midi_cc_map

midi_cc_map(
    midi_in: MidiIn,
    server: Server,
    synth: Synth | int,
    cc_map: dict[int, str],
    *,
    range_min: float = 0.0,
    range_max: float = 1.0,
) -> Callable[[], None]

Map MIDI CC numbers to synth parameters.

Each CC value (0--127) is linearly scaled to [range_min, range_max] and sent as the mapped parameter name.

Args: midi_in: The MidiIn instance to listen on. server: The Server for parameter updates. synth: Target synth (Synth proxy or node ID). cc_map: Mapping of CC number to parameter name. range_min: Output range minimum. range_max: Output range maximum.

Returns: A cleanup function that removes the handler.