Tutorial MIDI: Membuat Aplikasi Audio Berbasis Browser yang Dikendalikan oleh Perangkat Keras MIDI
Diterbitkan: 2022-03-11Sementara Web Audio API semakin populer, terutama di kalangan pengembang game HTML5, Web MIDI API masih sedikit dikenal di kalangan pengembang frontend. Sebagian besar dari ini mungkin berkaitan dengan kurangnya dukungan dan dokumentasi yang dapat diakses saat ini; Web MIDI API saat ini hanya didukung di Google Chrome, asalkan Anda mengaktifkan tanda khusus untuknya. Produsen browser saat ini tidak terlalu menekankan API ini, karena direncanakan menjadi bagian dari standar ES7.
Dirancang pada awal 80-an oleh beberapa perwakilan industri musik, MIDI (singkatan dari Musical Instrument Digital Interface), adalah protokol komunikasi standar untuk perangkat musik elektronik. Meskipun protokol lain, seperti OSC, telah dikembangkan sejak saat itu; tiga puluh tahun kemudian, MIDI masih menjadi protokol komunikasi de-facto untuk produsen perangkat keras audio. Anda akan kesulitan menemukan produser musik modern yang tidak memiliki setidaknya satu perangkat MIDI di studionya.
Dengan pengembangan dan adopsi Web Audio API yang cepat, kami sekarang dapat mulai membangun aplikasi berbasis browser yang menjembatani kesenjangan antara cloud dan dunia fisik. Web MIDI API tidak hanya memungkinkan kita untuk membangun synthesizer dan efek audio, tetapi kita bahkan dapat mulai membangun DAW (Digital Audio Workstation) berbasis browser yang serupa dalam fitur dan kinerja dengan rekan-rekan mereka saat ini berbasis flash (lihat Audiotool, misalnya ).
Dalam tutorial MIDI ini, saya akan memandu Anda melalui dasar-dasar Web MIDI API, dan kami akan membuat monosynth sederhana yang dapat Anda mainkan dengan perangkat MIDI favorit Anda. Kode sumber lengkap tersedia di sini, dan Anda dapat menguji demo langsung secara langsung. Jika Anda tidak memiliki perangkat MIDI, Anda masih dapat mengikuti tutorial ini dengan memeriksa cabang 'keyboard' dari repositori GitHub, yang memungkinkan dukungan dasar untuk keyboard komputer Anda, sehingga Anda dapat memainkan nada dan mengubah oktaf. Ini juga merupakan versi yang tersedia sebagai demo langsung. Namun, karena keterbatasan perangkat keras komputer, kecepatan dan detune keduanya dinonaktifkan setiap kali Anda menggunakan keyboard komputer untuk mengontrol synthesizer. Silakan merujuk ke file readme di GitHub untuk membaca tentang pemetaan kunci/catatan.
Prasyarat Tutorial Midi
Anda akan memerlukan yang berikut ini untuk tutorial MIDI ini:
- Google Chrome (versi 38 atau lebih tinggi) dengan tanda
#enable-web-midi
diaktifkan - (Opsional) Perangkat MIDI, yang dapat memicu catatan, terhubung ke komputer Anda
Kami juga akan menggunakan Angular.js untuk membawa sedikit struktur ke aplikasi kami; oleh karena itu, pengetahuan dasar tentang kerangka kerja merupakan prasyarat.
Mulai
Kami akan memodulasi aplikasi MIDI kami dari awal dengan memisahkannya menjadi 3 modul:
- WebMIDI: menangani berbagai perangkat MIDI yang terhubung ke komputer Anda
- WebAudio: menyediakan sumber audio untuk synth kami
- WebSynth: menghubungkan antarmuka web ke mesin audio
Modul App
akan menangani interaksi pengguna dengan antarmuka pengguna web. Struktur aplikasi kita bisa terlihat seperti ini:
|- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html
Anda juga harus menginstal pustaka berikut untuk membantu Anda membangun aplikasi Anda: Angular.js, Bootstrap, dan jQuery. Mungkin cara termudah untuk menginstal ini adalah melalui Bower.
Modul WebMIDI: Terhubung dengan Dunia Nyata
Mari kita mulai mencari tahu cara menggunakan MIDI dengan menghubungkan perangkat MIDI kita ke aplikasi kita. Untuk melakukannya, kami akan membuat pabrik sederhana yang mengembalikan metode tunggal. Untuk terhubung ke perangkat MIDI kami melalui Web MIDI API, kami perlu memanggil metode 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 }; }]);
Dan itu cukup banyak!
Metode requestMIDIAccess
mengembalikan janji, jadi kita bisa mengembalikannya secara langsung dan menangani hasil janji di pengontrol aplikasi kita:
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); }); }]);
Seperti disebutkan, metode requestMIDIAccess
mengembalikan janji, meneruskan objek ke metode then
, dengan dua properti: input dan output.
Di Chrome versi sebelumnya, kedua properti ini adalah metode yang memungkinkan Anda mengambil larik perangkat input dan output secara langsung. Namun, dalam pembaruan terbaru, properti ini sekarang menjadi objek. Ini membuat perbedaan yang cukup besar, karena sekarang kita perlu memanggil metode values
pada objek input atau output untuk mengambil daftar perangkat yang sesuai. Metode ini bertindak sebagai fungsi generator, dan mengembalikan iterator. Sekali lagi, API ini dimaksudkan untuk menjadi bagian dari ES7; oleh karena itu, menerapkan perilaku seperti generator masuk akal, meskipun tidak semudah implementasi aslinya.
Terakhir, kita dapat mengambil jumlah perangkat melalui properti size
dari objek iterator. Jika ada setidaknya satu perangkat, kami hanya mengulangi hasil dengan memanggil metode next
dari objek iterator, dan mendorong setiap perangkat ke array yang ditentukan pada $scope. Di front-end, kita dapat menerapkan kotak pilih sederhana yang akan mencantumkan semua perangkat input yang tersedia dan membiarkan kita memilih perangkat mana yang ingin kita gunakan sebagai perangkat aktif untuk mengontrol synth web:
<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>
Kami mengikat kotak pilih ini ke variabel $scope yang disebut activeDevice
yang nantinya akan kami gunakan untuk menghubungkan perangkat aktif ini ke synth.
Modul WebAudio: Membuat Kebisingan
WebAudio API memungkinkan kita untuk tidak hanya memutar file suara, tetapi juga menghasilkan suara dengan membuat ulang komponen penting dari synthesizer seperti osilator, filter, dan node gain.
Buat Osilator
Peran osilator adalah untuk menghasilkan bentuk gelombang. Ada berbagai jenis bentuk gelombang, di antaranya empat yang didukung di API WebAudio: sinus, persegi, segitiga, dan gigi gergaji. Bentuk gelombang dikatakan "berosilasi" pada frekuensi tertentu, tetapi juga memungkinkan bagi seseorang untuk menentukan tabel gelombang kustom mereka sendiri jika diperlukan. Rentang frekuensi tertentu dapat didengar oleh manusia - mereka dikenal sebagai suara. Atau, ketika mereka berosilasi pada frekuensi rendah, osilator juga dapat membantu kami membangun LFO ("osilator frekuensi rendah") sehingga kami dapat memodulasi suara kami (tetapi itu di luar cakupan tutorial ini).
Hal pertama yang perlu kita lakukan untuk membuat beberapa suara adalah membuat instance AudioContext
baru :
function _createContext() { self.ctx = new $window.AudioContext(); }
Dari sana, kita dapat membuat instance komponen apa pun yang disediakan oleh WebAudio API. Karena kita mungkin membuat beberapa instance dari setiap komponen, masuk akal untuk membuat layanan agar dapat membuat instance baru dan unik dari komponen yang kita butuhkan. Mari kita mulai dengan membuat layanan untuk menghasilkan osilator baru:
angular .module('WebAudio', []) .service('OSC', function() { var self; function Oscillator(ctx) { self = this; self.osc = ctx.createOscillator(); return self; } });
Kami sekarang dapat membuat instance osilator baru sesuai keinginan kami, meneruskan sebagai argumen instance AudioContext yang kami buat sebelumnya. Untuk mempermudah, kami akan menambahkan beberapa metode pembungkus - gula sintaksis belaka - dan mengembalikan fungsi 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;
Buat Filter Multipass dan Kontrol Volume
Kami membutuhkan dua komponen lagi untuk melengkapi mesin audio dasar kami: filter multipass, untuk memberikan sedikit bentuk pada suara kami, dan node gain untuk mengontrol volume suara kami dan menghidupkan dan mematikan volume. Untuk melakukannya, kita dapat melanjutkan dengan cara yang sama seperti yang kita lakukan untuk osilator: create services yang mengembalikan fungsi dengan beberapa metode pembungkus. Yang perlu kita lakukan adalah menyediakan instance AudioContext dan memanggil metode yang sesuai.
Kami membuat filter dengan memanggil metode createBiquadFilter
dari instance AudioContext:
ctx.createBiquadFilter();
Demikian pula, untuk node gain, kami memanggil metode createGain
:
ctx.createGain();
Modul WebSynth: Menghubungkan Semuanya
Sekarang kita hampir siap untuk membangun antarmuka synth kita dan menghubungkan perangkat MIDI ke sumber audio kita. Pertama, kita perlu menghubungkan mesin audio kita bersama-sama dan menyiapkannya untuk menerima catatan MIDI. Untuk menghubungkan mesin audio, kita cukup membuat instance baru dari komponen yang kita butuhkan, dan kemudian "menghubungkan" mereka bersama-sama menggunakan metode connect
yang tersedia untuk setiap instance komponen. Metode connect
membutuhkan satu argumen, yang merupakan komponen yang ingin Anda hubungkan dengan instance saat ini. Dimungkinkan untuk mengatur rantai komponen yang lebih rumit karena metode connect
dapat menghubungkan satu node ke beberapa modulator (memungkinkan untuk mengimplementasikan hal-hal seperti cross-fading dan banyak lagi).
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); }
Kami baru saja membangun kabel internal mesin audio kami. Anda dapat bermain-main sedikit dan mencoba kombinasi kabel yang berbeda, tetapi ingat untuk mengecilkan volume agar tidak menjadi tuli. Sekarang kita dapat menghubungkan antarmuka MIDI ke aplikasi kita dan mengirim pesan MIDI ke mesin audio. Kami akan menyiapkan pengamat di kotak pilih perangkat untuk secara virtual "menyambungkannya" ke synth kami. Kami kemudian akan mendengarkan pesan MIDI yang datang dari perangkat, dan meneruskan informasi ke mesin audio:
// 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; }
Di sini, kita mendengarkan event MIDI dari perangkat, menganalisis data dari Object MidiEvent, dan meneruskannya ke metode yang sesuai; noteOn
atau noteOff
, berdasarkan kode acara (144 untuk noteOn, 128 untuk noteOff). Sekarang kita dapat menambahkan logika di masing-masing metode dalam modul audio untuk benar-benar menghasilkan suara:
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); } }
Beberapa hal sedang terjadi di sini. Dalam metode noteOn
, pertama-tama kita mendorong catatan saat ini ke array catatan. Meskipun kita sedang membangun sebuah monosynth (artinya kita hanya bisa memainkan satu not pada satu waktu), kita masih bisa memiliki beberapa jari sekaligus pada keyboard. Jadi, kita perlu mengantri semua not tesis agar ketika kita melepaskan satu not, not berikutnya dimainkan. Kami kemudian perlu menghentikan osilator untuk menetapkan frekuensi baru, yang kami konversi dari catatan MIDI (skala dari 0 hingga 127) ke nilai frekuensi aktual dengan sedikit matematika:
function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }
Dalam metode noteOff
, pertama-tama kita mulai dengan menemukan catatan dalam larik catatan aktif dan menghapusnya. Kemudian, jika itu adalah satu-satunya catatan dalam array, kami cukup mematikan volumenya.
Argumen kedua dari metode setVolume
adalah waktu transisi, artinya berapa lama waktu yang dibutuhkan untuk mencapai nilai volume baru. Dalam istilah musik, jika not aktif, itu akan setara dengan waktu serangan, dan jika not mati, itu setara dengan waktu rilis.

