MIDI 튜토리얼: MIDI 하드웨어로 제어되는 브라우저 기반 오디오 애플리케이션 만들기
게시 됨: 2022-03-11Web Audio API는 특히 HTML5 게임 개발자들 사이에서 인기가 높아지고 있지만 Web MIDI API는 여전히 프론트엔드 개발자들 사이에서는 거의 알려지지 않았습니다. 이것의 큰 부분은 아마도 현재의 지원 부족과 접근 가능한 문서와 관련이 있을 것입니다. Web MIDI API는 현재 Google Chrome에서만 지원되며 특별한 플래그를 활성화하면 됩니다. 브라우저 제조업체는 현재 ES7 표준의 일부로 계획되어 있기 때문에 이 API에 거의 중점을 두지 않습니다.
80년대 초 여러 음악 업계 대표자들에 의해 설계된 MIDI(Musical Instrument Digital Interface의 약자)는 전자 음악 장치용 표준 통신 프로토콜입니다. 그 이후로 OSC와 같은 다른 프로토콜이 개발되었지만, 30년이 지난 후에도 MIDI는 여전히 오디오 하드웨어 제조업체를 위한 사실상의 통신 프로토콜입니다. 스튜디오에 MIDI 기기가 한 대도 없는 현대 음악 프로듀서를 찾기가 어려울 것입니다.
Web Audio API의 빠른 개발 및 채택으로 이제 클라우드와 실제 세계 사이의 격차를 해소하는 브라우저 기반 애플리케이션 구축을 시작할 수 있습니다. Web MIDI API를 사용하여 신디사이저 및 오디오 효과를 구축할 수 있을 뿐만 아니라 기능 및 성능 면에서 현재 플래시 기반 대응물과 유사한 브라우저 기반 DAW(디지털 오디오 워크스테이션) 구축을 시작할 수도 있습니다(예: Audiotool 확인 ).
이 MIDI 튜토리얼에서는 Web MIDI API의 기본 사항을 안내하고 좋아하는 MIDI 장치로 연주할 수 있는 간단한 모노신스를 구축할 것입니다. 전체 소스 코드는 여기에서 사용할 수 있으며 라이브 데모를 직접 테스트할 수 있습니다. MIDI 장치를 소유하지 않은 경우에도 GitHub 저장소의 'keyboard' 분기를 확인하여 이 튜토리얼을 따를 수 있습니다. 이 분기는 컴퓨터 키보드에 대한 기본 지원을 활성화하여 음표를 연주하고 옥타브를 변경할 수 있습니다. 이것은 또한 라이브 데모로 사용할 수 있는 버전입니다. 그러나 컴퓨터 하드웨어의 제한으로 인해 컴퓨터 키보드를 사용하여 신디사이저를 제어할 때마다 벨로시티와 디튠이 모두 비활성화됩니다. 키/노트 매핑에 대해 읽으려면 GitHub의 추가 정보 파일을 참조하십시오.
미디 튜토리얼 전제 조건
이 MIDI 튜토리얼을 위해 다음이 필요합니다.
-
#enable-web-midi
플래그가 활성화된 Google 크롬(버전 38 이상) - (선택 사항) 컴퓨터에 연결된 음을 트리거할 수 있는 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를 사용하는 방법을 알아보겠습니다. 그렇게 하기 위해 우리는 단일 메서드를 반환하는 간단한 팩토리를 만들 것입니다. Web MIDI API를 통해 MIDI 장치에 연결하려면 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의 일부입니다. 따라서 제너레이터와 유사한 동작을 구현하는 것은 원래 구현만큼 간단하지 않더라도 의미가 있습니다.
마지막으로 iterator 객체의 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>
이 선택 상자를 나중에 이 활성 장치를 신디사이저에 연결하는 데 사용할 activeDevice
라는 $scope 변수에 바인딩했습니다.
WebAudio 모듈: 소음 만들기
WebAudio API를 사용하면 사운드 파일을 재생할 수 있을 뿐만 아니라 오실레이터, 필터, 게인 노드와 같은 신디사이저의 필수 구성 요소를 재생성하여 사운드를 생성할 수 있습니다.
오실레이터 생성
발진기의 역할은 파형을 출력하는 것입니다. 다양한 유형의 파형이 있으며 그 중 4가지가 WebAudio API에서 지원됩니다: 사인, 정사각형, 삼각형 및 톱니. 파형은 특정 주파수에서 "진동"한다고 말하지만 필요한 경우 자체 사용자 정의 웨이브 테이블을 정의하는 것도 가능합니다. 사람이 들을 수 있는 특정 범위의 주파수를 소리라고 합니다. 또는 저주파에서 진동할 때 발진기는 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 인스턴스를 제공하고 적절한 메소드를 호출하기만 하면 됩니다.
AudioContext 인스턴스의 createBiquadFilter
메소드를 호출하여 필터를 생성합니다:
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의 경우 144, noteOff의 경우 128)에 따라 noteOn
또는 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 모듈: 사운드 시각화
신디사이저에 추가할 수 있는 또 다른 흥미로운 기능은 분석기 노드로, 캔버스를 사용하여 사운드 파형을 렌더링하여 표시할 수 있습니다. 분석기 노드를 생성하는 것은 실제로 분석을 수행하기 위해 scriptProcessor 노드도 생성해야 하기 때문에 다른 AudioContext 객체보다 조금 더 복잡합니다. 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
메서드를 호출하여 수행됩니다.
마지막으로 scriptProcessor는 일정 간격으로 'audioprocess' 이벤트를 발생시킵니다. 이 이벤트가 발생하면 분석기에 의해 캡처된 평균 주파수를 계산하고 캔버스를 지우고 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);
그리고 그게 다야! 이제 신디사이저를 연주할 때 키보드의 키를 얼마나 세게 치느냐에 따라 음량이 달라지는 것을 알 수 있습니다.
MIDI 키보드에서 Detune 휠 활성화
대부분의 MIDI 키보드에는 디튠 휠이 있습니다. 휠을 사용하면 현재 재생 중인 음의 주파수를 약간 변경하여 '디튠'으로 알려진 흥미로운 효과를 만들 수 있습니다. 이것은 미디 사용 방법을 배우면서 구현하기가 상당히 쉽습니다. 디튠 휠은 자체 이벤트 코드(224)로 MidiMessage 이벤트를 발생시키기 때문에 주파수 값을 다시 계산하고 오실레이터를 업데이트하여 이를 듣고 작동할 수 있습니다.
먼저 synth에서 이벤트를 잡아야 합니다. 그렇게 하려면 _onmidimessage
콜백에서 만든 switch 문에 추가 사례를 추가합니다.
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 튜토리얼에서는 많은 개념을 다루었습니다. 대부분 W3C의 공식 사양과 별도로 문서화되지 않은 WebMIDI API를 사용하는 방법을 발견했습니다. Google Chrome 구현은 매우 간단하지만 입력 및 출력 장치에 대한 반복자 개체로 전환하려면 이전 구현을 사용하는 레거시 코드에 대해 약간의 리팩토링이 필요합니다.
WebAudio API의 경우 이것은 매우 풍부한 API이며 이 자습서에서는 몇 가지 기능만 다루었습니다. WebMIDI API와 달리 WebAudio API는 특히 Mozilla 개발자 네트워크에서 매우 잘 문서화되어 있습니다. Mozilla 개발자 네트워크에는 사용자 정의 브라우저 기반 오디오 응용 프로그램을 구현하는 데 도움이 되는 각 구성 요소에 대한 다양한 인수 및 이벤트에 대한 많은 코드 예제와 자세한 목록이 포함되어 있습니다.
두 API가 계속 성장함에 따라 JavaScript 개발자에게 매우 흥미로운 가능성이 열릴 것입니다. Flash와 경쟁할 수 있는 모든 기능을 갖춘 브라우저 기반 DAW를 개발할 수 있습니다. 또한 데스크탑 개발자의 경우 node-webkit과 같은 도구를 사용하여 고유한 크로스 플랫폼 애플리케이션 생성을 시작할 수도 있습니다. 바라건대 이것은 물리적 세계와 클라우드 사이의 격차를 해소함으로써 사용자에게 힘을 실어줄 오디오 애호가를 위한 새로운 세대의 음악 도구를 탄생시킬 것입니다.