JavaScript library for MIDI communication that simplifies sending and receiving MIDI messages between browsers/Node.js and MIDI instruments
—
Message processing provides comprehensive functionality for parsing and understanding MIDI messages. The Message class represents parsed MIDI messages with type-specific properties and methods for easy access to message data.
Create Message objects from raw MIDI data.
class Message {
/**
* Create a Message object from raw MIDI data
* @param data - MIDI message data as Uint8Array
*/
constructor(data: Uint8Array);
}Usage Examples:
import { Message } from "webmidi";
// Create from raw MIDI data
const noteOnData = new Uint8Array([0x90, 60, 100]); // Note on, C4, velocity 100
const noteOnMessage = new Message(noteOnData);
const ccData = new Uint8Array([0xB0, 64, 127]); // Control change, sustain, max value
const ccMessage = new Message(ccData);
// Create from array (automatically converted to Uint8Array)
const pitchBendData = new Uint8Array([0xE0, 0x00, 0x40]); // Pitch bend, center position
const pitchBendMessage = new Message(pitchBendData);Access parsed message properties and data.
/**
* Original raw MIDI data as Uint8Array
*/
readonly rawData: Uint8Array;
/**
* MIDI data as regular array of numbers
*/
readonly data: number[];
/**
* MIDI status byte (first byte)
*/
readonly statusByte: number;
/**
* Raw data bytes as Uint8Array (excluding status byte)
*/
readonly rawDataBytes: Uint8Array;
/**
* Data bytes as regular array (excluding status byte)
*/
readonly dataBytes: number[];
/**
* Whether this is a channel message (0x80-0xEF)
*/
readonly isChannelMessage: boolean;
/**
* Whether this is a system message (0xF0-0xFF)
*/
readonly isSystemMessage: boolean;
/**
* MIDI command number (upper 4 bits of status byte)
*/
readonly command: number;
/**
* MIDI channel (1-16) for channel messages, undefined for system messages
*/
readonly channel?: number;
/**
* Manufacturer ID for SysEx messages (first data byte(s))
*/
readonly manufacturerId?: number | number[];
/**
* Human-readable message type string
*/
readonly type: string;Usage Examples:
const noteOnData = new Uint8Array([0x90, 60, 100]);
const message = new Message(noteOnData);
console.log(message.rawData); // Uint8Array([144, 60, 100])
console.log(message.data); // [144, 60, 100]
console.log(message.statusByte); // 144 (0x90)
console.log(message.dataBytes); // [60, 100]
console.log(message.isChannelMessage); // true
console.log(message.isSystemMessage); // false
console.log(message.command); // 9 (note on)
console.log(message.channel); // 1
console.log(message.type); // "noteon"Channel messages target specific MIDI channels (1-16).
// Note On (0x90-0x9F)
const noteOn = new Message(new Uint8Array([0x90, 60, 100]));
console.log(noteOn.type); // "noteon"
console.log(noteOn.channel); // 1
console.log(noteOn.dataBytes); // [60, 100] (note, velocity)
// Note Off (0x80-0x8F)
const noteOff = new Message(new Uint8Array([0x80, 60, 64]));
console.log(noteOff.type); // "noteoff"
console.log(noteOff.channel); // 1
console.log(noteOff.dataBytes); // [60, 64] (note, velocity)
// Control Change (0xB0-0xBF)
const controlChange = new Message(new Uint8Array([0xB0, 64, 127]));
console.log(controlChange.type); // "controlchange"
console.log(controlChange.channel); // 1
console.log(controlChange.dataBytes); // [64, 127] (controller, value)
// Program Change (0xC0-0xCF)
const programChange = new Message(new Uint8Array([0xC0, 25]));
console.log(programChange.type); // "programchange"
console.log(programChange.channel); // 1
console.log(programChange.dataBytes); // [25] (program)
// Pitch Bend (0xE0-0xEF)
const pitchBend = new Message(new Uint8Array([0xE0, 0x00, 0x40]));
console.log(pitchBend.type); // "pitchbend"
console.log(pitchBend.channel); // 1
console.log(pitchBend.dataBytes); // [0, 64] (LSB, MSB)
// Channel Aftertouch (0xD0-0xDF)
const channelAftertouch = new Message(new Uint8Array([0xD0, 80]));
console.log(channelAftertouch.type); // "channelaftertouch"
console.log(channelAftertouch.channel); // 1
console.log(channelAftertouch.dataBytes); // [80] (pressure)
// Key Aftertouch (0xA0-0xAF)
const keyAftertouch = new Message(new Uint8Array([0xA0, 60, 80]));
console.log(keyAftertouch.type); // "keyaftertouch"
console.log(keyAftertouch.channel); // 1
console.log(keyAftertouch.dataBytes); // [60, 80] (note, pressure)System messages are global and don't target specific channels.
// System Exclusive (0xF0)
const sysexData = new Uint8Array([0xF0, 0x41, 0x10, 0x16, 0x12, 0xF7]);
const sysex = new Message(sysexData);
console.log(sysex.type); // "sysex"
console.log(sysex.channel); // undefined
console.log(sysex.manufacturerId); // 65 (0x41 = Roland)
console.log(sysex.isSystemMessage); // true
// MIDI Time Code Quarter Frame (0xF1)
const mtcQuarterFrame = new Message(new Uint8Array([0xF1, 0x20]));
console.log(mtcQuarterFrame.type); // "timecode"
// Song Position Pointer (0xF2)
const songPosition = new Message(new Uint8Array([0xF2, 0x00, 0x10]));
console.log(songPosition.type); // "songposition"
// Song Select (0xF3)
const songSelect = new Message(new Uint8Array([0xF3, 0x05]));
console.log(songSelect.type); // "songselect"
// Tune Request (0xF6)
const tuneRequest = new Message(new Uint8Array([0xF6]));
console.log(tuneRequest.type); // "tunerequest"
// MIDI Clock (0xF8)
const clock = new Message(new Uint8Array([0xF8]));
console.log(clock.type); // "clock"
// Start (0xFA)
const start = new Message(new Uint8Array([0xFA]));
console.log(start.type); // "start"
// Continue (0xFB)
const continue_ = new Message(new Uint8Array([0xFB]));
console.log(continue_.type); // "continue"
// Stop (0xFC)
const stop = new Message(new Uint8Array([0xFC]));
console.log(stop.type); // "stop"
// Active Sensing (0xFE)
const activeSensing = new Message(new Uint8Array([0xFE]));
console.log(activeSensing.type); // "activesensing"
// System Reset (0xFF)
const systemReset = new Message(new Uint8Array([0xFF]));
console.log(systemReset.type); // "reset"function analyzeChannelMessage(message) {
if (!message.isChannelMessage) {
return "Not a channel message";
}
const analysis = {
type: message.type,
channel: message.channel,
command: message.command
};
switch (message.command) {
case 8: // Note Off
case 9: // Note On
analysis.note = message.dataBytes[0];
analysis.velocity = message.dataBytes[1];
analysis.noteName = midiNoteToName(analysis.note);
break;
case 11: // Control Change
analysis.controller = message.dataBytes[0];
analysis.value = message.dataBytes[1];
analysis.controllerName = getControllerName(analysis.controller);
break;
case 12: // Program Change
analysis.program = message.dataBytes[0];
break;
case 14: // Pitch Bend
analysis.lsb = message.dataBytes[0];
analysis.msb = message.dataBytes[1];
analysis.value = (analysis.msb << 7) | analysis.lsb;
analysis.normalizedValue = (analysis.value - 8192) / 8192; // -1 to 1
break;
}
return analysis;
}
// Helper functions
function midiNoteToName(noteNumber) {
const noteNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
const octave = Math.floor(noteNumber / 12) - 2;
const note = noteNames[noteNumber % 12];
return note + octave;
}
function getControllerName(ccNumber) {
const ccNames = {
0: "Bank Select MSB",
1: "Modulation",
7: "Volume",
10: "Pan",
11: "Expression",
64: "Sustain",
65: "Portamento",
91: "Reverb",
93: "Chorus"
};
return ccNames[ccNumber] || `CC ${ccNumber}`;
}function analyzeSysexMessage(message) {
if (message.type !== "sysex") {
return "Not a SysEx message";
}
const data = message.dataBytes;
const manufacturerId = data[0];
// Standard manufacturer IDs
const manufacturers = {
0x41: "Roland",
0x42: "Korg",
0x43: "Yamaha",
0x47: "Akai",
0x40: "Kawai"
};
return {
type: "sysex",
manufacturerId: manufacturerId,
manufacturer: manufacturers[manufacturerId] || "Unknown",
dataLength: data.length - 1, // Exclude manufacturer ID
data: data.slice(1) // Exclude manufacturer ID
};
}import { WebMidi } from "webmidi";
await WebMidi.enable();
const input = WebMidi.inputs[0];
// Raw message listener
input.addListener("midimessage", (e) => {
const message = e.message; // Message object
console.log("Received:", message.type);
console.log("Channel:", message.channel);
console.log("Data:", message.dataBytes);
// Process specific message types
switch (message.type) {
case "noteon":
handleNoteOn(message);
break;
case "controlchange":
handleControlChange(message);
break;
case "sysex":
handleSysex(message);
break;
}
});
function handleNoteOn(message) {
const [note, velocity] = message.dataBytes;
console.log(`Note ${note} played with velocity ${velocity} on channel ${message.channel}`);
}
function handleControlChange(message) {
const [controller, value] = message.dataBytes;
console.log(`CC ${controller} = ${value} on channel ${message.channel}`);
}
function handleSysex(message) {
const analysis = analyzeSysexMessage(message);
console.log(`SysEx from ${analysis.manufacturer}: ${analysis.dataLength} bytes`);
}// Filter messages by type
input.addListener("midimessage", (e) => {
const message = e.message;
// Only process note messages
if (message.type === "noteon" || message.type === "noteoff") {
processNoteMessage(message);
}
});
// Filter by channel
input.addListener("midimessage", (e) => {
const message = e.message;
// Only process messages from channel 1
if (message.channel === 1) {
processChannelOneMessage(message);
}
});
// Filter by data values
input.addListener("midimessage", (e) => {
const message = e.message;
// Only process high-velocity notes
if (message.type === "noteon" && message.dataBytes[1] > 100) {
processHighVelocityNote(message);
}
});// Note: Usually you don't need to create Message objects manually
// The Output methods handle this automatically
// But if you need to create raw messages:
function createNoteOnMessage(channel, note, velocity) {
const statusByte = 0x90 + (channel - 1); // Note on + channel
const data = new Uint8Array([statusByte, note, velocity]);
return new Message(data);
}
function createControlChangeMessage(channel, controller, value) {
const statusByte = 0xB0 + (channel - 1); // Control change + channel
const data = new Uint8Array([statusByte, controller, value]);
return new Message(data);
}
// Usage
const noteOnMsg = createNoteOnMessage(1, 60, 100); // Channel 1, C4, velocity 100
const ccMsg = createControlChangeMessage(1, 64, 127); // Channel 1, sustain ontype MessageType =
| "noteon" | "noteoff" | "keyaftertouch" | "controlchange"
| "programchange" | "channelaftertouch" | "pitchbend"
| "sysex" | "timecode" | "songposition" | "songselect"
| "tunerequest" | "clock" | "start" | "continue" | "stop"
| "activesensing" | "reset";
interface MessageAnalysis {
type: MessageType;
channel?: number;
command?: number;
note?: number;
velocity?: number;
controller?: number;
value?: number;
program?: number;
[key: string]: any;
}Install with Tessl CLI
npx tessl i tessl/npm-webmidi