กรณีศึกษา - JAM with Chrome

วิธีที่เราทำให้เสียงเจ๋ง

บทนำ

JAM with Chrome เป็นโปรเจ็กต์ดนตรีบนเว็บที่ Google สร้างขึ้น JAM with Chrome ช่วยให้ผู้คนจากทั่วโลกสามารถรวมกันเป็นวงดนตรีและเล่นดนตรีร่วมกันแบบเรียลไทม์ในเบราว์เซอร์ เราที่ DinahMoe รู้สึกยินดีอย่างยิ่งที่ได้เป็นส่วนหนึ่งของโปรเจ็กต์นี้ หน้าที่ของเราคือผลิตเพลงสำหรับแอปพลิเคชัน รวมถึงออกแบบและพัฒนาคอมโพเนนต์เพลง การพัฒนานี้ประกอบด้วย 3 หัวข้อหลัก ได้แก่ "เวิร์กสเตชันสำหรับทำเพลง" ซึ่งรวมถึงการเล่น MIDI, โปรแกรมซาวด์แซมเปิล, เอฟเฟกต์เสียง, การกำหนดเส้นทาง และการมิกซ์เพลง เครื่องมือทางตรรกะสำหรับควบคุมเพลงแบบอินเทอร์แอกทีฟแบบเรียลไทม์ และคอมโพเนนต์การซิงค์ที่ช่วยให้ผู้เล่นทุกคนในเซสชันได้ยินเพลงพร้อมกัน ซึ่งเป็นข้อกําหนดเบื้องต้นในการเล่นร่วมกัน

เราเลือกใช้ Web Audio API เพื่อให้ได้ระดับความน่าเชื่อถือ ความแม่นยำ และคุณภาพเสียงสูงสุด กรณีศึกษานี้จะกล่าวถึงความท้าทายบางอย่างที่เราพบและวิธีที่เราแก้ปัญหา บทความแนะนำที่ยอดเยี่ยมจำนวนหนึ่งเกี่ยวกับ Web Audio นั้นมีอยู่แล้วที่ HTML5Rocks เราจึงจะข้ามไปดูเรื่องขั้นสูงเลย

การเขียนเอฟเฟกต์เสียงที่กำหนดเอง

Web Audio API มีเอฟเฟกต์ที่มีประโยชน์หลายรายการรวมอยู่ในข้อกำหนดเฉพาะ แต่เราต้องการเอฟเฟกต์ที่ซับซ้อนมากขึ้นสำหรับเครื่องดนตรีใน JAM with Chrome ตัวอย่างเช่น มีโหนดการหน่วงเวลาแบบเนทีฟใน Web Audio แต่การหน่วงเวลามีหลายประเภท เช่น การหน่วงเวลาสเตอริโอ การหน่วงเวลาแบบปิงปอง การหน่วงเวลาแบบสแลบแบ็ก และอื่นๆ อีกมากมาย แต่โชคดีที่สามารถสร้างเอฟเฟกต์เหล่านี้ทั้งหมดใน Web Audio ได้โดยใช้โหนดเอฟเฟกต์แบบเนทีฟและจินตนาการ

เนื่องจากเราต้องการใช้โหนดเนทีฟและเอฟเฟกต์ที่กำหนดเองของเราในลักษณะที่โปร่งใสมากที่สุด เราจึงตัดสินใจว่าจำเป็นต้องสร้างรูปแบบ Wrapper ที่ทำได้ นอตแบบเนทีฟใน Web Audio ใช้เมธอด connect เพื่อลิงก์นอตเข้าด้วยกัน เราจึงต้องจำลองลักษณะการทำงานนี้ แนวคิดพื้นฐานมีดังนี้

var MyCustomNode = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    this.connect = function(target){
       output.connect(target);
    };
};

รูปแบบนี้ทำให้เราอยู่ใกล้กับโหนดเนทีฟมาก มาดูวิธีใช้กัน

//create a couple of native nodes and our custom node
var gain = audioContext.createGain(),
    customNode = new MyCustomNode(),
    anotherGain = audioContext.createGain();

