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

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

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

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

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

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

ข้อมูลโค้ดต่อไปนี้จะสร้าง 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 เช่น การสร้าง AudioNode และการถอดรหัสข้อมูลไฟล์เสียงเป็นเมธอดของ 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() เสร็จสิ้นแล้ว เมธอดนี้จะเรียกใช้ฟังก์ชัน Callback ซึ่งจะให้ข้อมูลเสียง 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 ช่วยให้นักพัฒนาซอฟต์แวร์กำหนดเวลาการเล่นได้อย่างแม่นยำ เราจะตั้งค่าแทร็กจังหวะอย่างง่ายเพื่อแสดงให้เห็นถึงความสามารถนี้ รูปแบบกลองชุดที่รู้จักกันอย่างแพร่หลายที่สุดน่าจะเป็นรูปแบบต่อไปนี้

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

ซึ่งจะเล่นไฮแฮตทุกโน้ตตัวเขบ็ต และเล่นกลองใหญ่และกลองสแนร์สลับกันทุกโน้ตตัวดำในจังหวะ 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);
    }
}

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

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

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

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

โฟลว์ชาร์ตแบบง่ายที่แสดงเสียงที่ไหลจากแหล่งที่มาไปยัง GainNode ไปยังปลายทาง

คุณสามารถตั้งค่าการเชื่อมต่อนี้ได้ดังนี้

// 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 เสียง

ตอนนี้สมมติว่าเรามีสถานการณ์ที่ซับซ้อนขึ้นเล็กน้อย โดยเรากำลังเล่นเสียงหลายเสียงแต่ต้องการเฟดเสียงระหว่างเสียงเหล่านั้น นี่เป็นกรณีทั่วไปในแอปพลิเคชันที่คล้ายกับ DJ ซึ่งเรามีเครื่องเล่นแผ่นเสียง 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
    };
}

การเฟดเสียงแบบกำลังเท่ากัน

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

การครอสเฟดเชิงเส้นแสดงเป็นเส้นตรง 2 เส้นที่เสริมกันซึ่งแมปเป็นระดับแอมพลิจูดตามเวลา
การเฟดเสียงเชิงเส้น

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

การจางเสียงแบบกำลังเท่ากันแสดงเป็นเส้นโค้ง 2 เส้นที่แมปเป็นระดับแอมพลิจูดตามเวลา
การเฟดเสียงแบบกำลังเท่ากัน

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

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

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

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 ไปยังปลายทาง

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

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

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

  • ตัวกรองความถี่ต่ำ
  • ตัวกรองความถี่สูง
  • ตัวกรองความถี่กลาง
  • ตัวกรองความถี่ต่ำแบบชั้น
  • ตัวกรองความถี่สูงแบบชั้น
  • ตัวกรองความถี่สูงสุด
  • ตัวกรองความถี่แคบ
  • ตัวกรองความถี่ทั้งหมด

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

มาตั้งค่าตัวกรองความถี่ต่ำอย่างง่ายเพื่อแยกเฉพาะเสียงเบสจากตัวอย่างเสียงกัน

// 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 แบบไดนามิก เราสามารถยกเลิกการเชื่อมต่อ AudioNode จากกราฟได้โดยเรียกใช้ 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