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:

  1. Requesting Access: The browser asks the operating system for permission to access connected MIDI hardware.
  2. Device Enumeration: JavaScript retrieves lists of available input devices (controllers, keyboards) and output devices (hardware synths, drum modules).
  3. Receiving Data (Inputs): An event listener captures incoming signals when physical controls are touched.
  4. 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:

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.