웹 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 (pull requests, new issue, open issues)
구현 보고서:
예비 상호 운용성 또는 구현 보고서가 존재하지 않는다.

초록

일부 사용자 에이전트에는 신시사이저, 키보드 및 기타 컨트롤러, 드럼 머신과 같은 음악 장치가 호스트 컴퓨터 또는 장치에 연결되어 있다. 널리 채택된 Musical Instrument Digital Interface (MIDI) 프로토콜은 전자 악기, 컨트롤러 및 컴퓨터가 서로 통신하고 동기화할 수 있게 한다. MIDI는 오디오 신호를 전송하지 않는다. 대신 음악 음표에 대한 이벤트 메시지, 볼륨, 비브라토 및 패닝 같은 매개변수에 대한 컨트롤러 신호, 템포를 설정하기 위한 큐와 클록 신호, 그리고 시스템별 MIDI 통신(예: 신시사이저별 패치 데이터를 원격으로 저장하기 위한 것)을 보낸다. 이 동일한 프로토콜은 쇼 컨트롤, 조명 및 특수 효과 제어와 같은 비음악적 용도를 위한 표준이 되었다.

이 명세는 MIDI 프로토콜을 지원하는 API를 정의하며, 웹 애플리케이션이 클라이언트 시스템에서 MIDI 입력 및 출력 장치를 열거하고 선택하며 MIDI 메시지를 보내고 받을 수 있게 한다. 이는 사용자의 시스템에서 사용할 수 있는 MIDI 장치에 대한 저수준 접근을 제공하여 음악 MIDI 애플리케이션뿐만 아니라 비음악 MIDI 애플리케이션도 가능하게 하려는 것이다. Web MIDI API는 음악이나 컨트롤러 입력을 의미론적으로 설명하려는 것이 아니다. 이는 MIDI 입력 및 출력 인터페이스의 동작 방식과 MIDI 메시지를 보내고 받는 실제적인 측면을 노출하도록 설계되었으며, 그러한 동작이 의미론적으로 무엇을 뜻하는지 식별하지 않는다(예: 컨트롤러 값을 변경하거나 우연히 G#7 코드를 나타내는 일련의 note-on 메시지를 보내는 것 외에, "비브라토를 20Hz로 변조한다" 또는 "G#7 코드를 연주한다"와 같은 의미).

일부 사용자에게 "MIDI"는 Standard MIDI Files 및 General MIDI와 동의어가 되었다. 이것은 이 API의 의도가 아니다. 단순히 .SMF 파일을 재생하는 사용 사례는 이 명세의 범위에 속하지 않는다(예를 들어 HTML audio 요소가 지원해야 할 다른 형식으로 간주될 수 있다). Web MIDI API는 MIDI에 응답하는 장치, 예를 들어 컨트롤러, 외부 신시사이저 또는 조명 시스템에 대한 직접 접근을 가능하게 하려는 것이다. Web MIDI API는 또한 물리적 버튼, 노브 및 슬라이더가 있는 외부 하드웨어 컨트롤러(키보드, 기타 또는 관악기 컨트롤러 같은 음악 컨트롤러뿐만 아니라)를 사용하여 웹 애플리케이션을 제어하는 등, MIDI 컨트롤러 입력에 응답할 수 있는 새로운 종류의 웹 애플리케이션을 가능하게 하도록 명시적으로 설계되었다.

Web MIDI API는 또한 웹 플랫폼의 다른 API 및 요소, 특히 Web Audio API와 함께 사용될 것으로 예상된다. 이 API는 또한 Apple의 CoreMIDI 및 Microsoft의 Windows MIDI API 같은 다른 시스템의 MIDI API 사용자에게 익숙하도록 의도되었다.

이 문서의 상태

이 절은 이 문서가 발행된 시점의 상태를 설명한다. 현재 W3C 발행물 목록과 이 기술 보고서의 최신 개정판은 https://www.w3.org/TR/의 W3C 기술 보고서 색인에서 찾을 수 있다.

이 문서는 Audio Working GroupRecommendation 트랙을 사용하여 작업 초안으로 발행하였다.

작업 초안으로 발행되었다는 것은 W3C와 그 회원들이 승인했음을 의미하지 않는다.

이 문서는 초안 문서이며 언제든지 다른 문서로 업데이트되거나, 대체되거나 폐기될 수 있다. 이 문서를 진행 중인 작업이 아닌 것으로 인용하는 것은 부적절하다.

이 문서는 W3C 특허 정책에 따라 운영되는 그룹에 의해 작성되었다. W3C는 해당 그룹의 산출물과 관련하여 이루어진 모든 특허 공개의 공개 목록을 유지한다. 그 페이지에는 특허 공개에 대한 지침도 포함되어 있다. 개인이 그 개인이 Essential Claim(s)을 포함한다고 믿는 특허에 대한 실제 지식을 가지고 있는 경우, 그 정보는 W3C 특허 정책의 6절에 따라 공개해야 한다.

이 문서는 2023년 11월 03일 W3C Process Document의 적용을 받는다.

1. 소개

이 절은 비규범적이다.

Web MIDI API 명세는 웹 개발자가 MIDI 장치를 열거하고, 조작하고, 접근할 수 있는 수단을 정의한다. 예를 들어 다른 장치가 연결될 수 있는 하드웨어 MIDI 포트를 제공하는 인터페이스와 USB-MIDI 명세를 지원하는 USB 장치가 이에 해당한다. MIDI를 위한 Web API가 있으면 기존 소프트웨어 및 하드웨어 신시사이저, 하드웨어 음악 컨트롤러, 조명 시스템 및 MIDI로 제어되는 기타 기계 장치를 사용하는 웹 애플리케이션을 가능하게 한다. 이 API는 이러한 다양한 사용 사례를 염두에 두고 정의되었다.

이 API가 취한 접근 방식은 Apple의 CoreMIDI API 및 Microsoft의 Windows MIDI API에서 취한 방식과 유사하다. 즉, 이 API는 개발자가 그 위에 강력한 MIDI 소프트웨어를 만들 수 있도록 MIDI의 저수준 소프트웨어 프로토콜을 나타내도록 설계되었다. 이 API는 개발자가 입력 및 출력 인터페이스를 열거하고 MIDI 메시지를 보내고 받을 수 있게 하지만, (앞서 언급한 API들과 유사하게) 현재 장치를 견고하게 지원하는 데 필요한 범위를 넘어 MIDI 메시지를 의미론적으로 정의하거나 해석하려고 시도하지 않는다.

Web MIDI API는 시퀀싱 같은 고수준 개념을 직접 구현하려는 것이 아니다. 예를 들어 Standard MIDI Files를 직접 지원하지는 않지만, Standard MIDI File 플레이어는 Web MIDI API 위에 구축할 수 있다. 또한 General MIDI가 하는 것처럼 패치나 컨트롤러 할당을 의미론적으로 포착하려는 것도 아니다. 그러한 해석은 Web MIDI API의 범위를 벗어난다(하지만 다시 말해, General MIDI는 Web MIDI API를 통해 쉽게 활용할 수 있다).

2. 준수

비규범적으로 표시된 절뿐만 아니라, 이 명세의 모든 작성 지침, 다이어그램, 예제 및 참고는 비규범적이다. 이 명세의 그 밖의 모든 것은 규범적이다.

이 문서의 핵심 단어 MUSTSHOULD는 여기에 표시된 것처럼 모두 대문자로 나타나는 경우에만, BCP 14 [RFC2119] [RFC8174]에 설명된 대로 해석되어야 한다.

이 명세는 단일 제품에 적용되는 준수 기준을 정의한다. 그 제품은 이 명세에 포함된 인터페이스를 구현하는 사용자 에이전트이다.

ECMAScript를 사용하여 이 명세에서 정의한 API를 구현하는 구현은 Web IDL 명세 [WEBIDL]에 정의된 ECMAScript 바인딩과 일관된 방식으로 이를 구현해야 한다. 이 명세는 해당 명세와 용어를 사용하기 때문이다.

3. 용어

Web Audio API와 그 관련 인터페이스 및 개념은 [webaudio]에서 정의된다.

용어 MIDI, MIDI 장치, MIDI 입력 포트, MIDI 출력 포트, MIDI 인터페이스, MIDI 메시지, System Real TimeSystem Exclusive는 [MIDI]에서 정의된다.

참고
유효한 MIDI 메시지는 [MIDI]에서 정의된다. 다음은 비규범적 지침으로 사용할 수 있다.
  • 첫 번째 바이트(상태 바이트)는 상위 비트가 설정되어 있어야 하며, 뒤따르는 바이트는 System Exclusive 메시지의 일부가 아닌 한 설정되어서는 안 된다
  • 16진수에서 상태 바이트의 상위 니블이 8, 9, A, B 또는 E이면 전체 메시지 길이는 3 바이트여야 한다
  • 상태 바이트의 상위 니블이 C 또는 D이면 전체 메시지 길이는 2바이트여야 한다
  • 상태 바이트가 F1 또는 F3이면 전체 메시지 길이는 2바이트여야 한다
  • 상태 바이트가 F2이면 전체 메시지 길이는 3바이트여야 한다
  • 상태 바이트가 F6, F8, FA, FB, FC, FE 또는 FF이면 전체 메시지 길이는 1바이트(상태 바이트만)여야 한다
  • 상태 바이트가 F0이면 이는 길이 제한이 없는 System Exclusive 메시지이며, 마지막 바이트는 F7이어야 한다
  • 상태 바이트가 F4, F5, F7, F9 또는 FD이면 그 메시지는 유효하지 않다

4. MIDI 장치에 대한 접근 획득

4.1 Permissions 통합

Web Midi API는 강력한 기능이며, 이름 "midi"로 식별된다. 이는 다음 권한 관련 플래그를 정의함으로써 Permissions와 통합된다.

권한 설명자 타입
WebIDLdictionary MidiPermissionDescriptor : PermissionDescriptor {
  boolean sysex = false;
};

{name: "midi", sysex: true}{name: "midi", sysex: false}보다 더 강하다.

4.2 Permissions Policy 통합

Web Midi API는 "midi"라는 정책 제어 기능을 정의하며, 이는 'self'기본 허용 목록을 가진다.

4.3 Navigator 인터페이스에 대한 확장

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

호출되면, 사용자의 시스템에 있는 MIDI 장치에 대한 접근 요청을 나타내는 Promise 객체를 반환한다.

MIDI 접근을 요청하면, 특히 System Exclusive 접근이 요청되는 경우, 사용자에게 MIDI 장치 접근을 묻는 프롬프트를 표시해야 한다. 어떤 시나리오에서는 이 권한이 이미 암시적으로 또는 명시적으로 부여되었을 수 있으며, 이 경우 이 프롬프트는 나타나지 않을 수 있다. 사용자가 명시적 권한을 주거나 호출이 다른 방식으로 승인되면, 제공된 Promise는 이행된다. 기반 시스템은 이 API에 노출할 특정 MIDI 인터페이스를 사용자가 선택할 수 있게 할 수도 있다 (즉, 개별 기준으로 인터페이스를 골라 선택함). 다만 이는 필수는 아니다. 또한 시스템은 System Exclusive 지원이 요청되는지 여부에 따라 프롬프트를 표시할지(또는 표시하지 않을지)를 선택할 수도 있다. System Exclusive 접근은 더 큰 개인정보 및 보안상의 영향을 가지기 때문이다.

사용자가 거절하거나 호출이 그 밖의 이유로 거부되면, Promise는 DOMException 매개변수와 함께 거부된다.

제공된 Promise가 모두 settled되지 않았더라도 requestMIDIAccess()를 여러 번 호출하는 것이 허용된다.

requestMIDIAccess() 메서드가 호출되면, 사용자 에이전트는 다음 단계를 실행해야 한다.

  1. promise를 새 Promise 객체로, resolver를 그 관련 resolver로 둔다.

  2. promise를 반환하고 다음 단계를 비동기적으로 실행한다.

  3. document를 호출 컨텍스트의 Document로 둔다.

  4. document사용하도록 허용되지 않았다면, 정책 제어 기능midi라는 이름의 기능에 대해, 아래 failure로 표시된 단계로 이동한다.

  5. 선택적으로, 예를 들어 이전에 설정된 사용자 선호, 보안상의 이유 또는 플랫폼 제한에 따라, 아래 failure로 표시된 단계로 이동한다.

  6. 선택적으로, 예를 들어 이전에 설정된 사용자 선호에 따라, 아래 success로 표시된 단계로 이동한다.

  7. 사용자 에이전트별 방식으로 사용자에게, 엔트리 스크립트의 origin에 사용자의 MIDI 장치에 대한 제어를 나타내는 MIDIAccess 객체를 제공할 권한을 묻는다. 이 프롬프트는 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 노출에 소프트웨어 신시사이저를 포함하지 않아야 한다.

소프트웨어 신시사이저 지원이 필요하지는 않지만 원하는 경우, 이로 인해 2단계 요청 절차가 발생할 수 있다는 점에 유의한다. MIDI 하드웨어 장치 접근이 허용될 때 소프트웨어 신시사이저가 비활성화될 수 있다.

5. MIDI API

5.1 MIDIInputMap 인터페이스

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

MIDIInputMap은 값이 MIDIInput 인스턴스이고 키가 그 ID인 maplike 인터페이스이다.

이 타입은 현재 사용 가능한 모든 MIDI 입력 포트를 나타내는 데 사용된다.

5.2 MIDIOutputMap 인터페이스

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

MIDIOutputMap은 값이 MIDIOutput 인스턴스이고 키가 그 ID인 maplike 인터페이스이다.

이 타입은 현재 사용 가능한 모든 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 속성을 변경할 때 호출되는 핸들러.

MIDIConnectionEvent 타입인 이 이벤트 핸들러MIDIAccess 인터페이스를 구현하는 모든 객체에서 지원되어야 한다.

참고

이 객체에 EventHandler를 연결된 채로 두면 이 객체가 가비지 컬렉션되는 것을 막는다는 점을 이해하는 것이 중요하다. MIDIAccess 사용을 마쳤으면 모든 onstatechange 리스너를 제거해야 한다.

이전에는 사용할 수 없었던 MIDI 포트를 사용할 수 있게 되거나, 기존 포트가 state 속성을 변경할 때마다, 사용자 에이전트는 다음 단계를 실행해야 한다.

  1. port를 새로 사용할 수 있게 된 포트 또는 기존 포트에 해당하는 MIDIPort로 둔다.
  2. 이벤트를 발생시키되, MIDIAccess에서 "statechange"라는 이름으로 발생시키고, MIDIConnectionEvent를 사용하며, portport로 설정한다.
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. 개발자는 이를 사용하여 사용자가 애플리케이션에서 선택한 포트를 기억할 수 있다. 사용자 에이전트는 id가 오직 해당 포트에 대해서만 고유하도록 보장해야 한다. 사용자 에이전트는 이 id가 애플리케이션 인스턴스 사이에서 유지되도록 보장해야 한다. 예를 들어 시스템이 재부팅될 때와 장치가 시스템에서 제거될 때도 유지되어야 한다. 애플리케이션은 MIDI 설정을 다시 만들기 위해 이러한 id를 로컬에 캐시하기를 원할 수 있다. 일부 시스템은 완전히 고유한 지속 식별자를 지원하지 않을 수 있다. 그런 경우에는 다른 인터페이스가 시스템에 추가되거나 제거될 때 식별자를 유지하기가 더 어려워진다. (이는 요청된 포트의 인덱스를 어긋나게 할 수 있다.) 시스템은 MIDI API의 인스턴스 사이에서 포트를 일치시키기 위해 최선을 다할 것으로 예상된다. 예를 들어 구현은 포트 인터페이스의 제조사, 이름 및 인덱스에 대한 어떤 형태의 해시를 불투명하게 id로 사용할 수 있으며, 그러면 그 포트 id에 대한 참조가 연결되었을 때 해당 포트와 일치할 가능성이 높다. 애플리케이션은 MIDIPort의 id 비교를 사용하여 동등성을 테스트할 수 있다.

manufacturer

포트의 제조사.

name

포트의 시스템 이름.

type

포트가 입력 포트인지 출력 포트인지 구별하기 위한 설명자 속성. MIDIOutput의 경우 이는 "output"이어야 한다. MIDIInput의 경우 이는 "input"이어야 한다.

version

포트의 버전.

state
장치의 상태.
connection
장치에 대한 연결 상태.
onstatechange

기존 포트가 state 또는 connection 속성을 변경할 때 호출되는 핸들러.

"statechange" 타입인 이 이벤트 핸들러MIDIPort 인터페이스를 구현하는 모든 객체에서 지원되어야 한다.

참고

이 객체에 EventHandler를 연결된 채로 두면 이 객체가 가비지 컬렉션되는 것을 막는다는 점을 이해하는 것이 중요하다. MIDIPort 사용을 마쳤으면 모든 onstatechange 리스너를 제거해야 한다.

open

MIDIPort에 해당하는 MIDI 장치를 명시적으로 사용할 수 있게 한다. 이 호출은 MIDIPort를 사용하는 데 필요하지 않다는 점에 유의한다. MIDIOutput에서 send()를 호출하거나, MIDIInput에 MIDIMessageEvent EventHandler를 연결하거나, MIDIInput에 MIDIMessageEvent EventListener를 추가하면 암시적 open()이 발생한다. 기반 구현은 이 호출에 응답하여 아무것도 할 필요가 없을 수도 있다. 그러나 일부 기반 구현은 MIDI 장치에 대한 공유 접근을 지원하지 못할 수 있으므로, 명시적 open() 및 close() 호출을 사용하면 MIDI 애플리케이션이 장치에 대한 이러한 독점 접근을 예측 가능하게 제어할 수 있다.

호출되면, 이 메서드는 사용자의 시스템에 있는 주어진 MIDI 포트에 대한 접근 요청을 나타내는 Promise 객체를 반환한다.

포트 장치의 상태가 "connected"인 경우, 포트에 대한 접근이 얻어지면(그리고 포트가 입력 또는 출력을 위해 준비되면), 제공된 Promise는 이행된다.

연결된 포트에 대한 접근을 사용할 수 없는 경우(예를 들어 포트가 독점 접근 전용 플랫폼에서 이미 사용 중인 경우), Promise는 거부된다.

open()"disconnected"인 포트에서 호출되면, 포트의 .connection은 포트가 "connected"가 되거나 그에 대한 모든 참조가 제거될 때까지 "pending"으로 전환된다.

제공된 Promise가 모두 settled되지 않았더라도 open()을 여러 번 호출하는 것이 허용된다.

이 메서드가 호출되면, 사용자 에이전트는 MIDIPort를 여는 알고리즘을 실행해야 한다.

  1. promise를 새 Promise 객체로, resolver를 그 관련 resolver로 둔다.

  2. promise를 반환하고 다음 단계를 비동기적으로 실행한다.

  3. port를 주어진 MIDIPort 객체로 둔다.

  4. 장치의 연결이 이미 "open"이면 (예: 이 MIDIPort에서 open()이 이미 호출되었거나 포트가 암시적으로 열렸다면), 아래 success로 표시된 단계로 이동한다.

  5. 장치의 연결이 "pending"이면 (즉, 연결이 열렸고 이후 장치가 연결 해제되었다면), 아래 success로 표시된 단계로 이동한다.

  6. 장치의 상태가 "disconnected"이면, MIDIPortconnection 속성을 "pending"으로 변경하고, 새 MIDIConnectionEventMIDIAccessstatechange 핸들러와 MIDIPortstatechange 핸들러에 큐에 넣고, 아래 success로 표시된 단계로 이동한다.

  7. 시스템에서 주어진 MIDI 장치에 대한 접근을 얻으려고 시도한다. 장치를 사용할 수 없으면 (예: 다른 프로세스에서 이미 사용 중이어서 열 수 없거나, 연결 해제되었으면) 아래 failure로 표시된 단계로 이동한다. 장치를 사용할 수 있고 접근을 얻으면, 다음 단계를 계속한다.

  8. MIDIPort의 connection 속성을 "open"으로 변경하고, 새 MIDIConnectionEventMIDIAccessstatechange 핸들러와 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"로 변경한다). 이 메서드의 호출이 성공하면 MIDIInput의 MIDIMessageEvent 핸들러로 MIDI 메시지가 더 이상 전달되지 않는다는 점에 유의한다(다만 새 핸들러를 설정하면 암시적 open()이 발생한다).

기반 구현은 이 호출에 응답하여 아무것도 할 필요가 없을 수도 있다. 그러나 일부 기반 구현은 MIDI 장치에 대한 공유 접근을 지원하지 못할 수 있으며, 명시적 close() 호출은 MIDI 애플리케이션이 다른 애플리케이션이 장치에 접근할 수 있도록 보장하게 한다.

호출되면, 이 메서드는 사용자의 시스템에 있는 주어진 MIDI 포트에 대한 접근 요청을 나타내는 Promise 객체를 반환한다. 포트가 닫히면(따라서 독점 접근 시스템에서는 포트를 다른 애플리케이션이 사용할 수 있게 되면), 제공된 Promise는 이행된다. 포트가 연결 해제되어 있으면 Promise는 거부된다.

제공된 Promise가 모두 settled되지 않았더라도 close()를 여러 번 호출하는 것이 허용된다.

close() 메서드가 호출되면, 사용자 에이전트는 다음 단계를 실행해야 한다.

  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"로 변경하고, 새 MIDIConnectionEventMIDIAccessstatechange 핸들러와 MIDIPortstatechange 핸들러에 큐에 넣는다.

  8. closed: resolveraccept(value) 메서드를 port를 value 인자로 하여 호출한다.

MIDIPort에 해당하는 MIDI 포트가 state 속성을 변경할 때마다, 사용자 에이전트는 다음 단계를 실행해야 한다.

  1. portMIDIPort로 둔다.

  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" 타입인 이 이벤트 핸들러MIDIInput 인터페이스를 구현하는 모든 객체에서 지원되어야 한다.

핸들러가 설정되어 있고 state 속성이 "opened"가 아니면, 기반 구현은 포트를 사용할 수 있게 하려고 시도하고, state 속성을 "opened"로 변경한다. 성공하면 MIDIConnectionEvent가 해당 MIDIPortMIDIAccess에 전달된다.

MIDIInput에 해당하는 MIDI 포트가 하나 이상의 MIDI 메시지 수신을 마칠 때마다, 사용자 에이전트는 다음 단계를 실행해야 한다.

  1. portMIDIInput으로 둔다.

  2. MIDIAccessSystem 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비트 정수로 강제 변환한다. Uint8Array가 아니라 sequence를 사용하면 개발자가 Uint8Array를 만들어야 하는 대신, 예를 들어 output.send( new Uint8Array( [ 0x90, 0x45, 0x7f ] ) );가 아니라 output.send( [ 0x90, 0x45, 0x7f ] );라는 편의성을 사용할 수 있다.

데이터는 하나 이상의 완전한 유효한 MIDI 메시지를 포함한다. 기반 시스템이 이를 지원하지 않을 수 있으므로, 실행 상태는 데이터에서 허용되지 않는다.

data가 유효한 시퀀스가 아니거나 유효한 MIDI 메시지를 포함하지 않으면, TypeError 예외를 던진다.

dataSystem Exclusive 메시지이고, MIDIAccessSystem Exclusive 접근을 활성화하지 않았다면, InvalidAccessError 예외를 던진다.

포트가 "disconnected"이면, InvalidStateError 예외를 던진다.

포트가 "connected"이지만 연결이 "closed"이면, 비동기적으로 포트를 열려고 시도한다.

sequence<octet> data
큐에 넣을 데이터. 각 시퀀스 항목은 데이터의 단일 바이트를 나타낸다.
optional DOMHighResTimeStamp timestamp
포트로 데이터 전송을 시작할 시간 (DOMHighResTimeStamp, 즉 문서의 navigation start를 기준으로 측정한 밀리초 수). timestamp가 0(또는 과거의 다른 시간)으로 설정되면, 데이터는 가능한 한 빨리 전송되어야 한다. 같은 타임스탬프로 send()를 여러 번 호출하면 호출된 순서대로 데이터가 전송되어야 한다.
clear

MIDIOutput의 큐에서 아직 전송되지 않은 큐에 들어간 모든 전송 데이터를 지운다. 구현은 MIDI 스트림이 양호한 상태로 남도록 보장해야 하므로, 출력 포트가 sysex 메시지의 중간에 있다면 sysex 종료 바이트(0xf7)를 보내야 한다.

5.4.3 MIDIPortType 열거형

WebIDLenum MIDIPortType {
  "input",
  "output",
};
input
MIDIPort가 입력 포트이면, type 멤버는 이 값이어야 한다.
output
MIDIPort가 출력 포트이면, type 멤버는 이 값이어야 한다.

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를 여는 알고리즘을 따른다). 이로 인해 연결 상태는 "open" 또는 "closed"로 전환된다.