//connect our custom node to the native nodes and send to the output
gain.connect(customNode.input);
customNode.connect(anotherGain);
anotherGain.connect(audioContext.destination);
การกำหนดเส้นทางโหนดที่กำหนดเอง

ความแตกต่างเพียงอย่างเดียวระหว่างโหนดที่กําหนดเองกับโหนดที่มาพร้อมระบบคือเราต้องเชื่อมต่อกับพร็อพเพอร์ตี้อินพุตของโหนดที่กําหนดเอง เราทราบดีว่าอาจมีวิธีหลีกเลี่ยงปัญหานี้ แต่วิธีนี้ก็เพียงพอสำหรับวัตถุประสงค์ของเรา รูปแบบนี้สามารถพัฒนาเพิ่มเติมเพื่อจำลองวิธีการยกเลิกการเชื่อมต่อของ AudioNodes เดิม รวมถึงรองรับอินพุต/เอาต์พุตที่ผู้ใช้กำหนดเมื่อเชื่อมต่อ และอื่นๆ โปรดดูข้อกําหนดเพื่อดูว่าโหนดแบบเนทีฟทําสิ่งใดได้บ้าง

ตอนนี้เรามีรูปแบบพื้นฐานในการสร้างเอฟเฟกต์ที่กำหนดเองแล้ว ขั้นตอนถัดไปคือการทำให้โหนดที่กำหนดเองมีลักษณะการทำงานที่กำหนดเอง มาดูโหนดดีเลย์แบบตบกลับกัน

ตอบกลับอย่างจริงจัง

ดีเลย์แบบสแลบแบ็ก (บางครั้งเรียกว่าเสียงสะท้อนแบบสแลบแบ็ก) เป็นเอฟเฟกต์คลาสสิกที่ใช้กับเครื่องดนตรีหลายชนิด ตั้งแต่เสียงร้องสไตล์ยุค 50 ไปจนถึงกีตาร์แนวเซอร์ฟ เอฟเฟกต์นี้จะรับเสียงที่เข้ามาและเล่นสำเนาของเสียงนั้นโดยมีความล่าช้าเล็กน้อยประมาณ 75-250 มิลลิวินาที ซึ่งให้ความรู้สึกเหมือนเสียงถูกตบกลับ จึงมีชื่อเช่นนั้น เราสามารถสร้างเอฟเฟกต์ได้ดังนี้

var SlapbackDelayNode = function(){
    //create the nodes we'll use
    this.input = audioContext.createGain();
    var output = audioContext.createGain(),
        delay = audioContext.createDelay(),
        feedback = audioContext.createGain(),
        wetLevel = audioContext.createGain();

    //set some decent values
    delay.delayTime.value = 0.15; //150 ms delay
    feedback.gain.value = 0.25;
    wetLevel.gain.value = 0.25;

    //set up the routing
    this.input.connect(delay);
    this.input.connect(output);
    delay.connect(feedback);
    delay.connect(wetLevel);
    feedback.connect(delay);
    wetLevel.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};
การกำหนดเส้นทางภายในของโหนด Slapback

ดังที่บางท่านอาจทราบแล้ว ดีเลย์นี้สามารถใช้กับเวลาหน่วงที่นานขึ้นได้ด้วย ซึ่งจะกลายเป็นดีเลย์โมโนแบบปกติที่มีเสียงสะท้อน ต่อไปนี้เป็นตัวอย่างการใช้การเลื่อนเวลานี้เพื่อให้คุณได้ยินเสียง

การกำหนดเส้นทางเสียง

