суббота, 8 сентября 2012 г.

Файлы как мелодия

Многие наверняка видели ролик на ютубе, посвященный визуализации и представлении результатов работы различных алгоритмов в звуковом ряде. Вообще, занятный опыт. Однако, по большому счету не ново, если совершить небольшой экскурс в компьютерную историю (вспомним Спектрумы и магнитофоны в качестве дисководов, и т.п.). Понятно, что в случае магнитофонных записей тогдашних ПК, мы имели байт-код на ленте, который вследствие способа хранения, воспринимался нами на слух как "звуковой набор данных". И речь даже не о 8-ми битной и трекерной игровой музыке. Нам стало любопытно, а как бы звучали программы сейчас? Да что там программы, вообще любые файлы. Как известно из любой книги по ЦОС, к примеру, из [1], в случае, представления фиксированного результата функции во времени, ее можно перенести в частотно-временную область. Например, условиться, что результат функции, значение – есть частота на текущий момент. Оно, значение, может занимать некоторые экстремумы, вот от них и будем отталкиваться...

Авторский материал из ж."Магия ПК", 2012, №8.
Краткий экскурс…

Каким образом будет реализовываться алгоритм представления бинарных данных в звуке? Откройте в блокноте любой файл, совершенно любой, и выберите вариант представления в HEX (16-тиричной кодировке). Как вот на рисунке 2:


Рис. 2. Сигнатура файла (кодировка HEX)

Что мы имеем? А имеем бинарное представление информации в виде набора байт в HEX отображении, составляющих сигнатуру файла. Каждый из этих байт может иметь значение не более 255 = $FF. Если мы будем последовательно считывать с начала и до конца файла все эти наборы, то условно получим значение функции в текущий (мгновенный) момент времени. Теперь вспомним, диапазон восприятия человеческого слуха составляет ~ от 20 до 20000 Гц, условно (в серой же массе восприимчивость еще меньше). Если значение функции перейдет в область ультразвука или инфразвука, вы его не услышите. Кроме того, нужно учитывать возможности самой аудиокарты (не всякий интегрированный чип потянет из-за ограниченной частоты дискретизации). Вот тут нам и понадобятся максимумы и минимумы. Условно примем максимум результата функции (те самые $FF) за 20 кГц, минимум (пусть будет $0) за 20 Гц. Таким образом, необходимо будет провести нормирование результата функции для звукового диапазона (см. формулу 1):
freq:= func() * koef / $ff; (1)
где: freq – частота в Гц, cfunc() – значение функции (от нуля до $FF),koef – условный коэффициент (для смещения области восприятия вверх или вниз).

В итоге, достаточно воспользоваться Waveform Audio API (см. MSDN) или, что еще лучше, MIDI*- устройством для генерации заданного тона в текущий момент времени, и вы его услышите.
* MIDI (Musical Instrument Digital Interface) – цифровой интерфейс музыкальных инструментов. Разработан в 1983 году ведущими производителями электронных музыкальных инструментов – Yamaha, Roland, Korg, E-mu и др. Изначально был предназначен для замены, принятого в то время, управления музыкальными инструментами при помощи аналоговых сигналов, управлением при помощи информационных сообщений, передаваемых по цифровому интерфейсу.
MIDI представляет собой событийно-ориентированный протокол связи между инструментами.  Спецификация MIDI состоит из аппаратной спецификации самого интерфейса и спецификации формата данных, или протокола – описания системы передаваемых сообщений. Соответственно, различается аппаратный MIDI- интерфейс и формат MIDI-данных (так называемая MIDI-партитура); интерфейс используется для физического соединения источника и приемника сообщений [2].

Предпосылки и реализация ПО

Итак, приступим к основной задаче. Для работы нам понадобится следующее:
  1. Отладка и компиляция тестового проекта с помощью IDE среды TurboDelphi Lite (TDL предназначен для некоммерческого использования) [3];
  2. MSDN-справка по WinAPI функциям http://msdn.microsoft.com/en-us/library/windows/
  3. Файлы для тестирования.
Всю разработку осуществим для консоли на WinAPI. Формы нам не понадобятся. Да и зачем? Нужно лишь воспроизведение тона (звука). Да и сам код будет мизерным. Прежде всего, запустим среду TurboDelphi-Lite portable и создадим пустой проект (cм. рисунок 3):


Рис. 3. Окно проекта в IDE TDL

