Воскресенье, 11.12.2016, 12:59
Приветствую Вас Гость

Портал светоэффектов

Меню сайта
Категории раздела
Наш опрос
Оцените мой сайт
Всего ответов: 580
Статистика

Онлайн всего: 5
Гостей: 4
Пользователей: 1
Петрин
Форма входа
Главная » Статьи » Цветомузыкальные установки

Захват звука средствами WIN API
Захват звука средствами WIN API
и получение спектра звукового сигнала

1. Введение

Не секрет, что большое количество людей занимаются конструированием различного типа цветомузыкальных устройств. Кто-то собирает "классические" ЦМУ на дискретных компонентах, а кто-то пытается использовать в своих разработках новую элементную базу - компьютеры и микропроцессоры. Вот для последних и предназначена данная статья, хотя никто не запрещает читать ее всем интересующимся темой.
Стимулом для написания данной статьи послужило сравнительно небольшое количество информации о программных методах и алгоритмах обработки звука. Надеюсь, что эта статья поможет Вам  в Ваших начинаниях и мы увидим плоды Вашей работы воплощенные в железо и строки программного кода.
Начнем статью с краткой экскурсии по методам захвата звука, наиболее часто используемые конструкторами в их работе. Сразу оговорюсь, что я не претендую на 100%-й охват всех методов и способов, а остановлюсь только на тех, которые используются наиболее часто. И еще, писать будем в среде DELPHI 7. Пользователи других языков программирования могут попытаться адаптировать код под свои нужды, благо, что средства WIN API при всей свое мудрености, позволяют использовать ресурсы практически из любого языка.
Итак, самый наверное известный среди любителей эксперимента — это знаменитый DirectX. Думаю, что все без исключения пользователи компьютеров начиная с Windows 95 устанавливали сей пакет на свои машины веря словам Билла, что это резко увеличит производительность системы. Честно говоря я не особо это замечал, но установить пакет было делом чести. По мере развития технологии производства микросхем все большее количество функций, в том числе и по работе со звуком, переносилось на аппаратную часть видео и звуковых карт, но традиция продолжается и на сегодняшний момент последняя версия DirectX обозначена цифрой 11. Не будем рассматривать все многообразие возможностей данного ПО, а кратко остановимся на вопросах работы со звуком.
DirectSound - сравнительно новый программный интерфейс, входящий в семейство интерфейсов DirectX (DirectDraw, Direct3D, DirectInput и т.п.). Это некий COM объект через интерфейсы которого и происходит "общение" программы с аппаратурой звуковой карты.
Поддержка интерфейсов DirectX есть в большинстве систем программирования C++, Pascal и Basic, выпущенных после 1995 года. Семейство имеет целый ряд версий; в настоящее время используется версия 11. Если в среде программирования поддерживаются только более старые версии, можно заменить включаемые файлы и библиотеки на более новые, содержащие обновленные версии подсистем, взяв их из новой версии DirectX SDK.
Вначале планировалось использовать именно такой способ захвата, но остановило меня вот что "...В основном недостатки DirectSound являются обратной стороной достоинств подсистемы. Ориентация на существующие звуковые ускорители впоследствии может оказаться несовместимой с новыми моделями аппаратуры, а текущая модель DirectSound - неэффективной.
Упор на работу с короткими звуками несколько затрудняет работу с длительными звуковыми потоками, однако это достаточно легко преодолевается..." Полное описание функций DirectX при работе со звуком можно прочесть тут.
Также необходимо отметить, что существует довольно большое количество оболочек для реализации функций DirectSound. Это всем хорошо известные BASS.dll, "дельфийский" MediaPlayer, набор компонентов DelphiX, NewAC и многие другие. Всех их объединяет одно, они пользуются интерфейсами СОМ объектов DirectX. 
Не большое отступление. Не редко можно прочесть фразы, что захват звука производится с микшера. Теперь я выяснил, что это не так. Просто внешне так оно и выглядит, но это только внешне. На самом деле если вы, например, понизили уровень записи микшера, то просто дали команду звуковой карте понизить этот уровень, а т.к. ваша программа не может восстановить его снова, то и работать она отказывается. На самом деле если из вашей программы передвинуть ползунок (если он там конечно есть) уровня записи на максимум, то все заработает, как нужно. Т.е. микшер — это просто программная оболочка, которая средствами WIN API управляет звуковой картой. Под программной оболочкой я конечно же имею ввиду то, что видит на своем мониторе рядовой пользователь. На самом деле микшер — это довольно сложный комплекс программно-аппаратных средств описание которых заняло бы не один десяток страниц, что не входит в тему нашего сегодняшнего разговора.  Если позволит время то следующую статью я посвящу именно возможностям работы с микшером, а в рамках этой мы попробуем регулировать уровень громкости.
Мой первый опыт захвата звука и получения спектра сигнала тоже основывался на библиотеке BASS. Только поработав с ней некоторое время начинаешь понимать, что она довольно тормозная, а реализация дискретного преобразования Фурье вызывает приступ головной боли. К тому же ее использование в коммерческих проектах платное. 
Попробовал я и DelphiX и NewAC... После всего этого понял одно если хочешь получить удовольствие от своей работы нужно использовать голый код без всяких там красивых оберток. Иногда под ними прячутся довольно неприятные вещи. Поэтому и остановил свой выбор на функциях работы со звуком предоставляемые интерфейсом WIN API. При этом мне не потребовалось использовать какие либо сторонние библиотеки, что очень удобно и в тоже время можно полностью подчинить себе все аппаратные возможности звуковой карты.
Дабы сильно не умничать приведу определение этого интерфейса из Википедии.
"Windows API (англ. application programming interfaces) — общее наименование целого набора базовых функций интерфейсов программирования приложений операционных систем семейств Microsoft Windows корпорации «Майкрософт». Является самым прямым способом взаимодействия приложений с Windows. Для создания программ, использующих Windows API, «Майкрософт» выпускает комплект разработчика программного обеспечения, который называется Platform SDK, и содержит документацию, набор библиотек, утилит и других инструментальных средств для разработки."
Существуют также и вовсе экстравагантные способы "откусить" кусочек от музыки . Например попытки считывать данные непосредственно с АЦП звуковой карты миную всяческие драйвера и саму Windows. Думаю, что всем понятно, что такой способ очень и очень трудоемок и привязан к конкретной модели карты. Да и плюс ко всему Windows ну очень уж неохотно допускает копание в "железе" миную саму себя и драйверы оборудования, так что лучше уж ее не нервировать, а поступать так, как велел нам ее создатель.

