תחילת העבודה עם Web Audio API

לפני רכיב ה-<audio> של HTML5 היה צורך ב-Flash או בפלאגין אחר כדי לשבור את השתיקה באינטרנט. כשהאודיו באינטרנט כבר לא דורש פלאגין, תג האודיו גורם למגבלות משמעותיות להטמיע משחקים מתוחכמים ואפליקציות אינטראקטיביות.

Web Audio API הוא ממשק JavaScript API ברמה גבוהה לעיבוד ול סינתזת אודיו באפליקציות אינטרנט. מטרת ה-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, כמו יצירה 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.

אחרי שהנתונים של קובץ האודיו (שלא מפוענח) מתקבלים, אפשר לשמור אותם. כדי לפענח אותו מאוחר יותר, או שאפשר לפענח אותו מיד באמצעות שיטת AudioContext decodeAudioData(). השיטה הזו לוקחת את ArrayBuffer מהנתונים של קובצי האודיו מאוחסנים בתיקייה request.response וגם מפענח אותו באופן אסינכרוני (לא חוסם את ההפעלה הראשית של 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 היא method שמפעילה מאגר הנתונים הזמני בזמן מוגדר, באופן הבא:

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;

עמעום הדרגתי בין שני צלילים

נניח שיש לנו תרחיש קצת יותר מורכב, שבו אנחנו משמיעים כמה צלילים, אבל רוצים לעבור ביניהם בעמעום. זהו כמו במקרה של אפליקציה דמוית די-ג'יי, שבה יש לנו שני פטיפונים רוצים להיות מסוגלים לנוע ממקור אודיו אחד למקור אחר.

ניתן לעשות זאת באמצעות תרשים האודיו הבא:

תרשים אודיו עם שני מקורות שמחוברים דרך צומתי רווח
תרשים אודיו עם שני מקורות שמחוברים דרך צומתי רווח

כדי להגדיר זאת, אנחנו יוצרים שני פונקציות 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
    };
}

עמעום בעוצמה שווה

גישה של עמעום ליניארי תותם מראה ירידה בנפח התנועה בין הדוגמאות.

עמעום ליניארי
עמעום הדרגתי ליניארי

כדי לטפל בבעיה הזאת, משתמשים בעקומת חזקה שווה, עקומות הרווח התואמות אינן ליניאריות, והן מצטלבות ברמה גבוהה יותר משרעת. הפעולה הזו מפחיתה את ירידות עוצמת הקול בין אזורי אודיו, עמעום בצורה שווה יותר בין אזורים שעשויים להיות מעט שונים ברמה שונה.

עמעום בעוצמה שווה.
עמעום בעוצמה שווה

עמעום הדרגתי של פלייליסט

אפליקציה נפוצה אחרת במעבר בין פלטפורמות מוצלבות היא אפליקציה של נגן מוזיקה. כששיר משתנה, אנחנו רוצים להפוך את הטראק הנוכחי לעמעום, חדשה, כדי להימנע ממעבר צורם. לשם כך, מתזמנים להפוך לשקוף בהדרגה אל העתיד. אפשר להשתמש ב-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 בין הצלילים המקור והיעד. צומת אודיו מהסוג הזה יכול לבצע מגוון פעולות מסננים בסדר יורד שבהם אפשר להשתמש כדי ליצור אקולייזרים גרפיים מורכבים יותר, בעיקר בבחירה באילו חלקים טווח תדרים של צליל שצריך להדגיש ואילו להכניע.

סוגי המסננים הנתמכים כוללים:

  • מסנן כרטיסים נמוכים
  • פילטר של תוצאות גבוהות
  • מסנן כרטיס תדרים
  • מסנן על מדף נמוך
  • פילטר של מדף גבוה
  • פילטר שיא
  • פילטר של חריצים
  • מסנן כל הכרטיסים

כל המסננים כוללים פרמטרים שמציינים gain, התדירות שבה צריך להחיל את המסנן וגורם איכות. מסנן הסף הנמוך שומר על טווח התדרים הנמוך יותר, אבל מבטל את הערך הגבוה תדרים מסוימים. נקודת ההפסקה נקבעת לפי ערך התדירות, וגורם ה-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 הוא 440hz ו-A5 הוא 880hz). פרטים נוספים זמינים במאמר הפונקציה 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, יוצר רצף צלילים שבו צלילים נוצרים על ידי בערימה של בלוקים תלת-ממדיים.
  • Plink, משחק שיתופי ליצירת מוזיקה באמצעות Web Audio ו-Web שקעים.