Огляд
Досі я показав вам двовимірну графіку, анімацію та 3D з апаратним прискоренням. Після того, як ви щось створите з цими технологіями, то помітите як чогось не вистачає звуку! Традиційно хороший звук в Інтернеті без плагінів варіюється від жахливого до неможливого, але останнім часом це змінилося завдяки новому API під назвою WebAudio.
Зверніть увагу, що цей API все ще змінюється, хоча він набагато стабільніший ніж раніше. Використовуйте WebAudio для експериментів, але не в робочому коді принаймні з альтернативою на Flash. Спробуйте SoundManager2 як запасний варіант.
Елемент <audio> проти WebAudio
Ви, можливо, чули про елемент <audio>. Цей новий елемент доданий до HTML5 і виглядає як <audio src="music.mp3">. Елемент <audio> чудово підходить для відтворення пісень. Ви просто вмикаєте його на свою сторінку так само, як зображення. Браузер відображає його з панеллю керування. Також є мінімальний API. На жаль, елемент <audio> є хорошим лише для відтворення музики. Ви не можете легко відтворювати короткі звуки і більшість реалізацій дозволяють відтворювати лише один звук за один раз. Що ще важливіше, ви не можете генерувати звук на льоту або отримати доступ до бібліотеки семплів для подальшої обробки. Елемент <audio> хороший для чого і призначений: відтворення музики, але дуже обмежене.
Для усунення цих недоліків виробники браузерів представили нову специфікацію під назвою WebAudio API . Вона визначає весь API обробки звуку в комплекті генерацією звуків, фільтрами, потоками та доступом до семплам. Якщо потрібно відтворити фонову музику, використовуйте елемент <audio>. Якщо ви хочете більше контролю — використовуйте WebAudio API.
Повний WebAudio API занадто великий, щоб охопити його в цьому уроці, так що я просто опишу ті частини, які, ймовірно, цікаві для розробників Canvas: звукові ефекти та візуальна обробка.
Просте відтворення
Для графіки ми використовуємо графічний контекст. З аудіо те саме — нам потрібен аудіо контекст. Оскільки специфікація ще не стандартизована, ми маємо використовувати webkitAudioContext(). Обов'язково створіть його після завантаження сторінки, оскільки ініціалізація звукової системи може тривати деякий час.
<code data-language="javascript">var ctx; //контекст аудіо var buf; //аудіобуфер //ініціалізація звукової системи function init() { console.log("in init"); try { ctx=новий webkitAudioContext(); //is there a better API для цього? loadFile(); } catch(e) { alert('you need webaudio support'); } } window.addEventListener('load',init,false);
Після створення контексту ми можемо завантажити звук. Ми завантажуємо звуки так само, як будь-який інший віддалений ресурс, використовуючи XMLHttpRequest. Однак ми повинні вказати тип як arraybuffer, а не текст, XML або JSON. Оскільки jQuery не підтримує arraybuffer, ми викликаємо XMLHttpRequest API безпосередньо.
<code data-language="javascript">//завантажуємо та декодуємо mp3-файл function loadFile() { var req=новий XMLHttpRequest(); req.open("GET","music.mp3",true); req.responseType="arraybuffer"; req.onload=function() { //декодуємо завантажені дані ctx.decodeAudioData(req.response, function(buffer) { buf=buffer; play(); }); }; req.send(); }
Після завантаження файлу він має бути декодований в аудіобуфер. Код вище робить це з іншою функцією зворотного дзвінка. Після декодування ми можемо відтворити звук.
<code data-language="javascript">//відтворення завантаженого файлу function play() { //створюємо вихідний вузол з буфера var src=ctx.createBufferSource() ; src.buffer=buf; //підключаємось до вихідного вузла (колонка) src.connect(ctx.destination); //відразу відтворюємо src.noteOn(0); }
Я збираюся пройтися цим фрагментом коду дуже ретельно, тому що для розуміння важливо що тут відбувається.
У WebAudio все обертається навколо концепції вузлів. Для маніпуляції звуком ми зв'язуємо вузли разом у ланцюжок чи схему і після цього запускаємо обробку. Для простого відтворення аудіо нам потрібен вузол вихідного коду та вузол призначення. ctx.createBufferSource() створює вихідний вузол, який ми прикріплюємо до аудіобуфера з нашим звуком. Властивість ctx.destination містить стандартний висновок призначення, який, як правило, має на увазі колонки комп'ютера. Два вузли з'єднуються функцією connect. Після підключення ми можемо відтворити звук викликавши noteOn(0) для вихідника.
Вузли WebAudio
Досі ми бачили лише вихідний вузол та вузол призначення, але WebAudio містить багато інших видів вузлів. Для створення програми з барабанами ми повинні створити кілька вихідних вузлів, по одному для кожного барабана, підключених до одного виходу за допомогою AudioChannelMerger. Ми могли б також змінити рівень кожного барабана за допомогою AudioGainNodes.
Якщо вузли WebAudio:
- JavaScriptAudioNode: пряма обробка через JavaScript;
- BiquadFilterNode: фільтр низьких і високих частот;
- DelayNode: затримка часу;
- ConvolverNode: лінійні ефекти в реальному часі на кшталт реверберації;
- RealtimeAnalyserNode: для візуалізації звуку:
- AudioPannerNode: для маніпуляції стерео, каналами та 3D-звуком;
- AudioChannelSplitter та AudioChannelMerger;
- Осцилятор: для прямої генерації сигналів.
Звукові ефекти
Звичайний елемент <audio> може бути використаний для звукових ефектів, але не дуже гарний для цього. У вас немає широкого контролю за тим, як і коли відтворюється звук. Деякі реалізації навіть не дозволяють відтворювати більше одного звуку за один раз. Це добре для пісень, але майже марно для звукових ефектів у грі. WebAudio API дозволяє запланувати відтворення кліпів, щоб вони грали в заданий час і навіть перекривати їх.
Щоб відтворити один звук кілька разів, ми не повинні робити нічого особливого; нам треба просто створити кілька буферів вихідника. Код нижче, визначає функцію play, яка створює буфер вихідного коду щоразу під час виклику і відразу ж відтворює звук.
<code data-language="javascript"> //відтворюємо завантажений файл function play() { //створюємо вихідний вузол із буфера var src=ctx.createBufferSource(); src.buffer=buf; //підключаємось до вихідного вузла (колонка) src.connect(ctx.destination); //відразу відтворюємо src.noteOn(0); }
Ви можете випробувати демо тут. Кожного разу, коли ви натискаєте кнопку відтворюється короткий звук лазера (дякую freesound.org). Якщо натиснути кнопку швидко, ви почуєте, що звуки складаються і перекриваються правильно. Ми не повинні робити нічого особливого, щоб це сталося, WebAudio обробляє все автоматично. У грі ми можемо викликати функцію play щоразу, коли персонаж стріляє з пістолета. Якщо чотири гравці стріляють одночасно, то все буде правильно.
Ми також можемо навмисно створювати нові звуки, що перекриваються. Функція noteOn() приймає позначку часу в секундах для відтворення звуку. Щоб створити новий звук, ми можемо відтворити кліп з лазером чотири рази, щоразу зміщуючи на 1/4 секунди. Таким чином вони будуть акуратно перекриватися, створюючи новий ефект.
<code data-language="javascript">var time=ctx.currentTime; for(var i=0; i<4; i++) { var src=ctx.createBufferSource(); src.buffer=buf; //підключаємось до вихідного вузла (колонка) src.connect(ctx.destination); //відразу відтворюємо src.noteOn(time+i/4); }
Зауважте, що ми повинні додати поточний час з контексту аудіо до зміщення, щоб отримати фінальний час для кожного кліпу.
Спробуйте остаточний варіант тут.
Візуалізація аудіо
RealtimeAnalyserNode.
Перед цим ми як і раніше завантажуємо аудіо. Я додав кілька додаткових змінних з іменами fft, samples та setup.
<code data-language="javascript">var ctx; //контекст аудіо var buf; //аудіобуфер var fft; //вузли аудіо var samples=128; var setup=false; //повідомляє, якщо аудіо вимагає налаштування //ініціалізація звукової системи function init() { console.log("in init"); try { ctx=новий webkitAudioContext(); //is there a better API для цього? setupCanvas(); loadFile(); } catch(e) { alert('you need webaudio support' + e); } } window.addEventListener('load',init,false); //завантажуємо mp3 файл function loadFile() { var req=new XMLHttpRequest(); req.open("GET","music.mp3",true); //ми не можемо використовувати jQuery, тому нам потрібний arraybuffer req.responseType="arraybuffer"; req.onload=function() { //декодуємо завантажені дані ctx.decodeAudioData(req.response, function(buffer) { buf=buffer; play(); }); }; req.send(); }
Ми відтворюємо музику як і раніше використовуючи вихідний вузол та вузол призначення, але цього разу ми вставимо між ними вузол аналізатора.
<code data-language="javascript">function play() { //створюємо вихідний вузол із буфера var src=ctx.createBufferSource(); src.buffer=buf; //створюємо fft fft=ctx.createAnalyser(); fft.fftSize=samples; //з'єднуємо в ланцюжок src.connect(fft); fft.connect(ctx.destination); //відразу відтворюємо src.noteOn(0); setup=true; }
Зверніть увагу, що функція створення вузла аналізатора createAnalyser пишеться з літерою S, а не Z. Я попався вперше (різниця між американською та британською англійською).
Я назвав вузол аналізатора fft це скорочення від Fast Fourier Transform (швидке перетворення Фур'є).
По швидкому пробіжимося з шаленої математики звуку.
Якщо ви подивитеся на буфер, який містить звук, то побачили б тільки купу семплів, швидше за все сорок чотири тисячі семплів на секунду. Вони є дискретні значення амплітуди. Щоб візуалізувати музику, ми хочемо не прямі семпли, а швидше форму сигналів. Коли ви чуєте особливий тон, то насправді чуєте купу сигналів нарізаних за часом в амплітудні семпли.
Ми хочемо список частот, а не амплітуд, тому нам потрібне перетворення. Звук діє області часу. Дискретне перетворення Фур'є перетворює з області часу область частот. Швидке перетворення Фур'є є приватний алгоритм, який може робити це перетворення дуже швидко. Математика для цього може бути заплутаною, але розумні хлопці з Chrome Team вже зробили це для нас у вузлі аналізатора. Ми просто повинні отримати фінальні значення, коли ми це хочемо.
Докладне пояснення дискретних перетворень Фур'є див.>Wikipedia.
Малювання частот
Тепер давайте щось намалюємо. Для цього ми повернемося до того, що ми дізналися на чолі про анімацію. Створюємо полотно, отримуємо контекст, потім викликаємо функцію малювання кожного кадру.
<code data-language="javascript">var gfx; function setupCanvas() { var canvas=document.getElementById('canvas'); gfx=canvas.getContext('2d'); webkitRequestAnimationFrame(update); }
Щоб отримати дані аудіо, нам потрібно місце, куди помістити їх. Скористаємося Uint8Array — новий тип JavaScript створений для підтримки аудіо та 3D. Замість типового масиву JavaScript, який може зберігати будь-що, Uint8Array спеціально розроблений, щоб зберігати вісім цілих біт, тобто це байт-масив. JavaScript представила цей новий тип масиву для підтримки швидкого доступу до двійкових даних, таких як 3D-буфер, семпли аудіо та кадри відео. Щоб завантажити дані, ми викликаємо fft.getByteFrequencyData(data).
<code data-language="javascript">function update() { webkitRequestAnimationFrame(update) ; if(!setup) return; gfx.clearRect(0,0,800,600); gfx.fillStyle='gray'; gfx.fillRect(0,0,800,600); var data=New Uint8Array(samples); fft.getByteFrequencyData(data); gfx.fillStyle='red'; for(var i=0; i<data.length; i++) { gfx.fillRect(100+i*4,100+256-data[i]*2,3,100); } }
Щойно ми отримаємо дані, ми можемо намалювати їх. Для простоти я намалюю серію смужок, положення яких ґрунтується на поточному значенні семпла даних. Оскільки ми використовуємо Uint8Array, то кожне значення лежить у діапазоні від 0 до 255, тому я множу на два, щоб зробити рух помітнішим. Ось як це виглядає:
Музичні смужки
Непогано для кількох рядків JavaScript. Я не впевнений, чому друга половина пласка. Можливо баг із стерео/моно?
Ось дивовижніша версія. Код той же, я просто змінив як малювати семпли. p>
Візуалізація у стилі WinAmp
Наступні кроки
Насправді з WebAudio ви можете робити набагато більше, ніж я тут описав. Спочатку я пропоную вам пройти через підручники на HTML5 Rocks:
Далі погляньте на 0xFE — Generating Tones with the Web Audio API, щоб дізнатися, як безпосередньо генерувати звук з математичних сигналів. Ще A Web Audio Spectrum Analyzer.
Повна специфікація WebAudio (чернетка)
У наступному розділі ми розглянемо доступ до веб-камери користувача.