เริ่มต้นใช้งาน Web Audio API

ก่อนที่จะมีองค์ประกอบ <audio> ของ HTML5 จำเป็นต้องใช้ Flash หรือปลั๊กอินอื่น เพื่อทำลายความเงียบของเว็บ แม้ว่าเสียงในเว็บจะไม่ต้องใช้ปลั๊กอินอีกต่อไป แต่แท็กเสียงจะมีข้อจำกัดที่สำคัญในการนำเกมที่ซับซ้อนและแอปพลิเคชันแบบอินเทอร์แอกทีฟมาใช้

Web Audio API คือ JavaScript API ระดับสูงสำหรับการประมวลผลและสังเคราะห์เสียงในเว็บแอปพลิเคชัน เป้าหมายของ API นี้คือต้องรวมความสามารถต่างๆ ที่พบในเครื่องมือเสียงเกมสมัยใหม่ รวมถึงงานด้านการมิกซ์ การประมวลผล และการกรองบางอย่างที่พบในแอปพลิเคชันการผลิตเสียงบนเดสก์ท็อปสมัยใหม่ สิ่งที่ตามมาคือข้อมูลเบื้องต้นเกี่ยวกับ การใช้ API ที่มีประสิทธิภาพนี้

การเริ่มต้นใช้งาน AudioContext

AudioContext มีไว้เพื่อจัดการและเล่นเสียงทั้งหมด หากต้องการสร้างเสียงโดยใช้ Web Audio API ให้สร้างแหล่งที่มาของเสียงอย่างน้อย 1 แหล่ง แล้วเชื่อมต่อกับปลายทางของเสียงที่ได้จากอินสแตนซ์ AudioContext การเชื่อมต่อนี้ไม่จำเป็นต้องเป็นแบบโดยตรง และสามารถดำเนินการผ่าน AudioNodes ตัวกลางจำนวนเท่าใดก็ได้ ซึ่งทำหน้าที่เป็นโมดูลการประมวลผลสำหรับสัญญาณเสียง การกำหนดเส้นทางนี้อธิบายไว้อย่างละเอียดในข้อกำหนดของ Web Audio

อินสแตนซ์เดียวของ AudioContext รองรับอินพุตเสียงและกราฟเสียงที่ซับซ้อนได้หลายรายการ เราจึงต้องใช้เพียง 1 รายการเท่านั้นสำหรับแอปพลิเคชันเสียงแต่ละรายการที่เราสร้างขึ้น

ข้อมูลโค้ดต่อไปนี้สร้าง AudioContext:

var context;
window.addEventListener('load', init, false);
function init() {
    try {
    context = new AudioContext();
    }
    catch(e) {
    alert('Web Audio API is not supported in this browser');
    }
}

สำหรับเบราว์เซอร์ WebKit รุ่นเก่า ให้ใช้คำนำหน้า webkit เช่นเดียวกับ webkitAudioContext

ฟังก์ชันการทำงานของ Web Audio API ที่น่าสนใจมากมาย เช่น การสร้าง AudioNodes และการถอดรหัสข้อมูลไฟล์เสียงคือเมธอดของ AudioContext

กำลังโหลดเสียง

Web Audio API ใช้ AudioBuffer สำหรับเสียงแบบสั้นถึงปานกลาง วิธีพื้นฐานคือการใช้ XMLHttpRequest สำหรับการดึงไฟล์เสียง

API รองรับการโหลดข้อมูลไฟล์เสียงในหลายรูปแบบ เช่น WAV, MP3, AAC, OGG และรูปแบบอื่นๆ เบราว์เซอร์รองรับเสียงรูปแบบต่างๆ แตกต่างกันไป

ข้อมูลโค้ดต่อไปนี้แสดงการโหลดตัวอย่างเสียง

var dogBarkingBuffer = null;
var context = new AudioContext();

function loadDogSound(url) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    // Decode asynchronously
    request.onload = function() {
    context.decodeAudioData(request.response, function(buffer) {
        dogBarkingBuffer = buffer;
    }, onError);
    }
    request.send();
}

ข้อมูลไฟล์เสียงเป็นไบนารี (ไม่ใช่ข้อความ) เราจึงตั้งค่า responseType ของคำขอเป็น 'arraybuffer' ดูข้อมูลเพิ่มเติมเกี่ยวกับ ArrayBuffers ได้จากบทความเกี่ยวกับ XHR2

