Nebula follows your drum machine now: clock in, timecode, program change and MPE

2026-11-03

Phase 11 — "MIDI controllers and hardware" — is finished in 0.3.0-alpha.278. MIDI learn, the shared CC map, auto-detect and clock send already shipped; this pass added everything that points the other way, so a hardware rig is now a first-class citizen instead of a one-way street.

Settings, MIDI page: input and output ports, the clock source and the Lua scripts folder

Nebula can follow your gear

Pick a MIDI input as the clock source and the transport follows whatever is on the other end: start, continue and stop, the song position, and a tempo smoothed from the incoming ticks. A steady master gives a steady tempo; a jittery one gives its average. If the clock stops arriving, Nebula free-runs again instead of freezing — because a message can never arrive to say "the clock has stopped", the receiver treats half a second of silence as "not locked".

Tempo is inferred from the spacing of the ticks, and that inference is kept off the audio thread: the reader thread does the maths and publishes a small set of plain atomics that the audio thread only reads. Nothing on the audio path allocates, locks or waits.

MIDI Timecode, both ways

A tick stream says how fast, never where. MIDI Timecode says where — and Nebula now reads and writes it, in both shapes (quarter-frame messages, one nibble at a time, and the full-frame SysEx), with exact 29.97 drop-frame and a frame-rate setting. That is the piece that makes lining up with picture, or with a second machine, possible at all.

Program change, and which track it means

A program change (0xC0) selects "the sound" on a device — but until now a track had no MIDI channel, so there was no way to say which track a change was for. Now:

  • each track has its own MIDI channel, and program change in selects that track's preset;
  • a program change is sent out when a track's preset changes;
  • each track also has its own output port, so drums can leave on one interface while a synth leaves on another, without splitting the rig outside Nebula.

A track that never asks for a port keeps using the song's port, and a song-level message (MTC, clock) is never sent down a track's port by accident.

MPE: expression per note

An ordinary keyboard sends every note on one channel, so moving the pitch wheel drags the whole chord. An MPE keyboard sends each note on its own channel, and that channel's bend, pressure and CC74 then belong to that one note.

Nebula now handles that in both directions: a note on a member channel binds that channel to the voice it was given, that voice follows only its own bend, and channel pressure and CC74 move that voice's own level and filter. Notes and bends leave again on each voice's own member channel, with the "my wheel bends ±N semitones" setup messages sent first — without them a receiving synth assumes ±2 semitones and every bend comes out far too small. The range is ±48 semitones, and the whole feature is off by default, so a project saved before this behaves exactly as it did.

Controller scripts, in Lua

There are things MIDI learn cannot express — "pad 36 toggles the metronome and sets the tempo" has nowhere to live. Now there is a sandboxed Lua host: a scripts folder, an enable switch and a Reload button turn MIDI input into actions.

Crucially, a script does not get a private back door: it goes through the same path MIDI learn uses, so an action a script triggers is the same undoable action a mapped knob triggers.

CC automation out, and tighter timing

  • CC 7 and CC 10 go out. A track's volume and pan curves are emitted as control changes when they change, so a hardware synth follows your mix moves and not only your notes.
  • The MIDI-out drain interval is configurable, and the default is unchanged. At 1 ms, the measured mean departure wait fell from 2.04 ms to 0.56 ms — the difference between a message that leaves when it is due and one that leaves when a timer next comes round.

What is not verified

No real MIDI hardware was attached while this release was built. The driver paths are verified against test drivers and a compiler check — the code is written and unit-tested, but nobody plugged a drum machine into it. That is worth saying out loud rather than implying otherwise.

The shared test tree passes 1340 of 1340 cases, with one case skipped that needs a 32-bit bridge path pointed at it.

An unhandled error has occurred. Reload 🗙

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.