Учебное пособие по MIDI: создание аудиоприложений на основе браузера, управляемых оборудованием MIDI
Опубликовано: 2022-03-11В то время как Web Audio API становится все популярнее, особенно среди разработчиков игр HTML5, Web MIDI API все еще малоизвестен среди разработчиков интерфейсов. Большая часть этого, вероятно, связана с текущим отсутствием поддержки и доступной документации; Web MIDI API в настоящее время поддерживается только в Google Chrome, при условии, что вы включили для него специальный флаг. В настоящее время производители браузеров уделяют мало внимания этому API, поскольку планируется, что он станет частью стандарта ES7.
Разработанный в начале 80-х несколькими представителями музыкальной индустрии, MIDI (сокращение от Musical Instrument Digital Interface) представляет собой стандартный протокол связи для электронных музыкальных устройств. Несмотря на то, что с тех пор были разработаны другие протоколы, такие как OSC; тридцать лет спустя MIDI по-прежнему является протоколом связи де-факто для производителей аудиооборудования. Вам будет трудно найти современного музыкального продюсера, у которого в студии нет хотя бы одного MIDI-устройства.
Благодаря быстрой разработке и внедрению API веб-аудио мы теперь можем приступить к созданию приложений на основе браузера, которые устранят разрыв между облаком и физическим миром. Мало того, что Web MIDI API позволяет нам создавать синтезаторы и звуковые эффекты, мы можем даже начать создавать DAW (Digital Audio Workstation) на основе браузера, схожие по функциям и производительности с их текущими аналогами на основе флэш-памяти (посмотрите, например, Audiotool). ).
В этом уроке по MIDI я познакомлю вас с основами Web MIDI API, и мы создадим простой моносинтезатор, который вы сможете использовать на своем любимом MIDI-устройстве. Полный исходный код доступен здесь, и вы можете напрямую протестировать живую демонстрацию. Если у вас нет MIDI-устройства, вы все равно можете следовать этому руководству, просмотрев ветку «клавиатура» репозитория GitHub, которая обеспечивает базовую поддержку клавиатуры вашего компьютера, чтобы вы могли играть ноты и менять октавы. Это также версия, доступная в качестве демо-версии. Однако из-за ограничений компьютерного оборудования скорость и расстройка отключаются всякий раз, когда вы используете клавиатуру компьютера для управления синтезатором. Пожалуйста, обратитесь к файлу readme на GitHub, чтобы узнать о сопоставлении клавиш/нот.
Требования к учебнику по миди
Для этого урока по MIDI вам понадобится следующее:
- Google Chrome (версия 38 или выше) с включенным флагом
#enable-web-midi
- (Необязательно) MIDI-устройство, которое может запускать ноты, подключенное к вашему компьютеру.
Мы также будем использовать Angular.js, чтобы немного структурировать наше приложение; поэтому базовые знания фреймворка являются необходимым условием.
Начиная
Мы разобьем наше MIDI-приложение на модули с нуля, разделив его на 3 модуля:
- WebMIDI: работа с различными MIDI-устройствами, подключенными к вашему компьютеру.
- WebAudio: предоставление источника звука для нашего синтезатора
- WebSynth: подключение веб-интерфейса к аудиодвижку
Модуль App
будет обрабатывать взаимодействие пользователя с пользовательским веб-интерфейсом. Структура нашего приложения может выглядеть примерно так:
|- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html
Вам также следует установить следующие библиотеки, которые помогут вам создать приложение: Angular.js, Bootstrap и jQuery. Вероятно, самый простой способ установить их — через Bower.
Модуль WebMIDI: связь с реальным миром
Давайте начнем выяснять, как использовать MIDI, подключив наши MIDI-устройства к нашему приложению. Для этого мы создадим простую фабрику, возвращающую единственный метод. Чтобы подключиться к нашим MIDI-устройствам через Web MIDI API, нам нужно вызвать метод navigator.requestMIDIAccess
:
angular .module('WebMIDI', []) .factory('Devices', ['$window', function($window) { function _connect() { if($window.navigator && 'function' === typeof $window.navigator.requestMIDIAccess) { $window.navigator.requestMIDIAccess(); } else { throw 'No Web MIDI support'; } } return { connect: _connect }; }]);
И это почти все!
Метод requestMIDIAccess
возвращает обещание, поэтому мы можем просто вернуть его напрямую и обработать результат обещания в контроллере нашего приложения:
angular .module('DemoApp', ['WebMIDI']) .controller('AppCtrl', ['$scope', 'Devices', function($scope, devices) { $scope.devices = []; devices .connect() .then(function(access) { if('function' === typeof access.inputs) { // deprecated $scope.devices = access.inputs(); console.error('Update your Chrome version!'); } else { if(access.inputs && access.inputs.size > 0) { var inputs = access.inputs.values(), input = null; // iterate through the devices for (input = inputs.next(); input && !input.done; input = inputs.next()) { $scope.devices.push(input.value); } } else { console.error('No devices detected!'); } } }) .catch(function(e) { console.error(e); }); }]);
Как уже упоминалось, метод requestMIDIAccess
возвращает обещание, передавая объект методу then
с двумя свойствами: входными данными и выходными данными.
В более ранних версиях Chrome эти два свойства были методами, позволяющими напрямую получать массив устройств ввода и вывода. Однако в последних обновлениях эти свойства теперь являются объектами. Это имеет большое значение, так как теперь нам нужно вызвать метод values
либо для входных, либо для выходных объектов, чтобы получить соответствующий список устройств. Этот метод действует как функция-генератор и возвращает итератор. Опять же, этот API должен быть частью ES7; поэтому реализация поведения, подобного генератору, имеет смысл, хотя и не так прямолинейна, как исходная реализация.
Наконец, мы можем получить количество устройств через свойство size
объекта итератора. Если есть хотя бы одно устройство, мы просто перебираем результат, вызывая метод next
объекта итератора и помещая каждое устройство в массив, определенный в $scope. Во внешнем интерфейсе мы можем реализовать простое поле выбора, в котором будут перечислены все доступные устройства ввода, и мы сможем выбрать, какое устройство мы хотим использовать в качестве активного устройства для управления веб-синтезатором:
<select ng-model="activeDevice" class="form-control" ng-options="device.manufacturer + ' ' + device.name for device in devices"> <option value="" disabled>Choose a MIDI device...</option> </select>
Мы привязали это поле выбора к переменной $scope с именем activeDevice
, которую позже будем использовать для подключения этого активного устройства к синтезатору.
Модуль WebAudio: Создание шума
WebAudio API позволяет нам не только воспроизводить звуковые файлы, но и генерировать звуки, воссоздавая основные компоненты синтезаторов, такие как генераторы, фильтры и узлы усиления среди прочего.
Создайте осциллятор
Роль осцилляторов состоит в том, чтобы выводить форму волны. Существуют различные типы сигналов, четыре из которых поддерживаются API WebAudio: синусоидальный, прямоугольный, треугольный и пилообразный. Говорят, что формы волн «колеблются» на определенной частоте, но при необходимости можно определить свою собственную волновую таблицу. Определенный диапазон частот, слышимых людьми, известен как звуки. В качестве альтернативы, когда они колеблются на низких частотах, осцилляторы также могут помочь нам построить LFO («низкочастотный осциллятор»), чтобы мы могли модулировать наши звуки (но это выходит за рамки этого урока).
Первое, что нам нужно сделать, чтобы создать какой-то звук, — это создать экземпляр нового AudioContext
:
function _createContext() { self.ctx = new $window.AudioContext(); }
Оттуда мы можем создать экземпляр любого компонента, доступного через WebAudio API. Поскольку мы можем создать несколько экземпляров каждого компонента, имеет смысл создавать службы, чтобы иметь возможность создавать новые уникальные экземпляры необходимых нам компонентов. Начнем с создания сервиса для генерации нового осциллятора:
angular .module('WebAudio', []) .service('OSC', function() { var self; function Oscillator(ctx) { self = this; self.osc = ctx.createOscillator(); return self; } });
Теперь мы можем создавать новые осцилляторы по своему желанию, передавая в качестве аргумента экземпляр AudioContext, который мы создали ранее. Чтобы упростить задачу в будущем, мы добавим несколько методов-оболочек — простой синтаксический сахар — и вернём функцию Oscillator:
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type } } Oscillator.prototype.setFrequency = function(freq, time) { self.osc.frequency.setTargetAtTime(freq, 0, time); }; Oscillator.prototype.start = function(pos) { self.osc.start(pos); } Oscillator.prototype.stop = function(pos) { self.osc.stop(pos); } Oscillator.prototype.connect = function(i) { self.osc.connect(i); } Oscillator.prototype.cancel = function() { self.osc.frequency.cancelScheduledValues(0); } return Oscillator;
Создайте многопроходный фильтр и регулятор громкости
Нам нужны еще два компонента, чтобы завершить наш основной звуковой движок: многопроходный фильтр, чтобы придать форму нашему звуку, и узел усиления, чтобы управлять громкостью нашего звука и включать и выключать громкость. Для этого мы можем действовать так же, как и для осциллятора: создавать сервисы, возвращающие функцию с некоторыми методами-оболочками. Все, что нам нужно сделать, это предоставить экземпляр AudioContext и вызвать соответствующий метод.
Мы создаем фильтр, вызывая метод createBiquadFilter
экземпляра AudioContext:
ctx.createBiquadFilter();
Точно так же для узла усиления мы вызываем метод createGain
:
ctx.createGain();
Модуль WebSynth: подключение
Теперь мы почти готовы создать наш синтезаторный интерфейс и подключить MIDI-устройства к нашему источнику звука. Во-первых, нам нужно соединить наш звуковой движок и подготовить его к приему MIDI-заметок. Чтобы подключить звуковой движок, мы просто создаем новые экземпляры необходимых нам компонентов, а затем «соединяем» их вместе, используя метод connect
, доступный для экземпляров каждого компонента. Метод connect
принимает один аргумент, который является просто компонентом, к которому вы хотите подключить текущий экземпляр. Можно организовать более сложную цепочку компонентов, поскольку метод connect
может подключать один узел к нескольким модуляторам (что позволяет реализовать такие вещи, как перекрестное затухание и многое другое).
self.osc1 = new Oscillator(self.ctx); self.osc1.setOscType('sine'); self.amp = new Amp(self.ctx); self.osc1.connect(self.amp.gain); self.amp.connect(self.ctx.destination); self.amp.setVolume(0.0, 0); //mute the sound self.filter1.disconnect(); self.amp.disconnect(); self.amp.connect(self.ctx.destination); }
Мы только что построили внутреннюю проводку нашего звукового движка. Вы можете немного поиграть и попробовать разные комбинации проводки, но не забудьте уменьшить громкость, чтобы не стать глухим. Теперь мы можем подключить MIDI-интерфейс к нашему приложению и отправлять MIDI-сообщения звуковому движку. Мы настроим наблюдатель в поле выбора устройства, чтобы виртуально «подключить» его к нашему синтезатору. Затем мы будем слушать MIDI-сообщения, поступающие от устройства, и передавать информацию звуковому движку:
// in the app's controller $scope.$watch('activeDevice', DSP.plug); // in the synth module function _onmidimessage(e) { /** * e.data is an array * e.data[0] = on (144) / off (128) / detune (224) * e.data[1] = midi note * e.data[2] = velocity || detune */ switch(e.data[0]) { case 144: Engine.noteOn(e.data[1], e.data[2]); break; case 128: Engine.noteOff(e.data[1]); break; } } function _plug(device) { self.device = device; self.device.onmidimessage = _onmidimessage; }
Здесь мы прослушиваем MIDI-события с устройства, анализируем данные из объекта MidiEvent и передаем их соответствующему методу; либо noteOn
, либо noteOff
в зависимости от кода события (144 для noteOn, 128 для noteOff). Теперь мы можем добавить логику в соответствующие методы аудио модуля, чтобы фактически генерировать звук:
function _noteOn(note, velocity) { self.activeNotes.push(note); self.osc1.cancel(); self.currentFreq = _mtof(note); self.osc1.setFrequency(self.currentFreq, self.settings.portamento); self.amp.cancel(); self.amp.setVolume(1.0, self.settings.attack); } function _noteOff(note) { var position = self.activeNotes.indexOf(note); if (position !== -1) { self.activeNotes.splice(position, 1); } if (self.activeNotes.length === 0) { // shut off the envelope self.amp.cancel(); self.currentFreq = null; self.amp.setVolume(0.0, self.settings.release); } else { // in case another note is pressed, we set that one as the new active note self.osc1.cancel(); self.currentFreq = _mtof(self.activeNotes[self.activeNotes.length - 1]); self.osc1.setFrequency(self.currentFreq, self.settings.portamento); } }
Здесь происходит несколько вещей. В методе noteOn
мы сначала помещаем текущую заметку в массив заметок. Несмотря на то, что мы строим моносинтезатор (это означает, что мы можем играть только одну ноту за раз), у нас все еще может быть несколько пальцев одновременно на клавиатуре. Итак, нам нужно поставить все эти ноты в очередь, чтобы, когда мы отпускаем одну ноту, проигрывалась следующая. Затем нам нужно остановить осциллятор, чтобы назначить новую частоту, которую мы преобразуем из ноты MIDI (шкала от 0 до 127) в фактическое значение частоты с небольшим количеством математики:
function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }
В методе noteOff
мы сначала находим заметку в массиве активных заметок и удаляем ее. Затем, если это была единственная нота в массиве, мы просто выключаем громкость.
Второй аргумент метода setVolume
— это время перехода, означающее, сколько времени требуется усилению для достижения нового значения громкости. С музыкальной точки зрения, если нота включена, это будет эквивалентно времени атаки, а если нота выключена, это будет эквивалентно времени восстановления.

Модуль WebAnalyser: визуализация нашего звука
Еще одна интересная функция, которую мы можем добавить к нашему синтезатору, — это узел анализатора, который позволяет нам отображать форму волны нашего звука, используя холст для его рендеринга. Создание узла анализатора немного сложнее, чем других объектов AudioContext, поскольку для фактического выполнения анализа требуется также создать узел scriptProcessor. Начнем с выбора элемента холста в DOM:
function Analyser(canvas) { self = this; self.canvas = angular.element(canvas) || null; self.view = self.canvas[0].getContext('2d') || null; self.javascriptNode = null; self.analyser = null; return self; }
Затем добавим метод connect
, в котором создадим и анализатор, и обработчик скриптов:
Analyser.prototype.connect = function(ctx, output) { // setup a javascript node self.javascriptNode = ctx.createScriptProcessor(2048, 1, 1); // connect to destination, else it isn't called self.javascriptNode.connect(ctx.destination); // setup an analyzer self.analyser = ctx.createAnalyser(); self.analyser.smoothingTimeConstant = 0.3; self.analyser.fftSize = 512; // connect the output to the destination for sound output.connect(ctx.destination); // connect the output to the analyser for processing output.connect(self.analyser); self.analyser.connect(self.javascriptNode); // define the colors for the graph var gradient = self.view.createLinearGradient(0, 0, 0, 200); gradient.addColorStop(1, '#000000'); gradient.addColorStop(0.75, '#ff0000'); gradient.addColorStop(0.25, '#ffff00'); gradient.addColorStop(0, '#ffffff'); // when the audio process event is fired on the script processor // we get the frequency data into an array // and pass it to the drawSpectrum method to render it in the canvas self.javascriptNode.onaudioprocess = function() { // get the average for the first channel var array = new Uint8Array(self.analyser.frequencyBinCount); self.analyser.getByteFrequencyData(array); // clear the current state self.view.clearRect(0, 0, 1000, 325); // set the fill style self.view.fillStyle = gradient; drawSpectrum(array); } };
Сначала мы создаем объект scriptProcessor и подключаем его к месту назначения. Затем мы создаем сам анализатор, на который мы подаем аудиовыход генератора или фильтра. Обратите внимание, что нам все еще нужно подключить аудиовыход к месту назначения, чтобы мы могли его слышать! Нам также необходимо определить цвета градиента нашего графика — это делается путем вызова метода createLinearGradient
элемента canvas.
Наконец, scriptProcessor будет запускать событие «аудиопроцесс» через определенный интервал; при запуске этого события мы вычисляем средние частоты, захваченные анализатором, очищаем холст и перерисовываем новый график частот, вызывая метод drawSpectrum
:
function drawSpectrum(array) { for (var i = 0; i < (array.length); i++) { var v = array[i], h = self.canvas.height(); self.view.fillRect(i * 2, h - (v - (h / 4)), 1, v + (h / 4)); } }
И последнее, но не менее важное: нам нужно немного изменить проводку нашего звукового движка, чтобы приспособить этот новый компонент:
// in the _connectFilter() method if(self.analyser) { self.analyser.connect(self.ctx, self.filter1); } else { self.filter1.connect(self.ctx.destination); } // in the _disconnectFilter() method if(self.analyser) { self.analyser.connect(self.ctx, self.amp); } else { self.amp.connect(self.ctx.destination); }
Теперь у нас есть хороший визуализатор, который позволяет нам отображать форму волны нашего синтезатора в реальном времени! Это требует некоторой работы по настройке, но это очень интересно и познавательно, особенно при использовании фильтров.
Создание нашего синтезатора: добавление скорости и расстройки
На данный момент в нашем руководстве по MIDI у нас есть довольно классный синтезатор, но он воспроизводит каждую ноту с одинаковой громкостью. Это связано с тем, что вместо правильной обработки данных о скорости мы просто устанавливаем громкость на фиксированное значение 1,0. Давайте начнем с исправления этого, а затем мы увидим, как мы можем включить колесо расстройки, которое вы найдете на большинстве распространенных MIDI-клавиатур.
Включение скорости
Если вы не знакомы с ним, «скорость» относится к тому, насколько сильно вы нажимаете клавишу на клавиатуре. В зависимости от этого значения создаваемый звук кажется либо тише, либо громче.
В нашем учебном синтезаторе MIDI мы можем эмулировать это поведение, просто играя с громкостью узла усиления. Для этого нам сначала нужно немного посчитать, чтобы преобразовать MIDI-данные в значение с плавающей запятой от 0,0 до 1,0, чтобы передать его узлу усиления:
function _vtov (velocity) { return (velocity / 127).toFixed(2); }
Диапазон динамической чувствительности MIDI-устройства составляет от 0 до 127, поэтому мы просто делим это значение на 127 и возвращаем значение с плавающей запятой с двумя десятичными знаками. Затем мы можем обновить метод _noteOn
, чтобы передать вычисленное значение узлу усиления:
self.amp.setVolume(_vtov(velocity), self.settings.attack);
Вот и все! Теперь, когда мы играем на нашем синтезаторе, мы заметим, что громкость меняется в зависимости от того, насколько сильно мы нажимаем клавиши на клавиатуре.
Включение колеса Detune на вашей MIDI-клавиатуре
Большинство MIDI-клавиатур имеют колесо расстройки; колесо позволяет вам немного изменить частоту воспроизводимой в данный момент ноты, создавая интересный эффект, известный как «расстройка». Это довольно легко реализовать, когда вы научитесь использовать MIDI, поскольку колесо расстройки также запускает событие MidiMessage со своим собственным кодом события (224), которое мы можем прослушивать и действовать, пересчитывая значение частоты и обновляя генератор.
Во-первых, нам нужно поймать событие в нашем синтезаторе. Для этого мы добавляем дополнительный регистр к оператору switch, который мы создали в _onmidimessage
:
case 224: // the detune value is the third argument of the MidiEvent.data array Engine.detune(e.data[2]); break;
Затем мы определяем метод detune
на звуковом движке:
function _detune(d) { if(self.currentFreq) { //64 = no detune if(64 === d) { self.osc1.setFrequency(self.currentFreq, self.settings.portamento); self.detuneAmount = 0; } else { var detuneFreq = Math.pow(2, 1 / 12) * (d - 64); self.osc1.setFrequency(self.currentFreq + detuneFreq, self.settings.portamento); self.detuneAmount = detuneFreq; } } }
Значение расстройки по умолчанию равно 64, что означает, что расстройка не применяется, поэтому в этом случае мы просто передаем текущую частоту генератору.
Наконец, нам также нужно обновить метод _noteOff
, чтобы учесть расстройку на случай, если в очереди появится другая нота:
self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);
Создание интерфейса
До сих пор мы создали только поле выбора, чтобы иметь возможность выбрать наше MIDI-устройство и визуализатор волновой формы, но у нас нет возможности изменять звук напрямую, взаимодействуя с веб-страницей. Давайте создадим очень простой интерфейс, используя общие элементы формы, и привяжем их к нашему звуковому движку.
Создание макета интерфейса
Мы создадим различные элементы формы для управления звуком нашего синтезатора:
- Группа переключателей для выбора типа генератора
- Флажок для включения/выключения фильтра
- Группа переключателей для выбора типа фильтра
- Два диапазона для управления частотой и резонансом фильтра
- Два диапазона для управления атакой и сбросом узла усиления
Создавая HTML-документ для нашего интерфейса, мы должны получить что-то вроде этого:
<div class="synth container" ng-controller="WebSynthCtrl"> <h1>webaudio synth</h1> <div class="form-group"> <select ng-model="activeDevice" class="form-control" ng-options="device.manufacturer + ' ' + device.name for device in devices"> <option value="" disabled>Choose a MIDI device...</option> </select> </div> <div class="col-lg-6 col-md-6 col-sm-6"> <h2>Oscillator</h2> <div class="form-group"> <h3>Oscillator Type</h3> <label ng-repeat="t in oscTypes"> <input type="radio" name="oscType" ng-model="synth.oscType" value="{{t}}" ng-checked="'{{t}}' === synth.oscType" /> {{t}} </label> </div> <h2>Filter</h2> <div class="form-group"> <label> <input type="checkbox" ng-model="synth.filterOn" /> enable filter </label> </div> <div class="form-group"> <h3>Filter Type</h3> <label ng-repeat="t in filterTypes"> <input type="radio" name="filterType" ng-model="synth.filterType" value="{{t}}" ng-disabled="!synth.filterOn" ng-checked="synth.filterOn && '{{t}}' === synth.filterType" /> {{t}} </label> </div> <div class="form-group"> <!-- frequency --> <label>filter frequency:</label> <input type="range" class="form-control" min="50" max="10000" ng-model="synth.filterFreq" ng-disabled="!synth.filterOn" /> </div> <div class="form-group"> <!-- resonance --> <label>filter resonance:</label> <input type="range" class="form-control" min="0" max="150" ng-model="synth.filterRes" ng-disabled="!synth.filterOn" /> </div> </div> <div class="col-lg-6 col-md-6 col-sm-6"> <div class="panel panel-default"> <div class="panel-heading">Analyser</div> <div class="panel-body"> <!-- frequency analyser --> <canvas></canvas> </div> </div> <div class="form-group"> <!-- attack --> <label>attack:</label> <input type="range" class="form-control" min="50" max="2500" ng-model="synth.attack" /> </div> <div class="form-group"> <!-- release --> <label>release:</label> <input type="range" class="form-control" min="50" max="1000" ng-model="synth.release" /> </div> </div> </div>
Украшение пользовательского интерфейса, чтобы оно выглядело причудливо, — это не то, что я буду рассматривать в этом базовом руководстве по MIDI; вместо этого мы можем сохранить его как упражнение, чтобы позже отшлифовать пользовательский интерфейс, возможно, чтобы он выглядел примерно так:
Привязка интерфейса к звуковому движку
Мы должны определить несколько методов для привязки этих элементов управления к нашему звуковому движку.
Управление осциллятором
Для осциллятора нам нужен только метод, позволяющий установить тип осциллятора:
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }
Управление фильтром
Для фильтра нам нужны три элемента управления: один для типа фильтра, один для частоты и один для резонанса. Мы также можем подключить методы _connectFilter
и _disconnectFilter
к значению флажка.
Filter.prototype.setFilterType = function(type) { if(type) { self.filter.type = type; } } Filter.prototype.setFilterFrequency = function(freq) { if(freq) { self.filter.frequency.value = freq; } } Filter.prototype.setFilterResonance = function(res) { if(res) { self.filter.Q.value = res; } }
Управление атакой и резонансом
Чтобы немного придать форму нашему звуку, мы можем изменить параметры атаки и восстановления узла усиления. Для этого нам понадобятся два метода:
function _setAttack(a) { if(a) { self.settings.attack = a / 1000; } } function _setRelease(r) { if(r) { self.settings.release = r / 1000; } }
Настройка наблюдателей
Наконец, в контроллере нашего приложения нам нужно всего лишь настроить несколько наблюдателей и привязать их к различным только что созданным методам:
$scope.$watch('synth.oscType', DSP.setOscType); $scope.$watch('synth.filterOn', DSP.enableFilter); $scope.$watch('synth.filterType', DSP.setFilterType); $scope.$watch('synth.filterFreq', DSP.setFilterFrequency); $scope.$watch('synth.filterRes', DSP.setFilterResonance); $scope.$watch('synth.attack', DSP.setAttack); $scope.$watch('synth.release', DSP.setRelease);
Заключение
В этом руководстве по MIDI было рассмотрено множество концепций; в основном мы узнали, как использовать WebMIDI API, который практически не документирован, если не считать официальной спецификации W3C. Реализация Google Chrome довольно прямолинейна, хотя переключение на объект итератора для устройств ввода и вывода требует небольшого рефакторинга устаревшего кода с использованием старой реализации.
Что касается API WebAudio, это очень богатый API, и в этом руководстве мы рассмотрели лишь некоторые из его возможностей. В отличие от WebMIDI API, WebAudio API очень хорошо документирован, в частности, в Mozilla Developer Network. Mozilla Developer Network содержит множество примеров кода и подробные списки различных аргументов и событий для каждого компонента, которые помогут вам реализовать собственные аудиоприложения на основе браузера.
Поскольку оба API продолжают развиваться, это откроет перед разработчиками JavaScript очень интересные возможности; что позволяет нам разрабатывать полнофункциональные DAW на основе браузера, которые смогут конкурировать со своими Flash-эквивалентами. А разработчики настольных компьютеров также могут начать создавать собственные кроссплатформенные приложения с помощью таких инструментов, как node-webkit. Надеемся, что это породит новое поколение музыкальных инструментов для аудиофилов, которые расширят возможности пользователей, сократив разрыв между физическим миром и облаком.