доступ к midi устройствам в браузере что это
Введение в Web MIDI
“Статья о MIDI в вебе? В 2016? Смешно, да.”
Нет. Это не то, что вы думаете. Для тех, кто помнит интернет 90х, одной фразы “MIDI в вебе” достаточно, чтобы вспомнить унылое лофайное проигрывание “The Final Countdown” при посещении гостевых книг особо одаренных веб-мастеров. Однако в 2016 году MIDI в вебе, а точнее Web MIDI API имеет большой потенциал.
MIDI стандарт отвечает за цифровой интерфейс музыкальных инструментов. Это протокол, который позволяет электронным музыкальным инструментам, компьютерам и прочим устройствам общаться друг с другом. Он работает посылая небольшие сообщения от устройства к устройству, передавая сообщения типа “нажата клавиша с нотой 12” или “клавишу с нотой 62 отпустили” в коротком цифровом формате.
Web MIDI API использует этот протокол и позволяет вам взять инструмент с MIDI, например, MIDI-клавиатуру, подсоединить к компьютеру и пересылать информацию с нее в браузер.
Но зачем подключать MIDI-клавиатуру к браузеру? Начнем с того, что для большинства музыкантов QWERTY-клавиатура не является полноценной заменой. А в реальности спектр музыкального оборудования, поддерживающего MIDI очень широк. Подключая MIDI-инструменты к браузеру и используя Web Audio API, мы можем создавать музыкальные инструменты прямо в вебе.
Хотите пианино? Просто подсоедините MIDI-клавиатуру и перейдите на страницу, использующую эти технологии для воспроизведения звука пианино. Нужен другой звук? Просто перейдите на другой сайт.
Итак, мы поняли, зачем нужен этот API, теперь будем разбираться, как он работает.
Доступ к MIDI-устройству
Теперь, когда мы убедились, что метод существует, вызовем его для доступа к любому MIDI-входу в браузере.
navigator.requestMIDIAccess() возвращает промис, это означает, что мы можем вызвать нужную функцию как в случае успешного соединения, так и в случае неудачи. Пока мы напишем две простые функции, просто выводящие в консоль результат подключения.
Краткое содержание цикла:
Декодирование MIDI-данных
Из всего содержимого MIDI-сообщения нам прежде всего интересны его данные — какой тип MIDI-событий передается, какая клавиша нажата?
Второй элемент массива это данные о нажатой/отпущенной клавиши. Всего для нот есть 128 номеров, этого достаточно для всех октав. В нашем случае нажата клавиша 61, по таблице номеров нот мы видим, что это C#.
Третий и последний элемент это скорость нажатия клавиш (velocity). Он может использоваться, например, для имитации пианино, клавиши которого могут нажиматься мягко или с силой.
Теперь, когда мы знаем, какая клавиша нажата или отпущена, попробуем эти сведения конвертировать во что-то полезное. Привяжем Web MIDI API к Web Audio API. Если вы не знакомы с Web Audio API, вам стоит прочитать несколько статей о нем.
Создание инструмента в браузере
Сделаем из нашего браузера небольшой синтезатор. Мы создадим осциллятор, генерирующий частоту нажатой клавиши, для этого нам надо конвертировать номер ноты в частоту. Алгоритм для этого нашелся в Википедии, вот как выглядит его реализация в JavaScript;
Просто отдаем ноту и получаем частоту. Используем это в функции onMIDIMessage :
Теперь мы хотим, чтобы нота с этой частотой проигрывалась при поступлении MIDI-сообщения с событием noteOn :
Но что у нас во второй части? Некоторые устройства вместо отправки сообщения noteOff передают noteOn со скоростью нажатия 0, поэтому мы проверяем, что значение скорости нажатия больше нуля.
Если нет, еще раз советую прочитать серию статей о Web Audio API, включая статью о создании синтезатора. Код в этой статье похож на то, что получилось у нас сейчас.
Что дальше?
Запомните, что noteOn и noteOff это лишь два из доступных типов сообщений и MIDI-клавиатура это лишь одно из очень разнообразных MIDI-устройств. И вы не обязаны использовать их для музыки. Может, мы еще дождемся HTML5 игр с управлением от MIDI-трубы.
Введение в Web MIDI
Russian (Pусский) translation by Marat Amerov (you can also view the original English article)
“Статья о MIDI в вебе? В 2016? Смешно, да.”
Нет. Это не то, что вы думаете. Для тех, кто помнит интернет 90х, одной фразы “MIDI в вебе” достаточно, чтобы вспомнить унылое лофайное проигрывание “The Final Countdown” при посещении гостевых книг особо одаренных веб-мастеров. Однако в 2016 году MIDI в вебе, а точнее Web MIDI API имеет большой потенциал.
MIDI стандарт отвечает за цифровой интерфейс музыкальных инструментов. Это протокол, который позволяет электронным музыкальным инструментам, компьютерам и прочим устройствам общаться друг с другом. Он работает посылая небольшие сообщения от устройства к устройству, передавая сообщения типа “нажата клавиша с нотой 12” или “клавишу с нотой 62 отпустили” в коротком цифровом формате.
Web MIDI API использует этот протокол и позволяет вам взять инструмент с MIDI, например, MIDI-клавиатуру, подсоединить к компьютеру и пересылать информацию с нее в браузер.
На данный момент Web MIDI API поддерживается только в Chrome и Opera, но вы можете наблюдать за продвижением работы по добавлению его в Firefox в соответствующем треде на сайте.
Но зачем подключать MIDI-клавиатуру к браузеру? Начнем с того, что для большинства музыкантов QWERTY-клавиатура не является полноценной заменой. А в реальности спектр музыкального оборудования, поддерживающего MIDI очень широк. Подключая MIDI-инструменты к браузеру и используя Web Audio API, мы можем создавать музыкальные инструменты прямо в вебе.
Хотите пианино? Просто подсоедините MIDI-клавиатуру и перейдите на страницу, использующую эти технологии для воспроизведения звука пианино. Нужен другой звук? Просто перейдите на другой сайт.
Итак, мы поняли, зачем нужен этот API, теперь будем разбираться, как он работает.
Доступ к MIDI-устройству
Теперь, когда мы убедились, что метод существует, вызовем его для доступа к любому MIDI-входу в браузере.
navigator.requestMIDIAccess() возвращает промис, это означает, что мы можем вызвать нужную функцию как в случае успешного соединения, так и в случае неудачи. Пока мы напишем две простые функции, просто выводящие в консоль результат подключения.
Чтобы получать MIDI-данные с нашего устройства, нам надо создать переменную и задать ей значение midi.inputs.values() примерно так:
Краткое содержание цикла:
Декодирование MIDI-данных
Второй элемент массива это данные о нажатой/отпущенной клавиши. Всего для нот есть 128 номеров, этого достаточно для всех октав. В нашем случае нажата клавиша 61, по таблице номеров нот мы видим, что это C#.
Третий и последний элемент это скорость нажатия клавиш (velocity). Он может использоваться, например, для имитации пианино, клавиши которого могут нажиматься мягко или с силой.
Теперь, когда мы знаем, какая клавиша нажата или отпущена, попробуем эти сведения конвертировать во что-то полезное. Привяжем Web MIDI API к Web Audio API. Если вы не знакомы с Web Audio API, вам стоит прочитать несколько статей о нем.
Создание инструмента в браузере
Сделаем из нашего браузера небольшой синтезатор. Мы создадим осциллятор, генерирующий частоту нажатой клавиши, для этого нам надо конвертировать номер ноты в частоту. К счастью, наш хороший друг Википедия дает нам небольшой алгоритм для этого. Вот как выглядит его реализация в JavaScript:
Но что у нас во второй части? Некоторые устройства вместо отправки сообщения noteOff передают noteOn со скоростью нажатия 0, поэтому мы проверяем, что значение скорости нажатия больше нуля.
Если нет, еще раз советую прочитать серию статей о Web Audio API, включая статью о создании синтезатора. Код в этом учебнике похож на то, что я сделал сейчас, поэтому было бы идеальным местом для применения того, что вы узнали здесь.
Что дальше?
Запомните, что noteOn и noteOff это лишь два из доступных типов сообщений и MIDI-клавиатура это лишь одно из очень разнообразных MIDI-устройств. И вы не обязаны использовать их для музыки. Может, мы еще дождемся HTML5 игр с управлением от MIDI-трубы. Похоже на мою вещь.
Лабаем на MIDI-клавиатуре в Angular
Web MIDI API — интересный зверь. Хоть он и существует уже почти пять лет, его все еще поддерживает только Chromium. Но это не помешает нам создать полноценный синтезатор в Angular. Пора поднять Web Audio API на новый уровень!
Программировать музыку, конечно, весело, но что если мы хотим ее играть? В 80-е годы появился стандарт обмена сообщениями между электронными инструментами — MIDI. Он активно используется и по сей день, и Chrome поддерживает его на нативном уровне. Это значит, что, если у вас есть синтезатор или MIDI-клавиатура, вы можете подключить их к компьютеру и считывать то, что вы играете. Можно даже управлять устройствами с компьютера, посылая исходящие сообщения. Давайте разберемся, как это сделать по-хорошему в Angular.
Web MIDI API
Dependency Injection
Теперь мы можем подписаться на все MIDI-события. Можно создать Observable одним из двух способов:
Поскольку в этот раз нам понадобится совсем немного преобразований, токен вполне подойдет. С обработкой отказа код подписки на все события выглядит так:
Если вам интересны подобные практичные мелочи про Angular — приглашаю почитать нашу серию твитов с полезными советами.
Аналогичным образом можно добывать и входные порты, а также запрашивать их по имени.
Операторы
Для работы с потоком событий нам потребуется создать свои операторы. В конце концов, мы же не хотим каждый раз ковыряться в исходном массиве данных.
Операторы можно условно разделить на две группы:
Вот так мы можем слушать события с определенного канала:
Status byte организован группами по 16: 128—143 отвечают за нажатые клавиши ( noteOn ) на каждом из 16 каналов. 144—159 — за отпускание зажатых клавиш ( noteOff ). Таким образом, если мы возьмем остаток от деления этого байта на 16 — получим номер канала.
Если нас интересуют только сыгранные ноты, поможет такой оператор:
Теперь можно строить цепочки операторов, чтобы получить стрим, который нам нужен:
Пора применить все это на практике!
Создаем синтезатор
С небольшой помощью библиотеки для Web Audio API, которую мы обсуждали ранеесоздадим приятно звучащий синтезатор всего за пару директив. Затем мы скормим ему ноты, которые играем через описанный выше стрим.
В качестве отправной точки используем последний кусок кода. Чтобы синтезатор был полифоническим, нужно отслеживать все сыгранные ноты. Для этого воспользуемся оператором scan:
Чтобы звук не прерывался резко и не звучал всегда на одной громкости, создадим полноценный ADSR-пайп. В прошлой статье была его упрощенная версия. Напомню, идея ADSR — менять громкость звука следующим образом:
Чтобы нота начиналась не резко, удерживалась на определенной громкости, пока клавиша нажата, а потом плавно затухала.
Теперь, когда мы нажимаем клавишу, громкость будет линейно нарастать за время attack. Затем она убавится до уровня sustain за время decay. А когда мы отпустим клавишу, громкость упадет до нуля за время release.
С таким пайпом мы можем набросать синтезатор в шаблоне:
Если у вас нет MIDI-клавиатуры — можете понажимать на ноты мышкой.
Живое демо доступно тут, однако браузер не позволит получить доступ к MIDI в iframe: https://stackblitz.com/edit/angular-midi
Заключение
В Angular мы привыкли работать с событиями с помощью RxJs. И Web MIDI API не сильно отличается от привычных DOM-событий. С помощью пары токенов и архитектурных решений мы смогли с легкостью добавить поддержку MIDI в наше Angular приложение. Описанное решение доступно в виде open-source библиотеки @ng-web-apis/midi. Она является частью большого проекта, под названием Web APIs for Angular. Наша цель — создание легковесных качественных оберток для использования нативного API в Angular приложениях. Так что если вам нужен, к примеру, Payment Request API или Intersection Observer — посмотрите все наши релизы.
Разрешения
Часть доступов и разрешений, необходимых для корректной работы мобильного Яндекс.Браузера, запрашивается при его установке.
Доступ к микрофону
Требуется для голосового поиска и разговоров с Алисой.
Если при установке вы отказались предоставить браузеру доступ к микрофону, вы можете сделать это позже:
Доступ к камере
Требуется для поиска по картинке.
Если при установке вы отказались предоставить браузеру доступ к камере, вы можете сделать это позже:
Доступ к местоположению
Используется в следующих случаях:
Если при установке вы отказались предоставить браузеру доступ к местоположению, вы можете сделать это позже:
Если вы не нашли информацию в Справке или у вас возникает проблема в работе Яндекс.Браузера, опишите все свои действия по шагам. Если возможно, сделайте скриншот. Это поможет специалистам службы поддержки быстрее разобраться в ситуации.
Разрешения
Часть доступов и разрешений, необходимых для корректной работы мобильного Яндекс.Браузера, запрашивается при его установке.
Доступ к микрофону
Требуется для голосового поиска и разговоров с Алисой.
Если при установке вы отказались предоставить браузеру доступ к микрофону, вы можете сделать это позже:
Доступ к камере
Требуется для поиска по картинке.
Если при установке вы отказались предоставить браузеру доступ к камере, вы можете сделать это позже:
Доступ к местоположению
Используется в следующих случаях:
Если при установке вы отказались предоставить браузеру доступ к местоположению, вы можете сделать это позже:
Если вы не нашли информацию в Справке или у вас возникает проблема в работе Яндекс.Браузера, опишите все свои действия по шагам. Если возможно, сделайте скриншот. Это поможет специалистам службы поддержки быстрее разобраться в ситуации.
Web-midi-api: Виртуальные MIDI-порты
Yamaha запросила ту же функцию, вплоть до создания эталонного программного синтезатора.
Я по-прежнему слышу спрос на это почти от каждого поставщика, с которым я разговариваю.
Все 36 Комментарий
В последнее время я потратил некоторое время на размышления об этой функции, и я хочу поделиться некоторыми идеями и открытыми вопросами, которые у меня есть. В самом простом случае, я вижу, что мы могли бы получить следующее:
Результирующие порты, естественно, не будут иметь id поскольку они не соответствуют никаким реальным портам, виртуальным или физическим.
Один открытый вопрос заключается в том, хотим ли мы иметь их как фабрики в интерфейсе MIDIAccesss или как глобальные конструкторы. С одной стороны, для базового использования портов нет никакого смысла требовать согласия пользователя, нет возможности снятия отпечатков пальцев, поскольку даже id не отображается, и порты действительно ничего не делают, если пользователь явно связывает их где-то вручную или через другое программное обеспечение, которое он запускает.
Однако это нужно продумать. Я слышал, что некоторые приложения iOS используют виртуальные MIDI-порты для связи друг с другом. Если это так, нам нужно подумать, следует ли рассматривать веб-приложение, выдавающее себя за другое собственное приложение, как потенциальный риск. В худшем случае кошмарного сценария приложение будет передавать Lua (или аналогичный) код через MIDI, что может привести к атаке сценариев между приложениями, возможно, используя все привилегии, которые пользователь предоставил этому приложению. Другой, гораздо более вероятный случай, когда учетные данные пользователя будут передаваться из одного приложения в другое, аналогично OAuth, за исключением того, что аутентификация будет происходить в другом приложении, а не в Интернете, и перехватывающее приложение может украсть эти учетные данные.
Вот мои мысли о виртуальных портах.
Извините, но эта заметка сосредоточена на другом моменте.
Если мы начнем поддерживать виртуальные порты, мы должны более серьезно рассмотреть задержку каждого устройства.
Здесь я предполагаю, что основными вариантами использования виртуальных портов являются программные синтезаторы.
Программные синтезаторы вызывают гораздо большую задержку, чем реальные устройства. В результате, без информации о задержках, должно быть очень сложно использовать и оборудование, и программное обеспечение одновременно в одном веб-приложении.
Также виртуальные порты можно использовать для управления удаленными MIDI-устройствами через Интернет. Даже в этом случае важна задержка, и с ней следует правильно обращаться.
Таким образом, в спецификации v2 MIDIPort может захотеть иметь дополнительный атрибут для задержки от поступления событий до воспроизведения звука.
Я согласен с задержкой, мы должны это учитывать. Некоторые варианты использования:
Итак, в основном, я думаю, что нам нужно для обычных портов способ считывать их задержку (если они недоступны, сообщать 0) и для виртуальных портов, чтобы записывать их задержку, например
Я не уверен, что 0 означает как задержку 0, так и неизвестно
Я не могу придумать ни одного случая, когда поведение по умолчанию в случае неизвестной задержки не предполагало бы нулевую задержку, поэтому в большинстве случаев не было бы никакой дополнительной работы для учета ситуации с неизвестной задержкой, отсюда и предложение. Если мы сможем придумать нормальные сценарии, в которых вы выиграете от того, что оно будет отличным от нуля и неизвестным, я буду счастлив использовать другое значение.
Я предпочитаю, чтобы это были конструкторы, а не фабрика
Я согласен, но мы должны тщательно оценить, есть ли в этом угроза безопасности.
Я согласен, но мы должны тщательно оценить, есть ли в этом угроза безопасности.
Я понимаю проблемы безопасности, о которых вы упомянули выше, но они, похоже, ортогональны конструктору или фабрике (возможно, я что-то упускаю).
но они кажутся ортогональными конструктору или фабрике
Все сводится к тому, нужно ли нам запрашивать разрешение или нет, и если мы это делаем, у фабричного метода уже настроена модель разрешений (для получения экземпляра MIDIAccess), тогда как для глобальных конструкторов ее нет. То есть, если конструктор не принимает экземпляр MIDIAccess в качестве аргумента (в этом случае я бы сказал, что нет смысла отделять его от MIDIAccess) или мы бросаем, если действительный экземпляр MIDIAccess не был создан во время текущий сеанс.
Я не уверен, что модель безопасности / конфиденциальности для виртуальных портов будет такой же, как и для невиртуальных портов, так как я ожидаю, что кто-то захочет, чтобы виртуальные порты также были доступны для нативного программного обеспечения?
Я по-прежнему слышу спрос на это почти от каждого поставщика, с которым я разговариваю.
126 довольно близок к этой проблеме, но:
Ситуация стала более острой, чем в прошлом году, потому что операционные системы больше не предоставляют GM Synth.
Независимо от того, попадает ли это в сам Web MIDI API или нет, было бы здорово иметь прокладку.
Просто для воспроизведения файла SMF с синтезаторами GM или вашими собственными синтезаторами Web MIDI вообще не нужен. Для этого достаточно Web Audio. Вы видели ссылку, которую я разместил в № 126?
Здесь важно то, что нам нужен стандартизированный способ совместного использования программных синтезаторов, написанных на JavaScript.
@notator Дельта между виртуальным портом вывода почти равна нулю. Если мы делаем одно, мы должны делать и другое.
Я думаю, что это отличная идея, но кажется, что она открыта для самых разных определений того, что составляет «виртуальное устройство», и где его реализация может находиться (на странице браузера? В самом браузере, постоянно? В нативных приложениях?).
Также есть некоторое совпадение с существующими механизмами «виртуальных» устройств для MIDI, например, в iOS.
И насколько стойким или непродолжительным является такое устройство? Это только тогда, когда страница, которая его инстанцирует, оказывается открытой?
Не говоря уже обо всех соображениях безопасности.
Короче говоря, виртуальные устройства кажутся крутыми, и я уверен, что поставщики их просят (эй, я тоже хочу их), но мне интересно, все ли мы точно знаем, что имеем в виду, когда говорим об этом, и имеем ли мы в виду то же самое. Похоже, что необходимы дополнительные исследования и обсуждения, чтобы выявить варианты использования и сделать эту идею достаточно определенной для реализации.
Кроме того, у Web Audio есть очень похожая проблема, которую необходимо решить с точки зрения взаимодействия между приложениями. Я надеюсь, что Web Audio и Web MIDI могут в конечном итоге принять аналогичный подход к абстрагированию источников и мест назначения как для аудио, так и для MIDI-данных.
Если бы мы решили проблему №99, а затем использовали ServiceWorkers (https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md), тогда было бы несложно расширить спецификацию, чтобы разрешить виртуальные порты и иметь разумная история жизненного цикла.
(По крайней мере, в Linux и Mac. В Windows нет стандартного способа создания виртуальных портов.)
SGTM о Рабочей истории. Я думаю, что использование виртуальных портов для приложений вне веб-браузера может быть необязательным на платформах, базовая система которых поддерживает такую функцию.
@toyoshim Привет! Спасибо за ссылку. Отличный материал! Это ваш сайт?
Конечно, есть что обсудить:
Чтобы лучше задуматься о многопоточности, рассмотрим пример использования:
Допустим, я пишу подготовленный эмулятор фортепиано. (На самом деле это не совсем так, но достаточно близко к истине для наших целей.)
К моему приложению подключена (настоящая) MIDI-клавиатура, и у меня все настроено так, что каждая клавиша связана с (довольно шумной) последовательностью MIDI-сообщений, ожидающих воспроизведения.
Отправка noteOn с клавиатуры запускает его последовательность. При отправке noteOff последовательность останавливается. Я не могу контролировать, сколько клавиш играет одновременно или когда они нажаты относительно друг друга. Все дело в том, что клавиатура никогда не блокируется. Исполнитель может просто играть.
Я новичок в веб-воркерах, но в настоящее время представьте, что настраиваю что-то вроде этого до начала производительности:
Устройство ввода находится в пользовательском потоке браузера.
Устройство вывода находится в SharedWorker ( @agoode или ServiceWorker?). Назовем его Маршалл.
Последовательности ключей выполняются в своих собственных (обычных) веб-воркерах и получают доступ к устройству вывода, отправляя сообщения в Marshall.
Если все действительно так работает, то я тот, кто контролирует жизненный цикл всех потоков и устройств.
Но могут ли Sharedworkers или ServiceWorkers получить доступ к виртуальным устройствам вывода? Я не смог узнать.
@notator Это не мой сайт, а сайт моего друга, известного в Японии разработчика музыкальных приложений.
Это хороший пример того, сколько программных синтезаторов может быть разработано сообществом за короткий период времени. После того, как он предложил идею WebMidiLink, многие люди разработали свои собственные синтезаторы, поддерживающие WebMidiLink. Это причина, по которой я не придерживаюсь ОС, предоставляющей синтезаторы. Мы можем разработать отличный синтезатор с помощью веб-аудио, а веб-сообщество может создавать различные виды синтезаторов.
@toyoshim Ах да, я забыл: +1 за «Здесь важно то, что нам нужен стандартизированный способ совместного использования программных синтезаторов, написанных на JavaScript».
Я думаю, что интерфейс, который должен быть реализован программными синтезаторами, может быть более или менее продиктован властями. Это намного проще, чем определять интерфейс для производителей оборудования. Я думаю, что интерфейс должен быть смоделирован по образцу аппаратных синтезаторов.
Для начала, я думаю, должна быть функция, возвращающая метаданные синтезатора. Посмотреть свойства в
http://www.g200kg.com/en/docs/webmidilink/synthlist.html
И, если это уже недостаточно ясно, моего первоначального запроса от № 126 больше нет на столе. 🙂
@agoode Service Worker НЕ БУДЕТ исправлять это. Вы не сможете поддерживать AudioContext внутри ПО; SW созданы для того, чтобы оживать, когда это необходимо, но не постоянно работать постоянно. Для софт-синтезатора вам нужно проснуться и быть живым (обычно при роутинге).
Давайте сосредоточимся на этом: ЭТА проблема связана с созданием виртуальных MIDI-портов, ввода и вывода, которые затем могут использоваться другими программами в системе, пока это приложение является резидентным, то есть создание MIDI-канала. Другая проблема (№124) касается управления виртуальными инструментами и обращения к ним, включая, предположительно, то, как вы их инициализируете. (Здесь может быть задействован ServiceWorker, но он не решит эту проблему сам по себе.)
Доступен ли MIDI API для рабочих (# 99), имеет значение, поскольку он вам, вероятно, понадобится в контексте любого контекста инициализации для # 124, но он также полезен в более узких контекстах (например, я хочу запустить свой секвенсор в потоке без пользовательского интерфейса).
Да, мне самому тоже нравится эта идея, и я иногда думаю о том, каким должен быть этот API-интерфейс, и делаю несколько прототипов. Но, вероятно, это следует обсудить после того, как будет готова вторая реализация браузера.