Наше практическое погружение описывает необычный сценарий — мы будем говорить не о том, что может HTML5, а о тех возможностях, которые на сегодня он еще не предоставляет и как эту неувязку можно обходить на практике.
HTML5 сегодня — это сериал, концовку которого не знают даже сценаристы, история, в которой есть как практически готовые главы и главы в черновых набросках, так и просто заметки для будущих сюжетов.
Визуализация аудио, точнее принципиальный низкоуровневый доступ к аудио-информации, находится где-то между черновыми набросками и заметками на будущее.
На самом деле, надо честно сказать, что это не единственные проблемы и сложности — если вы пробовали сделать сколь-нибудь сложное приложение с использованием нескольких аудиопотоков и необходимостью их синхронизации, почти наверняка, вы столкнетесь со сложностями в реализации.
Причем это будет зависеть не только от возможностей, предоставляемых спецификацией, но и (в большей степени) от реализации в конкретном браузере — не случайно Rovio и Google, делая Angry Birds на HTML5, оптимизированную для Chrome, отказались от идеи использовать для звуков audio-элементы HTML5. Вместо этого «Angry Birds на HTML5» использует Flash. (См. также обсуждение в блоге разработчиков.)
Для более глубокого погружения в тему <audio>-элемента также рекомендую статью Unlocking the power of HTML5 <audio>, описывающую основные приемы работы аудио в HTML5.
В настоящее время работа над созданием низкоуровневого API для доступа к аудиопотоку уже активно ведется в рамках Audio-группы W3C.
Разрабатываемое API будет предоставлять не только возможность получить низкоуровневый доступ к аудио-потоку, но и синтезировать аудио на лету.
На сегодня Mozilla и Google уже успели предоставить собственные версии API для доступа к аудио-информации.
Audio Data API от Mozilla предоставляет простой доступ к аудио-потоку на чтение и запись, задача реализации алгоритмов обработки аудио в реальном времени должна при этом решаться на стороне скрипта (на JavaScript). Спецификация для Webkit — Web Audio API от Google — предоставляет высокоуровневый API, при котором основные задачи обработки могут выполняться нативно браузером.
Рабочая группа W3C работает над выработкой общего подхода, в котором будет предоставлен двуслойный API для обеспечения более широких возможностей.
К слову, область деятельности группы помимо клиентского API для работы с Audio включает также задачи доступа к аудио-устройствам, включая микрофоны и другие источники звука, и работы с колонками, в том числе в многоканальном режиме.
Следить за новостями группы можно в твиттере @w3caudio.
Но это все лирика, давайте к практике!
Да-да! Вот так банально. Предобработка аудио-информации с последующей генерацией визуализации, синхронизированной с воспроизводимым аудио-потоком.
На самом деле, если речь идет о смысловом извлечении информации (например, текста песни), то предобработка — единственный практичный выход, причем как правило, эта обработка делается ручками.
В целом, если аудио-файл и механизм визуализации известны заранее, предобработка — это не просто хороший способ, но и единственный разумный, экономящий вычислительные ресурсы и, следовательно, снижающий нагрузку на клиентские машины.
Давайте смотреть, как это работает.
Chell in the Rain — это красивая аудио-текстовая визуализация песни Exile Vilify. Синхронно с аудио-потоком на экране возникают слова из текста песни.
Что внутри
Как все работает
Пропуская инициализацию аудио и обработчики событий для управления воспроизведением.
Вся песня предварительно побита на фрагменты, соответствующие началу той или иной фразы или этапу анимации. Начало каждого фрагмента хранится в массиве:
Отдельно хранится массив фраз из текста песни:
С привязкой к таймингу и на основании текущего момента в аудио-композиции срабатывает триггер на переход к новой фразе:
Далее в нужный момент срабатывает тот или иной триггер, запускающий средствами jQuery соответствующую анимацию:
Довольно просто и эффектно, согласитесь! Самое главное во всей этой истории — это легкость совмещения аудио-потока и возможностей HTML, CSS и JavaScript.
Music Can Be Fun — мини-игра на стыке искусства и музыки. Предлагаю сначала немного поиграться, чтобы было понятно, о чем пойдет речь ;)
Пример посложнее — и здесь уже активно используются возможности Canvas, но так как нас интересует только музыкальная составляющая, то все не так страшно!
Как и в предыдущем случае, здесь по ходу композиции воспроизводится текст песни, для чего в дебрях JS-кода зашита соответствующая привязка ко времени:
Помимо текста, если вы поигрались в игрушку, вы не могли не заметить спец-эффектов, также привязанных к музыкальной композиции и соответствующим переходам. Привязка делается абсолютно аналогично:
(На самом деле, ко времени привязаны даже частоты появления синих и красных шариков ;)
При обновлении момента проигрывания (событие onTimeUpdate) происходит применение тех или иных визуализаций:
По-прежнему просто и эффективно. Один и тот же прием легко применим не только к текстовой информации, но и к различным визуальным эффектам.
Остается понять, можно ли второе как-то автоматизировать, чтобы уж совсем все не делать ручками. Очевидно, можно — и Grant Skinner в своем блоге подсказывает, как это сделать ;)
В своем блоге в посте Music Visualizer in HTML5 / JS with Source Code Грант делится своим опытом в визуализации аудио с помощью HTML5.
Столкнувшись с тем, что HTML5 Audio не предоставляет API для экстракции низкоуровневых данных о проигрываемой композиции, Грант написал небольшое AIR-приложение (архив также содержит примеры), позволяющее вытащить из mp3-файла информацию об уровнях звука в текстовом виде или в виде изображения.
В увеличенном масштабе информация о музыкальной композиции выглядит так:
Имея данные в таком виде, их можно легко извлечь, например, средствами Canvas. В текстовом виде все еще проще (пример не привожу, т.к. данные в текстовом файле упакованы).
Чтобы работать с такими предобработанным данными, Грант написал специальную библиотеку на JavaScript (VolumeData.js в архиве).
Работа с библиотекой осуществляется довольно просто. Начинается все с загрузки информации о композиции:
где, внутри функции loadMusic, как вы уже догадались, загружается обычная картинка:
После загрузки всех необходимых компонент, из изображения извлекаются данные о звуке:
Далее в нужный момент времени из этих данных можно получить как усредненную информацию, так и информацию об уровне звука в левом и правых каналах:
Визуальные эффекты привязываются к этим данным. Для визуализации используется библиотека EaselJS.
Посмотреть, как это работает на практике, можно в примерах Star Field и Atomic.
HTML5 сегодня — это сериал, концовку которого не знают даже сценаристы, история, в которой есть как практически готовые главы и главы в черновых набросках, так и просто заметки для будущих сюжетов.
Визуализация аудио, точнее принципиальный низкоуровневый доступ к аудио-информации, находится где-то между черновыми набросками и заметками на будущее.
Что может и что не может <audio>
<audio>-элемент HTML5, как вы, наверняка, уже догадались, сам по себе никакого низкоуровневого API не предоставляет. Он позволяет только управлять воспроизведением аудио-потока: запускать, ставить на паузу, останавливать проигрывание, узнавать текущую позицию и общую длительность композиции.На самом деле, надо честно сказать, что это не единственные проблемы и сложности — если вы пробовали сделать сколь-нибудь сложное приложение с использованием нескольких аудиопотоков и необходимостью их синхронизации, почти наверняка, вы столкнетесь со сложностями в реализации.
Причем это будет зависеть не только от возможностей, предоставляемых спецификацией, но и (в большей степени) от реализации в конкретном браузере — не случайно Rovio и Google, делая Angry Birds на HTML5, оптимизированную для Chrome, отказались от идеи использовать для звуков audio-элементы HTML5. Вместо этого «Angry Birds на HTML5» использует Flash. (См. также обсуждение в блоге разработчиков.)
Для более глубокого погружения в тему <audio>-элемента также рекомендую статью Unlocking the power of HTML5 <audio>, описывающую основные приемы работы аудио в HTML5.
Стандарты на извлечение звука
В настоящее время работа над созданием низкоуровневого API для доступа к аудиопотоку уже активно ведется в рамках Audio-группы W3C.
Разрабатываемое API будет предоставлять не только возможность получить низкоуровневый доступ к аудио-потоку, но и синтезировать аудио на лету.
The audio API will provide methods to read audio samples, write audio data, create sounds, and perform client-side audio processing and synthesis with minimal latency. It will also add programmatic access to the PCM audio stream for low-level manipulation directly in script.
На сегодня Mozilla и Google уже успели предоставить собственные версии API для доступа к аудио-информации.
Audio Data API от Mozilla предоставляет простой доступ к аудио-потоку на чтение и запись, задача реализации алгоритмов обработки аудио в реальном времени должна при этом решаться на стороне скрипта (на JavaScript). Спецификация для Webkit — Web Audio API от Google — предоставляет высокоуровневый API, при котором основные задачи обработки могут выполняться нативно браузером.
Рабочая группа W3C работает над выработкой общего подхода, в котором будет предоставлен двуслойный API для обеспечения более широких возможностей.
К слову, область деятельности группы помимо клиентского API для работы с Audio включает также задачи доступа к аудио-устройствам, включая микрофоны и другие источники звука, и работы с колонками, в том числе в многоканальном режиме.
Следить за новостями группы можно в твиттере @w3caudio.
Но это все лирика, давайте к практике!
Практический подход: что работает сегодня?
Практический подход, работающий сегодня — это предобработка.Да-да! Вот так банально. Предобработка аудио-информации с последующей генерацией визуализации, синхронизированной с воспроизводимым аудио-потоком.
На самом деле, если речь идет о смысловом извлечении информации (например, текста песни), то предобработка — единственный практичный выход, причем как правило, эта обработка делается ручками.
В целом, если аудио-файл и механизм визуализации известны заранее, предобработка — это не просто хороший способ, но и единственный разумный, экономящий вычислительные ресурсы и, следовательно, снижающий нагрузку на клиентские машины.
Давайте смотреть, как это работает.
Пример из жизни: Chell in the Rain
Chell in the Rain — это красивая аудио-текстовая визуализация песни Exile Vilify. Синхронно с аудио-потоком на экране возникают слова из текста песни.
Что внутри
- jQuery + Sizzle.js (для селекторов)
- jPlayer (для проигрывания Audio и Video)
- собственный код, который нам, собственно и интересен ;)
Как все работает
Пропуская инициализацию аудио и обработчики событий для управления воспроизведением.
Вся песня предварительно побита на фрагменты, соответствующие началу той или иной фразы или этапу анимации. Начало каждого фрагмента хранится в массиве:
var timings = newArray();
timings[0] = 11.5;
timings[1] = 17;
timings[2] = 24;
timings[3] = 29;
timings[4] = 35.5;
...
Отдельно хранится массив фраз из текста песни:
var lyrics = newArray();
lyrics[0] = 'Exile';
lyrics[1] = 'It takes your mind... again';
lyrics[2] = "You've got sucker's luck";
lyrics[3] ='Have you given up?';
...
С привязкой к таймингу и на основании текущего момента в аудио-композиции срабатывает триггер на переход к новой фразе:
if(event.jPlayer.status.currentTime >= timings[currentTrigger] && nolyrics != true) {
fireTrigger(currentTrigger);
currentTrigger++;
}
Далее в нужный момент срабатывает тот или иной триггер, запускающий средствами jQuery соответствующую анимацию:
function fireTrigger(trigger) {
switch (trigger) {
case 0:
$('#lyrics1 p').addClass('vilify').html(lyrics[0]).fadeIn(1500);
break;
case 1:
$('#lyrics2 p').html(lyrics[1]).fadeIn(1000).delay(5000).fadeOut(1000);
$('#lyrics1 p').delay(6000).fadeOut(1000);
break;
case 2:
$('#lyrics1 p').fadeIn(1000);
break;
case 3:
$('#lyrics2 p').fadeIn(1000).delay(4000).fadeOut(1000);
$('#lyrics1 p').delay(5000).fadeOut(1000);
break;
case 4:
$('#lyrics1 p').removeClass('vilify').html(lyrics[2]).fadeIn(1000);
break;
case 5:
$('#lyrics2 p').html(lyrics[3]).fadeIn(1000).delay(3000).fadeOut(1000);
$('#lyrics1 p').delay(4000).fadeOut(1000);
break;
...
Довольно просто и эффектно, согласитесь! Самое главное во всей этой истории — это легкость совмещения аудио-потока и возможностей HTML, CSS и JavaScript.
Пример из жизни: Music Can Be Fun
Music Can Be Fun — мини-игра на стыке искусства и музыки. Предлагаю сначала немного поиграться, чтобы было понятно, о чем пойдет речь ;)
Пример посложнее — и здесь уже активно используются возможности Canvas, но так как нас интересует только музыкальная составляющая, то все не так страшно!
Как и в предыдущем случае, здесь по ходу композиции воспроизводится текст песни, для чего в дебрях JS-кода зашита соответствующая привязка ко времени:
var _lyrics = [
["00:17.94", "00:22.39", "When I have once or twice"],
["00:23.93", "00:30.52", "Thought I lived my life .. for"],
["00:40.74", "00:47.38", "Oh oh I'll wake up in a thousand years"],
["00:48.40", "00:52.06", "With every ghost I'm looking through"],
["00:53.33", "00:57.80", "I was a cold, cold boy"],
["00:59.52", "01:03.00", "Hey! Oh when I lie with you"],
...
Помимо текста, если вы поигрались в игрушку, вы не могли не заметить спец-эффектов, также привязанных к музыкальной композиции и соответствующим переходам. Привязка делается абсолютно аналогично:
var _effects = [
["00:06.00", 1],
["00:30.50", 1],
["00:42.50", 1],
["00:54.50", 2],
["00:57.00", 1],
...
(На самом деле, ко времени привязаны даже частоты появления синих и красных шариков ;)
При обновлении момента проигрывания (событие onTimeUpdate) происходит применение тех или иных визуализаций:
var _onTimeUpdate = function() {
var t = MusicManager.currentTime = _song.currentTime;
...
for (var i = _lyricsId; i < _lyrics.length; i++) {
if (MusicManager.currentTime < _lyrics[i][0]) break;
if (MusicManager.currentTime < _lyrics[i][1]) {
SubtitleManager.changeSubtitle(_lyrics[i][2]);
} else {
SubtitleManager.changeSubtitle("");
_lyricsId++;
}
}
for (var i = _effectsId; i < _effects.length; i++) {
if (MusicManager.currentTime < _effects[i][0]) break;
MusicManager.isEffect1Used = false;
MusicManager.isEffect2Used = !_effects[i][1] == 2;
_effectsId++;
}
}
...
}
По-прежнему просто и эффективно. Один и тот же прием легко применим не только к текстовой информации, но и к различным визуальным эффектам.
Остается понять, можно ли второе как-то автоматизировать, чтобы уж совсем все не делать ручками. Очевидно, можно — и Grant Skinner в своем блоге подсказывает, как это сделать ;)
Пример из жизни: извлечение данных
В своем блоге в посте Music Visualizer in HTML5 / JS with Source Code Грант делится своим опытом в визуализации аудио с помощью HTML5.
Столкнувшись с тем, что HTML5 Audio не предоставляет API для экстракции низкоуровневых данных о проигрываемой композиции, Грант написал небольшое AIR-приложение (архив также содержит примеры), позволяющее вытащить из mp3-файла информацию об уровнях звука в текстовом виде или в виде изображения.
В увеличенном масштабе информация о музыкальной композиции выглядит так:
Имея данные в таком виде, их можно легко извлечь, например, средствами Canvas. В текстовом виде все еще проще (пример не привожу, т.к. данные в текстовом файле упакованы).
Чтобы работать с такими предобработанным данными, Грант написал специальную библиотеку на JavaScript (VolumeData.js в архиве).
Работа с библиотекой осуществляется довольно просто. Начинается все с загрузки информации о композиции:
loadMusic("music.jpg");
где, внутри функции loadMusic, как вы уже догадались, загружается обычная картинка:
function loadMusic(dataImageURL) {
image = new Image();
image.src = dataImageURL;
playing = false;
Ticker.addListener(window);
}
После загрузки всех необходимых компонент, из изображения извлекаются данные о звуке:
volumeData = newVolumeData(image);
Далее в нужный момент времени из этих данных можно получить как усредненную информацию, так и информацию об уровне звука в левом и правых каналах:
var t = audio.currentTime;
var vol = volumeData.getVolume(t);
var avgVol = volumeData.getAverageVolume(t-0.1,t);
var volDelta = volumeData.getVolume(t-0.05);
volDelta.left = vol.left-volDelta.left;
volDelta.right = vol.right-volDelta.right;
Визуальные эффекты привязываются к этим данным. Для визуализации используется библиотека EaselJS.
Посмотреть, как это работает на практике, можно в примерах Star Field и Atomic.