Web MIDI API

W3C Working Draft

More details about this document
This version:
https://www.w3.org/TR/2024/WD-webmidi-20241011/
Latest published version:
https://www.w3.org/TR/webmidi/
Latest editor's draft:
https://webaudio.github.io/web-midi-api/
History:
https://www.w3.org/standards/history/webmidi/
Commit history
Test suite:
https://github.com/web-platform-tests/wpt/tree/master/webmidi
Editors:
(Google)
(Google)
Former editor:
Jussi Kalliokoski
Feedback:
GitHub WebAudio/web-midi-api (pull requests, new issue, open issues)
Implementation report:
No preliminary interoperability or implementation report exists.

Abstract

Some user agents have music devices, such as synthesizers, keyboard and other controllers, and drum machines connected to their host computer or device. The widely adopted Musical Instrument Digital Interface (MIDI) protocol enables electronic musical instruments, controllers and computers to communicate and synchronize with each other. MIDI does not transmit audio signals: instead, it sends event messages about musical notes, controller signals for parameters such as volume, vibrato and panning, cues and clock signals to set the tempo, and system-specific MIDI communications (e.g. to remotely store synthesizer-specific patch data). This same protocol has become a standard for non-musical uses, such as show control, lighting and special effects control.

This specification defines an API supporting the MIDI protocol, enabling web applications to enumerate and select MIDI input and output devices on the client system and send and receive MIDI messages. It is intended to enable non-music MIDI applications as well as music ones, by providing low-level access to the MIDI devices available on the users' systems. The Web MIDI API is not intended to describe music or controller inputs semantically; it is designed to expose the mechanics of MIDI input and output interfaces, and the practical aspects of sending and receiving MIDI messages, without identifying what those actions might mean semantically (e.g., in terms of "modulate the vibrato by 20Hz" or "play a G#7 chord", other than in terms of changing a controller value or sending a set of note-on messages that happen to represent a G#7 chord).

To some users, "MIDI" has become synonymous with Standard MIDI Files and General MIDI. That is not the intent of this API; the use case of simply playing back a .SMF file is not within the purview of this specification (it could be considered a different format to be supported by the HTML audio element, for example). The Web MIDI API is intended to enable direct access to devices that respond to MIDI - controllers, external synthesizers or lighting systems, for example. The Web MIDI API is also explicitly designed to enable a new class of applications on the web that can respond to MIDI controller inputs - using external hardware controllers with physical buttons, knobs and sliders (as well as musical controllers like keyboard, guitar or wind instrument controllers) to control web applications.

The Web MIDI API is also expected to be used in conjunction with other APIs and elements of the web platform, notably the Web Audio API. This API is also intended to be familiar to users of MIDI APIs on other systems, such as Apple's CoreMIDI and Microsoft's Windows MIDI API.

Status of This Document

This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.

This document was published by the Audio Working Group as a Working Draft using the Recommendation track.

Publication as a Working Draft does not imply endorsement by W3C and its Members.

This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 03 November 2023 W3C Process Document.

1. Introduction

This section is non-normative.

The Web MIDI API specification defines a means for web developers to enumerate, manipulate and access MIDI devices - for example, interfaces that may provide hardware MIDI ports with other devices plugged in to them and USB devices that support the USB-MIDI specification. Having a Web API for MIDI enables web applications that use existing software and hardware synthesizers, hardware music controllers and light systems and other mechanical apparatus controlled by MIDI. This API has been defined with this wide variety of use cases in mind.

The approaches taken by this API are similar to those taken in Apple's CoreMIDI API and Microsoft's Windows MIDI API; that is, the API is designed to represent the low-level software protocol of MIDI, in order to enable developers to build powerful MIDI software on top. The API enables the developer to enumerate input and output interfaces, and send and receive MIDI messages, but (similar to the aforementioned APIs) it does not attempt to semantically define or interpret MIDI messages beyond what is necessary to robustly support current devices.

The Web MIDI API is not intended to directly implement high-level concepts such as sequencing; it does not directly support Standard MIDI Files, for example, although a Standard MIDI File player can be built on top of the Web MIDI API. It is also not intended to semantically capture patches or controller assignments, as General MIDI does; such interpretation is outside the scope of the Web MIDI API (though again, General MIDI can easily be utilized through the Web MIDI API).

2. Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

The key words MUST and SHOULD in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

This specification defines conformance criteria that apply to a single product: the user agent that implements the interfaces that it contains.

Implementations that use ECMAScript to implement the APIs defined in this specification MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [WEBIDL], as this specification uses that specification and terminology.

3. Terminology

The Web Audio API and its associated interfaces and concepts are defined in [webaudio].

The terms MIDI, MIDI device, MIDI input port, MIDI output port, MIDI interface, MIDI message, System Real Time and System Exclusive are defined in [MIDI].

Note
Valid MIDI message is defined in [MIDI]. The following may be used as a non-normative guide:
  • The first byte (the status byte) should have the high bit set, any following bytes should not unless they are part of a System Exclusive message
  • If the high nibble of the status byte in hex is 8, 9, A, B, or E then the total message length should be 3 bytes
  • If the high nibble of the status byte is C or D then the total message length should be 2 bytes
  • If the status byte is F1 or F3 then the total message length should be 2 bytes
  • If the status byte is F2 then the total message length should be 3 bytes
  • If the status byte is F6, F8, FA, FB, FC, FE, or FF then the total message length should be 1 byte (only the status byte)
  • If the status byte is F0 then this is a System Exclusive message with no length restriction, and the last byte should be F7
  • If the status byte is F4, F5, F7, F9, or FD then the message is not valid

4. Obtaining Access to MIDI Devices

4.1 Permissions Integration

The Web Midi API is a powerful feature that is identified by the name "midi". It integrates with Permissions by defining the following permission-related flags:

permission descriptor type
WebIDLdictionary MidiPermissionDescriptor : PermissionDescriptor {
  boolean sysex = false;
};

{name: "midi", sysex: true} is stronger than {name: "midi", sysex: false}.

4.2 Permissions Policy Integration

The Web Midi API defines a policy-controlled feature named "midi" which has a default allowlist of 'self'.

4.3 Extensions to the Navigator interface

WebIDLpartial interface Navigator {
  [SecureContext]
  Promise <MIDIAccess> requestMIDIAccess(optional MIDIOptions options = {});
};
requestMIDIAccess() method

When invoked, returns a Promise object representing a request for access to MIDI devices on the user's system.

Requesting MIDI access SHOULD prompt the user for access to MIDI devices, particularly if System Exclusive access is requested. In some scenarios, this permission may have already been implicitly or explicitly granted, in which case this prompt may not appear. If the user gives express permission or the call is otherwise approved, the vended Promise is resolved. The underlying system may choose to allow the user to select specific MIDI interfaces to expose to this API (i.e. pick and choose interfaces on an individual basis), although this is not required. The system may also choose to prompt (or not) based on whether System Exclusive support is requested, as System Exclusive access has greater privacy and security implications.

If the user declines or the call is denied for any other reason, the Promise is rejected with a DOMException parameter.

When the requestMIDIAccess() method is called, the user agent MUST run the following steps:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. Let document be the calling context's Document.

  4. If document is not allowed to use the policy-controlled feature named midi, jump to the step labeled failure below.

  5. Optionally, e.g. based on a previously-established user preference, for security reasons, or due to platform limitations, jump to the step labeled failure below.

  6. Optionally, e.g. based on a previously-established user preference, jump to the step labeled success below.

  7. Prompt the user in a user-agent-specific manner for permission to provide the entry script's origin with a MIDIAccess object representing control over user's MIDI devices. This prompt may be contingent upon whether System Exclusive support was requested, and may allow the user to enable or disable that access.

    If permission is denied, jump to the step labeled failure below. If the user never responds, this algorithm will never progress beyond this step. If permission is granted, continue the following steps.

  8. success: Let access be a new MIDIAccess object. (It is possible to call requestMIDIAccess() multiple times; this may prompt the user multiple times, so it may not be best practice, and the same instance of MIDIAccess will not be returned each time.)

  9. Call resolver's accept(value) method with access as value argument.

  10. Terminate these steps.

  11. failure: Let error be a new DOMException. This exception's .name should be "NotAllowedError" if the user or their security settings denied the application from creating a MIDIAccess instance with the requested options, or if the error is the result of document not being allowed to use the feature, "AbortError" if the page is going to be closed for a user navigation, "InvalidStateError" if the underlying systems raise any errors, or otherwise it should be "NotSupportedError".

  12. Call resolver's reject(value) method with error as value argument.

