Skip to content

Midi

Advanced MIDI driver for any USB or virtual MIDI device. Live input and output, per-channel filtering, pass-through, internal 24-PPQN clock with transport, Standard MIDI File (SMF) playback and recording, panic/all-notes-off, and a learn helper for mapping unknown controls. Every incoming message is streamed as JSON for scripts and dashboards.

Installation: download muxit-midi-v0.1.0.muxdriver from the Extensions panel, or drop it into workspace/drivers/ and restart.

Properties

Device & connection

PropertyTypeAccessDescription
inputsstring[]RAvailable MIDI input device names
outputsstring[]RAvailable MIDI output device names
inputDevicestringRCurrently open input (empty if none)
outputDevicestringRCurrently open output (empty if none)
inputOpenboolRWhether an input port is open
outputOpenboolRWhether an output port is open

Traffic & state

PropertyTypeAccessUnitDescription
lastMessageobjectRMost recent accepted incoming message (same shape as the events stream)
messageCountintRIncoming messages since the input was opened
activeNotesobjectRHeld note numbers per channel — { "1":[60,64,67], "10":[36] }
tempodoubleR/WbpmDrives the internal clock and SMF playback
channelFilterint[]R/WAccept-list of incoming channels (1–16). Empty = accept all
passThroughboolR/WForward accepted input → output as received

Transport

PropertyTypeAccessUnitDescription
clockRunningboolRInternal MIDI clock is active
playbackStatestringR"stopped" | "playing" | "paused"
playbackPositiondoubleRsSMF playhead
playbackDurationdoubleRsLength of the loaded SMF
recordingboolRInput is being recorded to .mid
loadedFilestringRAbsolute path of the last-loaded SMF

Actions

Device management

ActionArgsDescription
refreshDevicesRe-enumerate system MIDI devices. Returns { inputs, outputs }
openInput{ name: string }Open a MIDI input by name
openOutput{ name: string }Open a MIDI output by name
closeClose both ports, stop clock / playback / recording

Live messages

ActionArgsDescription
noteOn{ channel, note, velocity }Send a Note On (channel 1–16, note/velocity 0–127)
noteOff{ channel, note, velocity? }Send a Note Off
playNote{ channel, note, velocity, durationMs }Note On + scheduled Note Off
controlChange{ channel, cc, value }Send a Control Change (CC)
programChange{ channel, program }Send a Program Change
pitchBend{ channel, value }Send Pitch Bend (value is −1.0…1.0)
aftertouch{ channel, pressure }Channel Aftertouch (pressure 0–127)
sendSysex{ bytes: int[] }Send a SysEx payload (F0/F7 are added automatically)
panicAll Notes Off + All Sound Off + Reset All Controllers on all 16 channels; also clears activeNotes

Transport / clock

ActionArgsDescription
startClockStart internal 24-PPQN MIDI clock to the open output
stopClockStop the internal clock
sendTransport{ command: "start" | "stop" | "continue" }Send a MIDI transport message

SMF files

Files are resolved under the midiFilesDir config option (default: midi/ under the workspace root).

ActionArgsDescription
loadFile{ path: string }Load a .mid / .midi file (path relative to the midi dir)
playStart or resume playback through the open output
pausePause playback (keeps the playhead)
stopStop playback and rewind to the beginning
seek{ seconds: double }Seek the playhead
listFilesList .mid / .midi files under the midi dir
startRecordingRecord incoming MIDI to memory (requires an open input)
stopRecording{ savePath: string }Stop recording and save to a .mid file (path relative to the midi dir)

Learn

ActionArgsDescription
learn{ timeoutMs?: int }Wait for the next incoming MIDI message and return it as a dict. Throws on timeout (default 5000 ms)

Streams

StreamFormatRateDescription
eventsJSONper-eventEvery accepted incoming message (channel-filtered, excludes clock ticks unless emitClockStream: true)
noteJSONper-eventOnly noteOn / noteOff messages — cheaper for drum-trigger UIs
clockJSON24 × BPM / 60 per secondTimingClock ticks. Off by default — enable with emitClockStream: true

Normalized message shape

Every incoming message (and lastMessage) has this dictionary form:

json
{ "type": "noteOn",        "channel": 1, "note": 60, "velocity": 100, "tsMs": 1713312321043 }
{ "type": "noteOff",       "channel": 1, "note": 60, "velocity": 0,   "tsMs": 1713312321498 }
{ "type": "controlChange", "channel": 1, "controller": 7, "value": 96, "tsMs": 1713312321981 }
{ "type": "programChange", "channel": 1, "program": 42, "tsMs": 1713312322004 }
{ "type": "pitchBend",     "channel": 1, "raw": 12288, "value": 0.5, "tsMs": 1713312322010 }
{ "type": "aftertouch",    "channel": 1, "pressure": 72, "tsMs": 1713312322011 }
{ "type": "sysex",         "bytes": [67, 0, 9, 1, 16], "tsMs": 1713312322012 }
{ "type": "start" }  { "type": "stop" }  { "type": "continue" }  { "type": "clock" }

Channel is always 1–16, notes and velocities 0–127. tsMs is a Unix millisecond timestamp captured when the driver received the message.

Config Options

OptionTypeDefaultDescription
inputNamestringOpen this input on init (optional)
outputNamestringOpen this output on init (optional)
tempodouble120Default BPM for the internal clock and SMF playback
channelFilterint[][]Accept-list for incoming channels (1–16). Empty = accept all
passThroughboolfalseForward accepted input to output
emitClockStreamboolfalseEmit TimingClock ticks on the clock stream (high-rate — off by default)
midiFilesDirstring"midi"Directory for loadFile / stopRecording, relative to the workspace root

Example connector

js
// workspace/connectors/midi-controller.js
export default {
  driver: "Midi",
  inputName: "loopMIDI Port",
  outputName: "loopMIDI Port",
  tempo: 120,

  poll: ["activeNotes", "playbackState", "playbackPosition", "inputOpen", "outputOpen"],

  ai: {
    instructions:
      "This is a MIDI controller. playChord takes (root, 'major'|'minor'). " +
      "Always call panic() if the user asks to stop all notes.",
  },

  methods: {
    playChord: {
      fn: (root, quality = "major", velocity = 100, durationMs = 500) => {
        const intervals = quality === "minor" ? [0, 3, 7] : [0, 4, 7];
        for (const i of intervals) driver.playNote({ channel: 1, note: root + i, velocity, durationMs });
      },
      description: "Play a triad on channel 1",
    },
    allOff: [() => driver.panic(), "Stop every note on every channel"],
  },

  properties: {
    heldNoteCount: {
      get: () => Object.values(driver.activeNotes || {}).reduce((a, b) => a + b.length, 0),
      poll: true,
    },
  },
};

Troubleshooting

  • Device not listed. Some operating systems cache the MIDI device list. Call refreshDevices after plugging/unplugging hardware or creating a virtual port.
  • "No MIDI output open" error. Call openOutput (or set outputName in the connector config) before any send action or play.
  • No hardware? Install loopMIDI to create a free virtual port for testing. Open it in both inputName and outputName plus passThrough: true to echo messages back through yourself.
  • High CPU from clock stream. Leave emitClockStream: false (the default). The internal clock still runs and transmits — the stream only surfaces the 24-PPQN ticks to the WebSocket for debugging.

Muxit — Hardware Orchestration Platform