Генерация звука в Flash Player 10 пятница
17 апреля
Все мы давно живем в мире цифровых технологий, которые преобладают над аналоговыми. Применительно к нашей теме, цифровой звук обладает огромными достоинствами — это возможность копирования цифрового оригинала без потерь, возможность сжатия и передачи по цифровым каналам, более простая обработка и редактирование.
Для лучшего понимания материала можно ознакомится с теорией, на мой взгляд неплохая статья. Тем, кому скучно читать теорию, смело двигайтесь дальше.
Немного истории
Перед основной темой хотел бы рассказать немного истории о том, как в FP10 появился прямой доступ к звуковому буферу и, в итоге, возможность генерации звука. Все началось с известного флешера Andre Michelle. Очень интересного и разностороннего человека, за экспериментами которого я постоянно слежу. После появления FP9, пока все изучали новое АПИ, Андре нашел хитрый путь, как в реальном времени генерировать звуковые данные. На эти эксперементы его сподвигли старые работы товарища Frank Baumgartner'a, который тоже использовал некую «технологию» для создания звуковых эффектов еще в далеком 2002 году. После нескольких, полностью работающих примеров (вот один из первых), которые Андре показал общественности, все были в недоумении и задавались вопросом — «Как это вообще возможно?». Исходные файлы пока были не доступны, но после появления первого декомпилера для AS3 «загадка» была раскрыта. Все оказалось достаточно просто, надо было включить фантазию и идти нестандартным путем. В AS3 появился очень гибкий метод — loadBytes класса Loader. Этот метод позволяет создать
- Генерация звуковых данных в байтовый массив с некоторым смещением от его начала (с учетом спецификации SWF файла).
- Загрузка
муви-клипа из байтового массива и импорт звука как класса. - Установка функции на событие onSoundComplete.
- Запуск воспроизведения звука через метод play().
- При окончании проигрывания повторяем все с первого этапа.
Краткий оригинал этой истории можно прочитать в блоге Андре. Подробнее про принцип генерации звука в FP9 читайте в блоге FlashBrighton, тоже интересно.
Технология доказала, что может существовать, быть интересной для людей и положила начало некоторым коммерческим проектам. Но случилось «несчастье», появилась операционная система «Виста», и различные билды
Вот несколько интересных проектов и экспериментов, основанных на доступе к звуковому буферу во Флеш:
- Hobnox audiotool
- SpliceMusic service
- 8bitboy player
- Andre experiments
Новое звуковое АПИ
Итак, что у нас появилось нового в АПИ, связанного со звуком и актуального нашей теме?
Новое событие класса Sound — «sampleData», вызывается при запросе
Самый простой пример генерации тона:
var sound:Sound = new Sound();
function soundUpdate(event:SampleDataEvent):void {
for ( var c:int=0; c<3072; c++ ) {
var sample:Number = Math.sin((Number(c+event.position)/Math.PI/2.0))*0.25;
event.data.writeFloat(sample); // записываем значение семпла в левый канал
event.data.writeFloat(sample); // и правый канал
}
}
sound.addEventListener('sampleData',soundUpdate);
var soundChannel:SoundChannel = sound.play();
Вот более продвинутый вариант примера, в котором частота генерируемого тона зависит от положения мышки над флешкой:
var phaseL:Number = 0;
var phaseR:Number = 0;
var incrementL:Number = 0;
var incrementR:Number = 0;
function soundUpdate(event:SampleDataEvent):void {
for ( var c:int = 0; c<3072; c++ ) {
// получим новое приращение исходя из позиции мышки
var incrementNewL:Number = 0.03 + 0.2 * stage.mouseY / stage.stageHeight;
var incrementNewR:Number = 0.03 + 0.2 * stage.mouseX / stage.stageWidth;
// нарастим текущее приращение (фильтр требуется для более плавного изменения тона)
incrementL += (incrementNewL — incrementL) * 0.0002;
incrementR += (incrementNewR — incrementR) * 0.0002;
// рассчитаем значения семплов для правого и левого звукового канала
var sampleL:Number = Math.sin(phaseL += incrementL);
var sampleR:Number = Math.sin(phaseR += incrementR);
// забишем семплы в звуковой буфер
event.data.writeFloat(sampleL);
event.data.writeFloat(sampleR);
}
}
var sound:Sound = new Sound();
sound.addEventListener('sampleData', soundUpdate);
var soundChannel:SoundChannel = sound.play();
Формат звука, используемый для генерации, всегда будет иметь частоту дискретизации 44100 Гц, обязательно два канала, а сэмплы всегда представлены
Величины, которые мы можем записать в звуковой буфер, должны быть в пределах от -1.0 до 1.0. Большие значения будут просто обрезаны звуковой картой до пороговых. Если мы записываем нули в буфер, то логично, что мы не услышим звука :).
Количество сэмплов, которые мы можем записать в буфер, может варьироваться в пределах от 2048 до 8192. Причем если мы запишем сэмплы в количестве, меньшем, чем 2048, то сэмплы будут проиграны, а потом
Если мы записываем минимальное количество сэмплов, равное 2048, то мы имеем минимальную задержку с момента начала записи в буфер до момента ее воспроизведения через звуковую карту. Эта задержка будет равна примерно 46 миллисекундам (t, сек =2048.0/44100.0). Но при малом количестве сэмплов появляется высокая вероятность щелчков и «разрыва» звука, если процессор компьютера сильно нагружен расчетами или перерисовкой. Перечисленные артефакты очень хорошо заметны на слух. Адоб не рекомендует использовать малое количество сэмплов, так как это может работать на разных конфигурациях компьютеров и ОС
На самом деле длительность задержки критична только в тех случаях, когда звук генерируется по событию от пользователя. Например, при нажатии на элементы интерфейса, или при использовании клавиатуры. В таких случаях пользователю можно предлагать на выбор величину буфера. Для остальных приложений, например, таких как плееры, существующая задержка вообще не имеет значения и нам лучше записывать в буфер максимально возможное количество сэмплов.
Также в АПИ появился новый полезный метод extract(target:ByteArray=null, length:Number=null, startPosition:Number = -1) для объекта Sound(). Он позволяет извлекать звуковые данные из звукового объекта (например, сжатого MP3 звука) и манипулировать ими, как угодно. На входе метода мы должны задать байтовый массив (объект ByteArray), и звуковые данные будут извлечены в массив, начиная с текущей позиции массива. Они всегда будут в формате 44100 Гц, стерео. Формат сэмпла —
Как мы это можем применить? К примеру, использовать простой ресемплинг, чтобы выводить звуки в другой тональности.
var sample:ByteArray = new ByteArray();
var sampleCount:Number = 119056;
var sampleLoop:Number = 89840;
var samplePosition:Number = 0;
var sampleIncrement:Number = 0;
var sampleVolume:Number = 0;
function soundUpdate(event:SampleDataEvent):void {
for (var i:int=0; i<3072; i++) {
samplePosition += sampleIncrement;
if (samplePosition>=sampleCount) {
samplePosition=sampleLoop;
sampleVolume-=0.3;
if (sampleVolume<=0) {
sampleVolume=0;
}
}
sample.position=Math.round(uint(samplePosition)<<3);
event.data.writeFloat( sampleVolume*sample.readFloat() );
}
}
var sound:Sound = new SoundPiano();
sound.extract(sample,sampleCount,0);
sound = new Sound();
sound.addEventListener('sampleData',soundUpdate);
sound.play();
В начале, мы извлекаем звуковые данные из звуковых объектов. Потом, как в предыдущем примере, создаем объект Sound() и присоединяем
// для MIDI-кода ноты
var step:Number = Math.pow(2,(midi_code-69)/12)
// для частоты ноты
var step:Number = frequency/440.0
Нам осталось извлечь звуковые данные из массива и записать их в звуковой буфер. Все очень просто, как вы сами видите. В итоге мы можем использовать короткие кусочки звуков для разных инструментов, которые мы слышим в нашей жизни, чтобы создать полноценную музыку. Причем файл будет маленького объема.
В
Также, кроме ресемплинга, мы можем обрабатывать звук перед выводом, например, через программный эквалайзер. Или использовать различные алгоритмы для наложения эффектов, таких как реверберация, фленжер и т.п.
Чип тьюнс
У меня остался еще один пример, из которого и родилась тема статьи. Это эмулятор старого музыкального процессора Yamaha YM2149, который использовался параллельно с компьютерами типа ZX Spectrum в далеких
Please, install Adobe Flash Player 10 [and enable JavaScript]
Этот плеер я написал для моего друга, который профессионально занимается музыкой на PC, но иногда у него есть желание понастольгировать и послушать свои старые музыкальные треки, с которых начиналась его творческая деятельность. Все его треки вы можете послушать здесь, а также подборку треков других авторов.
Плеер пока находится в постоянной доработке, поэтому исходные файлы размещу позже. Хотя, для ознакомления вы можете посмотреть классы эмулятора, это может быть интересно. Код сильно не критикуйте, плеер создавался на чистом энтузиазме долгими бессонными ночами :).- graFF
20 апреля - спасибо, очень познавательно :)
- Александр
20 апреля - Молодцы, классная штука.
- Constantiner
20 апреля - Будешь опенсорсить? Ждем с нетерпением :)
- Gaen
20 апреля - Да здравствует труъ восьмибитная музыка в играх! :)
- akira
20 апреля - Здорово! Только немножко притормаживает :(
- Vladimir
21 апреля - Вставить в блог плеер нельзя или я чтото не так сделал?
Пример — http://350d.ru/ay
- Vladimir
24 апреля - Все заработало, была ошибка с расширениями файлов при сохранении.
- Hyzhak
13 июля - отлично, спасибо =) очень интересная тема, жду открытия кода с нетерпением, но пока тоже поэкспериментирую. кстати ваш плеер уже и на http://zxtunes.com/ во всю мощь пашет =)
20 апреля