How to Send MIDI Messages in JavaScript

This article provides an overview of how web developers can transmit Musical Instrument Digital Interface (MIDI) messages directly to hardware synthesizers and software DAWs using modern JavaScript libraries. While the browser-native Web MIDI API provides low-level access to connected devices, modern wrapper libraries streamline the process by replacing complex raw hexadecimal arrays with clean, human-readable APIs for sending note triggers, pitch bends, and control changes.

The Problem with Native Web MIDI

Browsers natively support MIDI communication through navigator.requestMIDIAccess(). However, sending a simple note requires sending an array of raw binary or hexadecimal bytes:

// Native Web MIDI API: Sending Note On (C4, velocity 127) on Channel 1
const output = midiAccess.outputs.get(outputId);
output.send([0x90, 60, 0x7F]); 

Managing byte calculations, device connection lifecycles, and channel routing by hand quickly leads to verbose, error-prone code. Modern libraries solve this by abstracting the low-level protocols.

Sending MIDI with WebMidi.js

The most widely used library for browser-based MIDI communication is WebMidi.js. It handles device detection, provides cross-browser normalization, and turns low-level byte arrays into semantic method calls.

1. Installation

Install the package via npm:

npm install webmidi

Or load it via a CDN:

<script src="https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js"></script>

2. Initialization and Requesting Access

Before sending messages, initialize the library and request user permission:

import { WebMidi } from "webmidi";

async function setupMIDI() {
  try {
    await WebMidi.enable({ sysex: false });
    console.log("WebMIDI enabled!");
  } catch (err) {
    console.error("WebMIDI could not be enabled:", err);
  }
}

setupMIDI();

3. Sending Note Messages

Once enabled, access available output ports via WebMidi.outputs and send notes using standard musical notation:

// Target the first available MIDI output device
const output = WebMidi.outputs[0];

if (output) {
  // Play Middle C (C4) on channel 1 at full velocity for 1 second
  output.playNote("C4", { channels: 1, rawAttack: 127, duration: 1000 });

  // Play chords simultaneously
  output.playNote(["C4", "E4", "G4"], { channels: 1, duration: 2000 });
}

4. Sending Control Change (CC) and Pitch Bend

Beyond triggering notes, modern libraries make parameter modulation straightforward:

// Send a Control Change (CC) message: Controller 1 (Modulation Wheel), Value 64
output.sendControlChange(1, 64, { channels: 1 });

// Send Pitch Bend (value between -1 and 1)
output.sendPitchBend(0.5, { channels: 1 });

Alternative: JZZ.js for Universal Environments

If your application needs to support Node.js alongside modern browsers, JZZ.js is another powerful alternative. JZZ provides a consistent MIDI interface across platforms and supports virtual MIDI ports, which are useful for testing without physical hardware:

import JZZ from "jzz";

JZZ().openMidiOut().then((out) => {
  out.noteOn(0, "C5", 127)
     .wait(500)
     .noteOff(0, "C5");
});

Security and Browser Requirements

When implementing MIDI in web applications, keep the following constraints in mind: