Web Midi

MIDI is 40 years old and still ships in every browser except Safari. The Web MIDI API gives JavaScript direct access to synthesizers, keyboards, and drum machines -- and understanding the 3-byte message format is the unlock.

June 12, 20264 min read3 / 6

MIDI was designed in 1983 so that synthesizers from different manufacturers could talk to each other. It is a serial message protocol that carries musical events -- note presses, knob movements, program changes -- as compact 3-byte packets.

It has nothing to do with audio. MIDI carries events, not sound. The sound is synthesized separately, either in hardware or in software (like the Web Audio API).

The Web MIDI API brings that 40-year-old protocol directly into JavaScript.

Requesting MIDI Access

JavaScript
const midi = await navigator.requestMIDIAccess()

That is the entry point. The browser shows the user a one-time permission dialog. On success you get a MIDIAccess object. It has two maps: midi.inputs and midi.outputs.

Pass { sysex: true } if your device uses System Exclusive messages (custom manufacturer commands). SysEx requires an additional permission prompt in some browsers.

The 3-Byte Message

Every MIDI event is a Uint8Array of three bytes. The structure is fixed:

ByteNameWhat it contains
0Statusevent type (upper 4 bits) + channel 0-15 (lower 4 bits)
1Data 1note number 0-127 for note events
2Data 2velocity 0-127 for note events

The upper nibble of the status byte tells you what kind of event this is:

  • 0x80 -- Note Off
  • 0x90 -- Note On (if velocity is 0, treat as Note Off)
  • 0xA0 -- Aftertouch (key pressure)
  • 0xB0 -- Control Change (knobs, sliders, pedals)
  • 0xC0 -- Program Change (switch preset)

To get the event type while ignoring the channel: status & 0xF0.

Listening for Input

JavaScript
for (const input of midi.inputs.values()) { input.onmidimessage = (event) => { const [status, note, velocity] = event.data const type = status & 0xF0 if (type === 0x90 && velocity > 0) { console.log(`Note On: ${note}, velocity: ${velocity}`) } else if (type === 0x80 || (type === 0x90 && velocity === 0)) { console.log(`Note Off: ${note}`) } else if (type === 0xB0) { console.log(`Control Change: controller ${note}, value ${velocity}`) } } }

event.data is a Uint8Array. Destructure it directly -- no parsing required.

midi.inputs is a Map. Its entries fire a statechange event when devices are plugged or unplugged, which you can use to add new listeners dynamically.

Sending MIDI Output

JavaScript
const output = midi.outputs.values().next().value // first output // Play middle C (note 60) on channel 1, velocity 100 output.send([0x90, 60, 100]) // Release it after 500ms output.send([0x80, 60, 0], performance.now() + 500)

send() accepts a plain array or a Uint8Array. The optional second argument is a timestamp in milliseconds (using performance.now() base) for scheduled playback.

Connecting Web MIDI to Web Audio is a natural pairing. Use MIDI input to control oscillator frequency and gain, and Web Audio to synthesize the sound. The keyboard becomes a controller and the browser becomes the synthesizer.

Web MIDI: the 3-byte message anatomy, common status bytes, and the requestMIDIAccess connection flow ExpandWeb MIDI: the 3-byte message anatomy, common status bytes, and the requestMIDIAccess connection flow

What MIDI Controls Beyond Music

MIDI's age means it ended up in unexpected places. Lighting systems for live shows are often MIDI-controlled. Some video software accepts MIDI for scene switching. Accessibility tools use MIDI controllers as alternative input devices.

If you find yourself working with older professional hardware -- or with any system that predates USB HID -- there is a reasonable chance it speaks MIDI.

Support

Web MIDI is light-green tier. Chrome, Edge, and Firefox all support it. Safari started implementation but stopped. There is no polyfill that works without hardware access.

JavaScript
if ('requestMIDIAccess' in navigator) { // Web MIDI is available }

MIDI sends events to hardware. Web Serial is the next step down -- not a protocol for musical events, but raw byte communication with any serial device connected to the computer.

The Essentials

  1. MIDI carries events, not audio. A Note On event tells you a key was pressed. What sound plays is determined by the synthesizer receiving the event.
  2. Every MIDI message is three bytes: status (event type + channel), data 1 (note number), data 2 (velocity).
  3. status & 0xF0 extracts the event type. status & 0x0F extracts the channel. Both are needed if you are handling multi-channel MIDI.
  4. Note On with velocity 0 is treated as Note Off -- this is a MIDI convention, not a browser behavior.
  5. midi.inputs and midi.outputs are Map objects. Iterate with .values(), listen with .onmidimessage, send with .send([...bytes]).
  6. Light-green tier: Chrome and Firefox support it. Safari stopped implementation.

Further Reading and Watching