2. Создание приложения

Полагаю, что читатель уже немного притомился, читая это "краткое" вступление, поэтому всем, кто еще не уснул, предлагаю приступить к практической части нашего эксперимента. 
Оговорюсь, что не стану подробно описывать все действия по созданию приложений в Del-phi. Для тех, кто пишет в ней и так все должно быть понятно, ну а кто только что приступил к ее освоению нужно попрактиковаться на более простых примерах.
Исходный код будет также доступен для изучения, поэтому можно все создавать с нуля, следуя по нашему пути или просто открыть приложение и увидеть все там своими глазами. 
Итак, открыли Delphi, создали новое приложение, выбрали удобный размер окна и не забыли сохранить свой проект в отдельной папочке. Я дал имя свое форме frmMain, а модуль назвал Main.pas. Размер формы: W = 850 H = 645. 
Кидаем на форму два компонента TСhart, которые назовем Ch1 и Ch2 соответственно. Установим размер первого W = 825 H = 129 и поместим его в верхнюю часть формы, второго W = 825 Н = 473 и поместим в нижней части формы. В Ch1 будем выводить входной сигнал, а в Ch2 его спектр.
В компоненте Ch1 добавим Series Fast Line, выберем для нее цвет (я выбрал желтый), установим шкалу левой оси от 0 до 5, а на нижней установим шкалу автомат.
Для Ch2 добавим Series Bar, раскрасим в нужный вам цвет (я выбрал зеленый), установим шкалу левой оси от 0 до 40, нижней автомат. На вкладке Series – Format установите параметр Bar-Width в значение 70%.
Эти два компонента понадобятся нам для вывода самого сигнала Ch1 и его спектра Ch2.
Затем по мере необходимости мы добавим на форму и остальные компоненты. В частности раз я обещал регулятор громкости, то нам понадобится слайдер для ее регулировки и метка, для вывода значения громкости. 
Но это позже, а пока в результате всех проделанных манипуляций у вас должно получиться нечто похожее на это.



