Web MIDI API

W3C 工作草案

关于本文档的更多详细信息
本版本:
https://www.w3.org/TR/2025/WD-webmidi-20250121/
最新发布版本:
https://www.w3.org/TR/webmidi/
最新编辑草案:
https://webaudio.github.io/web-midi-api/
历史:
https://www.w3.org/standards/history/webmidi/
提交历史
测试套件:
https://github.com/web-platform-tests/wpt/tree/master/webmidi
编辑:
Google
Google
前任编辑:
Jussi Kalliokoski
反馈:
GitHub WebAudio/web-midi-api拉取请求新议题未解决议题
实现报告:
不存在初步的互操作性报告或实现报告。

摘要

某些用户代理有音乐设备,例如合成器、键盘和 其他控制器,以及连接到其宿主计算机或设备的鼓机。 广泛采用的乐器数字接口(MIDI)协议使电子乐器、 控制器和计算机能够相互通信并同步。MIDI 不传输 音频信号:相反,它发送关于音符的事件消息、 用于音量、颤音和声像等参数的控制器信号、 用于设置速度的提示和时钟信号,以及系统特定的 MIDI 通信(例如远程存储合成器特定的音色数据)。 同一协议也已成为非音乐用途的标准,例如演出控制、 灯光和特效控制。