4.3.1 MIDIOptions Dictionary

This dictionary contains optional settings that may be provided to the requestMIDIAccess() request.

WebIDLdictionary MIDIOptions {
  boolean sysex;
  boolean software;
};
sysex

This member informs the system whether the ability to send and receive System Exclusive messages is requested or allowed on a given MIDIAccess object. On the option passed to requestMIDIAccess(), if this member is set to true, but System Exclusive support is denied (either by policy or by user action), the access request will fail with a "NotAllowedError" error. If this support is not requested (and allowed), the system will throw exceptions if the user tries to send System Exclusive messages, and will silently mask out any System Exclusive messages received on the port.

software

This member informs the system whether the ability to utilize any software synthesizers installed in the host system is requested or allowed on a given MIDIAccess object. On requestMIDIAccess(), if this member is set to true, but software synthesizer support is denied (either by policy or by user action), the access request will fail with a "NotAllowedError" error. If this support is not requested, the system should not include any software synthesizers in the MIDIAccess exposure of available ports.

Note that may result in a two-step request procedure if software synthesizer support is desired but not required - software synthesizers may be disabled when MIDI hardware device access is allowed.

5. The MIDI API

5.1 MIDIInputMap Interface

WebIDL[SecureContext, Exposed=(Window,Worker)] interface MIDIInputMap {
  readonly maplike <DOMString, MIDIInput>;
};

The MIDIInputMap is a maplike interface whose value is a MIDIInput instance and key is its ID.

This type is used to represent all the currently available MIDI input ports. This enables:

// to tell how many entries there are:
let numberOfMIDIInputs = inputs.size;

// add each of the ports to a <select> box
inputs.forEach( function( port, key ) {
  let opt = document.createElement("option");
  opt.text = port.name;
  document.getElementById("inputportselector").add(opt);
});

// or you could express in ECMAScript 6 as:
for (let input of inputs.values()) {
  let opt = document.createElement("option");
  opt.text = input.name;
  document.getElementById("inputportselector").add(opt);
}

5.2 MIDIOutputMap Interface

WebIDL[SecureContext, Exposed=(Window,Worker)] interface MIDIOutputMap {
  readonly maplike <DOMString, MIDIOutput>;
};

The MIDIOutputMap is a maplike interface whose value is a MIDIOutput instance and key is its ID.

This type is used to represent all the currently available MIDI output ports. This enables:

// to tell how many entries there are:
let numberOfMIDIOutputs = outputs.size;

// add each of the ports to a <select> box
outputs.forEach( function( port, key ) {
  let opt = document.createElement("option");
  opt.text = port.name;
  document.getElementById("outputportselector").add(opt);
});

// or you could express in ECMAScript 6 as:
for (let output of outputs.values()) {
  let opt = document.createElement("option");
  opt.text = output.name;
  document.getElementById("outputportselector").add(opt);
}

5.3 MIDIAccess Interface

This interface provides the methods to list MIDI input and output devices, and obtain access to an individual device.

WebIDL[SecureContext, Exposed=(Window,Worker), Transferable] interface MIDIAccess: EventTarget {
  readonly attribute MIDIInputMap inputs;
  readonly attribute MIDIOutputMap outputs;
  attribute EventHandler onstatechange;
  readonly attribute boolean sysexEnabled;
};
inputs
The MIDI input ports available to the system.
outputs
The MIDI output ports available to the system.
onstatechange

The handler called when a new port is connected or an existing port changes the state attribute.

This event handler, of type MIDIConnectionEvent, MUST be supported by all objects implementing the MIDIAccess interface.

Note

It is important to understand that leaving an EventHandler attached to this object will prevent it from being garbage-collected; when finished using the MIDIAccess, you should remove any onstatechange listeners.

