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 webmidiOr 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:
- HTTPS Required: The Web MIDI API and its wrapper
libraries only operate in secure contexts (
https://orlocalhost). - Permissions: Browsers require explicit user permission before allowing web pages to access or send data to MIDI devices.
- SysEx Permission: If your application requires
sending System Exclusive (SysEx) messages for firmware updates or custom
device configurations, you must pass
{ sysex: true }during initialization, which triggers a stricter permission prompt.