ในขณะที่ Web Audio API กำลังได้รับความนิยมเพิ่มขึ้นโดยเฉพาะในกลุ่ม นักพัฒนาเกม HTML5 Web MIDI API ยังไม่ค่อยเป็นที่รู้จักในหมู่นักพัฒนาส่วนหน้า ส่วนใหญ่อาจเกี่ยวข้องกับการขาดการสนับสนุนและเอกสารที่สามารถเข้าถึงได้ในปัจจุบัน ขณะนี้ Web MIDI API ได้รับการสนับสนุนเฉพาะใน Google Chrome ซึ่งได้รับอนุญาตให้คุณเปิดใช้งานการตั้งค่าสถานะพิเศษสำหรับมัน ปัจจุบันผู้ผลิตเบราว์เซอร์ให้ความสำคัญกับ API นี้เพียงเล็กน้อยเนื่องจากมีแผนจะเป็นส่วนหนึ่งของมาตรฐาน ES7
MIDI (ย่อมาจาก Musical Instrument Digital Interface) ออกแบบในช่วงต้นทศวรรษ 80 เป็นโปรโตคอลการสื่อสารมาตรฐานสำหรับอุปกรณ์ดนตรีอิเล็กทรอนิกส์ แม้ว่าโปรโตคอลอื่น ๆ เช่น OSC จะได้รับการพัฒนาตั้งแต่นั้นมา สามสิบปีต่อมา MIDI ยังคงเป็นโปรโตคอลการสื่อสารโดยพฤตินัยสำหรับผู้ผลิตฮาร์ดแวร์เสียง คุณจะต้องลำบากใจในการค้นหาผู้ผลิตเพลงสมัยใหม่ที่ไม่มีอุปกรณ์ MIDI อย่างน้อยหนึ่งเครื่องในสตูดิโอของเขา
ด้วยการพัฒนาที่รวดเร็วและการนำ Web Audio API มาใช้ตอนนี้เราสามารถเริ่มสร้างแอปพลิเคชันบนเบราว์เซอร์ที่เชื่อมช่องว่างระหว่างระบบคลาวด์และโลกทางกายภาพ Web MIDI API ไม่เพียง แต่ช่วยให้เราสร้างซินธิไซเซอร์และเอฟเฟกต์เสียงเท่านั้น แต่เรายังสามารถเริ่มสร้าง DAW (Digital Audio Workstation) บนเบราว์เซอร์ที่มีคุณสมบัติและประสิทธิภาพคล้ายกับคู่ที่ใช้แฟลชในปัจจุบัน (ดู Audiotool , ตัวอย่างเช่น).
ในบทช่วยสอน MIDI นี้ฉันจะแนะนำคุณเกี่ยวกับพื้นฐานของ Web MIDI API และเราจะสร้าง monosynth ง่ายๆที่คุณจะสามารถเล่นกับอุปกรณ์ MIDI ที่คุณชื่นชอบได้ มีซอร์สโค้ดแบบเต็ม ที่นี่ และคุณสามารถทดสอบไฟล์ การสาธิตสด โดยตรง. หากคุณไม่ได้เป็นเจ้าของอุปกรณ์ MIDI คุณยังสามารถทำตามบทแนะนำนี้ได้โดยดูที่สาขา 'แป้นพิมพ์' ของ ที่เก็บ GitHub ซึ่งเปิดใช้งานการสนับสนุนพื้นฐานสำหรับแป้นพิมพ์คอมพิวเตอร์ของคุณคุณจึงสามารถเล่นโน้ตและเปลี่ยนอ็อกเทฟได้ นอกจากนี้ยังเป็นเวอร์ชันที่พร้อมใช้งานในรูปแบบการสาธิตสด อย่างไรก็ตามเนื่องจากข้อ จำกัด ของฮาร์ดแวร์คอมพิวเตอร์ความเร็วและการตรวจจับทั้งสองอย่างถูกปิดใช้งานทุกครั้งที่คุณใช้แป้นพิมพ์คอมพิวเตอร์เพื่อควบคุมซินธิไซเซอร์ โปรดดูไฟล์ readme บน GitHub เพื่ออ่านเกี่ยวกับการแมปคีย์ / โน้ต
คุณจะต้องมีสิ่งต่อไปนี้สำหรับบทช่วยสอน MIDI นี้:
#enable-web-midi
เปิดใช้งานแฟล็กนอกจากนี้เรายังจะใช้ Angular.js เพื่อนำโครงสร้างเล็กน้อยมาสู่แอปพลิเคชันของเรา ดังนั้นความรู้พื้นฐานของกรอบจึงเป็นสิ่งที่จำเป็นต้องมี
เราจะโมดูลาร์แอปพลิเคชัน MIDI ของเราตั้งแต่ต้นโดยแยกออกเป็น 3 โมดูล:
อัน App
โมดูลจะจัดการการโต้ตอบของผู้ใช้กับอินเทอร์เฟซผู้ใช้บนเว็บ โครงสร้างแอปพลิเคชันของเราอาจมีลักษณะดังนี้:
|- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html
คุณควรติดตั้งไลบรารีต่อไปนี้เพื่อช่วยในการสร้างแอปพลิเคชันของคุณ: Angular.js , Bootstrap และ jQuery . วิธีที่ง่ายที่สุดในการติดตั้งคือผ่าน โบเวอร์ .
มาเริ่มหาวิธีใช้ 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
คุณสมบัติของอ็อบเจ็กต์ iterator หากมีอุปกรณ์อย่างน้อยหนึ่งเครื่องเราจะทำซ้ำผลลัพธ์โดยเรียก next
เมธอดของอ็อบเจ็กต์ตัววนซ้ำและผลักแต่ละอุปกรณ์ไปยังอาร์เรย์ที่กำหนดไว้ในขอบเขต $ ในส่วนหน้าเราสามารถใช้กล่องเลือกง่ายๆซึ่งจะแสดงรายการอุปกรณ์อินพุตที่มีอยู่ทั้งหมดและให้เราเลือกอุปกรณ์ที่เราต้องการใช้เป็นอุปกรณ์ที่ใช้งานเพื่อควบคุมการสังเคราะห์เว็บ:
Choose a MIDI device...
เราผูกกล่องเลือกนี้ไว้กับตัวแปร $ ขอบเขตที่เรียกว่า activeDevice
ซึ่งต่อไปเราจะใช้เพื่อเชื่อมต่ออุปกรณ์ที่ใช้งานนี้กับ synth
WebAudio API ช่วยให้เราไม่เพียง แต่เล่นไฟล์เสียง แต่ยังสร้างเสียงด้วยการสร้างส่วนประกอบที่สำคัญของซินธิไซเซอร์เช่นออสซิลเลเตอร์ฟิลเตอร์และรับโหนด ท่ามกลางคนอื่น ๆ .
บทบาทของออสซิลเลเตอร์คือการส่งออกรูปคลื่น มีรูปแบบของคลื่นหลายประเภทซึ่งมีสี่รูปแบบที่รองรับใน 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 และเรียกใช้วิธีการที่เหมาะสม
เราสร้างตัวกรองโดยเรียก createBiquadFilter
วิธีการของอินสแตนซ์ AudioContext:
ctx.createBiquadFilter();
ในทำนองเดียวกันสำหรับโหนดกำไรเราเรียก createGain
วิธี:
ctx.createGain();
ตอนนี้เราเกือบพร้อมที่จะสร้างอินเทอร์เฟซ Synth และเชื่อมต่ออุปกรณ์ MIDI กับแหล่งเสียงของเรา ขั้นแรกเราต้องเชื่อมต่อเอ็นจิ้นเสียงของเราเข้าด้วยกันและเตรียมพร้อมที่จะรับโน้ต MIDI ในการเชื่อมต่อเอ็นจิ้นเสียงเราเพียงแค่สร้างอินสแตนซ์ใหม่ของส่วนประกอบที่เราต้องการจากนั้น 'เชื่อมต่อ' เข้าด้วยกันโดยใช้ connect
วิธีที่ใช้ได้สำหรับแต่ละอินสแตนซ์ของคอมโพเนนต์ connect
method ใช้อาร์กิวเมนต์เดียวซึ่งเป็นเพียงส่วนประกอบที่คุณต้องการเชื่อมต่ออินสแตนซ์ปัจจุบัน เป็นไปได้ที่จะจัดเรียงห่วงโซ่ส่วนประกอบที่ซับซ้อนมากขึ้นเป็น connect
เมธอดสามารถเชื่อมต่อโหนดหนึ่งเข้ากับโมดูเลเตอร์หลายตัวได้ (ทำให้สามารถใช้งานสิ่งต่างๆเช่น cross-fading และอื่น ๆ )
แผนภูมิเปรียบเทียบซอฟต์แวร์การจัดการโครงการ
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 ไปยังเครื่องเสียง เราจะตั้งค่าผู้เฝ้าดูในกล่องเลือกอุปกรณ์เพื่อ 'เสียบ' เข้ากับ synth ของเรา จากนั้นเราจะฟังข้อความ 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 Object และส่งต่อไปยังวิธีการที่เหมาะสม อย่างใดอย่างหนึ่ง 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
วิธีการแรกเราดันบันทึกปัจจุบันไปยังอาร์เรย์ของบันทึกย่อ แม้ว่าเราจะสร้าง monosynth (หมายความว่าเราสามารถเล่นได้ทีละโน้ตเท่านั้น) เรายังสามารถมีหลายนิ้วพร้อมกันบนคีย์บอร์ดได้ ดังนั้นเราจำเป็นต้องจัดคิวโน้ตทั้งหมดเพื่อที่เมื่อเราปล่อยโน้ตหนึ่งตัวโน้ตตัวถัดไปจะเล่น จากนั้นเราต้องหยุดออสซิลเลเตอร์เพื่อกำหนดความถี่ใหม่ซึ่งเราแปลงจากโน้ต MIDI (มาตราส่วนจาก 0 ถึง 127) เป็นค่าความถี่จริงด้วย คณิตศาสตร์เล็กน้อย :
function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }
ใน noteOff
วิธีแรกเราเริ่มต้นด้วยการค้นหาโน้ตในอาร์เรย์ของบันทึกย่อที่ใช้งานอยู่และลบออก จากนั้นหากเป็นโน้ตตัวเดียวในอาร์เรย์เราก็แค่ปิดเสียง
อาร์กิวเมนต์ที่สองของ setVolume
วิธีการคือเวลาในการเปลี่ยนแปลงซึ่งหมายถึงระยะเวลาที่ได้รับในการเข้าถึงค่าปริมาตรใหม่ ในแง่ดนตรีถ้าโน้ตเปิดอยู่มันจะเทียบเท่ากับเวลาในการโจมตีและถ้าโน้ตปิดอยู่มันจะเทียบเท่ากับเวลารีลีส
คุณสมบัติที่น่าสนใจอีกอย่างที่เราสามารถเพิ่มให้กับ synth ของเราคือโหนดตัววิเคราะห์ซึ่งช่วยให้เราสามารถแสดงรูปคลื่นของเสียงของเราโดยใช้ผ้าใบในการเรนเดอร์ การสร้างโหนดตัววิเคราะห์มีความซับซ้อนกว่าออบเจ็กต์ AudioContext อื่น ๆ เล็กน้อยเนื่องจากต้องสร้างโหนด scriptProcessor เพื่อทำการวิเคราะห์จริง เราเริ่มต้นด้วยการเลือกองค์ประกอบผ้าใบบน DOM:
function Analyser(canvas) null; self.view = self.canvas[0].getContext('2d')
จากนั้นเราเพิ่ม 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); }
ตอนนี้เรามี Visualiser ที่ดีซึ่งช่วยให้เราสามารถแสดงรูปคลื่นของ synth ของเราแบบเรียลไทม์! สิ่งนี้เกี่ยวข้องกับการตั้งค่าเล็กน้อย แต่น่าสนใจและเป็นข้อมูลเชิงลึกมากโดยเฉพาะเมื่อใช้ตัวกรอง
ณ จุดนี้ในบทช่วยสอน MIDI ของเราเรามี synth ที่ยอดเยี่ยม แต่มันเล่นทุกโน้ตในระดับเสียงเดียวกัน เนื่องจากแทนที่จะจัดการข้อมูลความเร็วอย่างถูกต้องเราเพียงแค่ตั้งค่าระดับเสียงเป็นค่าคงที่ที่ 1.0 เริ่มต้นด้วยการแก้ไขจากนั้นเราจะดูว่าเราจะเปิดใช้งานวงล้อ detune ที่คุณพบในคีย์บอร์ด 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);
เท่านี้ก็เรียบร้อย! ตอนนี้เมื่อเราเล่น Synth ของเราเราจะสังเกตได้ว่าปริมาณจะแตกต่างกันไปขึ้นอยู่กับว่าเรากดปุ่มบนคีย์บอร์ดของเราหนักแค่ไหน
แป้นพิมพ์ MIDI ส่วนใหญ่มีล้อเลื่อน วงล้อช่วยให้คุณปรับเปลี่ยนความถี่ของโน้ตที่กำลังเล่นอยู่ได้เล็กน้อยสร้างเอฟเฟกต์ที่น่าสนใจที่เรียกว่า 'detune' นี่เป็นเรื่องง่ายที่จะนำไปใช้เมื่อคุณเรียนรู้วิธีใช้ MIDI เนื่องจากวงล้อ detune จะยิงเหตุการณ์ MidiMessage ด้วยรหัสเหตุการณ์ของมันเอง (224) ซึ่งเราสามารถฟังและดำเนินการได้โดยการคำนวณค่าความถี่ใหม่และอัปเดตออสซิลเลเตอร์
ขั้นแรกเราต้องจับเหตุการณ์ใน synth ของเรา ในการทำเช่นนั้นเราได้เพิ่มกรณีพิเศษในคำสั่ง 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; } } }
ค่า detune เริ่มต้นคือ 64 ซึ่งหมายความว่าไม่มีการใช้ detune ดังนั้นในกรณีนี้เราเพียงแค่ส่งความถี่ปัจจุบันไปยัง oscillator
สุดท้ายเราต้องอัปเดต _noteOff
ด้วย วิธีการพิจารณา detune ในกรณีที่บันทึกอื่นอยู่ในคิว:
self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);
จนถึงตอนนี้เราสร้างเฉพาะกล่องเลือกเพื่อให้สามารถเลือกอุปกรณ์ MIDI ของเราและตัวแสดงรูปคลื่นได้ แต่เราไม่มีความเป็นไปได้ที่จะปรับเปลี่ยนเสียงโดยตรงโดยการโต้ตอบกับหน้าเว็บ มาสร้างอินเทอร์เฟซที่เรียบง่ายโดยใช้องค์ประกอบแบบฟอร์มทั่วไปและเชื่อมโยงเข้ากับเครื่องมือเสียงของเรา
เราจะสร้างองค์ประกอบฟอร์มต่างๆเพื่อควบคุมเสียงของซินธ์ของเรา:
การสร้างเอกสาร HTML สำหรับอินเทอร์เฟซของเราเราควรจะจบลงด้วยสิ่งนี้:
Choose a MIDI device... Oscillator
Oscillator Type
{{t}} Filter
enable filter Filter Type
{{t}} filter frequency: filter resonance: Analyser attack: release:
การตกแต่งอินเทอร์เฟซผู้ใช้ให้ดูหรูหราไม่ใช่สิ่งที่ฉันจะกล่าวถึงในบทช่วยสอน MIDI พื้นฐานนี้ แต่เราสามารถบันทึกเป็นแบบฝึกหัดเพื่อขัดอินเทอร์เฟซผู้ใช้ในภายหลังได้ซึ่งอาจมีลักษณะดังนี้:
เราควรกำหนดวิธีการสองสามวิธีเพื่อผูกการควบคุมเหล่านี้เข้ากับเอ็นจินเสียงของเรา
สำหรับออสซิลเลเตอร์เราต้องการเพียงวิธีที่อนุญาตให้เราตั้งค่าประเภทออสซิลเลเตอร์:
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }
สำหรับตัวกรองเราต้องการตัวควบคุมสามตัว: ตัวหนึ่งสำหรับประเภทตัวกรองหนึ่งตัวสำหรับความถี่และอีกตัวหนึ่งสำหรับการสั่นพ้อง เรายังสามารถเชื่อมต่อ _connectFilter
และ _disconnectFilter
วิธีการค่าของช่องทำเครื่องหมาย
โครงการเว็บเซิร์ฟเวอร์ raspberry pi
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 ค่อนข้างตรงไปตรงมาแม้ว่าการเปลี่ยนไปใช้วัตถุตัววนซ้ำสำหรับอุปกรณ์อินพุตและเอาต์พุตจะต้องมีการปรับโครงสร้างใหม่สำหรับรหัสเดิมโดยใช้การใช้งานแบบเก่า
สำหรับ WebAudio API นี่เป็น API ที่สมบูรณ์มากและเราได้กล่าวถึงความสามารถเพียงไม่กี่อย่างในบทช่วยสอนนี้ ซึ่งแตกต่างจาก WebMIDI API WebAudio API ได้รับการบันทึกไว้เป็นอย่างดีโดยเฉพาะใน Mozilla Developer Network Mozilla Developer Network มีตัวอย่างโค้ดมากมายและรายการโดยละเอียดของอาร์กิวเมนต์และเหตุการณ์ต่างๆสำหรับแต่ละองค์ประกอบซึ่งจะช่วยให้คุณใช้งานแอปพลิเคชันเสียงบนเบราว์เซอร์ที่คุณกำหนดเองได้
เนื่องจาก API ทั้งสองเติบโตขึ้นอย่างต่อเนื่องจะเปิดโอกาสที่น่าสนใจให้กับนักพัฒนา JavaScript ช่วยให้เราสามารถพัฒนา DAW ที่มีคุณสมบัติครบถ้วนซึ่งใช้เบราว์เซอร์ซึ่งจะสามารถแข่งขันกับ Flash ที่เทียบเท่าได้ และสำหรับนักพัฒนาเดสก์ท็อปคุณยังสามารถเริ่มสร้างแอปพลิเคชันข้ามแพลตฟอร์มของคุณเองโดยใช้เครื่องมือต่างๆเช่น โหนด webkit . หวังว่าสิ่งนี้จะทำให้เกิดคนรุ่นใหม่ เครื่องมือดนตรีสำหรับออดิโอไฟล์ ซึ่งจะช่วยให้ผู้ใช้สามารถเชื่อมช่องว่างระหว่างโลกทางกายภาพและระบบคลาวด์