W3docs

JavaScript Web MIDI API

Die JavaScript Web MIDI API: Zugriff anfordern, Ein- und Ausgaben auflisten, MIDIMessageEvent-Daten lesen, Note On/Off senden, Hot-Plugging und Sicherheitsanforderungen verstehen.

Die Web MIDI API ermöglicht es einer Webseite, direkt mit MIDI-Hardware zu kommunizieren — Keyboards, Drum-Pads, Control-Surfaces und Synthesizer — ohne Plugin oder native App. Du kannst in Echtzeit lesen, was ein Musiker spielt, und Noten sowie Control-Changes an ein Klangerzeugungsmodul zurücksenden.

Diese Anleitung deckt den gesamten Ablauf ab: Zugriff anfordern, Ein- und Ausgabe-Ports auflisten, Nachrichten mit MIDIMessageEvent lesen, Note On/Off- und Control-Change-Nachrichten senden, Geräte behandeln, die während des Seitenbesuchs ein- oder abgesteckt werden, sowie die Sicherheitskontextanforderungen und Berechtigungsregeln der API.

Was MIDI eigentlich ist

MIDI (Musical Instrument Digital Interface) überträgt kein Audio. Es überträgt kleine Ereignisse — „diese Taste wurde mit dieser Stärke gedrückt", „dieser Regler wurde auf diesen Wert bewegt". Der Browser stellt diese Ereignisse als rohe Bytes bereit; sie in Klang umzuwandeln ist Aufgabe eines Synthesizers, sei es externe Hardware oder eigener Code (zum Beispiel die Web Audio API).

Eine Standard-Kanalnachricht besteht aus drei Bytes:

ByteNameBedeutung
1StatusNachrichtentyp (oberes Nibble) + Kanal 0–15 (unteres Nibble)
2Data 1Notennummer (0127) oder Controller-Nummer
3Data 2Anschlagstärke (0127) oder Controller-Wert

Häufige Status-Bytes (Kanal 1, wobei das Kanal-Nibble 0 ist):

  • 0x90Note On (Anschlagstärke 0 wird als Note Off behandelt)
  • 0x80Note Off
  • 0xB0Control Change (Haltepedal, Modulation, Lautstärke, …)
  • 0xE0Pitch Bend

Notennummer 60 ist das mittlere C; Anschlagstärke 0127 beschreibt, wie hart die Taste gedrückt wurde.

Zugriff anfordern

Alles beginnt mit navigator.requestMIDIAccess(). Diese Methode gibt ein Promise zurück, das zu einem MIDIAccess-Objekt aufgelöst wird, das die verfügbaren Ports enthält. Der Browser kann den Benutzer beim ersten Aufruf um Erlaubnis bitten.

async function initMIDI() {
  // Feature-detect before calling — support is not universal.
  if (!navigator.requestMIDIAccess) {
    console.warn('Web MIDI API is not supported in this browser.');
    return;
  }

  try {
    // Pass { sysex: true } only if you genuinely need System Exclusive messages —
    // it triggers a stricter, separate permission prompt.
    const midiAccess = await navigator.requestMIDIAccess({ sysex: false });
    console.log('MIDI access granted', midiAccess);
    return midiAccess;
  } catch (err) {
    console.error('Could not access MIDI devices:', err);
  }
}

Sicherheitskontext und Berechtigungen

Die Web MIDI API funktioniert nur in einem sicheren Kontext — Seiten, die über https:// (oder http://localhost während der Entwicklung) ausgeliefert werden. Der Aufruf von requestMIDIAccess() auf einer unsicheren Seite lehnt das Promise ab.

Der Zugriff wird außerdem durch die Permissions Policy und eine Benutzerberechtigungsabfrage gesteuert. Wenn der Benutzer sie ablehnt (oder ein Permissions-Policy: midi=() Header die Funktion sperrt), wird das Promise abgelehnt — weshalb der Aufruf in try/catch eingeschlossen ist. Die Anforderung von sysex: true verlangt eine höhere Berechtigungsstufe und fragt separat nach, da SysEx-Nachrichten ein Gerät umprogrammieren können — fordere es daher nur bei Bedarf an.

Ein- und Ausgaben auflisten

MIDIAccess stellt zwei Map-ähnliche Sammlungen bereit — inputs (Geräte, die Daten an den Browser senden) und outputs (Geräte, an die der Browser Daten senden kann). Beide sind MIDIInputMap / MIDIOutputMap und lassen sich wie eine Map iterieren, mit einer stabilen Port-id als Schlüssel.

function listPorts(midiAccess) {
  console.log('Inputs:');
  for (const input of midiAccess.inputs.values()) {
    console.log(`  ${input.name} (${input.manufacturer}) — ${input.state}`);
  }

  console.log('Outputs:');
  for (const output of midiAccess.outputs.values()) {
    console.log(`  ${output.name} (${output.manufacturer}) — ${output.state}`);
  }
}

Jeder Port enthält nützliche Metadaten: id, name, manufacturer, type ("input" oder "output"), state ("connected" / "disconnected") und connection ("open", "closed" oder "pending").

MIDI-Eingabe lesen

Hänge einen onmidimessage-Handler an einen Eingabe-Port. Jedes Ereignis ist ein MIDIMessageEvent, dessen data-Eigenschaft ein Uint8Array der rohen Bytes ist (siehe Binäre Arrays, wie typisierte Arrays funktionieren). Dieses Callback-Muster ist dasselbe, das du anderswo mit JavaScript-Ereignissen verwendest.

