# Appendix C · MIDI Routing and Troubleshooting Reference This appendix is the single place to get MIDI *out* of Subsequence and into anything that makes sound — a hardware synth, a DAW, a software instrument, or several at once — and to diagnose it when no sound comes out. [Chapter 0](00-setup) gets you to your first loop; this is the depth it points to. Subsequence makes **no sound of its own**. It sends MIDI to a named **port**, and the thing on the other end makes the sound. So almost every "it doesn't work" turns out to be a routing question: *is the port name right, is anything listening, and is it on the channel you sent to?* The five sections below take those in order, ending with a symptom-to-fix table. ```{note} The runnable blocks here use a mocked MIDI backend (the same one that validates every example in this guide), so the port is the stand-in name `"Dummy MIDI"` and ``mido.get_output_names()`` returns exactly one entry. On your machine that list is your real gear. Blocks that show real hardware names (an IAC bus, a Minilogue) are marked **illustrative** — they're verified for API shape but name *your* ports, so they aren't executed here. ``` (sec-appC-choosing)= ## C.1 How Subsequence chooses an output Every `Composition` owns one **primary output**, called *device 0*. You either name it, or let Subsequence discover it. You name it with `output_device=` on the constructor — the exact port name from [§C.2](#sec-appC-listing): ```{doctest} appC >>> composition = Composition(bpm=120, output_device="Dummy MIDI") ``` Leave `output_device` out and Subsequence auto-discovers, following three rules that depend only on *how many* ports it can see: ```{list-table} :header-rows: 1 :widths: 30 70 * - Ports visible - What happens * - **Exactly one** - That port is selected automatically — no prompt, no argument needed. This is why the very first loop in Chapter 0 just works when one synth is plugged in. * - **Several** - Subsequence prints a numbered menu and waits for you to type a number. After you pick, it prints the `output_device="..."` line that would skip the prompt next time. * - **None** - It logs an error and opens no port. Nothing sounds; the run does not crash. ``` ```{important} The prompt is **interactive** — it calls `input()` and blocks for a keystroke. That is fine when you run a script in a terminal, but it will hang a non-interactive process (a cron job, a CI run, a background launcher) with no obvious error. In any unattended context, **always pass `output_device=` explicitly** so discovery never has to ask. ``` Auto-discovery is convenient for one-synth setups and for trying things quickly. For anything you'll run more than once — and for *every* multi-device rig — name the port. It documents your intent and removes the prompt. (sec-appC-listing)= ## C.2 Listing and matching ports To name a port you first need its exact spelling. Ask `mido` — the MIDI library Subsequence sits on — what your operating system is currently exposing: ```{doctest} appC >>> import mido >>> mido.get_output_names() ['Dummy MIDI'] ``` On your machine that list is your real ports, for example `['IAC Driver Bus 1', 'Minilogue']`. Copy the one you want **verbatim** into `output_device=`. ```{warning} **Matching is strict and exact.** The string you pass must equal an entry in ``mido.get_output_names()`` character for character. Subsequence does **not** match on substrings or guess from a partial name — `output_device="Minilogue"` will *not* find a port actually called `"Minilogue:Minilogue MIDI 1 24:0"`. This verbatim-match rule applies everywhere a device name is accepted — `Composition(output_device=…)`, `composition.midi_output(…)`, and `composition.midi_input(…)` — but the *consequence* of a miss differs by direction. A wrong **output** name fails quietly: the port simply doesn't open (the error is logged, not raised) and you get silence. A wrong **input** name is louder — it raises (see *Input ports* below). Either way the cure is the same: re-list and paste the exact string. ``` A throwaway one-liner from the shell is the fastest way to read the list before you write any code: ```bash python -c "import mido; print(mido.get_output_names())" ``` ```{tip} If a name has tricky punctuation or trailing numbers (common on Linux — see [§C.3](#sec-appC-virtual)), don't retype it. Print the list, copy the exact string, and paste it. One stray space or wrong digit is the difference between sound and silence. ``` ### Input ports Listing **input** ports — for live keyboards, external clock, or CC control — uses the matching call, and the same exact-name rule holds: ```{doctest} appC >>> mido.get_input_names() ['Dummy MIDI'] ``` Inputs differ in one way worth knowing: a *named* input that isn't present raises an error rather than falling back to another port. That is deliberate — an input drives clock-follow and live note capture, so silently listening to the wrong device would desynchronise or mis-record a take. (Live input is covered in [Chapter 14](14-live-and-data); this appendix is about getting MIDI *out*.) (sec-appC-virtual)= ## C.3 Virtual ports, per operating system A hardware instrument shows up as a port the moment you plug it in — there is nothing to install. A **software** instrument (a DAW track, a plug-in host, a sampler) lives *inside* the computer, so you need a **virtual MIDI port**: an operating-system cable that carries MIDI from Subsequence to the other app. Subsequence does not create one itself; you make it once at the OS level, and then it appears in `mido.get_output_names()` like any other port. ```{list-table} :header-rows: 1 :widths: 16 42 42 * - OS - Create a virtual port - Then it appears as… * - **macOS** - **Audio MIDI Setup** → **Window ▸ Show MIDI Studio** → double-click **IAC Driver** → tick **"Device is online"**. (Add more buses with **+** if you want several.) - `IAC Driver Bus 1` (one entry per bus you enable). * - **Windows** - Install [loopMIDI](https://www.tobias-erichsen.de/software/loopmidi.html) and click **+** to create a named port. Name it something you'll recognise. - The name you typed in loopMIDI (e.g. `loopMIDI Port`). * - **Linux** - Load the ALSA virtual-MIDI module once: `sudo modprobe snd-virmidi`. Or wire a port in your JACK / patchbay setup. - An ALSA name like `Virtual Raw MIDI 1-0:VirMIDI 1-0 24:0`. ``` Once the port exists, point your software at it: in the DAW, set a track's **MIDI input** to that virtual port, **arm** the track, and aim its instrument at it. Subsequence sends to the port; the DAW receives and plays the sound. ```{note} A virtual port is a one-way named pipe — there's nothing to "connect" beyond naming the same port on both ends. If you see the IAC bus or loopMIDI port in ``mido.get_output_names()`` but hear nothing, the gap is almost always on the *receiving* side: the DAW track isn't armed, or its input is set to a different port. See the table in [§C.5](#sec-appC-problems). ``` ### Linux: ALSA names drift, and the `audio` group Two Linux specifics catch people, both worth pinning down here. **Names include client and port IDs, and they move.** An ALSA port name carries trailing `:client:port` digits — for example `"Scarlett 2i4 USB:Scarlett 2i4 USB MIDI 1 16:0"`. Those numbers are assigned in *connection order*, so they can change between reboots, or when you unplug and replug a device, or when a virtual port is recreated. A name that worked yesterday can stop matching today. ```{tip} On Linux, treat the port name as **volatile**: re-run ``python -c "import mido; print(mido.get_output_names())"`` whenever a previously-working `output_device=` suddenly fails, and paste the fresh name. The musical part of the name (`"Scarlett 2i4 USB…"`) is stable; the trailing digits are what drift. ``` **Your user must be in the `audio` group.** The ALSA backend needs access to `/dev/snd/seq`. If opening a port fails with `open /dev/snd/seq failed: Permission denied`, add yourself to the group once: ```bash sudo usermod -a -G audio $USER ``` Then log out and back in (or run `newgrp audio` to start a single shell with the group already active). This is a one-time setup, separate from the build-tools note in [Chapter 0](00-setup). (sec-appC-multi)= ## C.4 Multiple devices and latency compensation A real rig has more than one destination. Subsequence addresses them by **device index**: the port from `Composition(output_device=...)` is **device 0** (the primary), and each call to **`composition.midi_output(device, name=, latency_ms=)`** registers another, returning its index — `1`, `2`, and so on. ```{doctest} appC >>> comp = Composition(bpm=120, output_device="Dummy MIDI") # device 0 >>> comp.midi_output("Dummy MIDI", name="sampler") # device 1 1 ``` The `name=` alias is optional but worth setting: it lets a pattern say `device="sampler"` instead of `device=1`. (Omit it and the raw port name becomes the alias.) A pattern with **no** `device=` always routes to device 0, so single-device compositions need no changes at all. ```python # Illustrative — names YOUR ports. API shape verified. import subsequence comp = subsequence.Composition(bpm=120, output_device="MOTU Express") # device 0 comp.midi_output("Roland Integra", name="strings") # device 1 comp.midi_output("Elektron Analog Four", name="bass") # device 2 @comp.pattern(channel=1, beats=4, device="strings") # plays on the Integra def pad(p): p.note(60, beat=0, duration=4) @comp.pattern(channel=1, beats=4) # no device= → device 0 def lead(p): p.note(72, beat=0) ``` The same `device=` keyword exists on `composition.layer(...)` and on `composition.trigger(...)`, and the `mirrors=[(device, channel), ...]` keyword fans a single pattern out to several destinations at once. Multi-device *routing* — the `device=`/`mirrors=` decorator keywords, per-destination drum maps, and `composition.layer` — is taught with full musical examples in [§13.5](13-expression-hardware). This appendix covers the *plumbing* underneath: registering the ports, and aligning them in time. ### Latency compensation When you mix instruments that respond at different speeds — a hardware synth fires almost instantly, while a software sampler might sound ~20 ms late because of its audio buffer — notes scheduled on the *same* beat don't *sound* together. You tell Subsequence each device's physical latency in milliseconds and it lines everything up. Because you can't send a note into the past, it aligns the rig by **delaying the faster devices to meet the slowest one**. Set `latency_ms=` on the constructor for the primary device, and on each `midi_output()` call for the rest: ```{doctest} appC >>> comp = Composition(bpm=120, output_device="Dummy MIDI", latency_ms=0) >>> comp.midi_output("Dummy MIDI", name="sampler", latency_ms=22) 1 ``` Here the sampler (device 1) is the slowest at 22 ms, so every event bound for device 0 is held back 22 ms; all of them then sound at the same instant. The figure is a non-negative number you measure once for your rig — a negative value is meaningless (a device can't sound before it's triggered) and raises a `ValueError`. ```{list-table} Things to know about latency compensation :header-rows: 1 :widths: 34 66 * - Behaviour - Why it matters * - **It's per device, so mirroring just works.** - Mirror a part from a fast synth to a slow sampler and each copy is compensated for its own destination — they still land together. * - **The recorded `.mid` is left uncompensated.** - The file captures the *musical* (logical) timing. Compensation is specific to your physical setup; re-playing the file through the same rig applies it again. * - **The MIDI clock is never delayed.** - Only note and expression events shift, so external gear slaved to the clock stays locked to the beat. * - **Watch the whole-rig latency.** - The slowest device sets how late *everything* responds to live input. Above ~30 ms Subsequence logs a warning, because tight live playing starts to suffer. ``` ```{warning} `latency_ms` is for **physical output latency you measured**, not a creative delay. The slowest device defines the alignment point and every faster device is held back to meet it, so an inflated figure makes the whole rig sluggish to live input. If you want a musical delay, use a CC or your instrument's own effects — not the compensation figure. ``` (sec-appC-problems)= ## C.5 Common problems and fixes Most routing failures fall into a handful of patterns. Find the symptom, apply the fix. ```{list-table} :header-rows: 1 :widths: 40 60 * - Symptom - Likely cause and fix * - **It prints a numbered list and waits for input.** - Several ports were found and auto-discovery is prompting ([§C.1](#sec-appC-choosing)). Type the number, or set `output_device=` to skip the prompt for good. * - **The script hangs with no output at all.** - The interactive prompt is blocking in a non-interactive context (CI, cron, a launcher). Pass `output_device=` explicitly so discovery never asks. * - **Logged "device not found", then silence.** - The name doesn't match `mido.get_output_names()` exactly ([§C.2](#sec-appC-listing)). Re-list and paste the string verbatim — no substrings, no partial names. * - **It worked yesterday, now "not found" (Linux).** - ALSA `:client:port` digits drifted across a reboot or replug ([§C.3](#sec-appC-virtual)). Re-list the ports and update `output_device=` with the current name. * - **No errors, but silence (hardware).** - MIDI is flowing but the synth isn't listening on that channel. Confirm the instrument's MIDI-receive channel matches your `channel=` (drums = channel 10), and that local control / the right multitimbral part is set up. * - **No errors, but silence (DAW / software).** - The virtual port exists but nothing receives it. In the DAW, set the track's **MIDI input** to that exact port, **arm** the track, and point its instrument at it ([§C.3](#sec-appC-virtual)). * - **`open /dev/snd/seq failed: Permission denied` (Linux).** - Your user isn't in the `audio` group. `sudo usermod -a -G audio $USER`, then log out and back in ([§C.3](#sec-appC-virtual)). * - **Wrong drum sounds on a second machine.** - A plain `(device, channel)` mirror copies the raw note number, which may hit a different voice. Add a per-destination drum map — `(device, channel, drum_note_map)` — so names re-resolve per device (see [§13.5](13-expression-hardware)). * - **Two devices don't sound together.** - Different physical latencies. Measure each and set `latency_ms=` so the faster devices are delayed to meet the slowest ([§C.4](#sec-appC-multi)). * - **`ValueError: latency_ms must be non-negative`.** - A negative `latency_ms` was passed. Latency is a physical figure ≥ 0; remove the minus sign. * - **A `device="name"` pattern plays on the wrong synth.** - The alias doesn't match a registered device, so it falls back to device 0 (a warning is logged). Check the `name=` you gave the matching `midi_output(...)`. ``` ```{tip} **The fastest sanity check is "is anything listening at all?"** Render to a file first (`composition.render(filename=...)`) to prove your *code* makes the notes you expect — that needs no hardware. If the file is right but you hear nothing live, the problem is downstream of Subsequence: the port name, the channel, or the receiving instrument. Splitting "did my code work?" from "did my routing work?" this way isolates the fault in seconds. ``` --- **You can now route Subsequence to any rig and diagnose it when it goes quiet.** For the musical side of multi-device work — doubling parts, per-device drum maps, and assembling layered patterns — see [§13.5](13-expression-hardware); for live input, clock, and OSC, see [Chapter 14](14-live-and-data); and for the exact signatures of every routing method, [Appendix D](appendix-d-api-reference).