Whenever a previously unavailable MIDI port becomes available for use, or an existing port changes the state attribute, the user agent SHOULD run the following steps:

  1. Let port be the MIDIPort corresponding to the newly-available, or the existing port.
  2. Fire an event named "statechange" at the MIDIAccess, using MIDIConnectionEvent with port set to port.
sysexEnabled
This attribute informs the user whether System Exclusive support is enabled on this MIDIAccess.

5.4 MIDIPort Interface

This interface represents a MIDI input or output port.

WebIDL[SecureContext, Exposed=(Window,Worker)] interface MIDIPort: EventTarget {
  readonly attribute DOMString id;
  readonly attribute DOMString? manufacturer;
  readonly attribute DOMString? name;
  readonly attribute MIDIPortType type;
  readonly attribute DOMString? version;
  readonly attribute MIDIPortDeviceState state;
  readonly attribute MIDIPortConnectionState connection;
  attribute EventHandler onstatechange;
  Promise <MIDIPort> open();
  Promise <MIDIPort> close();
};
id

A unique ID of the port. This can be used by developers to remember ports the user has chosen for their application. The User Agent MUST ensure that the id is unique to only that port. The User Agent SHOULD ensure that the id is maintained across instances of the application - e.g., when the system is rebooted - and when a device is removed from the system. Applications may want to cache these ids locally to re-create a MIDI setup. Some systems may not support completely unique persistent identifiers; in such cases, it will be more challenging to maintain identifiers when another interface is added or removed from the system. (This might throw off the index of the requested port.) It is expected that the system will do the best it can to match a port across instances of the MIDI API: for example, an implementation may opaquely use some form of hash of the port interface manufacturer, name and index as the id, so that a reference to that port id is likely to match the port when plugged in. Applications may use the comparison of id of MIDIPorts to test for equality.

manufacturer

The manufacturer of the port.

name

The system name of the port.

type

A descriptor property to distinguish whether the port is an input or an output port. For MIDIOutput, this MUST be "output". For MIDIInput, this MUST be "input".

version

The version of the port.

state
The state of the device.
connection
The state of the connection to the device.
onstatechange

The handler called when an existing port changes its state or connection attributes.

This event handler, of type "statechange", MUST be supported by all objects implementing MIDIPort interface.

Note

It is important to understand that leaving an EventHandler attached to this object will prevent it from being garbage-collected; when finished using the MIDIPort, you should remove any onstatechange listeners.

open

Makes the MIDI device corresponding to the MIDIPort explicitly available. Note that this call is NOT required in order to use the MIDIPort- calling send() on a MIDIOutput or attaching a MIDIMessageEvent handler on a MIDIInput will cause an implicit open(). The underlying implementation may not need to do anything in response to this call. However, some underlying implementations may not be able to support shared access to MIDI devices, so using explicit open() and close() calls will enable MIDI applications to predictably control this exclusive access to devices.

When invoked, this method returns a Promise object representing a request for access to the given MIDI port on the user's system.

If the port device has a state of "connected", when access to the port has been obtained (and the port is ready for input or output), the vended Promise is resolved.

If access to a connected port is not available (for example, the port is already in use in an exclusive-access-only platform), the Promise is rejected (if any) is invoked.

If open() is called on a port that is "disconnected", the port's .connection will transition to "pending", until the port becomes "connected" or all references to it are dropped.

When this method is called, the user agent MUST run the algorithm to open a MIDIPort:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. Let port be the given MIDIPort object.

  4. If the device's connection is already "open" (e.g. open() has already been called on this MIDIPort, or the port has been implicitly opened), jump to the step labeled success below.

  5. If the device's connection is "pending" (i.e. the connection had been opened and the device was subsequently disconnected), jump to the step labeled success below.

  6. If the device's state is "disconnected", change the connection attribute of the MIDIPort to "pending", and enqueue a new MIDIConnectionEvent to the statechange handler of the MIDIAccess and to the statechange handler of the MIDIPort and jump to the step labeled success below.

  7. Attempt to obtain access to the given MIDI device in the system. If the device is unavailable (e.g. is already in use by another process and cannot be opened, or is disconnected), jump to the step labeled failure below. If the device is available and access is obtained, continue the following steps.

  8. Change the connection attribute of the MIDIPort to "open", and enqueue a new MIDIConnectionEvent to the statechange handler of the MIDIAccess and to the statechange handler of the MIDIPort.

  9. If this port is an output port and has any pending data that is waiting to be sent, asynchronously begin sending that data.

  10. success: Call resolver's accept(value) method with port as value argument.

  11. Terminate these steps.

  12. failure: Let error be a new DOMException. This exception's .name should be "InvalidAccessError" if the port is unavailable.

  13. Call resolver's reject(value) method with error as value argument.

