المزج بين الصوت الموضعي وWebGL

مقدمة

سأتحدّث في هذه المقالة عن كيفية استخدام ميزة الصوت الموضعي في Web Audio API لإضافة صوت ثلاثي الأبعاد إلى مشاهد WebGL. لتسهيل تصديق الصوت، سأعرّفك أيضًا على التأثيرات البيئية الممكنة من خلال Web Audio API. للحصول على مقدمة أكثر شمولاً عن واجهة برمجة التطبيقات Web Audio، راجع مقالة بدء استخدام واجهة برمجة تطبيقات Web Audio التي كتبها "بوريس سموس".

لإجراء مقطع صوتي موضعي، يمكنك استخدام AudioPannerNode في واجهة برمجة تطبيقات Web Audio. تحدد AudioPannerNode موضع الصوت واتجاهه وسرعته. بالإضافة إلى ذلك، يحتوي سياق الصوت في Web Audio API على سمة مستمع تتيح لك تحديد موضع المستمع واتجاهه وسرعته. باستخدام هذين العنصرين، يمكنك إنشاء أصوات توجيهية باستخدام تأثيرات دوبلر والتصوير ثلاثي الأبعاد.

لنرَ كيف يبدو الرمز الصوتي للمشهد أعلاه. هذا هو رمز Audio API الأساسي جدًا. يمكنك إنشاء مجموعة من عُقد Audio API وربطها معًا. العقد الصوتية هي أصوات فردية، ووحدات تحكم في مستوى الصوت، وعُقد مؤثرة وأدوات تحليل، وما شابه ذلك. بعد إنشاء هذا الرسم البياني، تحتاج إلى ربطه بوجهة السياق الصوتي لجعله مسموعًا.

// Detect if the audio context is supported.
window.AudioContext = (
  window.AudioContext ||
  window.webkitAudioContext ||
  null
);

if (!AudioContext) {
  throw new Error("AudioContext not supported!");
} 

// Create a new audio context.
var ctx = new AudioContext();

// Create a AudioGainNode to control the main volume.
var mainVolume = ctx.createGain();
// Connect the main volume node to the context destination.
mainVolume.connect(ctx.destination);

// Create an object with a sound source and a volume control.
var sound = {};
sound.source = ctx.createBufferSource();
sound.volume = ctx.createGain();

// Connect the sound source to the volume control.
sound.source.connect(sound.volume);
// Hook up the sound volume control to the main volume.
sound.volume.connect(mainVolume);

// Make the sound source loop.
sound.source.loop = true;

// Load a sound file using an ArrayBuffer XMLHttpRequest.
var request = new XMLHttpRequest();
request.open("GET", soundFileName, true);
request.responseType = "arraybuffer";
request.onload = function(e) {

  // Create a buffer from the response ArrayBuffer.
  ctx.decodeAudioData(this.response, function onSuccess(buffer) {
    sound.buffer = buffer;

    // Make the sound source use the buffer and start playing it.
    sound.source.buffer = sound.buffer;
    sound.source.start(ctx.currentTime);
  }, function onFailure() {
    alert("Decoding the audio buffer failed");
  });
};
request.send();

الموضع

يستخدم نظام الصوت الموضعي موضع مصادر الصوت وموضع المستمع لتحديد طريقة مزج الصوت مع مكبرات الصوت. يكون مصدر الصوت في الجانب الأيسر من المستمع أعلى صوتًا في مكبّر الصوت الأيسر والعكس صحيح في الجانب الأيمن.

للبدء، أنشئ مصدر صوت وأرفقه بـ AudioPannerNode. ثم اضبط موضع AudioPannerNode. لديك الآن صوت ثلاثي الأبعاد متحرك. يكون موضع أداة معالجة السياق الصوتي على (0,0,0) بشكل تلقائي، ولذلك عند استخدامه بهذه الطريقة، يكون موضع AudioPannerNode متناسبًا مع موضع الكاميرا. وكلما حرّكت الكاميرا، تحتاج إلى تعديل موضع AudioPannerNode. لضبط موضع AudioPannerNode بالنسبة إلى العالم، يجب تغيير موضع أداة الاستماع لسياق الصوت إلى موضع الكاميرا.

