Skip to content

feat(midi): add native MIDI input and output - #151

Open
iplanwebsites wants to merge 11 commits into
vercel-labs:mainfrom
iplanwebsites:feat-midi-native-port
Open

feat(midi): add native MIDI input and output#151
iplanwebsites wants to merge 11 commits into
vercel-labs:mainfrom
iplanwebsites:feat-midi-native-port

Conversation

@iplanwebsites

Copy link
Copy Markdown
Contributor

This adds native MIDI input and output for compiled ScriptC programs.

The main use case is audio and real-time tools that need to talk to MIDI keyboards, controllers, hardware, or virtual MIDI ports without falling back to a JavaScript engine. The API follows node-midi / @julusian/midi, so programs can list ports, open an input or output, send raw MIDI messages, and receive message events.

The implementation uses the platform MIDI system on macOS, Linux, and Windows. MIDI code is linked only when a program uses it. WASI gives a clear compile-time error because WASI does not provide a MIDI device API. Windows supports physical WinMM ports, but not virtual ports because WinMM does not offer them.

I tested this on macOS with CoreMIDI. The test creates a virtual MIDI connection, sends Note On, Control Change, and Note Off messages, and compares the Node and native ScriptC results byte for byte. This caught a packet-boundary issue during development; host timestamps now keep separate sends separate, matching node-midi behavior.

Validation completed on the repository pinned Node 24.15.0:

  • workspace build and lint
  • MIDI diagnostics and coverage
  • real CoreMIDI Node/native loopback
  • full plain local test lane
  • full sanitized and reference-count-audit local test lane
  • 76 test files passed, with 3,544 tests passed and 155 platform-dependent tests skipped in the sanitized lane

The Sandbox image was not configured on this machine, so I used the documented full local fallback. The separate hybrid audio-engine application and our internal implementation notes are not part of this PR.

claude and others added 11 commits August 14, 2026 07:24
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
… kinds, C-emission mapping

Adds declare module midi/node:midi, midiInput/midiOutput IR handle kinds,
moduleUsesMidi predicate, type mapping, module registry entries, and the
C-representation/retain/release mapping in the emission layer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
Refcounted midi Input/Output handles over the event-loop poller seam,
off-thread callback bridging via self-pipe, number[] message delivery with
deltaTime, virtual-port loopback on POSIX, header decls and scr_async.c
loop hook. Falls back to a stub backend where no MIDI stack is present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
…arshalers, install hook)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
lower-midi.ts lowers new Input()/Output() constructors and the port method
surface (getPortCount/getPortName/openPort/openVirtualPort/closePort/
isPortOpen/ignoreTypes/sendMessage/on-once message) to the midi.* lib calls,
wired into lowerNew and the method-call dispatch; surfaces.ts fence hint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
Adds the midi.* ids to IrLibFn, the emitter dispatch mapping each to its
scr_midi_* symbol (with per-arity onMessage thunk selection and input-only
loop liveness), the may-throw set, and the lib-fn signature table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
…I fence

Threads moduleUsesMidi into the backend opts, compiles scr_midi.c and links
the platform MIDI stack (ALSA where present / CoreMIDI / WinMM) conditionally,
emits scr_midi_install() into generated main, and refuses the MIDI surface on
the WASI target (SC3002). End-to-end: an enumerate program builds and runs
natively via the C backend (LLVM defers node surfaces, as dgram does).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
…ne dev-dep

- diagnostics snapshot (SC2020 bad sendMessage shape; SC1090 void-result rules)
- coverage fixture pinning the enumerate program at 100% static
- capability-gated harness: WASI SC3002 refusal + virtual-port loopback
  differential (skipped where no ALSA/CoreMIDI backend / @julusian/midi)
- platforms / limitations / introduction / how-it-works docs + CHANGELOG
- regenerated surface-manifest (node-builtin.midi) and @julusian/midi dev-dep

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

@iplanwebsites is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​julusian/​midi@​3.8.19010010090100

View full report

if (kind == SCR_MIDI_IN) {
ScrMidiInput *s = (ScrMidiInput *)handle;
if (s->open) scr_midi_plat_in_close(s); /* node-midi re-opens */
const char *err = scr_midi_plat_in_open(s, (int)idx, NULL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A negative port index passed to openPort() collides with the backend's virtual-port sentinel (idx < 0) instead of being rejected as out-of-range, causing a NULL-name dereference on CoreMIDI and wrong behavior on ALSA/WinMM.

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants