SineMusitвид изнутри

Программа написана на Delphi в рамках VCL Win32 с использованием модуля DirectX - DirectSound. Если Вы хотите в своих программах на Delphi использовать средства DirectX, то самое первое, что Вам нужно сделать - это получить заголовочные файлы DirectX переведенные с языка С на Паскаль для Delphi.

Заголовочные файлы содержат объявления (заголовки) функций, типов, переменных и констант, размещенных, уже в откомпилированном виде, в динамических бибилиотеках (файлах с расширением dll). Нужны эти объявления для того, чтобы компилятор мог связать вашу программу с такими библиотеками и обеспечить правильный вызов находящихся в них функций. При этом полный исходный код самих функций компилятору не нужен, так как они уже прошли компиляцию. Получив заголовочные файлы, надо разместить их либо в каталоге проекта, либо в отдельном каталоге, указав к нему путь в настройках проекта (пункт меню: Project | Options | Directories/Conditionals | Search Path).

У программиста использующего динамическую библиотеку, как правило, нет необходимости вникать в ее исходный код, тем более, что он может быть вовсе не на том языке, которым программист владеет. Зато описание того, что делают функции библиотеки и как их правильно применять, нужно обязательно. Для серьезных сложных библиотек такое описание нередко называется SDK (Source Development Kit - набор поддержки разработки) и содержит, обычно, еще и множество примеров. Более подробно обо всем этом применительно к DirectX и Delphi можно прочитать в [1].

Официальной поддержки DirectX в Delphi нет, поэтому обычный источник заголовочных файлов для этой библиотеки - сайты энтузиастов, занимающихся переводом исходных файлов с языка C на Паскаль и адаптированием их для разных версий Delphi. Для SineMusit использовались файлы для DirectX 9.0 и Delphi6-7, взятые мной отсюда:

http://www.clootie.ru/delphi/download_dx90.html

Следующий шаг - скачать и установить SDK для DirectX. Хотя, если Вы планируете ограничиться простейшими проектами, вроде рассматриваемого здесь SineMusit'а, то с этим можно не спешить. А вот литература на русском языке, например [1,2], будет очень полезной. Правда, в [1] нет звука, а в [2] нет Delphi, но все равно то, что в этих книгах все-таки есть, вполне пригодится. Надеюсь, что и эта статья для кого-то окажется не лишней.

аналого-цифровое преобразование

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

Получившийся результат (цифра) называется в технической литературе по разному: отсчет, выборкa, сэмпл. Тактовая частота, с которой делаются выборки, называется частотой дискретизации (частотой сэмплирования). Сам же такой способ преобразования аналогового сигнала в цифровой называется импульсно-кодовой модуляцией (Pulse Code Modulation - PCM). Это не единственный способ, есть и другие, но именно он и только он применяется в DirectSound. Получившаяся в результате совокупность последовательных выборок (сэмплов) и есть цифровая форма аналогового сигнала (звуковые данные).

Из всего состава DirectX в SineMusit используется только DirectSound. В общем виде, суть возможностей этого модуля сводится к предоставлению разработчику, так называемых, звуковых буферов. Звуковой буфер это объект, состоящий из области памяти для хранения выборок (сэмплов) звукового сигнала и методов объекта, с этой областью работающих. Другими словами, содержимое буфера - это фрагмент звука. Продолжительность фрагмента определяется размером буфера и его форматом. Буферы бывают первичные и вторичные. Первичный буфер это тот, из которого звуковые данные отправляются прямо на цифро-аналоговый преобразователь, а затем на выход. Этот буфер всегда расположен в памяти звуковой карты. Данные в первичный буфер поступают из вторичных буферов. Эти буферы могут располагаться как в памяти звуковой карты, если ее достаточно, так и в системной. Вторичных буферов может быть сколько угодно. При одновременном воспроизведении (проигрывании) нескольких вторичных буферов, их данные смешиваются в первичном буфере.

Как правило, у программиста нет необходимости работать непосредственно с первичным буфером. Вместо этого он создает нужное количество вторичных, заполняет их своими данными, а затем в нужные моменты воспроизводит и смешивает в любой комбинации. При этом, есть возможность установить для каждого буфера его собственные параметры воспроизведения: громкость, баланс каналов, скорость воспроизведения. Кроме того, можно задать, будет ли воспроизведение конкретного буфера однократным или циклическим, узнать текущее и установить новое значение его позиции воспроизведения (положение курсора воспроизведения). Заносить в буфер данные можно не только тогда, когда он отстановлен, но и во время воспроизведения. Правда, в последнем случае требуется некоторая аккуратность. Имеется еще ряд возможностей, среди которых есть одна, представляющая особый интерес. В буфере можно установить, так называемые, позиции уведомления. Когда курсор воспроизведения достигает такой позиции, возникает системное событие, получив которое, можно выполнить какие-то действия, например, обновить данные в буфере. Уведомления - важный элемент устройства SineMusit, но об этом чуть позже.

Для того, чтобы использовать в своей программе те возможности, которые предлагает DirectSound, требуется определенная последовательноть действий. Первый шаг - создание главного объекта DirectSound.

var
  hr  : HRESULT;
  FDS : IDirectSound;
  ...
  hr := DirectSoundCreate(nil, FDS, nil);
  //  проверить успешность создания объекта
  if failed(hr) then
    ...      //  увы, создать объект не удалось

Первый параметр функции это GUID (уникальный глобальный идентификатор) того устройства, которое будет выводить звук. Если передать nil, то будет использовано устройство по умолчанию. В SineMusit так и сделано, хотя при необходимости, можно сначала использовать функцию DirectSoundEnumerate. Она позволяет получить список GUID всех устройств, способных работать с DirectSound [2]. Второй параметр это переменная, которая и будет объектом DirectSound (точнее указателем на объект). Тип IDirectSound это интерфейс. О том, что такое инерфейсы и для чего их придумали, можно подробно прочитать в [1,2,3], а здесь просто примем как факт - есть такой вид объектов. Третий параметр должен быть nil. При успешном завершении все функции DirectSound возвращают значение DS_OK. Можно непосредственно сравнивать результат с этой константой, а можно использовать системную функцию Failed (Windows.pas), которая возвращает true, если ее аргумент является кодом ошибки.

fanction Failed(Status: HRESULT): boolean;

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

  hr := FDS.SetCooperativeLevel(FormHandle, DSSCL_EXCLUSIVE);
  if failed(hr) then
    ...      //  задать режим совместного доступа не удалось

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

По умолчанию в Windows установлен формат первичного звукового буфера - 22,05 кГц, 8 бит. Это не позволяет формировать сигналы с частотой выше 10 кГц. Чтобы SineMusit все-таки мог формировать сигналы с частотой до 20 кГц, нужно изменить формат первичного буфера, устновив частоту дискретизации, как минимум 44,1 кГц (частота дискретизации копакт-диска). По этой причине, уровень DSSCL_NORMAL оказывается неприемлемым. С другой стороны, необходимости прямой записи в первичный буфер у SineMusit тоже нет, а значит уровень DSSCL_WRITEPRIMARY выглядит завышенным. Из двух оставшихся, выбран уровень DSSCL_EXCLUSIVE, чтобы случайные звуки какой-либо другой программы не "подмешивались" в сигнал SineMusit.

Третий шаг - оценка возможностей имеющегося звукового оборудования и сравнение их с минимальными требованиями прикладной программы. Хотя, если программа делается исключительно "для себя" и поэтому совершенно точно известно, что на компьютерах со слабыми звуковыми картами она никогда запускаться не будет, то этот шаг можно пропустить. С другой стороны, не так уж он и сложен. Для данных о возможностях оборудования в DirectSound предусмотрен тип TDSCaps. Тип содержит много полей, но в SineMusit проверяется только два - dwMaxSecondarySampleRate и dwFlags.

type
  TDSCAPS = packed record
    dwSize                   : DWORD;  // размер размер самой записи в байтах
    dwFlags                  : DWORD;  // комбинация флагов описывающая возможности звукового оборудования
    dwMinSecondarySampleRate : DWORD;  // минимальная частота дискретизации вторичных буферов(Гц)
    dwMaxSecondarySampleRate : DWORD;  // максимальная частота дискретизации вторичных буферов(Гц)
    ...
  end;

Если значение поля dwMaxSecondarySampleRate будет меньше чем 44100, то генератор просто не сможет правильно формировать сигналы во всем заявленном диапазоне частот. Кроме того, с целью упрощения исходного кода, SineMusit работает только в режиме стерео, 16 бит. Скорее всего, компьютеров не отвечающих таким требованиям, в природе уже не осталось, но на всякий случай надо проверить наличие флагов DSCAPS_SECONDARYSTEREO и DSCAPS_SECONDARY16BIT в поле dwFlags.

var
  DSCaps : TDSCaps;
  ...
  ZeroMemory(@DSCaps, SizeOf(DSCaps));
  DSCaps.dwSize := SizeOf(DSCaps);
  hr:= FDS.GetCaps(DSCaps);
  if failed(hr) then
    ...      //  получить характеристики не удалось
  if DSCaps.dwMaxSecondarySampleRate < 44100 then
    ...      //  низкая частота дискретизации
  if ((DSCaps.dwFlags and DSCAPS_SECONDARYSTEREO) = 0) or
     ((DSCaps.dwFlags and DSCAPS_SECONDARY16BIT) = 0)
  then
    ...      //  нет режима стерео, 16 бит

Самое первое действие - очистка памяти занимаемой переменной DSCaps. Все поля записей, передаваемых в функции DirectX в качестве параметров, должны быть заполнены в обязательном порядке. Все, без исключения. Действует правило - если значение поля равно нулю, или поле заполнено равными нулю байтами, значит оно оставляется на усмотрение DirectX, а если оно не равно нулю, значит его значение должно быть принято к исполнению. Поэтому, если в памяти занятой переменной, окажется какое-то случайное значение, то работа функции станет непредсказуемой. Поскольку, в данном случае, мы собираемся получать данные, а не устанавливать что-то свое, то все поля должны иметь значение ноль, кроме поля dwSize . В этом поле указывается размер самой переменной, что, кстати, является типичной практикой не только для DirectX, но и вообще для программирования на С. Только после этого можно вызывать метод GetCaps. Результатом его работы станет заполненная реальными значениями переменная DSCaps. Теперь можно анализировать поля переменной и по результатам анализа предпринимать какие-то дейсвия.

Четвертый шаг - установка формата первичного звукового буфера в соответствии с требованиями прикладной программы, в данном случае SineMusit. Для этого понадобится переменная типа TWaveFormatEx (Windows.pas) и переменная типа TDSBufferDesc (DirectSound.pas). Первый из этих типов предназначен для описания формата звуковых данных, а второй для описания параметров самого буфера:

type
  // ... модуль Windows.pas ...
  TWaveFormatEx = packed record
    wFormatTag     : Word;   // тип формата, для DirectSound всегда WAVE_FORMAT_PCM
    nChannels      : Word;   // количество каналов
    nSamplesPerSec : DWORD;  // частота дискретизации
    nAvgBytesPerSec: DWORD;  // количество байтов в секунду (nBlockAlign * nSamplesPerSec)
    nBlockAlign    : Word;   // размер минимального блока данных в байтах (wBitsPerSample div 8 * nChannels)
    wBitsPerSample : Word;   // количество бит на выборку (8, 16, ...)
    cbSize         : Word;   // количество байтов дополнительной инфорамации, для DirectSound всегда 0
  end;
  // ... модуль DirectSound.pas ...
  TDSBufferDesc = packed record
    dwSize         : DWORD;  // размер самой записи в байтах
    dwFlags        : DWORD;  // комбинация флагов определяющая возможности буфера
    dwBufferBytes  : DWORD;  // размер буфера в байтах
    dwReserved     : DWORD;
    lpwfxFormat    : PWaveFormatEx;  // указатель на переменную типа TWaveFormatEx
    guid3DAlgorithm: TGUID;
  end;

Создается буфер методом CreateSoundBuffer главного объекта DirectSound. В исходном коде это может выглядеть так:

var
  WFX : TWaveFormatEx =
    ( wFormatTag      : WAVE_FORMAT_PCM;
      nChannels       : 2;
      nSamplesPerSec  : 44100;
      nAvgBytesPerSec : 44100 * 2 * (16 div 8);
      nBlockAlign     : 2 * (16 div 8);
      wBitsPerSample  : 16;
      cbSize          : 0 );
  dspbd : TDSBufferDesc;       // переменная для описания первичного буфера
  FDSB  : IDirectSoundBuffer;  // объект - звуковой буфер
  ...
  //  подготовка переменной dspbd (первый параметр метода)
  ZeroMemory(@dspbd, SizeOf(dspbd));
  dspbd.dwSize := SizeOf(dspbd);
  dspbd.dwFlags := DSBCAPS_PRIMARYBUFFER;
  //  создание первичного звукового буфера
  hr := FDS.CreateSoundBuffer(dspbd, FDSB, nil);
  if failed(hr) then
    ...      //  получить доступ к первичному буферу не удалось
  //  изменение формата первичного звукового буфера
  hr:= FDSB.SetFormat(@WFX);
  if failed(hr) then
    ...      //  изменить формат первичного буфера не удалось
  //  удаление объекта первичного буфера, в случае ненадобности
  FDSB := nil;

Первый параметр метода - переменная типа TDSBufferDesc, описывающая характеристики создаваемого буфера. Здесь она названа dspbd. Как и в предыдущем случае, память, занимаемая переменной, должна быть очищена, а ее размер указан в поле dwSize. В поле флагов должен стоять флаг DSBCAPS_PRIMARYBUFFER, указывающий на то, что создаваемый объект будет объектом первичного буфера. Второй параметр метода это сама переменная создаваемого объекта. Третий параметр должен быть равен nil.

Имея буфер, с помощью его метода SetFormat, устанавливаем нужный формат буфера. У этого метода только один аргумент - адрес переменной типа TWaveFormatEx, заранее заполненной данными о нужном формате. Поскольку SineMusit больше ничего с первичным буфером не делает, то теперь объект можно уничтожить, чтобы не занимал напрасно память. На этом, действия рутинные, то есть примерно одинаковые для любой программы использующей DirectSound, закончены.


Назад: к общему описанию программы
Голос народа: отзывы, пожелания, мнения
1
2 3 4 5

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

Москва, 2013-2020 гг., © ZHarNS58
571