В принципе все готово для начала кодирования.
Сразу добавим в секцию USES модуль MMSYSTEM, т.к. описание всех функций, используемые нами находятся именно там. Опишем переменные и константы для наше программы.

var
  frmMain: TfrmMain;        //Собственно сама форма. 
  WaveIn: hWaveIn;          //Хэндл устройства захвата. Для непосвященных это можно пояснить так. Хендл (Handle) — это некий идентификатор (номерок) устройства, по которому система может определить кому предназначено то или иное сообщение. В системе не может быть двух одинаковых Хендлов.
  hBuf: THandle;            //Хендл буфера захвата
  BufHead: TWaveHdr;        //Структура WAVEHDR которая определяет заголовок, используемый для идентификации сигнала аудиоустройства.
  Stop: boolean;            //Переменная для пуска и остановки захвата.

const
  Fs = 22050;         //Частота дискретизации
  BitSample = 16;     //Размерность сэмпла в битах (8 или 16)
  Ch = 1;             //Канал (1-моно, 2-стерео)
  Fmax = 10000;       //Верхний предел частотного спектра, Гц
  F = 50;             //Частотное разрешение, Гц
  Ns = Fmax div F;    //Количество отсчетов спектра (10000/50=200)
  N = Fs div F * 2;   //Количество отсчетов (размер буферов 882 в нашем случае)

Определяем типы для буферов.
type
  TData = array[0..N-1] of smallint;  //Это основной буфер захвата
  PData = ^Tdata;                     //Это указатель на него

Используем две стандартные процедуры для запуска и остановки захвата.
procedure TfrmMain.FormCreate(Sender: TObject);
begin
 Stop:=True;      //Покажем, что нужно включить захват
 ConfigWave;      //Запуск процедуры
end;

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Stop:=False;      //Остановить захват
  ConfigWave;     
end;

Теперь мы должны создать процедуру ConfigWave. Не забудьте поместить ее описание в сек-цию Public. Привожу ее полностью и постараюсь подробно прокомментировать каждую строку.
procedure TfrmMain.ConfigWave;
var
  FHead: TWaveFormatEx;    //Структура WAVEFORMATEX определяющая формат аудиоданных
  BufLen: word;            //Размер буфера захвата
  buf: pointer;            //Указатель на буфер
begin
//Если разрешена работа системы захвата, выполним следующую подготовку
  if Stop=True then begin
//Для начала заполним структуру
    with FHead do begin
//Используем кодо-импульсную модуляцию
      wFormatTag := WAVE_FORMAT_PCM;
//Установим режим моно
      nChannels := Ch;
//Установим частоту дискретизации
      nSamplesPerSec := Fs;
//Укажем разрядность сэмпла
      wBitsPerSample := BitSample;
// Число байт в выборке
      nBlockAlign := nChannels * (wBitsPerSample div 8);
//Средняя скорость передачи данных бит/сек
      nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
//Для формата РСМ строка не нужна, просто обнуляем
      cbSize := 0;
    end;
//Теперь откроем наше устройство записи для чего вызовем следующую функцию 
    WaveInOpen(Addr(WaveIn), WAVE_MAPPER, Аddr(FHead), Handle, 0, CALLBACK_WINDOW);
//Для более лучшего понимания опишем параметры, которые передаются в функцию.
1. Addr(WaveIn) – это ничто иное, как адрес хэндла нашего устройства записи.
2. WAVE_MAPPER – здесь нужно вставлять идентификатор открытого устройства записи или просто флаг WAVE_MAPPER, что мы и сделали.
3. Addr(FHead) – адрес структуры  WAVEFORMATEX
4. Handle - адрес фиксированной функции обратного вызова, обработчик события, дескриптор окна или идентификатор потока вызываемый во время записи для обработки сообщений, связанных с ходом записи. Если функция обратного вызова не требуется, это значение может быть нулевым. В нашем случае это дескриптор окна программы.
5. 0 — так как мы используем механизм обратного вызова, то этот параметр равен 0.
6. CALLBACK_WINDOW – флаг открытого устройства. В нашем случае это означает, что параметром 4 является дескриптор открытого окна.
Вот такая мудреность, но не волнуйтесь, Билл Гейтс не зря ел свой хлеб, дальше будет еще страшнее :-)

  BufLen := FHead.nBlockAlign * N; //Подсчитаем сколько нам нужно байт для вуфера
  hBuf := GlobalAlloc(GMEM_MOVEABLE and GMEM_SHARE, BufLen); //Выделим место для нашего буфера в так называемой куче, а по простому в ОЗУ. при этом флагом GMEM_MOVEABLE укажем, что буфер в памяти может перемещаться. GMEM_SHARE позволяет использовать эту память для DDE и вводится только для совместимости с прежними версиями Windows. Ну, и наконец, укажем какой объем выделить.
    Buf := GlobalLock(hBuf); //Функция GlobalLock блокирует объект выделенной памяти и возвращает указатель на первый байт блок. Блок памяти, связанный с блокированной памятью не может быть перемещен или отброшен. На память, размещенную с флагом GMEM_MOVEABLE, функция увеличивает счетчик блокировок, связанных с объектом памяти.
    with BufHead do begin
//Теперь мы должны заполнить структуру WAVEHDR, для чего укажем
      lpData := Buf;            //адрес буфера захвата
      dwBufferLength := BufLen; //размер буфера в байтах
      dwFlags:= WHDR_BEGINLOOP  //производим захват непрерывно
    end;
//Займемся подготовкой буфера для захвата. Для этого передадим в следующей функции такие параметры:
1. WaveIN – хэндл нашего устройства.
2. Addr(BufHead) – адрес структуры WAVEHDR.
3. sizeof(BufHead) – размер структуры в байтах.
    WaveInPrepareHeader(WaveIn, Addr(BufHead), sizeof(BufHead));
//Создадим буфер передав в функцию те же параметры. Теперь когда буфер будет заполнен приложение получит соответствующее уведомление.
    WaveInAddBuffer(WaveIn, addr(BufHead), sizeof(BufHead));
//Ну, и наконец то запустим всю эту конструкцию, передав в качестве параметра хэндл открытого устройства.
    WaveInStart(WaveIn);
  end
  else begin
//Если флаг STOP=False закроем устройство ввода и освободим па-мять. Не буду подробно описывать эти функции. По моем и так все должно быть ясно.
    WaveInReset(WaveIn);
    WaveInUnPrepareHeader(WaveIn, addr(BufHead), sizeof(BufHead));
    WaveInClose(WaveIn);
    GlobalUnlock(hBuf);
    GlobalFree(hBuf);
  end;
end;

Ну как вам? Если голова еще не дымится, то перейдем к следующей части "Марлезонского балета", а именно к написанию CALLBACK функции для извлечения перехваченных данных. 
Короткое пояснение. Что это за тип функции такой CALLBACK? Обычно такие функции называются функциями обратного вызова. Если какую либо функцию должна вызывать не ваша программа, а операционная система, то вы должны указать ей тип передачи параметров как CALLBACK. Теперь вспомним вот это WaveInOpen(Addr(WaveIn), WAVE_MAPPER, Аddr(FHead), Handle, 0, CALLBACK_WINDOW); Мы сами сказали это в своей программе.
Как же Windows узнает, что эту функцию можно выполнить? Да очень просто. Припоминайте. Мы создали первичный буфер функцией WaveInAddBuffer. Теперь когда этот буфер будет полон, система получит соответствующее уведомление, а мы перехватив это уведомление запустим процедуру в которой и прочтем все, что нам нужно. Посмотрите, как описана эта процедура в секции Public нашей программы.
procedure OnWaveIn(var Msg: TMessage); message MM_WIM_DATA;
Функцию мы рассмотрим ниже, здесь нам интересна конструкция message MM_WIM_DATA. Это говорит о том, что когда наш буфер будет заполнен, наше окно получит данное сообщение, а так как мы привязали это сообщение к процедуре OnWaveIn, то именно она и будет выполняться. Видите, как все просто. При этом нам не нужны таймеры. Процедура будет выполняться непрерывно, пока мы не остановим программу.
Давайте наконец напишем эту самую процедуру. Она получиться довольно короткой.

