Multi-Device Setup
Conductor supports connecting and mapping multiple MIDI devices simultaneously (v4.19.0+, ADR-009). Each device can have independent mappings, and events are routed to the correct device-specific rules automatically.
Defining Devices
Use [[devices]] sections in your config.toml to define device identities:
[[devices]]
alias = "pads"
matchers = [{ type = "NameContains", value = "Mikro" }]
[[devices]]
alias = "keys"
matchers = [{ type = "NameContains", value = "Launchpad" }]
Each device needs:
- alias: A short name used in trigger mappings (e.g.,
"pads","keys") - matchers: One or more rules for matching physical MIDI ports
Matcher Types
| Matcher | Description | Example |
|---|---|---|
ExactName | Exact port name match | { type = "ExactName", value = "Maschine Mikro MK3" } |
NameContains | Substring match | { type = "NameContains", value = "Mikro" } |
NameRegex | Regex pattern match | { type = "NameRegex", value = "Mikro.*MK[23]" } |
UsbIdentifier | USB vendor/product ID | { type = "UsbIdentifier", vendor_id = 0x17CC, product_id = 0x1600 } |
CoreMidiUniqueId | macOS CoreMIDI unique ID | { type = "CoreMidiUniqueId", value = 12345 } |
Matchers have a specificity ordering for priority: CoreMidiUniqueId > UsbIdentifier > UsbTopology > ExactName > PlatformId > NameContains > NameRegex. When multiple identities could match a port, the most specific matcher wins.
Device-Specific Mappings
Add a device field to any trigger to restrict it to a specific device:
[[modes]]
name = "Default"
# Only triggers from the "pads" device
[[modes.mappings]]
trigger = { type = "Note", note = 36, device = "pads" }
action = { type = "Launch", app = "Finder" }
# Only triggers from the "keys" device
[[modes.mappings]]
trigger = { type = "Note", note = 36, device = "keys" }
action = { type = "Shell", command = "echo hello" }
# Triggers from any device (no device filter)
[[modes.mappings]]
trigger = { type = "Note", note = 60 }
action = { type = "Text", text = "any device" }
Priority Order
When an event arrives, Conductor checks rules in this order:
- Device-specific rules for the current mode (O(1) HashMap lookup)
- Any-device rules for the current mode (linear scan)
- Global device-specific rules
- Global any-device rules
The first match wins.
Hot-Plug Detection
Conductor automatically detects when devices are connected or disconnected (v4.22.0+). New ports are scanned every 5 seconds and matched against your [[devices]] configuration.
GUI Multi-Device Status
The GUI shows all connected devices with status indicators (v4.22.0+):
- Green dot: Active and receiving events
- Yellow dot: Muted (events ignored)
- Red dot: Disconnected
You can mute/unmute individual devices from the GUI.
MCP Multi-Device Tools
Three MCP tools are available for LLM-assisted multi-device management (v4.23.0+):
conductor_list_device_bindings: List all device bindings and their statusconductor_set_device_enabled: Mute/unmute a specific deviceconductor_scan_ports: Trigger a port rescan for hot-plug detection
Migrating from Legacy Config
If you have an existing [device] section, use the migration CLI:
# Preview what would change (dry-run)
conductorctl migrate-config
# Apply the migration (creates .bak backup)
conductorctl migrate-config --write
This converts:
[device]
name = "Mikro MK3"
auto_connect = true
Into:
[[devices]]
alias = "mikro-mk3"
matchers = [{ type = "NameContains", value = "Mikro MK3" }]
Config Validation
Conductor validates multi-device configs (v4.24.0+):
- Device aliases must be unique and non-empty
- Trigger
devicefields must reference a defined device alias - Invalid references produce clear error messages during config load
Complete Example
[[devices]]
alias = "pads"
matchers = [{ type = "NameContains", value = "Mikro" }]
[[devices]]
alias = "faders"
matchers = [{ type = "NameContains", value = "nanoKONTROL" }]
[[modes]]
name = "Production"
[[modes.mappings]]
trigger = { type = "Note", note = 36, device = "pads" }
action = { type = "Keystroke", keys = "space", modifiers = ["cmd"] }
[[modes.mappings]]
trigger = { type = "CC", cc = 0, device = "faders" }
action = { type = "VolumeControl", operation = "Set" }
[[modes]]
name = "Mixing"
[[modes.mappings]]
trigger = { type = "Note", note = 36, device = "pads" }
action = { type = "Keystroke", keys = "m" }
[[global_mappings]]
trigger = { type = "Note", note = 127 }
action = { type = "ModeChange", mode = "Production" }