لإعداد ميزة تتبّع الموضع، يجب إنشاء AudioPannerNode وتوصيله بمستوى الصوت الرئيسي.

...
sound.panner = ctx.createPanner();
// Instead of hooking up the volume to the main volume, hook it up to the panner.
sound.volume.connect(sound.panner);
// And hook up the panner to the main volume.
sound.panner.connect(mainVolume);
...

في كل إطار، حدّث مواضع AudioPannerNodes. سأستخدم Three.js في الأمثلة أدناه.

...
// In the frame handler function, get the object's position.
object.position.set(newX, newY, newZ);
object.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);

// And copy the position over to the sound of the object.
sound.panner.setPosition(p.x, p.y, p.z);
...

لتتبُّع موضع المستمع، يمكنك ضبط موضع مستمع السياق الصوتي لمطابقة موضع الكاميرا.

...
// Get the camera position.
camera.position.set(newX, newY, newZ);
camera.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(camera.matrixWorld);

// And copy the position over to the listener.
ctx.listener.setPosition(p.x, p.y, p.z);
...

السرعة المتّجهة

والآن بعد أن أصبح لدينا مواضع المستمع و AudioPannerNode، لنركز على سرعاتهما. من خلال تغيير خصائص السرعة للمستمع و AudioPannerNode، يمكنك إضافة تأثير دوبلر إلى الصوت. هناك بعض الأمثلة على تأثيرات دوبلر رائعة على صفحة أمثلة Web Audio API.

تتمثل أسهل طريقة للحصول على سرعات المستمع و AudioPannerNode في تتبع مواضع كل إطار. سرعة المستمع هي الموضع الحالي للكاميرا مطروحًا منه موضع الكاميرا في الإطار السابق. وبالمثل، فإن سرعة AudioPannerNode هي موضعه الحالي مطروحًا منه موضعه السابق.

يمكن تتبع السرعة من خلال الحصول على الموضع السابق للكائن وطرحه من الوضع الحالي وقسمة النتيجة على الوقت المنقضي منذ آخر إطار. وإليك كيفية القيام بذلك في Three.js:

...
var dt = secondsSinceLastFrame;

var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);
var px = p.x, py = p.y, pz = p.z;

object.position.set(newX, newY, newZ);
object.updateMatrixWorld();

var q = new THREE.Vector3();
q.setFromMatrixPosition(object.matrixWorld);
var dx = q.x-px, dy = q.y-py, dz = q.z-pz;

sound.panner.setPosition(q.x, q.y, q.z);
sound.panner.setVelocity(dx/dt, dy/dt, dz/dt);
...

الاتجاه

الاتجاه هو الاتجاه الذي يشير إليه مصدر الصوت والاتجاه الذي يواجهه المستمع. باستخدام الاتجاه، يمكنك محاكاة مصادر الصوت ذات الاتجاه. على سبيل المثال، فكر في مكبّر صوت توجيهي. فإذا قفت أمام مكبّر الصوت، سيكون الصوت أعلى ممّا لو كنت تقف خلف مكبّر الصوت. والأهم من ذلك أنّك تحتاج إلى اتّجاه المستمع لتحديد جانب المستمع الذي تصدر منه الأصوات. يجب تبديل الصوت الصادر من يسارك إلى اليمين عند الدوران.

للحصول على متجه الاتجاه لـ AudioPannerNode، يجب أن تأخذ جزء الدوران من مصفوفة النموذج للكائن ثلاثي الأبعاد الذي يصدر صوتًا ويضرب الخط vec3(0,0,1) به لنرى أين ينتهي التوجيه. بالنسبة إلى اتجاه مستمع السياق، تحتاج إلى الحصول على متجه اتجاه الكاميرا. يحتاج اتجاه المستمع أيضًا إلى متجه لأعلى، لأنه يحتاج إلى معرفة زاوية لف رأس المستمع. لحساب اتجاه المستمع، احصل على جزء التدوير من مصفوفة عرض الكاميرا واضرب vec3(0,0,1) للاتجاه وvec3(0,-1,0) للمتجه لأعلى.

