دراسة حالة - JAM مع Chrome

كيف ابتكرنا موسيقى الروك الصوتية

مقدمة

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

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

كتابة مؤثرات صوتية مخصّصة

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

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

var MyCustomNode = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    this.connect = function(target){
       output.connect(target);
    };
};

بهذا النمط نكون قد اقتربنا من العُقد الأصلية. لنرى كيف يمكن استخدام ذلك.

//create a couple of native nodes and our custom node
var gain = audioContext.createGain(),
    customNode = new MyCustomNode(),
    anotherGain = audioContext.createGain();

//connect our custom node to the native nodes and send to the output
gain.connect(customNode.input);
customNode.connect(anotherGain);
anotherGain.connect(audioContext.destination);
توجيه العقدة المخصصة

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

الآن وبعد أن أصبح لدينا النمط الأساسي لإنشاء تأثيرات مخصصة، كانت الخطوة التالية هي منح العقدة المخصصة بعض السلوك المخصص. لنلقِ نظرة على عقدة التأخير المفاجئ.

هتاف كما لو كنت تقصد

إنّ التأخير الصامت، والذي يُطلق عليه أحيانًا صدى الصوت الهزلي، هو أحد التأثيرات الكلاسيكية المستخدَمة في عدد من الآلات الموسيقية، بدءًا من أصوات الخمسينيات وصولاً إلى غيتارات ركوب الأمواج. يأخذ هذا التأثير الصوت الوارد ويشغِّل نسخة من الصوت مع تأخير بسيط يصل إلى 75-250 مللي ثانية تقريبًا. ويمنح ذلك شعورًا بصفع الصوت، وبالتالي تغيير الاسم. يمكننا إنشاء التأثير على النحو التالي:

var SlapbackDelayNode = function(){
    //create the nodes we'll use
    this.input = audioContext.createGain();
    var output = audioContext.createGain(),
        delay = audioContext.createDelay(),
        feedback = audioContext.createGain(),
        wetLevel = audioContext.createGain();

    //set some decent values
    delay.delayTime.value = 0.15; //150 ms delay
    feedback.gain.value = 0.25;
    wetLevel.gain.value = 0.25;

    //set up the routing
    this.input.connect(delay);
    this.input.connect(output);
    delay.connect(feedback);
    delay.connect(wetLevel);
    feedback.connect(delay);
    wetLevel.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};
التوجيه الداخلي لعقدة الرد الآلي

كما قد يكون البعض منكم قد أدرك بالفعل، يمكن استخدام هذا التأخير مع أوقات تأخير أكبر أيضًا، وبالتالي يصبح تأخيرًا أحاديًا منتظمًا مع الملاحظات. إليك مثال على استخدام هذا التأخير للسماح لك بسماع الصوت.

صوت التوجيه

عند التعامل مع آلات وأجزاء موسيقية مختلفة في التطبيقات الصوتية الاحترافية، من الضروري أن يتوفر لديك نظام توجيه مرن يسمح لك بمزج الأصوات وتعديلها بطرق فعّالة. في JAM التي تستخدم Chrome، طورنا نظامًا لنقل الصوت، يشبه ذلك النظام في ألواح الدمج المادية. يتيح لنا ذلك ربط جميع الآلات التي تحتاج إلى تأثير صدى إلى حافلة أو قناة مشتركة، ثم إضافة الصدى إلى تلك الحافلة بدلاً من إضافة صدى إلى كل آلة موسيقية منفصلة. يُعد هذا تحسينًا كبيرًا وننصح بشدة بإجراء شيء مشابه بمجرد بدء تنفيذ تطبيقات أكثر تعقيدًا.

توجيه AudioBus

ولحسن الحظ، من السهل تحقيق ذلك في Web Audio. يمكننا بشكل أساسي استخدام الهيكل الذي حددناه للتأثيرات واستخدامه بنفس الطريقة.

var AudioBus = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    //create effect nodes (Convolver and Equalizer are other custom effects from the library presented at the end of the article)
    var delay = new SlapbackDelayNode(),
        convolver = new tuna.Convolver(),
        equalizer = new tuna.Equalizer();

    //route 'em
    //equalizer -> delay -> convolver
    this.input.connect(equalizer);
    equalizer.connect(delay.input);
    delay.connect(convolver);
    convolver.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};

سيتم استخدام ذلك على النحو التالي:

//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
    instrument1 = audioContext.createOscillator(),
    instrument2 = audioContext.createOscillator(),
    instrument3 = audioContext.createOscillator();

//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);

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

إلى أين من هنا؟

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

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