Modul WebAnalyser: Memvisualisasikan Suara kita
Fitur menarik lainnya yang dapat kami tambahkan ke synth kami adalah node penganalisis, yang memungkinkan kami menampilkan bentuk gelombang suara kami menggunakan kanvas untuk merendernya. Membuat node penganalisis sedikit lebih rumit daripada objek AudioContext lainnya, karena juga perlu membuat node scriptProcessor untuk benar-benar melakukan analisis. Kita mulai dengan memilih elemen kanvas di 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; }
Kemudian, kami menambahkan metode connect
, di mana kami akan membuat penganalisis dan pemroses skrip:
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); } };
Pertama, kita membuat objek scriptProcessor dan menghubungkannya ke tujuan. Kemudian, kami membuat penganalisis itu sendiri, yang kami masukkan dengan output audio dari osilator atau filter. Perhatikan bagaimana kita masih perlu menghubungkan output audio ke tujuan agar kita dapat mendengarnya! Kita juga perlu mendefinisikan warna gradien dari grafik kita - ini dilakukan dengan memanggil metode createLinearGradient
dari elemen kanvas.
Terakhir, scriptProcessor akan menjalankan event 'audioprocess' pada suatu interval; ketika peristiwa ini dipicu, kami menghitung frekuensi rata-rata yang ditangkap oleh penganalisis, menghapus kanvas, dan menggambar ulang grafik frekuensi baru dengan memanggil metode 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)); } }
Last but not least, kita perlu sedikit memodifikasi kabel mesin audio kita untuk mengakomodasi komponen baru ini:
// 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); }
Kami sekarang memiliki visualiser yang bagus yang memungkinkan kami untuk menampilkan bentuk gelombang dari synth kami secara real time! Ini melibatkan sedikit pekerjaan untuk penyiapan, tetapi ini sangat menarik dan berwawasan luas, terutama saat menggunakan filter.
Membangun Synth kami: Menambahkan Kecepatan & Detune
Pada titik ini dalam tutorial MIDI kami, kami memiliki synth yang cukup keren - tetapi memainkan setiap nada pada volume yang sama. Ini karena alih-alih menangani data kecepatan dengan benar, kami hanya mengatur volume ke nilai tetap 1,0. Mari kita mulai dengan memperbaikinya, dan kemudian kita akan melihat bagaimana kita dapat mengaktifkan roda detune yang Anda temukan di keyboard MIDI paling umum.
Mengaktifkan Kecepatan
Jika Anda tidak terbiasa dengannya, 'kecepatan' berhubungan dengan seberapa keras Anda menekan tombol pada keyboard Anda. Berdasarkan nilai ini, suara yang dihasilkan tampak lebih lembut atau lebih keras.
Dalam synth tutorial MIDI kami, kami dapat meniru perilaku ini hanya dengan bermain dengan volume node gain. Untuk melakukannya, pertama-tama kita perlu melakukan sedikit matematika untuk mengubah data MIDI menjadi nilai float antara 0,0 dan 1,0 untuk diteruskan ke node gain:
function _vtov (velocity) { return (velocity / 127).toFixed(2); }
Rentang kecepatan perangkat MIDI adalah dari 0 hingga 127, jadi kita cukup membagi nilai itu dengan 127 dan mengembalikan nilai float dengan dua desimal. Kemudian, kita dapat memperbarui metode _noteOn
untuk meneruskan nilai yang dihitung ke node gain:
self.amp.setVolume(_vtov(velocity), self.settings.attack);
Dan itu saja! Sekarang, ketika kami memainkan synth kami, kami akan melihat volume bervariasi berdasarkan seberapa keras kami menekan tombol pada keyboard kami.
Mengaktifkan Roda Detune pada Keyboard MIDI Anda
Kebanyakan keyboard MIDI memiliki roda detune; roda memungkinkan Anda untuk sedikit mengubah frekuensi nada yang sedang dimainkan, menciptakan efek menarik yang dikenal sebagai 'detune'. Ini cukup mudah diterapkan saat Anda mempelajari cara menggunakan MIDI, karena roda detune juga mengaktifkan peristiwa MidiMessage dengan kode peristiwanya sendiri (224), yang dapat kita dengarkan dan tindak lanjuti dengan menghitung ulang nilai frekuensi dan memperbarui osilator.
Pertama, kita perlu menangkap acara di synth kita. Untuk melakukannya, kami menambahkan case tambahan ke pernyataan switch yang kami buat di _onmidimessage
callback:
case 224: // the detune value is the third argument of the MidiEvent.data array Engine.detune(e.data[2]); break;
Kemudian, kami mendefinisikan metode detune
pada mesin audio:
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; } } }
Nilai detune default adalah 64, yang berarti tidak ada detune yang diterapkan, jadi dalam hal ini kita cukup meneruskan frekuensi arus ke osilator.
Terakhir, kita juga perlu memperbarui metode _noteOff
, untuk mempertimbangkan detune jika ada catatan lain yang diantrekan:
self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);
Membuat Antarmuka
Sejauh ini, kami hanya membuat kotak pilih untuk dapat memilih perangkat MIDI dan visualiser bentuk gelombang, tetapi kami tidak memiliki kemungkinan untuk mengubah suara secara langsung dengan berinteraksi dengan halaman web. Mari buat antarmuka yang sangat sederhana menggunakan elemen bentuk umum, dan ikat ke mesin audio kita.
Membuat Tata Letak untuk Antarmuka
Kami akan membuat berbagai elemen bentuk untuk mengontrol suara synth kami:
- Grup radio untuk memilih jenis osilator
- Kotak centang untuk mengaktifkan / menonaktifkan filter
- Grup radio untuk memilih jenis filter
- Dua rentang untuk mengontrol frekuensi dan resonansi filter
- Dua rentang untuk mengontrol serangan dan pelepasan node gain
Membuat dokumen HTML untuk antarmuka kita, kita akan berakhir dengan sesuatu seperti ini:
<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>
Mendekorasi antarmuka pengguna agar terlihat mewah bukanlah sesuatu yang akan saya bahas dalam tutorial MIDI dasar ini; sebagai gantinya kita dapat menyimpannya sebagai latihan untuk memoles antarmuka pengguna nanti, mungkin terlihat seperti ini:
Mengikat Antarmuka ke Mesin Audio
Kita harus menentukan beberapa metode untuk mengikat kontrol ini ke mesin audio kita.
Mengontrol Osilator
Untuk osilator, kami hanya membutuhkan metode yang memungkinkan kami untuk mengatur jenis osilator:
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }
Mengontrol Filter
Untuk filter, kita memerlukan tiga kontrol: satu untuk jenis filter, satu untuk frekuensi dan satu untuk resonansi. Kami juga dapat menghubungkan metode _connectFilter
dan _disconnectFilter
ke nilai kotak centang.
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; } }
Mengontrol Serangan dan Resonansi
Untuk membentuk suara kita sedikit, kita dapat mengubah parameter serangan dan pelepasan dari node gain. Kami membutuhkan dua metode untuk ini:
function _setAttack(a) { if(a) { self.settings.attack = a / 1000; } } function _setRelease(r) { if(r) { self.settings.release = r / 1000; } }
Menyiapkan Pengamat
Terakhir, di pengontrol aplikasi kita, kita hanya perlu menyiapkan beberapa pengamat dan mengikatnya ke berbagai metode yang baru saja kita buat:
$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);
Kesimpulan
Banyak konsep yang tercakup dalam tutorial MIDI ini; kebanyakan, kami menemukan cara menggunakan WebMIDI API, yang cukup tidak terdokumentasikan terlepas dari spesifikasi resmi dari W3C. Implementasi Google Chrome cukup mudah, meskipun peralihan ke objek iterator untuk perangkat input dan output memerlukan sedikit refactoring untuk kode lama menggunakan implementasi lama.
Adapun WebAudio API, ini adalah API yang sangat kaya, dan kami hanya membahas beberapa kemampuannya dalam tutorial ini. Berbeda dengan WebMIDI API, WebAudio API didokumentasikan dengan sangat baik, khususnya di Mozilla Developer Network. Jaringan Pengembang Mozilla berisi banyak sekali contoh kode dan daftar terperinci dari berbagai argumen dan peristiwa untuk setiap komponen, yang akan membantu Anda menerapkan aplikasi audio berbasis browser kustom Anda sendiri.
Karena kedua API terus berkembang, ini akan membuka beberapa kemungkinan yang sangat menarik bagi pengembang JavaScript; memungkinkan kami mengembangkan DAW berfitur lengkap, berbasis browser, yang akan mampu bersaing dengan Flash yang setara. Dan untuk pengembang desktop, Anda juga dapat mulai membuat aplikasi lintas platform Anda sendiri menggunakan alat seperti node-webkit. Mudah-mudahan, ini akan menelurkan generasi baru alat musik untuk audiophiles yang akan memberdayakan pengguna dengan menjembatani kesenjangan antara dunia fisik dan cloud.