Информация по домену noinimod.ru:история с 25.02.2014, есть вес в Яндексе: 10 ТИЦ, есть вес в Google: 2 PR, посещаемость за месяц: 366 просмотров, страниц в Яндексе: 74
 
У домена есть позиции в Яндексе и Google:
Поисковый запросПозиция в ЯндексеПозиция в GoogleРегион
генерация звука3Москва
разработать концепцию сайта3Москва
звук player6Москва
разработка концепции сайта6Москва
как разработать концепцию сайта8Москва
скролл на мышке12Москва
концепция сайта14Москва
шарж 23 февраля14Москва
flash player звук14Москва
червь в яблоке15Москва
скрол мышки16Москва
колесо мыши17Москва
html полоса прокрутки div18Москва
jquery событие колеса мыши711Москва
не работает скролл на мышке18Москва
flash звук19Москва
генерируется это19Москва
рисовать браузере19Москва
div скроллинг20Москва
не работает скролл мыши20Москва
скролл js20Москва
генерация звука2Санкт-Петербург
звук player5Санкт-Петербург
js style left6Санкт-Петербург
скролл js6Санкт-Петербург
скролл на мышке7Санкт-Петербург
javascript scroller7Санкт-Петербург
шарж 23 февраля8Санкт-Петербург
разработка концепции сайта8Санкт-Петербург
червь в яблоке13Санкт-Петербург
концепция сайта14Санкт-Петербург
прокручивается колесико мышки15Санкт-Петербург
генерируется это18Санкт-Петербург
 
Данные о позициях в Яндексе и Google собраны 27.11.2014
 
Михаил Востриков

Генерация звука в Flash Player 10

Все мы давно живем в мире цифровых технологий, которые преобладают над аналоговыми. Применительно к нашей теме, цифровой звук обладает огромными достоинствами — это возможность копирования цифрового оригинала без потерь, возможность сжатия и передачи по цифровым каналам, более простая обработка и редактирование.

Для лучшего понимания материала можно ознакомится с теорией, на мой взгляд неплохая статья. Тем, кому скучно читать теорию, смело двигайтесь дальше.

Немного истории

Перед основной темой хотел бы рассказать немного истории о том, как в FP10 появился прямой доступ к звуковому буферу и, в итоге, возможность генерации звука. Все началось с известного флешера Andre Michelle. Очень интересного и разностороннего человека, за экспериментами которого я постоянно слежу. После появления FP9, пока все изучали новое АПИ, Андре нашел хитрый путь, как в реальном времени генерировать звуковые данные. На эти эксперементы его сподвигли старые работы товарища Frank Baumgartner'a, который тоже использовал некую «технологию» для создания звуковых эффектов еще в далеком 2002 году. После нескольких, полностью работающих примеров (вот один из первых), которые Андре показал общественности, все были в недоумении и задавались вопросом — «Как это вообще возможно?». Исходные файлы пока были не доступны, но после появления первого декомпилера для AS3 «загадка» была раскрыта. Все оказалось достаточно просто, надо было включить фантазию и идти нестандартным путем. В AS3 появился очень гибкий метод — loadBytes класса Loader. Этот метод позволяет создать муви-клип из обычных бинарных данных, находящихся в байтовом массиве ByteArray. Андре создал простой некомпрессированный SWF файл, который содержал в себе звук в формате PCM (это обычный WAV файл без сжатия) пригодный для импорта в сцену как класс. И поместил его в байтовый массив. Немного изучив спецификацию формата SWF, нетрудно найти сами звуковые данные и записывать вместо них необходимую информацию. В итоге алгоритм генерации звука состоял из нескольких этапов и выглядел так:

  1. Генерация звуковых данных в байтовый массив с некоторым смещением от его начала (с учетом спецификации SWF файла).
  2. Загрузка муви-клипа из байтового массива и импорт звука как класса.
  3. Установка функции на событие onSoundComplete.
  4. Запуск воспроизведения звука через метод play().
  5. При окончании проигрывания повторяем все с первого этапа.

Краткий оригинал этой истории можно прочитать в блоге Андре. Подробнее про принцип генерации звука в FP9 читайте в блоге FlashBrighton, тоже интересно.

