MIDI Bridge vs Generated Python Device APIs in Nallely-MIDI

The MIDI Bridge provides raw, low-level access to all 128 MIDI CCs through generic virtual ports, while generated Python device APIs offer semantic, named parameter mappings with full metadata for specific hardware synthesizers.

The nallely-midi repository implements two complementary strategies for Python-based MIDI device control. While both the MIDIBridge and generated device classes inherit from the core MidiDevice class, they serve opposite ends of the abstraction spectrum—generic pass-through versus hardware-specific semantics.

Core Architectural Differences

MIDI Bridge: Generic Virtual Port Implementation

Located in nallely/core/bridge_device.py, the MIDIBridge class acts as a low-level abstraction layer designed for rapid prototyping. It creates a single virtual I/O port using mido.open_ioport, explicitly disabling automatic device connection via autoconnect=False. The bridge exposes eight auto-generated sections (X0Section through X7Section), each containing ModuleParameter instances that map directly to MIDI CC numbers.

Each section includes:

  • notes via ModulePadsOrKeys
  • pitchwheel data via ModulePitchwheel
  • 16 ModuleParameter objects (cc_1 through cc_128) addressing controllers 0–127

Unlike generated devices, the bridge carries no semantic metadata—parameters lack defined ranges, conversion policies, or default values, exposing only raw CC numbers for immediate access.

Generated Device APIs: Semantic Hardware Mapping

Found in files like nallely/devices/minilogue.py, generated device APIs are produced by the midi_module_generator.py pipeline. These classes parse CSV or YAML specifications to create named sections such as GeneralSection or LfoSection, with ModuleParameter objects that include full metadata: CC numbers, valid ranges, initial values, accepted string values, and conversion policies. Each generated class targets specific hardware, opening virtual or physical ports defined by device names like "minilogue _ SOUND".

Source Code Implementation Details

Bridge Device Structure

The MIDIBridge implementation hard-codes eight sections (X0–X7) in bridge_device.py. Properties on the bridge class forward attribute access to self.modules.X0, creating a flat namespace for raw MIDI manipulation. Incoming and outgoing traffic share the same port instance (self.inport = self.outport), enabling immediate reflection of transmitted values back into the parameter attributes.

from nallely.core.bridge_device import MIDIBridge
import mido

# Create a virtual port named "MyBridge0"

bridge = MIDIBridge(device_name="MyBridge0")

# Send raw CC 74 (filter cutoff) with value 100

bridge.outport.send(mido.Message('control_change', control=74, value=100))

# Access the reflected value via section X4

print(bridge.X4.cc_74.value)  # Returns 100

Code Generation Pipeline

Two files orchestrate device API creation: virtual_module_autogen.py parses doc-strings and builds an AST to inject VirtualParameter definitions into target classes, while midi_module_generator.py reads hardware specifications and writes complete Python modules. The entry-point function generate_api(input_path, output_path) transforms CSV or YAML device configs into typed Python classes.


# Generated device usage from nallely/devices/minilogue.py

from nallely.devices.minilogue import Minilogue

synth = Minilogue()
synth.filter.cutoff = 127  # Maps to CC 43 with validation

Practical Usage Comparison

Rapid Prototyping and Routing: Choose the MIDI Bridge when building custom mapping layers, analyzing raw MIDI streams, or developing software that must adapt to unknown hardware. The instant availability of all 128 CCs across eight sections eliminates the need for device-specific code maintenance.

Production Hardware Integration: Choose Generated Device APIs when targeting specific synthesizers. The semantic naming (e.g., lfo.rate versus raw cc_80), built-in range validation, and type hints prevent errors and make automation scripts self-explanatory.

Decision Guide

  • Select the MIDI Bridge for generic MIDI routing, OSC bridging, or when working with hardware lacking existing device definitions in the repository.
  • Select Generated APIs when requiring type safety, documentation, and semantic clarity for known synthesizers like the KORG Minilogue.
  • Both approaches share the same underlying MidiDevice architecture, allowing migration from raw CC access to semantic parameters as project requirements solidify.

Summary

  • The MIDI Bridge (nallely/core/bridge_device.py) provides raw CC access through eight generic sections (X0–X7) without metadata, ideal for prototyping and routing.
  • Generated Device APIs (nallely/devices/*.py) offer semantic parameter names and validation, created by midi_module_generator.py from CSV/YAML specs.
  • Both implementations inherit from MidiDevice but differ in abstraction level: generic virtual ports versus hardware-specific semantic mappings.
  • The bridge uses a shared input/output port via mido.open_ioport with autoconnect=False, while generated devices open named ports specific to hardware.
  • The code generation pipeline relies on virtual_module_autogen.py for AST manipulation and midi_module_generator.py for specification parsing.

Frequently Asked Questions

Can the MIDI Bridge communicate with physical hardware?

Yes. While the MIDIBridge creates a virtual port by default, it can route messages to physical devices through MIDI loopback or by connecting the virtual port to hardware inputs using system MIDI routing utilities. The bridge handles the protocol layer via mido.open_ioport, regardless of whether the endpoint is virtual or physical.

How does the MIDI Bridge organize the 128 MIDI CCs?

The bridge divides controllers into eight sections (X0 through X7) defined in bridge_device.py. Each section contains exactly 16 ModuleParameter instances labeled cc_1 through cc_128, mapping directly to MIDI CC numbers. This structure provides immediate access to any controller change message without requiring device-specific configuration files.

What input formats does the device API generator support?

The midi_module_generator.py pipeline accepts both CSV and YAML specifications. These files define hardware control surfaces including section names, parameter CC numbers, valid ranges, initial values, and conversion policies. The generate_api() function processes these inputs to produce typed Python modules in the nallely/devices/ directory.

Is it possible to migrate from the MIDI Bridge to a generated API?

Yes. Since both implementations share the MidiDevice base class, migration primarily involves replacing raw CC access (e.g., bridge.X2.cc_43) with semantic attributes (e.g., synth.filter.cutoff). The underlying ModuleParameter and VirtualParameter architecture remains consistent, allowing MIDI message handling code to remain unchanged while gaining type safety and inline documentation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →