SineMusitвид изнутри

Работающая в потоке, процедура PlayExecute представляет собой бесконечный цикл, в котором вызывается функция ожидания WaitForMultipleObjects. Когда курсор воспроизведения оказывается на одной из позиций уведомления, функция ожидания возвращает системный номер соответствующего события. Вычитая из него системную константу WAIT_OBJECT_0, получаем индекс события в массиве PlayEvents, передававшимся в функцию ожидания во втором параметре. Далее, в зависимости от значения индекса, определяем, какую половину буфера надо заполнять. Само заполнение выполняет процедура FillBufferSegment. Первый параметр процедуры - смещение, в байтах, начала заполняемого участка (сегмента) относительно начала буфера. Второй параметр - длина участка.

procedure PlayExecute;
var
  evt    : DWORD;  //  номер события
  offset : DWORD;  //  положение начала сегмента относительно начала буфера
begin
  while true do begin
    //  ожидание события
    evt:= WaitForMultipleObjects(2, PWOHandleArray(@PlayEvents), false, INFINITE);
    //  определение индекса события
    evt:= evt - WAIT_OBJECT_0;
    //  определение заполняемой половины буфера
    if evt = 0
      then offset:= PlayEventsPos[1].dwOffset  // заполняется вторая половина
      else offset:= 0;                         // заполняется первая половина
    //--------------------------------------------------
    FillBufferSegment(offset, PlayEventsPos[1].dwOffset);
    //--------------------------------------------------
  end;
end;

Сама процедура FillBufferSegment выглядит так:

procedure FillBufferSegment(offset, l : DWORD);
  // offset, l - положение и длина заполняемого сегмента буфера
var
  ad1, ad2 : pointer;
  l1, l2   : DWORD;
begin
  //  блокирование буфера
  ad1 := nil;
  ad2 := nil;
  hr := FDSB.Lock(offset, l, @ad1, @l1, @ad2, @l2, 0);
  if failed(hr) then begin
    ShowMessage('Ошибка при блокировании звукового буфера');
    halt;
  end;
  //---------------------
  CalcSignalForm(ad1, l1);
  //---------------------
  hr := FDSB.UnLock( ad1, l1, ad2, l2);
  if failed(hr) then begin
    ShowMessage('Ошибка при разблокировании звукового буфера');
    halt;
  end;
end;

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

С блокированием связана некая неприятная особенность DirectSound - возможная потеря буфера. В этом случае, метод Lock возвращает соответствующий код ошибки. В SDK подробно не указываются причины, по которым буфер может быть потерян. На практике, сам я никогда с этим не сталкивался. Поэтому в SineMusit восстановление потерянного буфера не производится и в данной статье не рассматривается. Вместо этого, программа завершает свою работу при любом коде ошибки метода Lock. Более подробно об этом можно прочитать в [2].

Располагая адресом, полученным от метода Lock, буфер можно заполнять, а заполнив, обязательно разблокировать методом Unlock. Заполнение выполняет процедура SineMusit CalcSignalForm. Ее первый параметр - только что полученный адрес заполняемого участка, а второй - длина участка. Исходный код процедуры выглядит так:

var
  ...
  PeriodOffset : real = 0;
  ...

procedure CalcSignalForm(ad: pointer; l: DWORD);
const
  pi2 = 2*pi;
var
  i, j     : integer;
  SampSize : DWORD;    // размер в байтах одной выборки (сэмла) сигнала для одного канала
  ff       : real;     // доля периода сигнала приходящаяся на один период дискретизации
  x        : real;     // положение в периоде сигнала
  amp      : smallint; // выборка (сэмпл) - мгновенная величина сигнала
begin
  SampSize:= WFX.wBitsPerSample div 8;
  //  доля периода сигнала приходящаяся на один период дискретизации
  ff:= SigFreq / WFX.nSamplesPerSec;
  for i:=0 to l div WFX.nBlockAlign - 1 do begin
    x := Frac(i * ff + PeriodOffset);  //  положение в периоде сигнала
    amp := Round(High(smallint) * Sin(pi2*x));
    for j:=1 to WFX.nChannels do begin
      PSmallInt(ad)^ := amp;
      DWORD(ad) := DWORD(ad) + SampSize;
    end;
  end;
  //  положение в периоде для следующего вызова
  PeriodOffset:= Frac((l div WFX.nBlockAlign) * ff + PeriodOffset);
end;

