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

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

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

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

อินสแตนซ์เดียวของ 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 เช่น การสร้าง 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

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

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

ซึ่งจะเล่นไฮแฮททุกโน้ต 8 และเล่นเบสและฉาบสลับกันทุก 4 โน้ตในจังหวะ 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 ได้

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

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

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

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

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

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

มาตั้งค่าตัวกรอง 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 แบบไดนามิก เรายกเลิกการเชื่อมต่อ 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 Socket