5.5 MIDIMessageEvent 인터페이스

이 인터페이스를 구현하는 이벤트 객체는 MIDI 메시지가 수신될 때 MIDIInput의 onmidimessage 핸들러로 전달된다. DOM Event timeStamp 속성은 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를 여는 알고리즘을 실행하여 포트를 다시 열려고 시도한다. 이 전환이 실패하면(예: 포트가 기반 시스템에서 다른 것에 의해 예약되어 있어 사용할 수 없는 경우) 연결 상태는 "closed"로 이동하고, 그렇지 않으면 다시 "open"으로 전환된다. 이는 장치 상태 변경에 대한 statechange 이벤트 전에 수행되므로, 이벤트가 장치 상태뿐만 아니라 최종 연결 상태도 반영하게 된다.

일부 기반 시스템은 장치 연결 상태에 대한 알림 이벤트를 제공하지 않을 수 있다. 그러한 시스템은 새 장치를 드물게 폴링하므로 긴 시간 지연이 있을 수 있다. 따라서 연결 이벤트에 과도하게 의존하지 않는 것이 좋다.

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 인터페이스를 열거하도록 허용하는 것은 fingerprinting의 잠재적 대상이다. 즉, 사용자가 연결한 특정 MIDI 인터페이스로 사용자를 고유하게 식별하는 것이다.

