לפני שהאלמנט <audio> של HTML5 היה קיים, היה צורך ב-Flash או בפלאגין אחר כדי להשמיע צלילים באינטרנט. אמנם כבר לא צריך פלאגין כדי להשמיע אודיו באינטרנט, אבל תג האודיו מציב מגבלות משמעותיות על הטמעה של משחקים מתוחכמים ואפליקציות אינטראקטיביות.
Web Audio API הוא ממשק API ברמה גבוהה ב-JavaScript לעיבוד ולסינתזה של אודיו באפליקציות אינטרנט. המטרה של ה-API הזה היא לכלול יכולות שקיימות במנועי אודיו של משחקים מודרניים, וחלק ממשימות המיקס, העיבוד והסינון שקיימות באפליקציות מודרניות להפקת אודיו במחשב. בהמשך מופיע מבוא קצר לשימוש ב-API היעיל הזה.
איך מתחילים להשתמש ב-AudioContext
AudioContext משמש לניהול ולהפעלה של כל הצלילים. כדי להפיק צליל באמצעות Web Audio API, יוצרים מקור צליל אחד או יותר ומחברים אותם ליעד הצליל שסופק על ידי המכונה של AudioContext. החיבור הזה לא חייב להיות ישיר, והוא יכול לעבור דרך מספר כלשהו של 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, כמו יצירת 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.
אחרי שנתוני קובץ האודיו (שלא פוענחו) מתקבלים, אפשר לשמור אותם לפענוח מאוחר יותר, או לפענח אותם מיד באמצעות השיטה AudioContext decodeAudioData(). השיטה הזו מקבלת את ה-ArrayBuffer של נתוני קובץ האודיו שמאוחסנים ב-request.response ומפענחת אותם באופן אסינכרוני (בלי לחסום את ה-thread הראשי של הפעלת JavaScript).
כשהפעולה של decodeAudioData() מסתיימת, היא מפעילה פונקציית קריאה חוזרת שמספקת את נתוני האודיו המפוענחים של PCM בתור AudioBuffer.
השמעת צלילים
אחרי שנטען AudioBuffers אחד או יותר, אפשר להשמיע צלילים. נניח שרק טענו 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, וברגע שהם ייטענו, נפעיל אותם בו-זמנית.
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 כדי לשנות את עוצמת הקול:

כדי להגדיר את החיבור הזה:
// 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;
מעבר הדרגתי בין שני צלילים
עכשיו נניח שיש לנו תרחיש קצת יותר מורכב, שבו אנחנו משמיעים כמה צלילים אבל רוצים לעשות מעבר הדרגתי ביניהם. זהו מקרה נפוץ באפליקציה דמוית DJ, שבה יש לנו שני פטיפונים ואנחנו רוצים להיות מסוגלים לעבור ממקור סאונד אחד לאחר.
אפשר לעשות את זה באמצעות תרשים האודיו הבא:
כדי להגדיר את זה, אנחנו פשוט יוצרים שני AudioGainNodes ומחברים כל מקור דרך הצמתים, באמצעות פונקציה כמו זו:
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
};
}
מעבר הדרגתי בעוצמה שווה
בגישה של דעיכה לינארית פשוטה, עוצמת הקול יורדת כשעוברים בין הדגימות.
כדי לפתור את הבעיה, אנחנו משתמשים בעקומת הספק שווה, שבה עקומות ההגברה המתאימות הן לא לינאריות, ומצטלבות באמפליטודה גבוהה יותר. כך אפשר לצמצם את הירידות בעוצמת הקול בין אזורי האודיו, וליצור מעבר הדרגתי אחיד יותר בין אזורים שעשויים להיות שונים מעט ברמה.
מעבר הדרגתי בין שירים בפלייליסט
אפליקציה נפוצה נוספת של crossfader היא אפליקציה של נגן מוזיקה.
כשהשיר משתנה, אנחנו רוצים שהטראק הנוכחי יתפוגג והטראק החדש יתגבר, כדי למנוע מעבר צורם. כדי לעשות את זה, מתזמנים מעבר הדרגתי לעתיד. אפשר להשתמש ב-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.
הוספת אפקט של מסנן פשוט לצליל

Web Audio API מאפשר להעביר צליל מצומת אודיו אחד לאחר, וליצור שרשרת מורכבת של מעבדים כדי להוסיף אפקטים מורכבים לצורות הגל של הצלילים.
אחת הדרכים לעשות את זה היא להציב BiquadFilterNode בין מקור הצליל ליעד. צומת אודיו מהסוג הזה יכול לבצע מגוון של מסננים מסדר נמוך, שניתן להשתמש בהם כדי ליצור אקולייזרים גרפיים ואפילו אפקטים מורכבים יותר, בעיקר כדי לבחור אילו חלקים של ספקטרום התדרים של צליל להדגיש ואילו להחליש.
סוגי המסננים הנתמכים כוללים:
- מסנן תדרים נמוכים (LPF)
- מסנן תדרים גבוהים (HPF)
- מסנן פס תדרים
- מסנן מדף נמוך
- מסנן מדף גבוה
- מסנן פיקינג
- מסנן תדרים צר (Notch filter)
- מסנן להעברת הכול
כל המסננים כוללים פרמטרים לציון כמות מסוימת של הגבר, התדירות שבה המסנן יופעל וגורם איכות. מסנן התדרים הנמוכים שומר על טווח התדרים הנמוך, אבל מסנן את התדרים הגבוהים. נקודת החיתוך נקבעת לפי ערך התדירות, ומקדם האיכות הוא חסר יחידות וקובע את צורת הגרף. ההגבר משפיע רק על מסננים מסוימים, כמו מסנני low-shelf ו-peaking, ולא על מסנן ה-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 הוא 440Hz ו-A5 הוא 880Hz). לפרטים נוספים, עיינו בפונקציה 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, סיקוונסר שבו הצלילים נוצרים על ידי ערימת בלוקים תלת-ממדיים.
- Plink, משחק ליצירת מוזיקה בשיתוף פעולה באמצעות Web Audio ו-Web Sockets.