procedure TfrmMain.OnWaveIn;
var
  i: integer;
  CapData: PData;      //Буфер захвата
  BufData: BufD;       //Промежуточный буфер
begin
//Непосредственно сам захват. При этом в буфер CapData помещаются данные, расположенные по адресу, который нам поступил в сообщении от буфера и которое находится в поле lpData этого сообщения. Слава Биллу! 
    CapData := PData(PWaveHdr(Msg.lParam)^.lpData);
//Очистим верхний график от старого изображения
    Ch1.Series[0].Clear;
//Перепишем захваченные данные в промежуточный буфер и отрисуем звук на графике
  for i:=0 to N-1 do begin
    BufData[i]:=CapData^[i];
   Ch1.Series[0].AddXY(i,BufData[i]/10000); //Чуток уменьшим размах, а то слишком большие числа
  end;
//Теперь опять создадим буфер по тому же адресу (грубо говоря очистим его) и закончим наконец то свои мучения до поступления следующего сообщения о том, что буфер полон. Вот так и будет все это крутиться, пока вам не надоест.
  if Stop=True then
   WaveInAddBuffer(WaveIn, PWaveHdr(Msg.lParam),SizeOf(TWaveHdr))
  else
   Stop:= True;
end;

Ну что, теперь самое время выпить чашечку чая или бокал Шато Лафит урожая 2000 года, кому что больше нравится и приступить к следующему шагу, выковыриванию спектра нашего перехваченного звука.
Сразу оговорюсь, что постараюсь поменьше пудрить мозги математикой, а подробнее остановлюсь на практической реализации, но совсем уж без царицы всех наук обойтись не возможно.
Как я заметил, в своей практике для получения спектра практически все используют алгоритм быстрого преобразования Фурье (БПФ), а точнее его разновидность дискретное преобразование Фурье (ДПФ). Это классический способ и о нем много написано и сказано, но я в своей работе остановлюсь на алгоритме Герцеля (АГ) для расчета дискретного преобразования Фурье (ДПФ).
Основное отличие этих алгоритмов состоит в следующем. Для выделения какой либо часто-ты при ДПФ требуется произвести расчет все последовательности, а пользуясь АГ мы можем выделять только те частоты, которые нам необходимы. Например мы можем из нашей выборки выделить 1000 Гц и исследовать ее поведение. Надеюсь, что это понятно и мы можем двигаться дальше.

