W3docs

JavaScript Web MIDI API

Aprenda a Web MIDI API do JavaScript: solicite acesso, enumere entradas e saídas, leia dados de MIDIMessageEvent, envie mensagens Note On/Off, gerencie hot-plugging de dispositivos e cumpra os requisitos de contexto seguro.

A Web MIDI API permite que uma página web se comunique diretamente com hardware MIDI — teclados, pad de bateria, superfícies de controle e sintetizadores — sem nenhum plugin ou aplicativo nativo. Você pode ler o que um músico toca em tempo real e enviar notas e mudanças de controle de volta para um módulo de som.

Este guia cobre todo o fluxo: solicitar acesso, enumerar portas de entrada e saída, ler mensagens com MIDIMessageEvent, enviar mensagens Note On/Off e Control Change, lidar com dispositivos conectados ou removidos enquanto a página está aberta, e as regras de contexto seguro e permissão que a API impõe.

O que é MIDI de fato

MIDI (Musical Instrument Digital Interface) não transporta áudio. Ele transporta pequenos eventos — "esta tecla foi pressionada com esta intensidade", "este botão girou para este valor". O navegador expõe esses eventos como bytes brutos; transformá-los em som é responsabilidade de um sintetizador, seja hardware externo ou seu próprio código (por exemplo, a Web Audio API).

Uma mensagem de canal padrão possui três bytes:

ByteNomeSignificado
1StatusTipo de mensagem (nibble alto) + canal 0–15 (nibble baixo)
2Dado 1Número da nota (0127) ou número do controlador
3Dado 2Velocidade (0127) ou valor do controlador

Bytes de status comuns (canal 1, onde o nibble de canal é 0):

  • 0x90Note On (velocidade 0 é tratada como Note Off)
  • 0x80Note Off
  • 0xB0Control Change (pedal de sustain, modulação, volume, …)
  • 0xE0Pitch Bend

O número de nota 60 é o Dó central; a velocidade 0127 descreve com que força a tecla foi pressionada.

Solicitando acesso

Tudo começa com navigator.requestMIDIAccess(). Ele retorna uma Promise que resolve para um objeto MIDIAccess contendo as portas disponíveis. O navegador pode solicitar permissão ao usuário no primeiro uso.

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

Contexto seguro e permissões

A Web MIDI API funciona apenas em um contexto seguro — páginas servidas via https:// (ou http://localhost durante o desenvolvimento). Chamar requestMIDIAccess() em uma página insegura rejeita a promise.

O acesso também é controlado pela Permissions Policy e por um prompt de permissão do usuário. Se o usuário negar (ou um cabeçalho Permissions-Policy: midi=() bloquear o recurso), a promise é rejeitada — é por isso que a chamada está envolvida em try/catch. Solicitar sysex: true pede um nível de privilégio mais alto e exibe um prompt separado, pois mensagens SysEx podem reprogramar um dispositivo — portanto, solicite apenas quando necessário.

Enumerando entradas e saídas

MIDIAccess expõe duas coleções semelhantes a Mapinputs (dispositivos que enviam dados ao navegador) e outputs (dispositivos para os quais o navegador pode enviar dados). Ambas são MIDIInputMap / MIDIOutputMap, então você as itera como um Map, com chave pelo id estável da 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}`);
  }
}

Cada porta possui metadados úteis: id, name, manufacturer, type ("input" ou "output"), state ("connected" / "disconnected") e connection ("open", "closed" ou "pending").

Lendo entradas MIDI

Anexe um manipulador onmidimessage a uma porta de entrada. Cada evento é um MIDIMessageEvent cuja propriedade data é um Uint8Array dos bytes brutos (veja Arrays binários para entender como arrays tipados funcionam). Este é o mesmo padrão de callback que você usa em outros lugares com eventos 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));
  }
}

Mascarar o byte de status com & 0xf0 e & 0x0f separa o tipo de mensagem do canal, para que um único manipulador funcione independentemente de qual dos 16 canais MIDI o dispositivo transmite.

Enviando saída MIDI

Para controlar hardware ou software externo, pegue uma porta de saída e chame output.send(data), onde data é um array (ou Uint8Array) de bytes.

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() aceita um timestamp opcional (um DOMHighResTimeStamp de performance.now()). Agendar o Note Off no futuro é mais confiável do que setTimeout, porque o timing é gerenciado pelo subsistema MIDI em vez do loop de eventos JavaScript. Enviar 0 sem timestamp significa "agora mesmo."

Evitando notas presas

O bug mais comum é uma nota presa — um Note On sem o Note Off correspondente, deixando o som tocando indefinidamente. Sempre os emparelhe: rastreie quais notas estão ativas e envie Note Off quando a tecla for liberada ou a página for descarregada.

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

Lidando com hot-plugging

Dispositivos MIDI USB são conectados e desconectados enquanto a página está aberta. Ouça o evento statechange no objeto MIDIAccess para poder anexar manipuladores a novas entradas conectadas e atualizar sua interface quando algo for desconectado.

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

Suporte a navegadores e boas práticas

  • Detecte o recurso com if (navigator.requestMIDIAccess) antes de chamar — o Safari adicionou suporte relativamente recentemente e alguns ambientes o desativam.
  • Sirva via HTTPS (ou localhost); o requisito de contexto seguro não é opcional.
  • Solicite sysex: true apenas quando necessário, pois isso aciona um prompt mais restritivo.
  • Sempre emparelhe Note On / Note Off e silencie todas as notas em pagehide/beforeunload.
  • Use timestamps em send() para timing preciso em vez de setTimeout.
  • Para som gerado pelo navegador (em vez de hardware externo), combine a entrada MIDI com a Web Audio API.

Resumo

A Web MIDI API oferece aos aplicativos web um canal direto e de baixa latência para hardware musical. Solicite um objeto MIDIAccess com navigator.requestMIDIAccess(), itere seus inputs e outputs, leia eventos recebidos de MIDIMessageEvent.data e envie mensagens de três bytes com port.send(). Respeite as regras de contexto seguro e permissão, gerencie o hot-plugging de dispositivos via statechange e sempre limpe as notas para evitar o clássico bug de nota presa. A partir daí, você pode criar teclados virtuais, controladores MIDI e instrumentos que tocam direto no navegador.

Prática

Prática
Quais são as capacidades da Web MIDI API do JavaScript?
Quais são as capacidades da Web MIDI API do JavaScript?
Was this page helpful?