لكي يؤثر الاتجاه في الأصوات، يجب أيضًا تحديد المخروط للصوت. يأخذ مخروط الصوت زاوية داخلية وزاوية خارجية وكسب خارجي. يتم تشغيل الصوت بالحجم الطبيعي داخل الزاوية الداخلية ويتغير تدريجيًا إلى الزيادة الخارجية مع الاقتراب من الزاوية الخارجية. ويتم تشغيل الصوت خارج الزاوية الخارجية.

يعد تتبع الاتجاه في Three.js أكثر تعقيدًا لأنه يتضمن بعض عمليات حسابية المتجهات وأصفار جزء الترجمة في مصفوفات عالم 4×4. ومع ذلك، ليس هناك الكثير من سطور الرمز.

...
var vec = new THREE.Vector3(0,0,1);
var m = object.matrixWorld;

// Save the translation column and zero it.
var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;

// Multiply the 0,0,1 vector by the world matrix and normalize the result.
vec.applyProjection(m);
vec.normalize();

sound.panner.setOrientation(vec.x, vec.y, vec.z);

// Restore the translation column.
m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...

يتطلب تتبع اتجاه الكاميرا أيضًا الخط المتجه لأعلى، لذا تحتاج إلى ضرب الخط المتجه لأعلى في مصفوفة التحويل.

...
// The camera's world matrix is named "matrix".
var m = camera.matrix;

var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;

// Multiply the orientation vector by the world matrix of the camera.
var vec = new THREE.Vector3(0,0,1);
vec.applyProjection(m);
vec.normalize();

// Multiply the up vector by the world matrix.
var up = new THREE.Vector3(0,-1,0);
up.applyProjection(m);
up.normalize();

// Set the orientation and the up-vector for the listener.
ctx.listener.setOrientation(vec.x, vec.y, vec.z, up.x, up.y, up.z);

m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...

لضبط مخروط الصوت الخاص بك، يمكنك تعيين الخصائص المناسبة لعقدة أداة التحريك. الزوايا المخروطية هي بالدرجات وتتراوح من 0 إلى 360.

...
sound.panner.coneInnerAngle = innerAngleInDegrees;
sound.panner.coneOuterAngle = outerAngleInDegrees;
sound.panner.coneOuterGain = outerGainFactor;
...

كل الخدمات معًا

من خلال وضع كل ذلك معًا، يتتبّع مستمع السياق الصوتي موضع الكاميرا واتجاهها وسرعتها، وتتّبع عُقد AudioPannerNode مواضع واتجاهات وسرعات مصادر الصوت المعنية. يجب تحديث مواضع وسرعات واتجاهات AudioPannerNodes ومستمع سياق الصوت في كل إطار.

التأثيرات البيئية

بعد إعداد ميزة "الصوت الموضعي"، يمكنك ضبط التأثيرات البيئية للصوت من أجل تحسين التجربة الغامرة للمشهد الثلاثي الأبعاد. لنفترض أن المشهد يقع في كاتدرائية كبيرة. في الإعدادات التلقائية، يبدو الصوت في المشهد وكأنك تقف في الهواء الطلق. إنّ هذا التناقض بين المرئيات والصوتيات يجعل تجربة المشاهدة أقل تشويقًا ويساهم في تقليل مستوى التأثير في المشهد.

تحتوي واجهة برمجة التطبيقات Web Audio API على أداة ConvolverNode التي تتيح لك ضبط التأثير البيئي لأحد الأصوات. يمكنك إضافته إلى الرسم البياني الخاص بمعالجة مصدر الصوت وستتمكّن من ضبط الصوت على الإعدادات المناسبة. يمكنك العثور على عينات استجابة تحفيزية على الويب يمكنك استخدامها مع ConvolverNodes، كما يمكنك إنشاؤها بنفسك. قد تكون التجربة مرهقة بعض الشيء حيث تحتاج إلى تسجيل الاستجابة الاندفاعية للمكان الذي ترغب في محاكته، لكن القدرة موجودة إذا كنت بحاجة إليها.