close

Makes the MIDI device corresponding to the MIDIPort explicitly unavailable (subsequently changing the state from "open" to "closed"). Note that successful invocation of this method will result in MIDI messages no longer being delivered to MIDIMessageEvent handlers on a MIDIInput (although setting a new handler will cause an implicit open()).

The underlying implementation may not need to do anything in response to this call. However, some underlying implementations may not be able to support shared access to MIDI devices, and the explicit close() call enables MIDI applications to ensure other applications can gain access to devices.

When invoked, this method returns a Promise object representing a request for access to the given MIDI port on the user's system. When the port has been closed (and therefore, in exclusive access systems, the port is available to other applications), the vended Promise is resolved. If the port is disconnected, the Promise is rejected.

When the close() method is called, the user agent MUST run the following steps:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. Let port be the given MIDIPort object.

  4. If the port is already closed (its .connection is "closed" - e.g. the port has not yet been implicitly or explicitly opened, or close() has already been called on this MIDIPort), jump to the step labeled closed below.

  5. If the port is an input port, skip to the next step. If the output port's .state is not "connected", clear all pending send data and skip to the next step. Clear any pending send data in the system with timestamps in the future, then finish sending any send messages with no timestamp or with a timestamp in the past or present, prior to proceeding to the next step.

  6. Close access to the port in the underlying system if open, and release any blocking resources in the underlying system.

  7. Change the connection attribute of the MIDIPort to "closed", and enqueue a new MIDIConnectionEvent to the statechange handler of the MIDIAccess and to the statechange handler of the MIDIPort.

  8. closed: Call resolver's accept(value) method with port as value argument.

  9. Terminate these steps.

Whenever the MIDI port corresponding to the MIDIPort changes the state attribute, the user agent SHOULD run the following steps:

  1. Let port be the MIDIPort.

  2. Fire an event named statechange at the MIDIPort, and statechange at the MIDIAccess, using MIDIConnectionEvent with the port attribute set to port.

5.4.1 MIDIInput Interface

WebIDL[SecureContext, Exposed=(Window,Worker)] interface MIDIInput: MIDIPort {
  attribute EventHandler onmidimessage;
};
onmidimessage

This event handler, of type "midimessage", MUST be supported by all objects implementing MIDIInput interface.

If the handler is set and the state attribute is not "opened", underlying implementation tries to make the port available, and change the state attribute to "opened". If succeeded, MIDIConnectionEvent is delivered to the corresponding MIDIPort and MIDIAccess.

Whenever the MIDI port corresponding to the MIDIInput finishes receiving one or more MIDI messages, the user agent MUST run the following steps:

  1. Let port be the MIDIInput.

  2. If the MIDIAccess did not enable System Exclusive access, and the message is a System Exclusive message, abort this process.

  3. Fire an event named "midimessage" at port, using MIDIMessageEvent with the timeStamp attribute set to the time the message was received by the system, and with the data attribute set to a Uint8Array of MIDI data bytes representing a single MIDI message.

It is specifically noted that MIDI System Real Time messages may actually occur in the middle of other messages in the input stream; in this case, the System Real Time messages will be dispatched as they occur, while the normal messages will be buffered until they are complete (and then dispatched).

5.4.2 MIDIOutput Interface

WebIDL[SecureContext, Exposed=(Window,Worker)] interface MIDIOutput : MIDIPort {
  undefined send(sequence<octet> data, optional DOMHighResTimeStamp timestamp = 0);
  undefined clear();
};
send

