W3docs

JavaScript Web MIDI API

Scopri la JavaScript Web MIDI API: richiedi accesso, enumera input e output, leggi i dati MIDIMessageEvent, invia messaggi Note On/Off, gestisci il collegamento a caldo dei dispositivi e i requisiti di contesto sicuro.

La Web MIDI API consente a una pagina web di comunicare direttamente con l'hardware MIDI — tastiere, drum pad, superfici di controllo e sintetizzatori — senza alcun plugin o app nativa. È possibile leggere in tempo reale ciò che suona un musicista e inviare note e cambiamenti di controllo a un modulo sonoro.

Questa guida copre l'intero flusso: richiedere l'accesso, enumerare le porte di input e output, leggere i messaggi con MIDIMessageEvent, inviare messaggi Note On/Off e Control Change, gestire dispositivi collegati o rimossi mentre la pagina è aperta, e le regole di contesto sicuro e permessi imposte dall'API.

Cos'è effettivamente il MIDI

MIDI (Musical Instrument Digital Interface) non trasmette audio. Trasmette piccoli eventi — "questo tasto è stato premuto con questa intensità," "questa manopola è stata spostata su questo valore." Il browser espone questi eventi come byte grezzi; trasformarli in suono è compito di un sintetizzatore, che sia hardware esterno o il proprio codice (ad esempio, la Web Audio API).

Un messaggio di canale standard è composto da tre byte:

ByteNomeSignificato
1StatusTipo di messaggio (nibble alto) + canale 0–15 (nibble basso)
2Data 1Numero di nota (0127), o numero di controller
3Data 2Velocità (0127), o valore del controller

Byte di stato comuni (canale 1, dove il nibble di canale è 0):

  • 0x90Note On (velocità 0 viene trattata come Note Off)
  • 0x80Note Off
  • 0xB0Control Change (pedale sustain, modulazione, volume, …)
  • 0xE0Pitch Bend

Il numero di nota 60 corrisponde al Do centrale; la velocità 0127 descrive con quanta forza è stato premuto il tasto.

Richiedere l'accesso

Tutto inizia con navigator.requestMIDIAccess(). Restituisce una Promise che si risolve in un oggetto MIDIAccess contenente le porte disponibili. Il browser può chiedere all'utente il permesso al primo utilizzo.

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

Contesto sicuro e permessi

La Web MIDI API funziona solo in un contesto sicuro — pagine servite tramite https:// (o http://localhost durante lo sviluppo). Chiamare requestMIDIAccess() su una pagina non sicura rifiuta la promise.

L'accesso è anche controllato dalla Permissions Policy e da una richiesta di permesso all'utente. Se l'utente lo nega (o un header Permissions-Policy: midi=() blocca la funzionalità), la promise viene rifiutata — motivo per cui la chiamata è racchiusa in un try/catch. Richiedere sysex: true richiede un livello di privilegio più elevato e genera un prompt separato, poiché i messaggi SysEx possono riprogrammare un dispositivo, quindi richiederlo solo quando necessario.

Enumerare input e output

MIDIAccess espone due collezioni simili a Mapinputs (dispositivi che inviano dati al browser) e outputs (dispositivi a cui il browser può inviare dati). Entrambe sono MIDIInputMap / MIDIOutputMap, quindi si iterano come una Map, con chiave sul id stabile della porta.

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

Ogni porta ha metadati utili: id, name, manufacturer, type ("input" o "output"), state ("connected" / "disconnected"), e connection ("open", "closed", o "pending").

Leggere l'input MIDI

Collega un handler onmidimessage a una porta di input. Ogni evento è un MIDIMessageEvent la cui proprietà data è un Uint8Array dei byte grezzi (vedi Binary arrays per come funzionano gli array tipizzati). Questo è lo stesso pattern di callback che si usa altrove con gli eventi JavaScript.

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

Mascherare il byte di stato con & 0xf0 e & 0x0f separa il tipo di messaggio dal canale, così un singolo handler funziona indipendentemente da quale dei 16 canali MIDI trasmette un dispositivo.

Inviare output MIDI

Per controllare hardware o software esterni, prendi una porta di output e chiama output.send(data), dove data è un array (o Uint8Array) di byte.

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() accetta un timestamp opzionale (un DOMHighResTimeStamp da performance.now()). Pianificare il Note Off nel futuro è più affidabile di setTimeout, perché il timing è gestito dal sottosistema MIDI anziché dall'event loop JavaScript. Inviare 0 senza timestamp significa "adesso."

Evitare le note bloccate

Il bug più comune in assoluto è una nota bloccata — un Note On senza il corrispondente Note Off, che lascia il suono in riproduzione per sempre. Abbinali sempre: tieni traccia di quali note sono attive e invia Note Off quando il tasto viene rilasciato o la pagina viene scaricata.

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

Gestire il collegamento a caldo

I dispositivi MIDI USB vengono collegati e scollegati mentre la pagina è aperta. Ascolta statechange sull'oggetto MIDIAccess in modo da poter collegare handler ai nuovi input connessi e aggiornare l'interfaccia utente quando qualcosa viene scollegato.

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

Supporto browser e best practice

  • Rileva le funzionalità con if (navigator.requestMIDIAccess) prima di chiamare — Safari ha aggiunto il supporto solo di recente, e alcuni ambienti lo disabilitano.
  • Servi tramite HTTPS (o localhost); il requisito del contesto sicuro non è opzionale.
  • Richiedi sysex: true solo quando necessario, poiché attiva un prompt più restrittivo.
  • Abbina sempre Note On / Note Off e silenzia tutte le note su pagehide/beforeunload.
  • Usa i timestamp di send() per un timing preciso invece di setTimeout.
  • Per il suono generato dal browser (anziché hardware esterno), combina l'input MIDI con la Web Audio API.

La Web MIDI API offre alle web app un canale diretto e a bassa latenza verso l'hardware musicale. Richiedi un oggetto MIDIAccess con navigator.requestMIDIAccess(), itera i suoi inputs e outputs, leggi gli eventi in arrivo da MIDIMessageEvent.data, e invia messaggi di tre byte con port.send(). Rispetta le regole di contesto sicuro e permessi, gestisci il collegamento a caldo dei dispositivi tramite statechange, e ripulisci sempre le note per evitare il classico bug delle note bloccate. Da lì puoi costruire tastiere virtuali, controller MIDI e strumenti che suonano direttamente nel browser.

Esercitazione

Pratica
Quali sono le capacità della JavaScript Web MIDI API?
Quali sono le capacità della JavaScript Web MIDI API?
Was this page helpful?