Web MIDI API: Control Hardware with JavaScript
The Web MIDI API bridges web browsers and physical musical instruments, enabling developers to build interactive, audio-driven web applications that communicate directly with hardware synthesizers, drum machines, and MIDI controllers. This article explains the core concepts of the Web MIDI API, outlines how JavaScript sends and receives MIDI data, breaks down the structure of MIDI messages, and demonstrates the basic code required to connect your browser to external musical devices.
What is the Web MIDI API?
The Web MIDI (Musical Instrument Digital Interface) API is a W3C standard that allows web applications to enumerate, access, and interact with MIDI input and output devices connected to a user’s computer via USB, Bluetooth, or virtual ports.
Unlike the Web Audio API, which generates and processes actual sound signals, the Web MIDI API does not transmit audio. Instead, it transmits lightweight control protocol data—instructions such as which musical note to play, how hard to press it (velocity), pitch bends, and modulation adjustments.
How JavaScript Interacts with Hardware
JavaScript interacts with external devices through an asynchronous pipeline provided by the browser:
- Requesting Access: The browser asks the operating system for permission to access connected MIDI hardware.
- Device Enumeration: JavaScript retrieves lists of available input devices (controllers, keyboards) and output devices (hardware synths, drum modules).
- Receiving Data (Inputs): An event listener captures incoming signals when physical controls are touched.
- Sending Data (Outputs): JavaScript sends structured byte arrays to trigger hardware functions.
1. Requesting Access to MIDI Devices
Interaction starts with the
navigator.requestMIDIAccess() method. Because MIDI access
can expose hardware information, modern browsers require this to run in
a secure context (HTTPS).
navigator.requestMIDIAccess()
.then(onMIDISuccess, onMIDIFailure);
function onMIDISuccess(midiAccess) {
console.log("MIDI ready!");
listInputsAndOutputs(midiAccess);
}
function onMIDIFailure(msg) {
console.error(`Failed to get MIDI access: ${msg}`);
}2. Reading Incoming MIDI Data
To listen to notes played on a physical keyboard, iterate through the
available inputs and assign an onmidimessage event
handler:
function listInputsAndOutputs(midiAccess) {
for (const input of midiAccess.inputs.values()) {
input.onmidimessage = handleMIDIMessage;
}
}
function handleMIDIMessage(event) {
const [status, note, velocity] = event.data;
console.log(`Command: ${status}, Note: ${note}, Velocity: ${velocity}`);
}3. Sending MIDI Messages to Hardware
To make an external synthesizer play a note from code, locate an output port and send a three-byte array containing the command, note number, and velocity:
function playHardwareNote(midiAccess, noteNumber) {
const outputs = Array.from(midiAccess.outputs.values());
if (outputs.length > 0) {
const output = outputs[0]; // Select the first connected device
const NOTE_ON = 0x90; // 144 in decimal
const NOTE_OFF = 0x80; // 128 in decimal
const VELOCITY = 0x7f; // Maximum velocity (127)
// Play note
output.send([NOTE_ON, noteNumber, VELOCITY]);
// Stop note after 1 second
setTimeout(() => {
output.send([NOTE_OFF, noteNumber, 0]);
}, 1000);
}
}Structure of a MIDI Message
Standard MIDI messages consist of 1 to 3 bytes:
- Status Byte: Identifies the action and channel. For
example,
0x90represents a “Note On” command on MIDI channel 1, while0xB0represents a “Control Change” command. - Data Byte 1: Identifies the target parameter, such as the note number (0–127, where 60 is Middle C) or the controller number (e.g., CC1 for the mod wheel).
- Data Byte 2: Specifies the value (0–127), such as how loud the note should play (velocity) or the position of a knob.
Security and Browser Support
The Web MIDI API is supported natively across Chromium-based browsers (Google Chrome, Microsoft Edge, Opera) on desktop and Android. Access to advanced system-exclusive (SysEx) messages—used for firmware updates and preset dumps—requires explicit, heightened permission from the user during the initial access request.