Коротко о самом алгоритме.
Пусть исходный сигнал s(n) состоит из 5 отсчетов s(i), где i  лежит в диапазоне от 0 до 4.
Примем частоту дискретизации равной Fs и разнос спектра dF.
Определим размер необходимого буфера N = Fs/dF * 2. Мы специально удвоили размер необходимого буфера для более качественного различения частот, хотя можно было бы обойтись размером Fs/dF байт.
Рассчитываем номера спектральных отсчетов по формуле к(i) = N*F/Fs, где F – частота, спектр которой мы хотим выделить.
Проведем расчет коэффициента alpha по формуле alpha(i) = 2*cos(2*Pi*(k(i)/N))   
В этом алгоритме есть такая гадость, как поворотные коэффициенты. Только не спрашивайте меня что это такое, не знаю, но нам необходимо их определить. Вот формулы.
Реальная часть wr(i) = cos(2*Pi*(k(i)/N))
Мнимая часть   wi(i) = sin(2*Pi*(k(i)/N))
И вот настал наш звездный час. Мы наконец то определим амплитуду спектра для первого отсчета. Но радоваться еще рано, т.к. нам необходимо произвести итерационный (какое страшное слово) коэффициентов B. Что такое итерации? Если простым языком, то это когда в очередном вычислении используются данные предыдущего вычисления. А как же быть с тем фактом, когда предыдущего вычисления нет? Да очень просто, мы сами зададим значения по которым начнем расчет. Считать будем по формуле B(i) = s(i) + alpha(i) * B(i-1) – B(i-2)
Надеюсь, что все члены уравнения вам знакомы и мы двигаемся дальше. Т.к. для определения B(0) у нас не хватает B(-1) и B(-2) мы их зададим принудительно.
B(-1) = s(0) и B(-2) = s(1) + alpha(1) * B(-1) 
Вот так все просто. Начинаем считать следующим образом:
B(0) = s(0) + alpha(0) * B(-1) – B(-2)
B(1) = s(1) + alpha(1) * B(0) – B(-1)
B(2) = s(2) + alpha(2) * B(1) – B(0)
B(3) = s(3) + alpha(3) * B(2) – B(1)
B(4) = s(4) + alpha(4) * B(3) – B(2)
А теперь забудьте все, что вы считали до этого и наконец то определите амплитуду гармони-ки для первого отсчета вот по этой формуле.
Реальная часть   Wr = B(N-1) * wr(1) - B(N-2) = B(4) * wr(1) - B(3)
Мнимая часть    Wi = B(N-1) * wi(1) = B(4) * wi(1)
Вот такая у нас с вами получилась чехарда. Делали кучу расчетов, а в результате использова-ли только два последних результата. Вот за это и ругают этот алгоритм, но ругают я думаю зря. Его достоинства с лихвой перекрывают его недостатки.
Ну, а нам теперь осталось всего лишь из комплексного числа получить вещественное, что мы и сделаем с превеликим удовольствием.
Т.к. в наших отсчетах вполне себе могут и реально появляются отрицательные числа, то пер-во наперво избавимся от минусов. Это просто.
   Re = Wr * Wr
   Im = Wi * Wi
Выделим и отмасштабируем амплитуду гармоники.
   Ampl = (SQRT(Re+Im))/150000
Число 150000 я взял чисто произвольно, вы можете использовать совсем другие значения или вовсе не использовать.

Вот такой он получился алгоритм Герцеля для дискретного преобразования Фурье. Надеюсь, что получился он не очень страшным, а вовсе даже добродушным и ласковым, но перейдем теперь к реализации всей этой красоты в нашем приложении.
Для этого примем следующие постулаты. 
Зададимся верхней границей спектра в 10 кГц и разносом частот в спектре 50 Гц. Думаю, что для построения ЦМУ этого с лихвой хватит. Кому будет мало он сможет самостоятельно определить эти параметры.
Чтобы сообщить это нашей программе добавим в раздел констант следующие значения (три последних).
Припоминаете, что в секции констант мы написали вот такие строки.
  F = 50;             //Частотное разрешение, Гц  
  Fmax = 10000;       //Верхний предел частотного спектра, Гц
  Ns = Fmax div F;    //Количество отсчетов спектра (10000/50=200)
Т.е. в результате мы должны увидеть 200 полосок на графике, каждая из которых представля-т собой амплитуду гармоники начиная с 50 Гц и заканчивая 10 кГц через каждые 50 Гц. Получим следующий ряд гармоник 50, 100, 150, 200 ... 10000 Гц. После этого вы, зная номер гармоники можете выполнить с ней любые операции, которые сочтете необходимыми.
Также для расчета спектра нам понадобится еще один буфер, который мы и должны добавить в описания типов.

type
  TData = array[0..N-1] of smallint;  //Это основной буфер захвата
  PData = ^Tdata;                     //Это указатель на него
  BufD = array[0..N-1] of double;     //Буфер для ДПФ