이 문맥에서 열거될 수 있는 것은 MIDI 인터페이스라는 점에 유의한다. 여기에는 USB로 호스트 컴퓨터에 연결된 대부분의 장치가 포함된다. USB-MIDI 장치는 일반적으로 자체 MIDI 인터페이스를 가지므로 열거된다. 5핀 DIN 케이블로 MIDI 인터페이스에 연결된 개별 샘플러 또는 신시사이저 MIDI 장치는 열거되지 않는다. fingerprinting될 수 있는 인터페이스는 MIDI "포트"와 동등하며, 각 MIDI 인터페이스에 대해 API는 장치의 이름, 제조사 및 MIDI 인터페이스의 불투명 식별자를 노출한다.

대부분의 시스템에는 연결된 MIDI 인터페이스가 없다. 많은 수의 MIDI 인터페이스가 연결된 시스템은 드물다. 따라서 MIDI 장치 열거로 인한 추가적인 fingerprinting 노출은 Gamepad API가 게임패드 열거를 통해 갖는 추가적인 fingerprinting 노출과 유사하다. 일반적인 사용자는 많아야 몇 개의 장치만 연결하고, 그 구성은 바뀔 수 있으며, 노출되는 정보는 인터페이스 자체에 관한 것이다(즉, 사용자가 구성한 데이터는 아니다).

