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.
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
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:
| Byte | Name | What it contains |
|---|---|---|
| 0 | Status | event type (upper 4 bits) + channel 0-15 (lower 4 bits) |
| 1 | Data 1 | note number 0-127 for note events |
| 2 | Data 2 | velocity 0-127 for note events |
The upper nibble of the status byte tells you what kind of event this is:
0x80-- Note Off0x90-- 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
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
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.
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.
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
- 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.
- Every MIDI message is three bytes: status (event type + channel), data 1 (note number), data 2 (velocity).
status & 0xF0extracts the event type.status & 0x0Fextracts the channel. Both are needed if you are handling multi-channel MIDI.- Note On with velocity 0 is treated as Note Off -- this is a MIDI convention, not a browser behavior.
midi.inputsandmidi.outputsareMapobjects. Iterate with.values(), listen with.onmidimessage, send with.send([...bytes]).- Light-green tier: Chrome and Firefox support it. Safari stopped implementation.
Further Reading and Watching
- JavaScript MIDI Synth Tutorial - Part 1 | Getting MIDI in the browser (YouTube) -- walks through
requestMIDIAccess, enumerating devices, and reading MIDI messages from a keyboard connected to Chrome - Web MIDI API -- MDN -- full reference for MIDIAccess, MIDIInput/Output, MIDIMessageEvent, and statechange handling