# MIDI Bridge vs Generated Python Device APIs in Nallely-MIDI

> Compare MIDI Bridge's raw CC access versus generated Python APIs with semantic mappings and metadata for specific hardware in Nallely MIDI. Understand your options.

- Repository: [dr-schlange/nallely-midi](https://github.com/dr-schlange/nallely-midi)
- Tags: comparison
- Published: 2026-02-28

---

**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`](https://github.com/dr-schlange/nallely-midi/blob/main/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`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/devices/minilogue.py), generated device APIs are produced by the [`midi_module_generator.py`](https://github.com/dr-schlange/nallely-midi/blob/main/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`](https://github.com/dr-schlange/nallely-midi/blob/main/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.

```python
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`](https://github.com/dr-schlange/nallely-midi/blob/main/virtual_module_autogen.py) parses doc-strings and builds an AST to inject `VirtualParameter` definitions into target classes, while [`midi_module_generator.py`](https://github.com/dr-schlange/nallely-midi/blob/main/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.

```python

# 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`](https://github.com/dr-schlange/nallely-midi/blob/main/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`](https://github.com/dr-schlange/nallely-midi/blob/main/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`](https://github.com/dr-schlange/nallely-midi/blob/main/virtual_module_autogen.py) for AST manipulation and [`midi_module_generator.py`](https://github.com/dr-schlange/nallely-midi/blob/main/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`](https://github.com/dr-schlange/nallely-midi/blob/main/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`](https://github.com/dr-schlange/nallely-midi/blob/main/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.