CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-webmidi

JavaScript library for MIDI communication that simplifies sending and receiving MIDI messages between browsers/Node.js and MIDI instruments

Pending
Overview
Eval results
Files

message-processing.mddocs/

Message Processing

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.

Capabilities

Message Construction

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);

Message Properties

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"

Message Types

Channel Messages

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

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"

Message Analysis

Channel Message Analysis

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}`;
}

SysEx Message Analysis

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
  };
}

Working with Input Messages

Processing Incoming Messages

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`);
}

Message Filtering

// 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);
  }
});

Message Creation

Creating Messages for Sending

// 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 on

Types

type 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

docs

constants.md

index.md

message-forwarding.md

message-processing.md

midi-input.md

midi-output.md

note-processing.md

utilities.md

webmidi-interface.md

tile.json