Enqueues the message to be sent to the corresponding MIDI port. The underlying implementation will (if necessary) coerce each member of the sequence to an unsigned 8-bit integer. The use of sequence rather than a Uint8Array enables developers to use the convenience of output.send( [ 0x90, 0x45, 0x7f ] ); rather than having to create a Uint8Array, e.g. output.send( new Uint8Array( [ 0x90, 0x45, 0x7f ] ) );

The data contains one or more complete, valid MIDI messages. Running status is not allowed in the data, as underlying systems may not support it.

If data is not a valid sequence or does not contain a valid MIDI message, throw a TypeError exception.

If data is a System Exclusive message, and the MIDIAccess did not enable System Exclusive access, throw an InvalidAccessError exception.

If the port is "disconnected", throw an InvalidStateError exception.

If the port is "connected" but the connection is "closed", asynchronously try to open the port.

sequence<octet> data
The data to be enqueued, with each sequence entry representing a single byte of data.
optional DOMHighResTimeStamp timestamp
The time at which to begin sending the data to the port (as a DOMHighResTimeStamp - a number of milliseconds measured relative to the navigation start of the document). If timestamp is set to zero (or another time in the past), the data is to be sent as soon as possible.
clear

Clears any enqueued send data that has not yet been sent from the MIDIOutput's queue. The implementation will need to ensure the MIDI stream is left in a good state, so if the output port is in the middle of a sysex message, a sysex termination byte (0xf7) should be sent.

5.4.3 MIDIPortType Enum

WebIDLenum MIDIPortType {
  "input",
  "output",
};
input
If a MIDIPort is an input port, the type member MUST be this value.
output
If a MIDIPort is an output port, the type member MUST be this value.

5.4.4 MIDIPortDeviceState Enum

WebIDLenum MIDIPortDeviceState {
  "disconnected",
  "connected",
};
disconnected
The device that MIDIPort represents is disconnected from the system. When a device is disconnected from the system, it should not appear in the relevant map of input and output ports.
connected
The device that MIDIPort represents is connected, and should appear in the map of input and output ports.

5.4.5 MIDIPortConnectionState Enum