本规范定义了一个支持 MIDI 协议的 API,使 Web 应用程序 能够枚举并选择客户端系统上的 MIDI 输入和输出设备,并发送和接收 MIDI 消息。它旨在 通过提供对用户系统上可用的 MIDI 设备的低级访问, 同时支持非音乐 MIDI 应用和音乐 MIDI 应用。Web MIDI API 并非旨在 从语义上描述音乐或控制器输入;它被设计为暴露 MIDI 输入和输出接口的机制, 以及发送和接收 MIDI 消息的实际方面,而不标识这些 操作在语义上可能意味着什么(例如,根据“以 20Hz 调制颤音” 或“演奏一个 G#7 和弦”来理解,除了根据改变一个 控制器值或发送一组恰好表示 G#7 和弦的 note-on 消息来理解之外)。

对某些用户来说,“MIDI”已经成为标准 MIDI 文件 和 General MIDI 的同义词。这并不是此 API 的意图;仅仅播放 .SMF 文件的用例不在本规范的范围内(例如,它可以被视为一种 由 HTML audio 元素支持的不同格式)。Web MIDI API 旨在支持对响应 MIDI 的设备的直接访问 - 例如控制器、外部合成器或照明系统。 Web MIDI API 也明确设计用于支持 Web 上一类新的 应用程序,这类应用程序可以响应 MIDI 控制器输入 - 使用带有物理按钮、旋钮和滑块的外部硬件控制器 (以及键盘、吉他或管乐器控制器等音乐控制器) 来控制 Web 应用程序。

Web MIDI API 还预期与 Web 平台的其他 API 和元素结合使用, 尤其是 Web Audio API。此 API 还旨在让 其他系统上的 MIDI API 用户感到熟悉,例如 Apple 的 CoreMIDI 和 Microsoft 的 Windows MIDI API。

本文档状态

本节描述本文档在发布时的状态。当前 W3C 发布物列表以及本技术报告的最新修订版,可以在 W3C 技术 报告索引中找到,地址为 https://www.w3.org/TR/。

本文档由 Audio Working Group 作为 Working Draft 发布,并使用 Recommendation track

作为 Working Draft 发布并不 意味着 W3C 及其成员的认可。

这是一份草案文档,可能随时被其他文档更新、替换或废弃。 除作为进行中的工作外,不适合引用本文档。

本文档由一个根据 W3C 专利 政策 运作的团体制作。 W3C 维护一个 与该团体交付物相关的任何专利披露公开列表; 该页面还包含披露专利的说明。任何实际知晓某项专利, 且该个人认为该专利包含 基本权利要求 的个人,必须依照 W3C 专利政策第 6 节 披露该信息。

本文档受 2023 年 11 月 03 日 W3C 流程文档管辖。

1. 简介

本节是非规范性的。

Web MIDI API 规范为 Web 开发者定义了一种方式,用于 枚举、操作和访问 MIDI 设备 - 例如, 可能提供硬件 MIDI 端口的接口,其他设备插入这些端口, 以及支持 USB-MIDI 规范的 USB 设备。有了用于 MIDI 的 Web API, Web 应用程序就可以使用现有的软件和硬件合成器、 硬件音乐控制器、灯光系统以及其他由 MIDI 控制的机械装置。 定义此 API 时已考虑到这种广泛的用例。

此 API 采用的方法类似于 Apple 的 CoreMIDI API 和 Microsoft 的 Windows MIDI API;也就是说,该 API 被设计为表示 MIDI 的低级软件协议,以使开发者能够在其之上 构建强大的 MIDI 软件。该 API 使开发者能够枚举输入和输出接口, 并发送和接收 MIDI 消息,但(类似于上述 API) 除了为了稳健支持当前设备所必需的内容之外, 它并不试图从语义上定义或解释 MIDI 消息

Web MIDI API 并不旨在直接实现排序等高级概念; 例如,它不直接支持标准 MIDI 文件,尽管可以在 Web MIDI API 之上构建标准 MIDI 文件播放器。它也不旨在像 General MIDI 那样 从语义上捕获音色或控制器分配;这种解释超出了 Web MIDI API 的范围(不过同样,General MIDI 可以很容易地通过 Web MIDI API 使用)。

2. 一致性

除标记为非规范性的章节外,本规范中的所有编写指南、图表、示例和注释均为非规范性的。 本规范中的其他所有内容均为规范性的。

本文档中的关键词 MUSTSHOULD 应按 BCP 14 [RFC2119] [RFC8174] 中的描述解释,且仅当它们如这里所示以全大写形式出现时才如此解释。

本规范定义了适用于单一产品的一致性标准:即实现其中所含接口的 用户代理

使用 ECMAScript 来实现本规范所定义 API 的实现,MUST 以与 Web IDL 规范 [WEBIDL] 中定义的 ECMAScript 绑定一致的方式实现这些 API,因为 本规范使用了该规范及其术语。

3. 术语

Web Audio API 及其相关接口和概念在 [webaudio] 中定义。

术语 MIDIMIDI 设备MIDI 输入 端口MIDI 输出端口MIDI 接口MIDI 消息System Real TimeSystem Exclusive 在 [MIDI] 中定义。

有效 MIDI 消息 在 [MIDI] 中定义。 以下内容可用作非规范性指南:
  • 第一个字节(状态字节)应设置高位, 后续任何字节都不应设置高位,除非它们是 System Exclusive 消息的一部分
  • 如果状态字节的高半字节按十六进制表示为 89ABE,则消息总长度应为 3 字节
  • 如果状态字节的高半字节为 CD,则 消息总长度应为 2 字节
  • 如果状态字节为 F1F3,则消息总 长度应为 2 字节
  • 如果状态字节为 F2,则消息总长度 应为 3 字节
  • 如果状态字节为 F6F8FAFBFCFEFF,则消息总长度应为 1 字节(仅 状态字节)
  • 如果状态字节为 F0,则这是一个没有长度限制的 System Exclusive 消息,且最后一个 字节应为 F7
  • 如果状态字节为 F4F5F7F9FD,则 该消息无效

4. 获取对 MIDI 设备的访问权限

4.1 权限集成

Web Midi API 是一个由 强大功能, 其 名称"midi"。它 通过定义以下与权限相关的标志,与 Permissions 集成:

权限描述符 类型
WebIDLdictionary MidiPermissionDescriptor : PermissionDescriptor {
  boolean sysex = false;
};

{name: "midi", sysex: true} 强于 {name: "midi", sysex: false}

4.2 权限策略集成

Web Midi API 定义了一个名为 "midi"策略控制功能, 其 默认 允许列表'self'

4.3 Navigator 接口的扩展

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

调用时,返回一个 Promise 对象,表示对用户系统上 MIDI 设备访问权限的请求。

请求 MIDI 访问权限SHOULD 提示用户授予对 MIDI 设备的访问权限,特别是在请求 System Exclusive 访问权限时。 在某些场景中,此权限可能已经被隐式或显式授予, 在这种情况下该提示可能不会出现。如果用户给予明确许可, 或调用以其他方式获得批准,则所给出的 Promise 会被解决。 底层系统可以选择允许用户选择要暴露给此 API 的特定 MIDI 接口(即逐个接口挑选), 尽管这不是必需的。系统还可以根据是否请求 System Exclusive 支持来选择是否提示用户,因为 System Exclusive 访问具有更大的隐私和 安全影响。

如果用户拒绝,或调用因任何其他原因被拒绝, Promise 将以 DOMException 参数被拒绝。

即使并非所有给出的 Promise 都已敲定,也允许多次调用 requestMIDIAccess()

当调用 requestMIDIAccess() 方法时,用户代理 MUST 运行以下步骤:

  1. promise 为一个新的 Promise 对象,并令 resolver 为其关联的 resolver。

  2. 返回 promise,并异步运行以下步骤。

  3. document 为调用上下文的 Document

  4. 如果 document 未被 允许 使用名为 midi策略控制 功能,则跳转到下面标记为 failure 的步骤。

  5. 可选地,例如基于先前建立的用户偏好、 出于安全原因,或由于平台限制,跳转到下面标记为 failure 的步骤。

  6. 可选地,例如基于先前建立的用户偏好,跳转到下面标记为 success 的步骤。

  7. 以用户代理特定的方式提示用户授予权限,以便向入口脚本的源提供一个 MIDIAccess 对象,该对象表示对用户 MIDI 设备的控制。此 提示可以取决于是否请求了 System Exclusive 支持,并且可以允许用户启用或禁用该访问权限。

    如果权限被拒绝,则跳转到下面标记为 failure 的步骤。如果用户从不响应, 此算法将永远不会越过此步骤继续进行。如果权限 被授予,则继续以下步骤。

  8. success:令 access 为一个新的 MIDIAccess 对象。(可以多次调用 requestMIDIAccess();这可能会多次提示用户, 因此可能不是最佳实践,并且每次不会返回同一个 MIDIAccess 实例。)

  9. 调用 resolveraccept(value) 方法, 并以 access 作为 value 实参。

  10. 终止这些步骤。

  11. failure:令 error 为一个新的 DOMException。 如果用户或其安全设置拒绝应用程序使用所请求的选项 创建 MIDIAccess 实例,或者如果该错误是 document 未被 允许 使用该功能的结果,则此异常的 .name 应为 "NotAllowedError"; 如果页面将因用户导航而被关闭,则为 "AbortError"; 如果底层系统引发任何错误,则为 "InvalidStateError"; 否则应为 "NotSupportedError"。

  12. 调用 resolverreject(value) 方法, 并以 error 作为 value 实参。

4.3.1 MIDIOptions 字典

此字典包含可提供给 requestMIDIAccess() 请求的可选设置。

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

此成员告知系统,在给定的 MIDIAccess 对象上, 是否请求或允许发送和接收 System Exclusive 消息的能力。在传递给 requestMIDIAccess() 的选项上,如果此成员被设置为 true,但 System Exclusive 支持被拒绝(无论是由策略还是由 用户操作导致),则访问请求将以 "NotAllowedError" 错误失败。如果未请求(并允许)此支持,则在用户尝试发送 System Exclusive 消息时,系统将抛出异常,并会静默屏蔽 在端口上收到的任何 System Exclusive 消息。

software

此成员告知系统,在给定的 MIDIAccess 对象上, 是否请求或允许利用安装在宿主系统中的任何软件合成器的能力。在 requestMIDIAccess() 中,如果此成员被设置为 true,但 软件合成器支持被拒绝(无论是由策略还是由 用户操作导致),则访问请求将以 "NotAllowedError" 错误失败。如果未请求此支持,则 系统不应在可用端口的 MIDIAccess 暴露中包含任何软件合成器。

请注意,如果需要但不要求软件合成器支持, 这可能导致两步请求过程 - 当 MIDI 硬件设备访问 被允许时,软件合成器可能会被禁用。

5. MIDI API

5.1 MIDIInputMap 接口

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

MIDIInputMap 是一个 maplike 接口,其值为 MIDIInput 实例,键为其 ID。

此类型用于表示所有当前可用的 MIDI 输入 端口

5.2 MIDIOutputMap 接口

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

MIDIOutputMap 是一个 maplike 接口,其值为 MIDIOutput 实例,键为其 ID。

此类型用于表示所有当前可用的 MIDI 输出 端口

5.3 MIDIAccess 接口

此接口提供用于列出 MIDI 输入和输出设备,并获取对单个设备的访问权限的方法。

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
系统可用的 MIDI 输入端口
outputs
系统可用的 MIDI 输出端口
onstatechange

当新端口连接,或现有端口更改 state 属性时调用的处理程序。

事件 处理程序,类型为 MIDIConnectionEventMUST 由所有实现 MIDIAccess 接口的对象支持。

必须理解,将 EventHandler 留在此对象上会阻止该对象被垃圾回收;当使用完 MIDIAccess 后,应移除任何 onstatechange 监听器。

每当先前不可用的 MIDI 端口变为可用,或现有端口更改 state 属性时,用户代理 SHOULD 运行以下步骤:

  1. port 为与新近可用的端口或现有端口 相对应的 MIDIPort
  2. MIDIAccess触发一个 名为 "statechange" 的事件, 使用 MIDIConnectionEvent,并将 port 设置为 port
sysexEnabled
此属性告知用户,在此 MIDIAccess 上是否启用了 System Exclusive 支持。

5.4 MIDIPort 接口

此接口表示 MIDI 输入或输出端口。

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

端口的唯一 ID。开发者可用它记住用户为其应用程序选择的端口。 用户代理 MUST 确保 id 仅对该端口唯一。 用户代理 SHOULD 确保该 id 在应用程序的多个实例之间保持不变 - 例如,当系统重新启动时 - 以及当设备从系统中移除时保持不变。 应用程序可能希望在本地缓存这些 id,以重新创建 MIDI 设置。 某些系统可能不支持完全唯一的持久标识符;在这种情况下,当另一个接口 被添加到系统或从系统移除时,维护标识符会更具挑战性。 (这可能会打乱所请求端口的索引。)预期系统会尽其所能, 在 MIDI API 的多个实例之间匹配端口:例如,实现可以不透明地 使用端口接口制造商、名称和索引的某种哈希作为 id, 这样对该端口 id 的引用在插入时很可能匹配该端口。 应用程序可以使用 MIDIPort 的 id 比较来测试相等性。

manufacturer

端口的制造商。

name

端口的系统名称。

type

用于区分端口是输入端口还是输出端口的描述符属性。对于 MIDIOutput,此属性 MUST"output"。对于 MIDIInput,此属性 MUST"input"

version

端口的版本。

state
设备的状态。
connection
到设备的连接状态。
onstatechange

当现有端口更改其 state 或 connection 属性时调用的处理程序。

事件 处理程序,类型为 "statechange",MUST 由所有实现 MIDIPort 接口的对象支持。

必须理解,将 EventHandler 留在此对象上会阻止该对象被垃圾回收;当使用完 MIDIPort 后,应移除任何 onstatechange 监听器。

open

使与 MIDIPort 相对应的 MIDI 设备显式可用。请注意,为了使用 MIDIPort,并不要求调用此方法 - 在 MIDIOutput 上调用 send(), 在 MIDIInput 上附加 MIDIMessageEvent EventHandler, 或在 MIDIInput 上添加 MIDIMessageEvent EventListener 都会导致隐式 open()。底层实现可能无需响应此调用执行任何操作。 然而,某些底层实现可能无法支持对 MIDI 设备的共享访问, 因此使用显式 open() 和 close() 调用将使 MIDI 应用程序能够 可预测地控制对设备的这种独占访问。

调用时,此方法返回一个 Promise 对象,表示对用户系统上给定 MIDI 端口 访问权限的请求。

如果端口设备的状态为 "connected",当已获得对该端口的访问权限 (并且该端口已准备好进行输入或输出)时,所给出的 Promise 会被解决。

如果无法访问已连接的端口(例如,该端口已在仅允许独占访问的平台中被使用), 则 Promise 会被拒绝(如果有)。

如果在 "disconnected" 的端口上调用 open(),该端口的 .connection 将转换为 "pending", 直到该端口变为 "connected",或对它的所有引用都被丢弃。

即使并非所有给出的 Promise 都已敲定,也允许多次调用 open()

当调用此方法时,用户代理 MUST 运行 打开 MIDIPort 的算法

  1. promise 为一个新的 Promise 对象,并令 resolver 为其关联的 resolver。

  2. 返回 promise,并异步运行以下步骤。

  3. port 为给定的 MIDIPort 对象。

  4. 如果设备的 connection 已经是 "open"(例如,已在此 MIDIPort 上调用 open(), 或端口已被隐式打开),则跳转到下面标记为 success 的步骤。

  5. 如果设备的 connection 为 "pending"(即连接已被打开, 而设备随后断开连接),则跳转到下面标记为 success 的步骤。

  6. 如果设备的 state 为 "disconnected",则将 connection 属性更改为 "pending",并将一个新的 MIDIConnectionEvent 加入 MIDIAccessstatechange 处理程序队列,以及 MIDIPortstatechange 处理程序队列, 然后跳转到下面标记为 success 的步骤。

  7. 尝试在系统中获取对给定 MIDI 设备的访问权限。如果设备不可用(例如已被另一个进程使用且无法打开, 或已断开连接),则跳转到下面标记为 failure 的步骤。 如果设备可用且已获得访问权限,则继续以下步骤。

  8. 将 MIDIPort 的 connection 属性更改为 "open", 并将一个新的 MIDIConnectionEvent 加入 MIDIAccessstatechange 处理程序队列,以及 MIDIPortstatechange 处理程序队列。

  9. 如果此端口是输出端口,并且存在任何带有未来时间戳的已排队发送数据, 则异步开始发送这些数据。

  10. success:调用 resolveraccept(value) 方法,并以 port 作为 value 实参。

  11. 终止这些步骤。

  12. failure:令 error 为一个新的 DOMException。 如果端口不可用,则此异常的 .name 应为 "InvalidAccessError"

  13. 调用 resolverreject(value) 方法, 并以 error 作为 value 实参。

close

使与 MIDIPort 相对应的 MIDI 设备显式不可用(随后将状态从 "open" 更改为 "closed")。请注意,成功调用此方法将导致 MIDI 消息不再被传递给 MIDIInput 上的 MIDIMessageEvent 处理程序 (尽管设置新的处理程序会导致隐式 open())。

底层实现可能无需响应此调用执行任何操作。然而,某些底层实现 可能无法支持对 MIDI 设备的共享访问,而显式 close() 调用使 MIDI 应用程序能够确保 其他应用程序可以获得对设备的访问权限。

调用时,此方法返回一个 Promise 对象,表示对用户系统上给定 MIDI 端口 访问权限的请求。当端口已关闭(因此,在独占访问系统中,该端口可供 其他应用程序使用)时,所给出的 Promise 会被解决。如果端口断开连接, 则 Promise 会被拒绝。

即使并非所有给出的 Promise 都已敲定,也允许多次调用 close()

当调用 close() 方法时,用户代理 MUST 运行以下步骤:

  1. promise 为一个新的 Promise 对象,并令 resolver 为其关联的 resolver。

  2. 返回 promise,并异步运行以下步骤。

  3. port 为给定的 MIDIPort 对象。

  4. 如果端口已关闭(其 .connection"closed" - 例如,端口尚未被 隐式或显式打开,或已在此 MIDIPort 上调用 close()), 则跳转到下面标记为 closed 的步骤。

  5. 如果端口是输入端口,则跳到下一步。如果输出端口的 .state 不是 "connected", 或其 .connection 为 "pending",则清除所有已排队发送数据,并跳到下一步。 清除系统中任何带有未来时间戳的已排队发送数据,然后在继续下一步之前, 完成发送任何没有时间戳或时间戳位于过去或现在的发送消息。

  6. 如果底层系统中对端口的访问已打开,则关闭该访问,并释放底层系统中的 任何阻塞资源。

  7. 将 MIDIPort 的 connection 属性更改为 "closed", 并将一个新的 MIDIConnectionEvent 加入 MIDIAccessstatechange 处理程序队列,以及 MIDIPortstatechange 处理程序队列。

  8. closed:调用 resolveraccept(value) 方法,并以 port 作为 value 实参。

每当与 MIDIPort 相对应的 MIDI 端口更改 state 属性时,用户代理 SHOULD 运行以下步骤:

  1. port 为该 MIDIPort

  2. MIDIPort触发一个事件, 其名称为 statechange,并在 MIDIAccess 上触发 statechange, 使用 MIDIConnectionEvent,并将 port 属性设置为 port

5.4.1 MIDIInput 接口

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

事件 处理程序,类型为 "midimessage",MUST 由所有实现 MIDIInput 接口的对象支持。

如果设置了处理程序且 state 属性不是 "opened", 底层实现会尝试使该端口可用,并将 state 属性更改为 "opened"。如果成功,则会将 MIDIConnectionEvent 传递给相应的 MIDIPortMIDIAccess

每当与 MIDIInput 相对应的 MIDI 端口完成接收一个或多个 MIDI 消息时,用户代理 MUST 运行以下步骤:

  1. port 为该 MIDIInput

  2. 如果 MIDIAccess 未启用 System Exclusive 访问权限,且该消息是 System Exclusive 消息,则中止此过程。

  3. port触发一个 名为 "midimessage" 的事件,使用 MIDIMessageEvent,并将 timeStamp 属性设置为系统接收到该消息的时间,并将 data 属性设置为 一个表示单个 MIDI 消息的 MIDI 数据字节 Uint8Array

特别指出,MIDI System Real Time 消息实际上可能 出现在输入流中其他消息的中间;在这种情况下, System Real Time 消息会在其出现时被派发, 而普通消息会被缓冲,直到它们完整(然后再派发)。

5.4.2 MIDIOutput 接口

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

将要发送到相应 MIDI 端口的消息入队。底层实现将(如有必要) 将序列的每个成员强制转换为无符号 8 位整数。使用 sequence 而不是 Uint8Array,使开发者能够利用 output.send( [ 0x90, 0x45, 0x7f ] ); 的便利,而不必创建 Uint8Array,例如 output.send( new Uint8Array( [ 0x90, 0x45, 0x7f ] ) );

数据包含一个或多个完整的 有效 MIDI 消息。 数据中不允许使用 running status,因为底层系统可能不支持它。

如果 data 不是有效序列,或不包含 有效 MIDI 消息,则抛出 TypeError 异常。

如果 dataSystem Exclusive 消息, 且 MIDIAccess 未启用 System Exclusive 访问权限,则抛出 InvalidAccessError 异常。

如果端口为 "disconnected",则抛出 InvalidStateError 异常。

如果端口为 "connected",但 connection 为 "closed",则异步尝试 打开端口

sequence<octet> data
要入队的数据,每个序列条目表示单个数据字节。
optional DOMHighResTimeStamp timestamp
开始向端口发送数据的时间(作为 DOMHighResTimeStamp - 相对于文档导航开始测量的毫秒数)。如果 timestamp 被设置为零(或过去的另一个时间),则应尽快发送数据。 多次以相同 timestamp 调用 send() 必须导致数据按调用顺序发送。
clear

清除 MIDIOutput 队列中任何尚未发送的 已排队发送数据。实现需要确保 MIDI 流保持良好状态,因此如果输出端口 正处于 sysex 消息中间,则应发送 sysex 终止字节(0xf7)。

5.4.3 MIDIPortType 枚举

WebIDLenum MIDIPortType {
  "input",
  "output",
};
input
如果 MIDIPort 是输入端口,则 type 成员 MUST 为此值。
output
如果 MIDIPort 是输出端口,则 type 成员 MUST 为此值。

5.4.4 MIDIPortDeviceState 枚举

WebIDLenum MIDIPortDeviceState {
  "disconnected",
  "connected",
};
disconnected
MIDIPort 所表示的设备 已从系统断开连接。当设备从系统断开连接时,它不应出现在相关的输入和输出端口映射中。
connected
MIDIPort 所表示的设备 已连接,并应出现在输入和输出端口映射中。

5.4.5 MIDIPortConnectionState 枚举

WebIDLenum MIDIPortConnectionState {
  "open",
  "closed",
  "pending",
};
open
MIDIPort 所表示的设备 已被打开(无论是 隐式还是显式),并且可供使用。
closed
MIDIPort 所表示的设备 未被打开,或已被显式关闭。在 MIDIPort 被显式打开 (通过 MIDIPort.open())或隐式打开 (通过在输入端口上添加 midimessage 事件处理程序,或在输出端口上调用 MIDIOutput.send())之前, 这应为设备的默认状态。
pending
MIDIPort 所表示的设备 已被打开(无论是 隐式还是 显式),但设备随后已断开连接且不可用。如果设备重新连接, 在发送 statechange 事件之前,系统应尝试重新打开设备(遵循 打开 MIDIPort 的算法);这将导致 connection 状态转换为 "open" 或 "closed"。

5.5 MIDIMessageEvent 接口

当收到 MIDI 消息时,实现此接口的事件对象会被传递给 MIDIInput 的 onmidimessage 处理程序。 请注意,DOM EventtimeStamp 属性被定义为 DOMHighResTimeStamp, 并表示事件被接收或将被发送时的高精度时间。

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

一个包含单个 MIDI 消息的 MIDI 数据字节的 Uint8Array。

5.5.1 MIDIMessageEventInit 字典

WebIDLdictionary MIDIMessageEventInit: EventInit {
  Uint8Array data;
};
data

一个包含单个 MIDI 消息的 MIDI 数据字节的 Uint8Array。

5.6 MIDIConnectionEvent 接口

当新端口变为可用(例如,当 MIDI 设备首次插入计算机时), 当先前可用的端口变为不可用,或再次变为可用(例如,当 MIDI 接口 断开连接后又重新连接时),实现此接口的事件对象会被传递给 MIDIAccessonstatechange 处理程序, 并且(如果存在)也会被传递给任何引用该端口的 MIDIPortonstatechange 处理程序。

MIDIPort 处于 "pending" 状态且设备重新连接到宿主系统时, 在触发 statechange 事件之前,会在其上运行 打开 MIDIPort 的算法,以尝试重新打开端口。如果此转换失败 (例如,端口被底层系统中的其他事物保留,因此不可用),connection 状态会变为 "closed",否则会转换回 "open"。这是在设备 state 变化的 statechange 事件之前完成的,以便该事件既反映最终的 connection 状态,也反映设备 state。

某些底层系统可能不提供设备连接状态的通知事件;这类系统可能因不频繁轮询 新设备而出现较长延迟。因此,建议不要过度依赖连接事件。

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

已连接或断开连接的端口。

5.6.1 MIDIConnectionEventInit 字典

WebIDLdictionary MIDIConnectionEventInit: EventInit {
  MIDIPort port;
};
port

已连接或断开连接的端口。

6. 隐私考虑

允许枚举用户的 MIDI 接口 是指纹识别的潜在目标;也就是说,通过用户连接的特定 MIDI 接口来唯一 标识用户。

注意,在此上下文中,可枚举的是 MIDI 接口。这包括大多数通过 USB 连接到 宿主计算机的设备,因为 USB-MIDI 设备 通常拥有自己的 MIDI 接口,因此会被枚举。通过 5 针 DIN 线缆插入 MIDI 接口的单个采样器或合成器 MIDI 设备不会被枚举。 可被指纹识别的接口等同于 MIDI “端口”,并且对于每个 MIDI 接口,API 将暴露该 设备的名称、制造商以及该 MIDI 接口的不透明标识符。

大多数系统没有连接 MIDI 接口。只有少数 系统会连接大量 MIDI 接口。 因此,枚举 MIDI 设备所带来的额外指纹识别暴露 类似于 Gamepad API 通过枚举游戏手柄所带来的 额外指纹识别暴露:典型用户最多只会连接少量设备, 其配置可能会变化,并且所暴露的信息是关于接口本身的 (即没有用户配置的数据)。

7. 安全考虑

第一批 MIDI 设备于 1983 年发布,当时 Web 平台及其安全风险尚不存在。许多 MIDI 设备在其制造商 停止支持很久之后仍在使用。MIDI 已经适应了 原始串行连接之外的传输方式,例如 FireWire、 USB 和 Bluetooth。这带来了安全挑战,因为来自不同时代的大量设备 没有官方支持却仍在积极使用,并以其设计者未曾 预期的方式连接到计算机和 Web。

7.1 恶意固件更新

一种令人担忧的理论攻击涉及对 USB-MIDI 设备进行恶意 固件更新。一般来说,USB 设备可以根据其设备描述符 执行操作,而该描述符由 USB 设备本身发送。如果 USB-MIDI 设备的 固件可以修改所发送的描述符,它 就可以让自己表现为人机接口设备。这 可能允许恶意网站读取或注入宿主计算机上的 按键或其他事件,从而可能导致系统被完全攻陷。

攻击将按如下方式进行:

  1. 恶意站点诱骗用户授予 Web MIDI 权限。
  2. 恶意站点枚举连接到用户机器的 MIDI 设备, 并识别出易受攻击的设备。
  3. 恶意站点向易受攻击的设备发送一组预先制作的 MIDI 消息, 通过覆盖其固件并向其 USB 描述符添加人机 接口设备来攻陷该设备。
  4. 被攻陷的设备注入按键,以下载或以其他方式复制 预先制作的安全漏洞利用程序并执行它,从而攻陷系统。

为了促成上述攻击,一个 MIDI 设备 需要满足以下所有条件:

容易受到恶意固件更新影响但不满足其他条件的 MIDI 设备,不能被此攻击用于攻陷 宿主系统。恶意固件更新仍然可能导致这些 MIDI 设备停止 工作或以不期望的方式运行。

为缓解此风险,实现者应在其实现中强调以下事项:

显式允许或阻止已知 MIDI 设备列表也可能有助于缓解此特定攻击, 但许多小公司和个人会构建 MIDI 设备,并且许多 MIDI 设备 已不再受支持,因此这样做会显著降低 Web MIDI API 的可用性。

7.2 其他安全考虑

除了识别可用端口所带来的指纹识别问题之外,还存在 发送和接收 MIDI 消息方面的问题。这些问题将在 下文更深入地探讨。

MIDI 消息可分为 System Exclusive 消息和短消息(非 System Exclusive 消息)。 System Exclusive 消息还可进一步细分为 Universal System Exclusive 消息,例如常见的 MIDI Time Code 和 MIDI Sample Dump Standard,以及设备特定的消息,例如“Roland Jupiter-80 合成器的音色控制数据”,这类消息不适用于其他设备。

在讨论安全问题之前,先考察 MIDI 使用这些 功能所支持的场景会很有帮助:

这些场景各自的潜在安全影响如下:

8. 变更日志

8.1 自 2015 年 3 月 17 日 Working Draft 以来的变更

A. 参考文献

A.1 规范性参考文献

[dom]
DOM 标准. Anne van Kesteren. WHATWG. 现行标准。URL:https://dom.spec.whatwg.org/
[hr-time]
高精度时间. Yoav Weiss. W3C. 2024 年 11 月 7 日。W3C 工作草案。URL:https://www.w3.org/TR/hr-time-3/
[html]
HTML 标准. Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. 活 标准。URL:https://html.spec.whatwg.org/multipage/
[infra]
Infra 标准. Anne van Kesteren; Domenic Denicola. WHATWG. 现行标准。URL:https://infra.spec.whatwg.org/
[MIDI]
MIDI 1.0 核心 规范. MIDI 制造商协会。2014。URL:https://midi.org/midi-1-0-core-specifications
[Permissions]
权限. Marcos Caceres; Mike Taylor. W3C. 2024 年 12 月 20 日。W3C 工作草案。URL:https://www.w3.org/TR/permissions/
[permissions-policy]
权限策略. Ian Clelland. W3C. 2025 年 1 月 13 日。W3C 工作草案。URL:https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
用于 RFC 中表示 要求级别的关键词. S. Bradner. IETF. 1997 年 3 月。最佳当前实践。URL:https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
RFC 2119 关键词中大写与小写的歧义. B. Leiba. IETF. 2017 年 5 月。最佳当前实践。URL:https://www.rfc-editor.org/rfc/rfc8174
[webaudio]
Web Audio API. Paul Adenot; Hongchan Choi. W3C. 2021 年 6 月 17 日。W3C 推荐标准。URL:https://www.w3.org/TR/webaudio-1.0/
[WEBIDL]
Web IDL 标准. Edgar Chen; Timothy Gu. WHATWG. 现行标准。URL:https://webidl.spec.whatwg.org/