เมื่อได้รับข้อมูลไฟล์เสียง (ที่ไม่ได้เข้ารหัส) แล้ว คุณจะเก็บข้อมูลไว้เพื่อถอดรหัสในภายหลัง หรือถอดรหัสทันทีโดยใช้เมธอด decodeAudioData() AudioContext ก็ได้ เมธอดนี้จะนำ ArrayBuffer ของข้อมูลไฟล์เสียงที่จัดเก็บไว้ใน request.response และถอดรหัสแบบไม่พร้อมกัน (ไม่บล็อกเทรดการดำเนินการ JavaScript หลัก)

เมื่อเล่นเสร็จแล้ว decodeAudioData() จะเรียกฟังก์ชันเรียกกลับซึ่งให้ข้อมูลเสียง PCM ที่ถอดรหัสแล้วเป็น AudioBuffer

กำลังเล่นเสียง

กราฟเสียงแบบง่าย
กราฟเสียงแบบง่าย

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

var context = new AudioContext();

function playSound(buffer) {
    var source = context.createBufferSource(); // creates a sound source
    source.buffer = buffer;                    // tell the source which sound to play
    source.connect(context.destination);       // connect the source to the context's destination (the speakers)
    source.noteOn(0);                          // play the source now
}

ระบบจะเรียกฟังก์ชัน playSound() นี้ทุกครั้งที่มีคนกดแป้นหรือคลิกด้วยเมาส์

ฟังก์ชัน noteOn(time) ช่วยให้กำหนดเวลาเล่นเสียงที่แม่นยำสำหรับเกมและแอปพลิเคชันอื่นๆ ที่สำคัญต่อเวลาได้ แต่เพื่อให้การตั้งเวลานี้ทำงานได้อย่างถูกต้อง โปรดตรวจสอบว่าโหลดบัฟเฟอร์เสียงไว้ล่วงหน้าแล้ว

การดึง Web Audio API

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

ต่อไปนี้คือตัวอย่างวิธีการใช้คลาส BufferLoader เรามาสร้าง AudioBuffers ขึ้นมา 2 รายการกันเลย และเล่นกลับพร้อมกันทันทีที่โหลดขึ้นมา

window.onload = init;
var context;
var bufferLoader;

function init() {
    context = new AudioContext();

    bufferLoader = new BufferLoader(
    context,
    [
        '../sounds/hyper-reality/br-jam-loop.wav',
        '../sounds/hyper-reality/laughter.wav',
    ],
    finishedLoading
    );

    bufferLoader.load();
}

function finishedLoading(bufferList) {
    // Create two sources and play them both together.
    var source1 = context.createBufferSource();
    var source2 = context.createBufferSource();
    source1.buffer = bufferList[0];
    source2.buffer = bufferList[1];

    source1.connect(context.destination);
    source2.connect(context.destination);
    source1.noteOn(0);
    source2.noteOn(0);
}

รับมือกับเวลา: เปิดเสียงตามจังหวะ

Web Audio API ช่วยให้นักพัฒนาซอฟต์แวร์กำหนดเวลาการเล่นได้อย่างแม่นยํา ถ้าอยากเห็นภาพนี้ เรามาตั้งค่าแทร็กจังหวะง่ายๆ กัน ตัวอย่างรูปแบบกลองชุดที่เป็นที่รู้จักมากที่สุดคือรูปแบบต่อไปนี้

รูปแบบกลองร็อกแบบเรียบง่าย
รูปแบบกลองร็อกแบบเรียบง่าย

โดยมีการเล่นฮิแฮททุกโน้ตตัว 8 โดยเล่นเตะและสแนร์สลับกันทุกไตรมาส โดยเล่น 4/4

สมมติว่าเราโหลดบัฟเฟอร์ kick, snare และ hihat แล้ว โค้ดสำหรับขั้นตอนนี้ง่ายๆ คือ

for (var bar = 0; bar < 2; bar++) {
    var time = startTime + bar * 8 * eighthNoteTime;
    // Play the bass (kick) drum on beats 1, 5
    playSound(kick, time);
    playSound(kick, time + 4 * eighthNoteTime);

    // Play the snare drum on beats 3, 7
    playSound(snare, time + 2 * eighthNoteTime);
    playSound(snare, time + 6 * eighthNoteTime);

    // Play the hi-hat every eighth note.
    for (var i = 0; i < 8; ++i) {
    playSound(hihat, time + i * eighthNoteTime);
    }
}

ในโน้ตเพลง เราจะทำซ้ำการวนซ้ำเพียง 1 ครั้งแทนที่จะเป็นการวนซ้ำแบบไม่จำกัดที่เห็นในโน้ตเพลง ฟังก์ชัน playSound คือเมธอดที่เล่นบัฟเฟอร์ในเวลาที่ระบุ ดังนี้