WebIDLenum MIDIPortConnectionState {
  "open",
  "closed",
  "pending",
};
open
The device that MIDIPort represents has been opened (either implicitly or explicitly) and is available for use.
closed
The device that MIDIPort represents has not been opened, or has been explicitly closed. Until a MIDIPort has been opened either explicitly (through MIDIPort.open()) or implicitly (by adding a midimessage event handler on an input port, or calling MIDIOutput.send() on an output port, this should be the default state of the device.
pending
The device that MIDIPort represents has been opened (either implicitly or explicitly), but the device has subsequently been disconnected and is unavailable for use. If the device is reconnected, prior to sending a statechange event, the system should attempt to reopen the device (following the algorithm to open a MIDIPort); this will result in either the connection state transitioning to "open" or to "closed".

5.5 MIDIMessageEvent Interface

An event object implementing this interface is passed to a MIDIInput's onmidimessage handler when MIDI messages are received. Note that the DOM Event timeStamp attribute is defined as a DOMHighResTimeStamp, and represents the high-resolution time of when the event was received or is to be sent.

WebIDL[SecureContext, Exposed=(Window,Worker)]
interface MIDIMessageEvent : Event {
  constructor(DOMString type, optional MIDIMessageEventInit eventInitDict = {});
  readonly attribute Uint8Array? data;
};
data

A Uint8Array containing the MIDI data bytes of a single MIDI message.

5.5.1 MIDIMessageEventInit Dictionary

WebIDLdictionary MIDIMessageEventInit: EventInit {
  Uint8Array data;
};
data

A Uint8Array containing the MIDI data bytes of a single MIDI message.

5.6 MIDIConnectionEvent Interface

An event object implementing this interface is passed to a MIDIAccess' onstatechange handler when a new port becomes available (for example, when a MIDI device is first plugged in to the computer), when a previously-available port becomes unavailable, or becomes available again (for example, when a MIDI interface is disconnected, then reconnected) and (if present) is also passed to the onstatechange handlers for any MIDIPorts referencing the port.

When a MIDIPort is in the "pending" state and the device is reconnected to the host system, prior to firing a statechange event the algorithm to open a MIDIPort is run on it to attempt to reopen the port. If this transition fails (e.g. the Port is reserved by something else in the underlying system, and therefore unavailable for use), the connection state moves to "closed", else it transitions back to "open". This is done prior to the statechange event for the device state change so that the event will reflect the final connection state as well as the device state.

Some underlying systems may not provide notification events for device connection status; such systems may have long time delays as they poll for new devices infrequently. As such, it is suggested that heavy reliance on connection events not be used.

WebIDL[SecureContext, Exposed=(Window,Worker)]
interface MIDIConnectionEvent : Event {
  constructor(DOMString type, optional MIDIConnectionEventInit eventInitDict = {});
  readonly attribute MIDIPort? port;
};
port

The port that has been connected or disconnected.

5.6.1 MIDIConnectionEventInit Dictionary

WebIDLdictionary MIDIConnectionEventInit: EventInit {
  MIDIPort port;
};
port

The port that has been connected or disconnected.

6. Privacy Considerations

Allowing the enumeration of the user's MIDI interfaces is a potential target for fingerprinting; that is, uniquely identifying a user by the specific MIDI interfaces they have connected.

Note that in this context what can be enumerated is the MIDI interfaces. This includes most devices connected to the host computer with USB, since USB-MIDI devices typically have their own MIDI interface and would be enumerated. An individual sampler or synthesizer MIDI device plugged into a MIDI interface with a 5-pin DIN cable would not be enumerated. The interfaces that could be fingerprinted are equivalent to MIDI "ports", and for each MIDI interface the API will expose the name of the device, manufacturer, and opaque identifier of the MIDI interface.

Most systems have no MIDI interfaces attached. Few systems will have large numbers of MIDI interfaces attached. Thus, the additional fingerprinting exposure of enumerating MIDI devices is similar to the Gamepad API’s additional fingerprinting exposure through gamepad enumeration: typical users will have at most a few devices connected, their configuration may change, and the information exposed is about the interface itself (i.e., no user-configured data).

7. Security Considerations

Separate from the fingerprinting concerns of identifying the available ports are concerns around sending and receiving MIDI messages. Those issues are explored in more depth below.

In brief, the general categories of things you can do with MIDI ports are:

  1. Sending short messages (all messages except SysEx)
  2. Receiving short messages (all messages except SysEx)
  3. Sending SysEx messages. SysEx messages include both commonly recognized MIDI Time Code and MIDI Sample Dump Standard, as well as device-specific messages (like “patch control data for a Roland Jupiter-80 synthesizer”) that do not apply to other devices.
  4. Receiving SysEx messages.

The impact of each of these is:

  1. Sending short messages: sending note-on/note-off/controller messages would let you cause sounds to be played by attached devices, including (on Mac and Windows) any default virtual synthesizers. This by itself does not cause any concerning exposure - you can already make sounds without interaction, through <audio>, Flash, or Web Audio. Some attached devices might be professional lighting control systems, so it’s possible you could control stage lighting; however, this is extremely rare, and no known system has the ability to cause lasting damage or information leakage based solely on short messages; at worst, a malicious page could flash lights, and the user could close the page and reset their lighting controller.
  2. Receiving short messages: receiving note-on/note-off/controller messages would not cause any information exposure or security issues, as there is no identifying data being received, just a stream of controller messages - all of which must be initiated by the user on that MIDI device (except clock-type messages). This is very analogous to receiving keyboard or mouse events.
  3. Sending and Receiving SysEx. This is the biggest concern, because it would be possible to write code that looked for system-specific responses to sysex messages, which could identify the hardware available, and then use it to download data - e.g. samples stored in a sampler - or replace that data (erasing sample data or patches in the device), although both these scenarios would have to be coded for a particular device. It is also possible that some samplers might enable a System Exclusive message to start recording a sample - so if the sampler happened to have a dedicated microphone attached (uncommon in practice, but possible), it would be possible to write code specific to a particular device that could record a short sample of sound and then upload it to the network without further user intervention. (You could not stream audio from the device, and most samplers have fairly limited memory, and MIDI Sample Dump sysex is a slow way to transfer data - it has to transcode into 7-bit - so it’s unlikely you could listen in for long periods.) More explicit fingerprinting is a concern, as the patch information/stored samples/user configuration could uniquely identify the system (although again, this requires much device-specific code; there is not standardized “grab all patches and hash it” capability.) This does suggest that System Exclusive messages are in a security category of their own.

It's also useful to examine what scenarios are enabled by MIDI, mapped against these features:

  1. Receiving short messages. This is the most attractive scenario for Web MIDI, as it enables getting input from keyboards, drum pads, guitars, wind controllers, DJ/controllerist controllers, and more, and use those messages as input to control instruments and features in the Web Audio API as well as other control scenarios (MIDI is the protocol of choice for the multi-billion-dollar music production industry for getting physical controllers like knobs and buttons attached to your computer, both in pro/prosumer audio and media applications as well as consumer applications like Garageband.)
  2. Sending short messages - it’s tempting to say sending is significantly less interesting, as the scenario of attached output devices like hardware synthesizers is less common in today's market. The major exception to this is that many of the MIDI controllers have external host control of their indicator lights, and this makes them dramatically more useful. For example, the very popular Novation Launchpad controller uses MIDI note on/off messages sent to it to turn on/off and change colors of the buttons. The same is true of nearly all DJ controllers.
  3. Sending and receiving SysEx - obviously, for more advanced communication with high-end hardware devices, SysEx is required. Unfortunately, some common MIDI commands are also sent as System Exclusive messages (MIDI Machine Control, for example - generic start/stop/rew/ffw commands) - and many devices use System Exclusive to program patches, send advanced controller messages, download firmware, etc., which are much-demanded scenarios for Web MIDI. Some devices use sysex as a direct control protocol, as they can pack more data into a single “message”, and most devices use SysEx as way to save and restore patches and configuration information on less-expensive computer storage. Several of the major music hardware producers have expressed strong interest in using Web MIDI to provide web-based configuration and programming interfaces to their hardware. In short, disabling sysex altogether does not only disable high-end scenarios.

The additional security concern for receiving short messages is also small - it’s analogous to listening to keyboard, mouse, mobile/laptop accelerometer, touch input or gamepad events; there is no additional information exposed, and all messages other than clock signals must be initiated by the user.

The additional concerns about sending short messages are analogous to any audio output - you cannot overwrite user information or expose use information, but you can make sounds happen, change patches, or (in rare configurations) toggle lights - but non-destructively, and not persistently.

System Exclusive, on the other hand, has a much less bounded potential, and it seems that distinguishing requests for SysEx separately in the API is a good idea, in order to more carefully provide user security hooks. The suggested security model explicitly allows user agents to require the user's approval before giving access to MIDI devices, although it is not currently required to prompt the user for this approval - but it also detailed that System Exclusive support must be requested as part of that request.

8. Changelog

8.1 Changes since Working Draft 17 March 2015

A. References

A.1 Normative references

[dom]
DOM Standard. Anne van Kesteren. WHATWG. Living Standard. URL: https://dom.spec.whatwg.org/
[hr-time]
High Resolution Time. Yoav Weiss. W3C. 19 July 2023. W3C Working Draft. URL: https://www.w3.org/TR/hr-time-3/
[html]
HTML Standard. Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[infra]
Infra Standard. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. URL: https://infra.spec.whatwg.org/
[MIDI]
MIDI 1.0 Core Specifications. The MIDI Manufacturers Association. 2014. URL: https://midi.org/midi-1-0-core-specifications
[Permissions]
Permissions. Marcos Caceres; Mike Taylor. W3C. 19 March 2024. W3C Working Draft. URL: https://www.w3.org/TR/permissions/
[permissions-policy]
Permissions Policy. Ian Clelland. W3C. 25 September 2024. W3C Working Draft. URL: https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba. IETF. May 2017. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc8174
[webaudio]
Web Audio API. Paul Adenot; Hongchan Choi. W3C. 17 June 2021. W3C Recommendation. URL: https://www.w3.org/TR/webaudio/
[WEBIDL]
Web IDL Standard. Edgar Chen; Timothy Gu. WHATWG. Living Standard. URL: https://webidl.spec.whatwg.org/