Технология доказала, что может существовать, быть интересной для людей и положила начало некоторым коммерческим проектам. Но случилось «несчастье», появилась операционная система «Виста», и различные билды 9-го плеера стали вести себя по разному на этой ОС. Событие onSoundComplete стало приходить иногда с задержкой, иногда нормально. Звук «рвался» у половины пользователей сервиса. Последний билд №115 ничего не изменил к лучшему. За всем этим от Андре последовало несколько писем в Адоби, а потом просьба, чтобы в следующей версии флеш-плеера был нормальный доступ к звуковому буферу. Просьбу поддержали и проголосовали за нее не только фаны, но и много других флешеров, оценивших будущие возможности. Для обсуждения проблемы была создана целая кампания по улучшению звуковых возможностей во Флеш, к которой, в последствии, подключилась и Flj. В итоге, спустя год-два, мы получили полноценный доступ к звуковому буферу и теперь можем его использовать без проблем и «хаков» (скажем большое спасибо Tinic Uro  — Flashplayer engineer из Адоб.).

Вот несколько интересных проектов и экспериментов, основанных на доступе к звуковому буферу во Флеш:

  • Hobnox audiotool
  • SpliceMusic service
  • 8bitboy player
  • Andre experiments

Новое звуковое АПИ

Итак, что у нас появилось нового в АПИ, связанного со звуком и актуального нашей  теме?

Новое событие класса Sound — «sampleData», вызывается при запросе аудио-данных от плеера. Как раз оно дает нам возможность генерировать звук в реальном времени. Использовать его очень просто, достаточно создать новый экземпляр класса Sound, который изначально будет «пустым», то есть не будет содержать в себе звуковых данных. Добавить функцию обработки на событие «sampleData». И запустить воспроизведение звука с помощью метода play(), который в свою очередь вернет объект SoundChannel, звуковой канал. Функция-обработчик события на входе будет получать объект SampleDataEvent и используя его свойство data можно записывать звуковые сэмплы в буфер, как в обычный байтовый массив (ByteArray) методом writeFloat(). Потом записанные нами сэмплы будут воспроизведены звуковой картой. Флеш-плеер будет посылать события, пока мы не прекратим записывать сэмплы в звуковой буфер или пока звуковой канал (SoundChannel) не будет остановлен методом stop().

Самый простой пример генерации тона:


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 Гц, обязательно два канала, а сэмплы всегда представлены 32-битным Float числом (числом с плавающей запятой, 4 байта). В итоге на каждый сэмпл звука должно быть записано 8 байт.

Величины, которые мы можем записать в звуковой буфер, должны быть в пределах от -1.0 до 1.0.  Большие значения будут просто обрезаны звуковой картой до пороговых. Если мы записываем нули в буфер, то логично, что мы не услышим звука :).

Количество сэмплов, которые мы можем записать в буфер, может варьироваться в пределах от 2048 до 8192. Причем если мы запишем сэмплы в количестве, меньшем, чем 2048, то сэмплы будут проиграны, а потом флеш-плеер пошлет событие SoundComplete, что будет значить, что наш звук полностью проигран и завершен, и конечно после этого звуковой канал будет остановлен.

Если мы записываем минимальное количество сэмплов, равное 2048, то мы имеем минимальную задержку с момента начала записи в буфер до момента ее воспроизведения через звуковую карту. Эта задержка будет равна примерно 46 миллисекундам (t, сек =2048.0/44100.0). Но при малом количестве сэмплов появляется высокая вероятность щелчков и «разрыва» звука, если процессор компьютера сильно нагружен расчетами или перерисовкой. Перечисленные артефакты очень хорошо заметны на слух. Адоб не рекомендует использовать малое количество сэмплов, так как это может работать на разных конфигурациях компьютеров и ОС по-разному. Если мы будем записывать 8192 сэмпла, то вероятность «разрыва» звука становится минимальной, и на разных платформах это будет работать максимально стабильно, хотя опять же не идеально. Задержка составит уже 186 миллисекунд.

На самом деле длительность задержки критична только в тех случаях, когда звук генерируется по событию от пользователя. Например, при нажатии на элементы интерфейса, или при использовании клавиатуры. В таких случаях пользователю можно предлагать на выбор величину буфера. Для остальных приложений, например, таких как плееры, существующая задержка вообще не имеет значения и нам лучше записывать в буфер максимально возможное количество сэмплов.

Также в АПИ появился новый полезный метод extract(target:ByteArray=null, length:Number=null, startPosition:Number = -1) для объекта Sound(). Он позволяет извлекать звуковые данные из звукового объекта (например, сжатого MP3 звука) и манипулировать ими, как угодно. На входе метода мы должны задать байтовый массив (объект ByteArray), и звуковые данные будут извлечены в массив, начиная с текущей позиции массива. Они всегда будут в формате 44100 Гц, стерео. Формат сэмпла — 32-битное число с плавающей запятой, оно будет преобразовано в тип Number при извлечении сэмпла из байтового массива методом readFloat(). Извлечение данных может занять порой достаточно большое время — 200—500 миллисекунд, и сами данные могут занять приличный объем памяти. Если учесть, что одна секунда несжатого звука занимает примерно 350 килобайт, то пятиминутный звук займет примерно 100 мегабайт. Все это надо учитывать при написании программы.