function playSound(buffer, time) {
    var source = context.createBufferSource();
    source.buffer = buffer;
    source.connect(context.destination);
    source.noteOn(time);
}

การเปลี่ยนระดับเสียง

วิธีพื้นฐานที่สุดอย่างหนึ่งที่คุณอาจควรทำกับเสียงก็คือ การปรับระดับเสียง เมื่อใช้ Web Audio API เราสามารถกำหนดเส้นทางต้นทางของเราไปยังปลายทางผ่าน AudioGainNode เพื่อควบคุมระดับเสียง ดังนี้

กราฟเสียงที่มีโหนดเกน
กราฟเสียงที่มีโหนดเกน

โดยการตั้งค่าการเชื่อมต่อนี้ทําได้ดังนี้

// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source.connect(gainNode);
// Connect the gain node to the destination.
gainNode.connect(context.destination);

หลังจากตั้งค่ากราฟแล้ว คุณจะเปลี่ยนระดับเสียงแบบเป็นโปรแกรมได้โดยจัดการ gainNode.gain.value ดังนี้

// Reduce the volume.
gainNode.gain.value = 0.5;

การค่อยๆ เบาลงระหว่างเสียง 2 เสียง

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

ซึ่งทำได้ด้วยกราฟเสียงต่อไปนี้

กราฟเสียงที่มี 2 แหล่งที่มาเชื่อมต่อกันผ่านโหนดเกน
กราฟเสียงที่มี 2 แหล่งที่มาเชื่อมต่อกันผ่านโหนดเกน

ในการตั้งค่านี้ เพียงสร้าง AudioGainNodes 2 รายการ และเชื่อมต่อแต่ละแหล่งที่มาผ่านโหนดโดยใช้คำสั่งต่อไปนี้

function createSource(buffer) {
    var source = context.createBufferSource();
    // Create a gain node.
    var gainNode = context.createGainNode();
    source.buffer = buffer;
    // Turn on looping.
    source.loop = true;
    // Connect source to gain.
    source.connect(gainNode);
    // Connect gain to destination.
    gainNode.connect(context.destination);

    return {
    source: source,
    gainNode: gainNode
    };
}

พาวเวอร์ครอสเฟดแบบเท่ากัน

วิธีการครอสเฟดเชิงเส้นแบบไร้เดียงสาจะแสดงความดิ่งลงเมื่อคุณเลื่อนดูระหว่างตัวอย่างเพลง

ครอสเฟดเชิงเส้น
ครอสเฟดเชิงเส้น

ในการแก้ปัญหานี้ เราใช้เส้นโค้งกำลังเท่ากัน ซึ่งเส้นโค้งเกนที่สอดคล้องกันไม่ใช่เชิงเส้นและตัดกันที่แอมพลิจูดที่สูงกว่า ซึ่งจะลดการลดระดับเสียงระหว่างภูมิภาคเสียง ทำให้มีการครอสเฟดระหว่างภูมิภาคต่างๆ มากยิ่งขึ้น ซึ่งอาจมีระดับแตกต่างกันเล็กน้อย

ครอสเฟดที่มีค่าเท่ากัน
ครอสเฟดที่เท่ากันของพลังงานอย่างเท่าเทียม

การครอสเฟดของเพลย์ลิสต์

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

ดังนั้น เนื่องด้วยเพลย์ลิสต์ เราสามารถเปลี่ยนระหว่างแทร็กต่างๆ ได้โดยกำหนดเวลาลดลงสำหรับแทร็กที่กำลังเล่นอยู่ และสามารถเพิ่มส่วนเพิ่มของแทร็กถัดไปเล็กน้อยก่อนที่แทร็กปัจจุบันจะเล่นจบ

function playHelper(bufferNow, bufferLater) {
    var playNow = createSource(bufferNow);
    var source = playNow.source;
    var gainNode = playNow.gainNode;
    var duration = bufferNow.duration;
    var currTime = context.currentTime;
    // Fade the playNow track in.
    gainNode.gain.linearRampToValueAtTime(0, currTime);
    gainNode.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
    // Play the playNow track.
    source.noteOn(0);
    // At the end of the track, fade it out.
    gainNode.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
    gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
    // Schedule a recursive track change with the tracks swapped.
    var recurse = arguments.callee;
    ctx.timer = setTimeout(function() {
    recurse(bufferLater, bufferNow);
    }, (duration - ctx.FADE_TIME) - 1000);
}

Web Audio API มีชุดเมธอด RampToValue ที่สะดวกในการค่อยๆ เปลี่ยนค่าของพารามิเตอร์ เช่น linearRampToValueAtTime และ exponentialRampToValueAtTime