Для упрощения работы с программой вызов диалога выбора файла для “экзекуции” определим при запуске (в самом начале). API реализация данного вызова представлена в листинге-1:
ЛИСТИНГ-1
// API диалог ---------------------------------------
Uses commdlg;

function get_file(dir: string): string;
var ofn: openfilename;
begin
 // выделяем память под структуру
 zeromemory(@ofn, sizeof(openfilename));

 with ofn do begin
  lstructsize:=sizeof(openfilename);
  lpstrinitialdir:= pansichar(dir);
  // текстовое значение заголовка диалога выбора
  lpstrtitle:= 'выберите файл для воспроизведения...';
  nmaxfile:= 255;
  lpstrfile:=virtualalloc(0, 255, mem_commit, page_readwrite);
  // устанавиливаем маску для показа всех файлов
  lpstrfilter:='ALL'#0+'*.*'+#0#0;
  flags:= ofn_filemustexist + ofn_hidereadonly + ofn_pathmustexist
 end;

 if getopenfilename(ofn) then
  result:= ofn.lpstrfile;
 virtualfree(ofn.lpstrfile, 0, mem_release)
end;
Для работы с MIDI подключим к проекту модуль MMSYSTEM. Оттуда нам понадобятся всего три функции: midiOutOpen() – для открытия устройства MIDI, midiOutClose() – для закрытия, midiOutShortMsg() – для передачи команд устройству. Синтаксис использования данных функций представлен в [4…6].

К слову сказать, количество доступных инструментов для MIDI равно 127-ми:
'AcousticGrandPiano', 'BrightAcousticPiano', 'ElectricGrandPiano', 'HonkyTonkPiano', , 'VoiceOohs', 'SynthVoice', 'OrchestraHit', 'Trumpet', 'Trombone', 'MutedTrumpet', 'FrenchHorn', 'BrassSection', 'SynthBrass1', 'SynthBrass2', 'SopranoSax', 'AltoSax', 'TenorSax', 'BaritoneSax', 'EnglishHorn', 'Bassoon', 'Clarinet', 'Piccolo', 'Flute', 'Recorder', 'PanFlute', 'BlownBottle', 'Shakuhachi', 'Whistle', 'Ocarina', 'SquareLead', 'SawtoothLead', 'CalliopeLead', 'ChiffLead', 'CharangLead', 'VoiceLead', 'FifthsLead', 'BassandLead', 'NewAgePad', 'WarmPad', 'PolySynthPad', 'ChoirPad', 'BowedPad', 'MetallicPad', 'HaloPad', 'SweepPad', 'SynthFXRain', 'SynthFXSoundtrack', 'SynthFXCrystal', 'SynthFXAtmosphere', 'SynthFXBrightness', 'SynthFXGoblins', 'SynthFXEchoes', 'SynthFXSciFi', 'Sitar', 'Banjo', 'Shamisen', 'Kalimba', 'Bagpipe', 'Fiddle', 'Shanai', 'TinkleBell', 'Agogo', 'SteelDrums', 'Woodblock', 'TaikoDrum', 'MelodicTom', 'SynthDrum', 'ReverseCymbal', 'GuitarFretNoise', 'BreathNoise', 'Seashore', 'BirdTweet', 'TelephoneRing', 'Helicopter', 'Applause', 'Gunshot'.
Не правда-ли, неплохой наборчик? Выбор инструмента из данного массива осуществляется по нехитрому алгоритму (см. формулу 2):
Instr = $C0 + $100 * n; (2)
где: Instr – значение для выбора инструмента из набора, $C0 и $100 – значение коэффициентов, n – номер инструмента в вышеуказанном массиве.

Установка частоты (ноты), громкости и канала воспроизведения осуществляется по следующему алгоритму (см. формулу 3):
Msg:= $90 + $100*nota + $10000*vol + chan; (3)
где: Msg – значение сообщения для настройки MIDI устройства, nota – нормированная частота из формулы (1), vol – величина громкости в канале (0..255), chan – номер канала** (по-умолчанию, примем за нуль).
** Причем, сообщения в канале делятся на два типа: голосовые и сообщения режима канала. Первые связаны со звукообразованием, вторые являются управляющими для MIDI-устройства. Голосовое сообщение заставляет тон-генератор произвести какое-либо изменение в звуке. Например, если исполнитель нажимает клавишу, в тон-генератор посылается сообщение Note On, на которое тот реагирует воспроизведением ноты. Если исполнитель поворачивает колесо модуляции, тон-генератор изменяет глубину модуляции звучащей ноты [7].
Чтение байтов из файла осуществим в цикле мультимедийного таймера, для этого используем функцию TimeSetEvent() [8] через обычный класс TMemoryStream. Метод Read() данного класса достаточно быстр при работе с любыми файлами. Реализация подобного подхода представлена в листинге 2:
ЛИСТИНГ-2
{ ОСНОВНОЙ АЛГОРИТМ}