//Расчет по Герцелю
//Предварительный расчет альфа и поворотных коэффициентов для всех 882 отсчетов
 Frq:=-100;                       //Начальная частота равна -100 Гц. Это нам позволит позиционировать наш спектр точно по частоте.
 For i:=0 To N-1 do begin
  k:=Trunc(N*Frq/Fs);             //Определим номер расчетной гармоники
  alpha[i]:=2*cos(2*Pi*(k/N));    //Расчет коэфф. Альфа
  wr[i] := cos(2*Pi*(k/N));       //Поворотный коэфф. реальная часть
  wi[i] := sin(2*Pi*(k/N));       //Поворотный коэфф. мнимая часть
  Frq:=Frq+F;                     //Перейдем на следующую частоту
 end;
//Цикл расчета по частотам от 50 до 10000 Гц с разносом 50 Гц
  For j:=0 to Ns-1 do begin
//Для начала расчета зададим B[-1] и B[-2]
   B[0] := BufData[0];
   B[1] := BufData[1]+alpha[j+1]*B[0];
//Цикл расчета B
    For i:=2 to N-1 do begin
     B[i] := BufData[i]+alpha[j+2]*B[i-1]-B[i-2];
    end;
//реальная и мнимая части спектрального отсчета
   Wr[j]:= B[N-1]*wr[j]-B[N-2];
   Wi[j]:= B[N-1]*wi[j];
//Избавимся от минусов
   Re:=Wr[j]*Wr[j];
   Im:=Wi[j]*Wi[j];
//Выделим и отмасштабируем амплитуду гармоники
   Ampl[j]:=(SQRT(Re+Im))/150000;
   If j=0 Then Ampl[j]:=0;
  end;
//Выведем спектр на график
 For i:=0 to Ns-1 do begin
  Ch2.Series[0].AddXY(i*50,Ampl[i]);
 end;

Ну вот теперь то можно вытереть пот со лба и вздохнуть полной грудью. Про Шато Лафит я больше не вспоминаю. Запустим приложение, включим любимую мелодию и насладимся свой работой. Кстати, я заметил, что просмотр спектров может приносить довольно приличное эстетическое наслаждение. На нем же, при известной сноровке можно вполне осознано оценить качество записи ваших треков. Т.е. не ваших конечно, а тех, которые вы любите слушать. 
Обратите внимание на верхний график на котором отображается перехваченный сигнал. Очень часто наблюдается его ограничение по амплитуде. Это не недостаток метода, а вполне себе реальный глюк того человека, который сводил трек или который его редактировал. Частенько для уменьшения размера файла некоторые кудесники специально ухудшают качество исходного мате-риала, что и приводит вот к таким артефактам.
Очень красивый спектр получается при прослушивании фортепианных пьес, особенно когда в ней есть довольно быстрые и длинные переходы по нотам. Тогда мы видим, как спектр движется вслед за руками пианиста. Очень впечатляет. В этом случае мы можем видеть довольно чистый спектр с минимумом гармоник. 
В общем можно сказать, что алгоритм Герцеля, на мой взгляд на порядок качественнее быстрого преобразования Фурье и скорости современных компьютеров за глаза хватает для его реализации. Некоторые могут спросить про его реализацию в микроконтроллере на что я отвечу, что не знаю, не пробовал. Хотя на мой взгляд что 20 Мгц-ой АВР-ки при известной сноровке думаю вполне должно хватить. Была ведь реализация ДПФ, а чем АГ хуже.
Ну и чтобы закончить все эти теоретические излияния приведу несколько скриншотов работы нашего приложения.
Начнем с того, что сравним два метода. Для этого я буду использовать программный генератор частоты. На обоих картинках один и тот же сигнал.

Это спектр полученный библиотекой Bass.dll



А это наш с вами спектр. Как говорится это две большие разницы.



Думаю, что комментарии тут будут лишними. Как говориться поверь глазам своим.
Ну, и напоследок приведу еще пару скриншотов уже реальной музыки.
Так выглядит спектрограмма фрагмента моей любимой группы Pink Floyd 
из альбома Wish You Were Here. 



Композиция называется Shine On You Crazy Diamond
 (кстати, если найдутся человеки, которые еще не слышали это, то обязательно послушайте)
Обратите внимание на исходный сигнал вверху. Очень хорошо заметно ограничение по амплитуде. 
А так выглядит фрагмент Каприза в исполнении Виктора Зинчука.