В глобальной переменной PeriodOffset хранится положение в периоде (угол, фаза) сигнала, с которого надо начиннать вычисление выборок сигнала нужной частоты и формы. Разумеется, при старте воспроизведения значение этой переменной устанавливается в ноль. Расчет идет с шагом равным доле периода приходящейся на один такт частоты дискретизации. Для простоты здесь показан вариант расчета только для одной формы сигнала - синусоидальной. Эта форма получается с помощью стандартной функции Sin. Выборки звуковых каналов в буфере располагаются поочередно - левый, правый, левый, правый, .... После того, как буфер будет заполнен, вычисляется то положение в периоде, с которого вычисления будут продолжены при следующем вызове процедуры. Полученное значение сохраняется в глобальной переменной PeriodOffset. На этом работа процедуры завершается.

Вот собственно и все характерные особенности устройства SineMusit. Как обычно, в статьях раздела "вид изнутри" не рассматриваются типовые вопросы создания прикладных программ. Предполагается, что базовыми навыками программирования читатель уже владеет. На следующей странице размещен исходный код модуля, содержащего всё, что описывалось выше и еще несколько других процедур и функций. Это немного упрощенная версия модуля реально используемого в SineMusit. Упрощения сделаны для того, чтобы код был яснее и лучше читался. Например, возможность переключать частоту дискретизации заметно загромождает код, но принципиальных вопросов вызывать не должна. Модуль нетрудно доработать для каких-то других задач. Например, замена процедуры расчета формы сигнала, на процедуру, подгружающую следующий фрагмент музыкального файла, сделает этот модуль основой проигрывателя.

Несколько слов о генерации сигналов прямоугольной, пилообразной и треугольной формы. Да, такая "фишка" в SineMusit как бы есть, но именно "как бы". Не стоит забывать, что сигналы этих форм имеют, по теории, бесконечный спектр. Аппаратные генераторы таких сигналов легко обеспечивают спектр в десятки мегагерц, даже на элементной базе сорокалетней давности. В то же время, спектр сигнала звуковой карты принципиально ограничен ее выходным фильтром на уровне 20 кГц максимум. Следовательно, с помощью звуковой карты можно получить лишь подобие таких сигналов. Причем похожесть будет тем выше, чем ниже частота первой гармоники. При повышении частоты, форма сигнала будет все больше приближаться к синусоидальной. Кроме того, чтобы форма была чистой, без выбросов и колебаний после фронтов, надо применять специальные алгоритмы. При написании SineMusit этому вопросу уделялось минимальное внимание, поэтому соответствующие функции реализованы предельно упрощенно и в данной статье нет смысла их подробно рассматривать.

изображение программы

На последней странице размещен исходный код еще одного модуля, который содержит массивы всех наборов частот генерируемых SineMusit. Вы можете свободно использовать оба модуля в своих проектах. Для примера представлен маленький генератор для настройки шестиструнной гитары. На форме размещены семь кнопок типа TSpeedButton (рис. справа). Свойство AllowAllUp всех кнопок имеет значение true, а свойство GroupIndex установлено в 1. Все кнопки имеют общий обработчик события OnClick. Чтобы общий обработчик по разному реагировал на события разных кнопок, свойство Tag каждой кнопки содержит индекс ее частоты в массиве частот. Например, у кнопки с надписью "6" (самая толстая струна - самая низкая частота), свойство Tag имеет значение 0, у кнопки с надписью "5", Tag равно 1 и т.д.. Ниже приведен исходный код модуля главной формы этого примера.


unit SGForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Buttons, StdCtrls,
  //-------------
  SGFreq, DSTools;

type
  TMForm = class(TForm)
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    SpeedButton3: TSpeedButton;
    SpeedButton4: TSpeedButton;
    SpeedButton5: TSpeedButton;
    SpeedButton6: TSpeedButton;
    SpeedButton7: TSpeedButton;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MForm: TMForm;

implementation

{$R *.dfm}

procedure TMForm.FormCreate(Sender: TObject);
begin
  InitDS(MForm.Handle);
end;

procedure TMForm.SpeedButton1Click(Sender: TObject);
begin
  if TSpeedButton(Sender).Down then begin
    // установить новую частоту
    SigFreq := Freq_git6c[TSpeedButton(Sender).Tag];
    // если буфер в состоянии "остановлен", то включить проигрывание
    if not PlayingStatus then
      PlayBuffer;
  end
  else
    StopBuffer;
end;

end.
СПИСОК ЛИТЕРАТУРЫ
  1. Краснов М.В. DirectX. Графика в проектах Delphi. - СПб.; БХВ-Петербург, 2001. - 416 с.; ил.
  2. Гончаров Д., Салихов Т. DirectX 7.0 для программистов. Учебный курс (+CD). - СПб.; Питер, 2001 - 528 с.; ил.
  3. Архангельский А.Я. Программирование в Delphi для Windows. Версии 2006, 2007, Turbo Delphi. - М.; ООО "Бином-пресс", 2007 г. - 1248 с.; ил.

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

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

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