Как мы это можем применить? К примеру, использовать простой ресемплинг, чтобы выводить звуки в другой тональности.


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() и присоединяем функцию-обработчик события «sampleData». Так же мы создадим переменную, которая будет обозначать текущую позицию в байтовом массиве, содержащем звуковые данные. И еще одну переменную, которая будет шагом приращения позиции. Если шаг равен единице, то мы услышим оригинал звука, если шаг больше единицы, то звук будет воспроизведен в более высокой тональности, а если меньше единицы, то в более низкой. Рассчитать шаг, имея частоту ноты или ее миди-код, очень просто — вот формулы:


// для MIDI-кода ноты
var step:Number = Math.pow(2,(midi_code-69)/12)
// для частоты ноты
var step:Number = frequency/440.0
 

Нам осталось извлечь звуковые данные из массива и записать их в звуковой буфер. Все очень просто, как вы сами видите. В итоге мы можем использовать короткие кусочки звуков для разных инструментов, которые мы слышим в нашей жизни, чтобы создать полноценную музыку. Причем файл будет маленького объема.

В 90-е годы, когда памяти было мало, процессоры были медленные, а объемы жестких дисков тоже оставляли желать лучшего, использовали именно этот метод, чтобы добавить музыку в приложения и игры. Такая музыка называлась трекерной, и существовало несколько популярных форматов музыкальных файлов — MOD, S3M, XM и т.п.  8bitboy player как раз предназначен для проигрывания MOD-файлов.

Также, кроме ресемплинга, мы можем обрабатывать звук перед выводом, например, через программный эквалайзер. Или использовать различные алгоритмы для наложения эффектов, таких как реверберация, фленжер и т.п.

Чип тьюнс

У меня остался еще один пример, из которого и родилась тема статьи. Это эмулятор старого музыкального процессора Yamaha YM2149, который использовался параллельно с компьютерами типа ZX Spectrum в далеких 90-х годах. В итоге, на основе эмулятора, получился простой плеер.

Please, install Adobe Flash Player 10 [and enable JavaScript]

Get Adobe Flash player

Этот плеер я написал для моего друга, который профессионально занимается музыкой на PC, но иногда у него есть желание понастольгировать и послушать свои старые музыкальные треки, с которых начиналась его творческая деятельность. Все его треки вы можете послушать здесь, а также подборку треков других авторов.

Плеер пока находится в постоянной доработке, поэтому исходные файлы размещу позже. Хотя, для ознакомления вы можете посмотреть классы эмулятора, это может быть интересно. Код сильно не критикуйте, плеер создавался на чистом энтузиазме долгими бессонными ночами :).
Дмитрий
20 апреля
Респект.
graFF
20 апреля
спасибо, очень познавательно :)
Александр
20 апреля
Молодцы, классная штука.
Constantiner
20 апреля
Будешь опенсорсить? Ждем с нетерпением :)
Gaen
20 апреля
Да здравствует труъ восьмибитная музыка в играх! :)
akira
20 апреля
Здорово! Только немножко притормаживает :(
Vladimir
21 апреля
Вставить в блог плеер нельзя или я чтото не так сделал?
Пример — http://350d.ru/ay
Михаил Востриков
22 апреля
Насчет опенсорса — классы в общем то опубликованы, их можно поюзать. В самом плеере код пока не причесан, выглядит страшно :) Постараюсь найти время, чтобы все это привести в порядок и документировать.

По поводу вставки в блог — видимо путь к папке\файлу с плейлистом задан неправильный. Ну и в самом плейлисте пути к файлам тоже должны быть корректными.

А вообще требуется уйма времени, чтобы подготовить полный пакет для релиза, который будет содержать конвертер треков в fym формат, сам плеер с поддержкой кастомной цветовой схемы как минимум (а лучше скинование), документацию и т.п. Посмотрим, время покажет.
Vladimir
24 апреля
Все заработало, была ошибка с расширениями файлов при сохранении.
Hyzhak
13 июля
отлично, спасибо =) очень интересная тема, жду открытия кода с нетерпением, но пока тоже поэкспериментирую. кстати ваш плеер уже и на http://zxtunes.com/ во всю мощь пашет =)