เมื่อทำงานกับเครื่องดนตรีและส่วนต่างๆ ของเพลงในแอปพลิเคชันเสียงระดับมืออาชีพ คุณต้องมีระบบการกำหนดเส้นทางที่ยืดหยุ่นซึ่งช่วยให้คุณผสมและปรับแต่งเสียงได้อย่างมีประสิทธิภาพ ใน JAM with Chrome เราได้พัฒนาระบบบัสเสียงที่คล้ายกับระบบที่มีอยู่ในบอร์ดมิกซ์จริง วิธีนี้ช่วยให้เราเชื่อมต่อเครื่องดนตรีทั้งหมดที่ต้องการเอฟเฟกต์เสียงสะท้อนกับบัสหรือแชแนลทั่วไป แล้วเพิ่มเสียงสะท้อนลงในบัสนั้นแทนที่จะเพิ่มเสียงสะท้อนลงในเครื่องดนตรีแต่ละเครื่องแยกกัน นี่เป็นการเพิ่มประสิทธิภาพที่สำคัญและเราขอแนะนำให้ทำสิ่งต่อไปนี้ทันทีที่คุณเริ่มทำแอปพลิเคชันที่ซับซ้อนมากขึ้น

การกำหนดเส้นทางของ AudioBus

โชคดีที่การดำเนินการนี้ทำได้ง่ายมากใน Web Audio โดยพื้นฐานแล้ว เราสามารถใช้โครงกระดูกที่เรากำหนดไว้สำหรับเอฟเฟกต์และใช้โครงกระดูกนั้นในลักษณะเดียวกัน

var AudioBus = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    //create effect nodes (Convolver and Equalizer are other custom effects from the library presented at the end of the article)
    var delay = new SlapbackDelayNode(),
        convolver = new tuna.Convolver(),
        equalizer = new tuna.Equalizer();

    //route 'em
    //equalizer -> delay -> convolver
    this.input.connect(equalizer);
    equalizer.connect(delay.input);
    delay.connect(convolver);
    convolver.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};

การใช้งานจะมีลักษณะดังนี้

//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
    instrument1 = audioContext.createOscillator(),
    instrument2 = audioContext.createOscillator(),
    instrument3 = audioContext.createOscillator();

//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);

และเราก็ได้ใส่เอฟเฟกต์ดีเลย์ อีควอไลเซอร์ และรีเวิร์บ (ซึ่งเป็นเอฟเฟกต์ที่ค่อนข้างแพงในแง่ประสิทธิภาพ) ในราคาเพียงครึ่งเดียวเมื่อเทียบกับการใช้เอฟเฟกต์กับเครื่องดนตรีแต่ละชิ้นแยกกัน หากต้องการเพิ่มลูกเล่นให้กับบัส เราอาจเพิ่มโหนดการเพิ่มใหม่ 2 โหนด ได้แก่ preGain และ postGain ซึ่งจะช่วยให้เราปิดหรือเลือนเสียงในบัสได้ 2 วิธี โดยใส่ preGain ไว้ก่อนเอฟเฟกต์ และใส่ postGain ไว้ที่ท้ายเชน หากเราค่อยๆ ลดระดับของ preGain เสียงเอฟเฟกต์จะยังคงดังอยู่หลังจากที่ Gain ลดลงจนสุด แต่หากเราค่อยๆ ลดระดับของ postGain เสียงทั้งหมดจะปิดลงพร้อมกัน

ขั้นตอนถัดไป

วิธีการเหล่านี้ที่เราอธิบายไว้ที่นี่สามารถและควรได้รับการพัฒนาเพิ่มเติม สิ่งต่างๆ เช่น อินพุตและเอาต์พุตของโหนดที่กำหนดเอง และเมธอดเชื่อมต่อ ควร/อาจใช้การสืบทอดตามต้นแบบ โดยบัสควรสร้างเอฟเฟกต์แบบไดนามิกได้ด้วยการรับรายการเอฟเฟกต์

เพื่อเป็นการฉลองการเปิดตัว JAM กับ Chrome เราจึงตัดสินใจที่จะทําให้เฟรมเวิร์กของเอฟเฟกต์เป็นแบบโอเพนซอร์ส หากสนใจ โปรดดูและมีส่วนร่วม ขณะนี้มีการประชุมกันที่นี่เกี่ยวกับการกำหนดรูปแบบมาตรฐานสำหรับเอนทิตีเสียงบนเว็บที่กำหนดเอง มาร่วมกับเรา