А вот тут ограничений практически нет. Зато какова картинка! Ноты можно вычислять! Это я конечно погорячился, но согласитесь, очень красиво получилось.
Я не стал описывать здесь обещанный регулятор громкости. Статья получилась и так объемная, но в исходнике все есть. Вдумчивые разберутся. Раздвиньте форму справа и найдете там регулятор. Спасибо за терпение всем тем, кто добрался до конца. 


С уважением  Владимир Степанов. 

E-mail: sva-donDOGyandex.ru

Вопросы можно на почту.

Категория: Цветомузыкальные установки | Добавил: defaultNick (15.06.2013) | Автор: Radan
Просмотров: 6395 | Комментарии: 16
Всего комментариев: 16
9  
happy Огромное спасибо за статью! Долго искал нечто подобное и вот наконец-то как поётся в одной старинной песенке "Кто весел тот смеётся! Кто хочет тот добьётся! Кто ищет тот всегда найдёт!"
Для меня эта статья оказалась очень полезной! Еще раз СПАСИБО! clap clap clap

10  
Можем надеятся поделитесь своим конкретным воплощением? Ну когда оно хоть в каком виде уже будет существовать, конечно wink

11  
Как только добьюсь нужных мне результатов - сразу напишу!))

12  
Может, есть какие хорошие результаты? wink
Или, если уже об этом где то говорилось - извиняюсь. Бывает я пропускаю и забыть тоже могу. Старость!!! biggrin :D

13  
Кое-какие результаты есть, но не совсем то что я хотел... Пока еще рано показывать этот "ужас парящий на крыльях ночи..."))) Сейчас "борюсь" с изображением световых пятен переменной яркости - не выходит у меня сделать их достаточно яркими.... sad

14  
Желаю везенья и успехов!  cool

15  
Спасибо!))
Буду стараться!)) А вдруг получится??? surprised

16  
Kak tut u Avtora, est li kakie novosti? smile

8  
в win7 не заработало у меня.. какойто фон очень- очень маленьки показывает - независимый от воспроизводимой музыки.. и то после уменьшения делителей в175 строк е Ch1.Series[0].AddXY(i,BufData[i]/100);
и в 203
Ampl[j]:=(SQRT(Re+Im))/4500;

наверное устройства както по другому надо выбирать?

7  
Juris_3D, буду очень признателен, моя почта rgb73(собака)yandex.ua. С УВ.

5  
svadon, не в тему конечно, сам надеюсь, что смогу показать плоды труда. Как не крути, а аналоговая часть подготовки сигнала для дальнейшей обработки, вызывает трудности, нахожусь в поиске схемного решения.

6  
Уважаемый RGB7 ну и все заинтересованные, я недавно наткнулся на существование одной статьи в немецком журнале ELV. Искал полный вариант в "обычных местах", не нашёл, решил купить статью, и так и сделал. Это несколько PDF файлов. Там на мой взгляд интересная аналоговая входная схема цветомузыки, на мой взгляд "на все случаи жизни": микрофонный и линейный входы, всё это с АРУ, и ещё хороший фильтр среза лишних верхних частот (делал симульацию, смотрел АЧХ, ну и можно пересчитать на другой срез по надобности). Сам пока не собрал, тут были летние праздники у нас так на недельку-две smile
Могу даром поделится схемой, текстом, только даите знать кому и как smile Не хочу выкладывать в публичных местах.

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

2  
Спасибо за программу, поможет мне в настройке цветомузыки.

4  
Приятно прочесть, что твой труд кому то пригодился. Спасибо.
Надеюсь, что все наше сообщество сможет увидеть плоды Вашего труда.:)

1  
Уважаемый sva-don, спасибо за статью! Раньше публиковалась Ваша конструкция USB-цветомузыки, но эта тема затухла. Есть ли какое продолжение, или планы на будущее? Спасибо! smile

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
YOUTUBE LIGHTPORTAL
ALIEXPRESS
Поиск
Translation
Donate
QR
Часики
 
Облако тегов
Друзья сайта
Портал светоэффектов
Catcatcat Electronics
Color Music Beniamina Grinberg