يتطلب استخدام ConvolverNodes لإجراء صوت في البيئة المحيطة إعادة توصيل الرسم البياني لمعالجة الصوت. فبدلاً من تمرير الصوت مباشرةً إلى وحدة التخزين الرئيسية، تحتاج إلى توجيهه عبر ConvolverNode. ونظرًا لرغبتك في التحكم في التأثير البيئي، يجب أيضًا توجيه الصوت حول عقدة ConvolverNode. للتحكم في مستويات صوت المزج، يلزم إرفاق GainNodes والصوت العادي لـ ConvolverNode والصوت العادي.

يحتوي الرسم البياني النهائي لمعالجة الصوت الذي أستخدمه على صوت العناصر التي تمر من خلال GainNode كجهاز مزج الصوت. من المزج، أدخل الصوت في ConvolverNode وعقدة GainNode أخرى للتحكم في مستوى صوت الصوت العادي. يتم توصيل ConvolverNode في GainNode للتحكم في مستوى صوت الصوت المحول. يتم توصيل مخرجات GainNode بوحدة التحكم الرئيسية في مستوى الصوت.

...
var ctx = new webkitAudioContext();
var mainVolume = ctx.createGain();

// Create a convolver to apply environmental effects to the audio.
var convolver = ctx.createConvolver();

// Create a mixer that receives sound from the panners.
var mixer = ctx.createGain();

sounds.forEach(function(sound){
  sound.panner.connect(mixer);
});

// Create volume controllers for the plain audio and the convolver.
var plainGain = ctx.createGain();
var convolverGain = ctx.createGain();

// Send audio from the mixer to plainGain and the convolver node.
mixer.connect(plainGain);
mixer.connect(convolver);

// Hook up the convolver to its volume control.
convolver.connect(convolverGain);

// Send audio from the volume controls to the main volume control.
plainGain.connect(mainVolume);
convolverGain.connect(mainVolume);

// Finally, connect the main volume to the audio context's destination.
volume.connect(ctx.destination);
...

لكي تعمل ConvolverNode، يجب تحميل عينة استجابة النبضات في مخزن مؤقت وجعل ConvolverNode تستخدمها. يتم تحميل العيّنة بالطريقة نفسها التي يتم بها تحميل العيّنات الصوتية العادية. وفي ما يلي مثال على إحدى الطرق للقيام بذلك:

...
loadBuffer(ctx, "impulseResponseExample.wav", function(buffer){
  convolver.buffer = buffer;
  convolverGain.gain.value = 0.7;
  plainGain.gain.value = 0.3;
})
...
function loadBuffer(ctx, filename, callback) {
  var request = new XMLHttpRequest();
  request.open("GET", soundFileName, true);
  request.responseType = "arraybuffer";
  request.onload = function() {
    // Create a buffer and keep the channels unchanged.
    ctx.decodeAudioData(request.response, callback, function() {
      alert("Decoding the audio buffer failed");
    });
  };
  request.send();
}

ملخّص

تعرّفت في هذه المقالة على كيفية إضافة صوت موضعي إلى المشاهد الثلاثية الأبعاد باستخدام Web Audio API. توفّر لك واجهة Web Audio API طريقة لضبط موضع مصادر الصوت وجهاز الاستماع واتجاهها وسرعتها. من خلال تعيين هذه الإعدادات لتتبع الكائنات في المشهد الثلاثي الأبعاد، يمكنك إنشاء مظهر صوتي غني للتطبيقات الثلاثية الأبعاد.

لجعل تجربة الصوت أكثر جاذبية، يمكنك استخدام ConvolverNode في Web Audio API لإعداد الصوت العام للبيئة. يمكنك محاكاة مجموعة متنوعة من المؤثرات والبيئات باستخدام Web Audio API، بدءًا من الكاتدرائيات ووصولاً إلى الغرف المغلقة.

المراجع