function startListening(midiAccess) {
  midiAccess.inputs.forEach((input) => {
    input.onmidimessage = onMIDIMessage;
  });
}

function onMIDIMessage(event) {
  // event.data is a Uint8Array; channel messages are usually 3 bytes.
  const [status, data1, data2] = event.data;
  const command = status & 0xf0; // high nibble = message type
  const channel = status & 0x0f; // low nibble = channel 0–15

  switch (command) {
    case 0x90: // Note On
      if (data2 > 0) {
        console.log(`Note On  — note ${data1}, velocity ${data2}, ch ${channel}`);
      } else {
        console.log(`Note Off — note ${data1} (velocity 0)`);
      }
      break;
    case 0x80: // Note Off
      console.log(`Note Off — note ${data1}, ch ${channel}`);
      break;
    case 0xb0: // Control Change
      console.log(`Control Change — controller ${data1}, value ${data2}`);
      break;
    default:
      console.log('Other message:', Array.from(event.data));
  }
}

Das Maskieren des Status-Bytes mit & 0xf0 und & 0x0f trennt den Nachrichtentyp vom Kanal, sodass ein Handler funktioniert, unabhängig davon, auf welchem der 16 MIDI-Kanäle ein Gerät sendet.

MIDI-Ausgabe senden

Um externe Hardware oder Software zu steuern, hol dir einen Ausgabe-Port und rufe output.send(data) auf, wobei data ein Array (oder Uint8Array) von Bytes ist.

function sendNote(midiAccess) {
  const output = midiAccess.outputs.values().next().value; // first available port
  if (!output) {
    console.log('No MIDI outputs available.');
    return;
  }

  output.send([0x90, 60, 100]); // Note On: Middle C, velocity 100, channel 1
  output.send([0x80, 60, 0], performance.now() + 500); // Note Off scheduled 500 ms later
}

send() akzeptiert einen optionalen Zeitstempel (ein DOMHighResTimeStamp von performance.now()). Das Planen des Note Off in der Zukunft ist zuverlässiger als setTimeout, da das Timing vom MIDI-Subsystem und nicht von der JavaScript-Ereignisschleife übernommen wird. Das Senden von 0 ohne Zeitstempel bedeutet „sofort".

Hängende Noten vermeiden

Der häufigste Fehler ist eine hängende Note — ein Note On ohne passendes Note Off, das den Klang endlos spielen lässt. Paare sie immer: Verfolge, welche Noten aktiv sind, und sende Note Off, wenn die Taste losgelassen wird oder die Seite entladen wird.

const activeNotes = new Set();

function noteOn(output, note, velocity = 100) {
  output.send([0x90, note, velocity]);
  activeNotes.add(note);
}

function noteOff(output, note) {
  output.send([0x80, note, 0]);
  activeNotes.delete(note);
}

// Panic: silence everything (e.g. on window 'pagehide')
function allNotesOff(output) {
  for (const note of activeNotes) output.send([0x80, note, 0]);
  activeNotes.clear();
}

Hot-Plugging behandeln

USB-MIDI-Geräte werden während des Seitenbesuchs ein- und abgesteckt. Höre auf statechange am MIDIAccess-Objekt, um Handler an neu verbundene Eingaben anzuhängen und die Benutzeroberfläche zu aktualisieren, wenn etwas abgesteckt wird.

async function setupMIDI() {
  const midiAccess = await navigator.requestMIDIAccess();

  function attachInputHandlers() {
    midiAccess.inputs.forEach((input) => {
      input.onmidimessage = onMIDIMessage;
    });
  }

  attachInputHandlers();

  midiAccess.onstatechange = (event) => {
    const port = event.port;
    console.log(`${port.type} "${port.name}" is now ${port.state}`);
    if (port.type === 'input' && port.state === 'connected') {
      attachInputHandlers(); // wire up the device that just appeared
    }
  };
}

Browserunterstützung und Best Practices

  • Feature-Detection mit if (navigator.requestMIDIAccess) vor dem Aufruf — Safari hat die Unterstützung erst kürzlich hinzugefügt, und einige Umgebungen deaktivieren sie.
  • Über HTTPS (oder localhost) ausliefern; der Sicherheitskontextanforderung ist nicht optional.
  • sysex: true nur bei Bedarf anfordern, da es eine strengere Abfrage auslöst.
  • Note On / Note Off immer paaren und alle Noten bei pagehide/beforeunload stummschalten.
  • send()-Zeitstempel verwenden für präzises Timing anstelle von setTimeout.
  • Für browserseitig erzeugten Klang (statt externer Hardware) kombiniere MIDI-Eingabe mit der Web Audio API.

Zusammenfassung

Die Web MIDI API gibt Webanwendungen einen direkten, latenzarmen Kanal zu musikalischer Hardware. Fordere ein MIDIAccess-Objekt mit navigator.requestMIDIAccess() an, iteriere über seine inputs und outputs, lese eingehende Ereignisse aus MIDIMessageEvent.data und sende Drei-Byte-Nachrichten mit port.send(). Beachte die Sicherheitskontext- und Berechtigungsregeln, behandle das Hot-Plugging von Geräten über statechange und bereinige stets die Noten, um den klassischen Fehler hängender Noten zu vermeiden. Darauf aufbauend kannst du virtuelle Keyboards, MIDI-Controller und Instrumente erstellen, die direkt im Browser spielen.

Übungen

Übung
Was sind die Fähigkeiten der JavaScript Web MIDI API?
Was sind die Fähigkeiten der JavaScript Web MIDI API?
Was this page helpful?