var fDAT: tmemorystream;
    buf: byte; // размер
    freq, i, FTimer: integer;
    t: string;

    // параметры для midi
    _Out: HMIDIOUT;
    msg : DWORD;       // вывод управления
    nota: dword;       // нота
    vol : dword = 127; // громкость
    chan: dword = 0;   // канал
    n   : dword;       // номер инструмента

type
  TFNTimeCallBack = procedure(uTimerID, uMessage: UINT;
    dwUser, dw1, dw2: DWORD) stdcall;
 function timeSetEvent(uDelay, uResolution: UINT;
  // нормировка
 freq:= buf * 79 div $ff;

 nota:= freq;
 Msg:= $90 + $100*nota + $10000*vol + chan; // стоп: $80+$100*nota+chan

 n:= 10; // n - номер инструмента
 midiOutShortMsg(_Out, $C0 + $100 * n);

 // воспроизведение
 midiOutShortMsg(_Out, Msg)
end;

begin
 // вызов диалога выбора файла
 t:= get_file(paramstr(0));
 // если ничего не выбрали, завершаем программу
 if then exit;

 // создаем класс для загрузки файла
 fDAT:= TMemoryStream.Create;
 // загружаем файл
 fDAT.LoadFromFile(t);

 // открываем MIDI по-умолчанию и выдираем хэндл на него
 midiOutOpen(@_Out,0,0,0,0);

 i:= 0;
 // период таймера устанавливаем = 400 мс
 FTimer:= timeSetEvent(400, 0, @RemoteTimeProc, 0, 1);

 // пока не будет считан последний байт, таймер не убивать
 while (i < fdat.Size-1) do ;
 timeKillEvent(FTimer);

 // по окончанию считывания закрываем MIDI устройство
 midiOutClose(_Out);
 // подчищаем память
 fdat.Free
end.
Теперь быстренько жмакаем кнопку 'F9' для компиляции проекта и выбираем любой файл для воспроизведения, хотя бы и сам себя (см. рисунок 4):


Рис. 4. Диалог выбора файла для озвучивания

Пошла мелодия? Во-о-т, теперь можно поиграться различными инструментами для воспроизведения. К примеру, довольно оригинально звучит под ElectricGrandPiano.

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

Видео


Ресурсы
  1. Юкио Сато. Обработка сигналов. Первое знакомство. – М., Додэка, 2002, 174 с.ил.
  2. Описание интерфейса MIDI http://ru.wikipedia.org/wiki/MIDI
  3. Скачать бесплатный IDE TurboDelphi-Lite http://www.andyaska.com/?act=download&mode=get&id=34
  4. MSDN. MidiOutOpen function http://msdn.microsoft.com/en-us/library/windows/desktop/dd798476(v=vs.85).aspx
  5. MSDN. MidiOutClose function http://msdn.microsoft.com/en-us/library/windows/desktop/dd798468(v=vs.85).aspx
  6. MSDN. MidiOutShortMsg function http://msdn.microsoft.com/en-us/library/windows/desktop/dd798481(v=vs.85).aspx
  7. MIDI в деталях. Часть 2 — Сообщения канала http://www.muzoborudovanie.ru/articles/midi/midi2.php
  8. MSDN. TimeSetEvent http://msdn.microsoft.com/en-us/library/aa448195.aspx
  9. MSDN. MemoryStream Class http://msdn.microsoft.com/en-us/library/system.io.memorystream(v=vs.71).aspx
  10. Ресурсы тестового проекта звукового транслятора файлов http://raxp.radioliga.com/cnt/s.php?p=aud.zip

Комментариев нет:

Отправить комментарий

В комментариях уважайте собеседника, внимательно читайте посты и не додумывайте. Просьбы и предложения из разряда: «можно ваш Skype/Viber/телефон», «напишите мне в vk/FB», а также другие им подобные — игнорируются. Выход новых версий ПО, внешняя ссылка, переставшая работать с течением времени и т.п. не является основанием для претензий. Желающие спокойно подискутировать и высказаться — Welcome. Желающие спонсировать блог — Donate. Нарушение этих простых правил ведет к бану и удалению комментариев без предупреждения.