แม้ว่าคุณจะเลือกฟังก์ชันเวลาในการเปลี่ยนจากเชิงเส้นและเลขชี้กำลังในตัวได้ (ดังด้านบน) แต่คุณยังระบุเส้นโค้งค่าของคุณเองผ่านอาร์เรย์ของค่าโดยใช้ฟังก์ชัน setValueCurveAtTime ได้อีกด้วย

การใช้เอฟเฟกต์ฟิลเตอร์ง่ายๆ กับเสียง

กราฟเสียงที่มี BiquadFilterNode
กราฟเสียงที่มี BiquadFilterNode

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

วิธีหนึ่งในการทำเช่นนี้คือการวาง BiquadFilterNode ไว้ระหว่างต้นทางและปลายทางเสียง โหนดเสียงประเภทนี้สามารถสร้างฟิลเตอร์อันดับต่ำได้หลากหลาย สามารถใช้สร้างอีควอไลเซอร์สำหรับกราฟิก และเอฟเฟกต์ที่ซับซ้อนยิ่งขึ้น โดยส่วนใหญ่เกี่ยวข้องกับการเลือกส่วนของความถี่สเปกตรัมของเสียงที่จะเน้นและส่วนที่ต้องการลดเสียง

ประเภทตัวกรองที่รองรับ ได้แก่

  • ตัวกรองโลว์พาส
  • ฟิลเตอร์ High Pass
  • ตัวกรองย่านความถี่
  • ตัวกรองชั้นวางต่ำ
  • ตัวกรองชั้นวางสูง
  • ฟิลเตอร์พีค
  • ฟิลเตอร์รอยบาก
  • ตัวกรองบัตรทั้งหมด

และตัวกรองทั้งหมดจะมีพารามิเตอร์เพื่อระบุปริมาณกำไร ความถี่ในการใช้ตัวกรอง และปัจจัยด้านคุณภาพ ตัวกรองความถี่ต่ำจะคงช่วงความถี่ต่ำไว้ แต่ละทิ้งความถี่สูง จุดคลอดกำหนดโดยค่าความถี่ และตัวประกอบ Q จะไม่มีหน่วยเป็นหน่วย และเป็นตัวกำหนดรูปร่างของกราฟ ค่าเกนจะมีผลกับตัวกรองบางตัวเท่านั้น เช่น ตัวกรองชั้นวางต่ำและตัวกรองพีค แต่ไม่ใช่ตัวกรองแบบ Low-pass นี้

เรามาตั้งค่าตัวกรองโลว์พาสอย่างง่ายเพื่อแยกเฉพาะฐานจากตัวอย่างเสียงกัน

// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source.connect(filter);
filter.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.noteOn(0);

โดยทั่วไปแล้ว ต้องปรับเปลี่ยนการควบคุมความถี่เพื่อให้ใช้สเกลลอการิทึมได้ เนื่องจากระบบการได้ยินของมนุษย์เองก็ทำงานโดยใช้หลักการเดียวกัน (คือ A4 คือ 440 Hz และ A5 คือ 880 Hz) ดูรายละเอียดเพิ่มเติมได้ที่ฟังก์ชัน FilterSample.changeFrequency ในลิงก์ซอร์สโค้ดด้านบน

สุดท้าย โปรดทราบว่าโค้ดตัวอย่างช่วยให้คุณสามารถเชื่อมต่อและยกเลิกการเชื่อมต่อตัวกรอง ซึ่งจะเปลี่ยนแปลงกราฟ AudioContext แบบไดนามิก เรายกเลิกการเชื่อมต่อ AudioNodes จากกราฟได้โดยการเรียกใช้ node.disconnect(outputNumber) ตัวอย่างเช่น ในการเปลี่ยนเส้นทางกราฟจากตัวกรองไปยังการเชื่อมต่อโดยตรง เราสามารถทำได้ดังนี้

// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);

ฟังต่อ

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

หากคุณกำลังแสวงหาแรงบันดาลใจ นักพัฒนาซอฟต์แวร์จำนวนมากได้สร้างผลงานอันยอดเยี่ยมไว้แล้วโดยใช้ Web Audio API สิ่งที่ผมชอบ ได้แก่

  • AudioJedit เครื่องมือเชื่อมต่อเสียงในเบราว์เซอร์ที่ใช้ลิงก์ถาวรของ SoundCloud
  • ToneCraft คือตัวจัดลำดับเสียงที่สร้างเสียงขึ้นจากบล็อก 3 มิติซ้อนกัน
  • Plink เป็นเกมสร้างสรรค์เพลงแบบทำงานร่วมกันโดยใช้ Web Audio และ Web Sockets