7. 보안 고려사항

최초의 MIDI 장치는 웹 플랫폼과 그 보안 위험이 존재하기 전인 1983년에 출시되었다. 많은 MIDI 장치는 제조사가 지원을 중단한 지 오래된 후에도 여전히 사용되고 있다. MIDI는 FireWire, USB, Bluetooth와 같이 원래의 직렬 연결을 넘어서는 전송 방식에도 적응해 왔다. 이는 보안상의 문제를 제기한다. 공식 지원은 없지만 여전히 활발히 사용되고 있으며, 설계자들이 예상하지 못한 방식으로 컴퓨터와 웹에 연결되는, 서로 다른 시대의 장치들이 길게 이어져 있기 때문이다.

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 추가 보안 고려사항

사용할 수 있는 포트를 식별하는 fingerprinting 우려와는 별개로, MIDI 메시지를 보내고 받는 것과 관련된 우려가 있다. 이러한 문제는 아래에서 더 깊이 살펴본다.

MIDI 메시지는 System Exclusive 메시지와 짧은(비-System Exclusive) 메시지로 나눌 수 있다. System Exclusive 메시지는 일반적으로 알려진 MIDI Time Code 및 MIDI Sample Dump Standard 같은 Universal System Exclusive 메시지와, 다른 장치에는 적용되지 않는 “Roland Jupiter-80 신시사이저용 패치 제어 데이터” 같은 장치별 메시지로 다시 나눌 수 있다.

보안 우려를 논의하기 전에, 이러한 기능을 사용하는 MIDI가 어떤 시나리오를 가능하게 하는지 살펴보는 것이 유용하다.

이들 각각의 잠재적 보안 영향은 다음과 같다.

8. 변경 기록

8.1 2015년 3월 17일 작업 초안 이후 변경사항

A. 참고문헌

A.1 규범 참고문헌

[dom]
DOM 표준. Anne van Kesteren. WHATWG. Living Standard. 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. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[infra]
Infra 표준. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. URL: https://infra.spec.whatwg.org/
[MIDI]
MIDI 1.0 핵심 명세. The MIDI Manufacturers Association. 2014. URL: https://midi.org/midi-1-0-core-specifications
[Permissions]
Permissions. Marcos Caceres; Mike Taylor. W3C. 2024년 12월 20일. W3C 작업 초안. URL: https://www.w3.org/TR/permissions/
[permissions-policy]
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. Living Standard. URL: https://webidl.spec.whatwg.org/