Генерация и воспроизведение звука в среде Lazarus (язык pascal)
В данной статье рассмотрим решение задачи создания генератора звука.
Решаемые задачи:
1. Организация алгоритма изменения параметров звука;
2. Управление громкостью;
3. Вывод звука через аудио плату.
Сделано в последней на данный момент версии Lazarus 2.0.8 (в других вервиях возможны незначительное изменения).
Задачей будет ввод частоты импульса, длительности импульса, периода повторения и громкости (амплитуды).
Вначале необходимо создать пустой проект и настроить его в меню Проект -> Параметры проекта.
Сохранить проект в отдельной папке: Проект -> Сохранить проект как…
На форму поместить следующие элементы:
1. TLabel (из палитры Standart)- для подписей «Частота», «Длительность», «Амплитуда», «Правый», «Левый», «Период импульсов» (нужно изменить свойство Caption)
2. TSpinEdit (из палитры Misc) — для ввода целочисленных параметров:
Частоты импульсов TSpinEdit1 (свойства: Value: 1000 (текущая частота в Гц), MinValue:20 (минимальная звуковая частота в Гц), MaxValue: 20000 (максимальная звуковая частота в Гц))
Длительности импульсов TSpinEdit2 (свойства: Value: 200 (текущее значение длительности импульса в мс), MinValue:1 (минимальная длительность импульса в мс), MaxValue: 1000 (максимальная длительность импульса в мс))
Амплитуды импульсов TSpinEdit3 (свойства: Value: 128 (текущая громкость), MinValue:0 (минимальная громкость), MaxValue: 128 (максимальная громкость))
Амплитуды Правого канала TSpinEdit4 (свойства: Value: 30000 (текущая амплитуда правого канала), MinValue:0 (минимальная амплитуда правого канала), MaxValue: 65000 (максимальная амплитуда правого канала), Increment: 500 (шаг изменения амплитуды для удобства))
Амплитуды Левого канала TSpinEdit5 (свойства: Value: 30000 (текущая амплитуда левого канала), MinValue:0 (минимальная амплитуда левого канала), MaxValue: 65000 (максимальная амплитуда левого канала), Increment: 500 (шаг изменения амплитуды для удобства))
3. TTrackBar (из палитры Common Controls) — для регулирования значения периода следования импульсов в мс (свойства: Position: 1000 (текущее), Min: 1 (минимальная длительность 1 мс), Max: 2000 (максимальная длительность 2 секунды))
4. TButton (из палитры Standart) — для кнопок «Запуск» (Button1) и «Остановка» (Button2). Названия меняются в свойстве Caption.
5. TTimer (из палитры System) — для задания периода генерации импульсов (свойства: Enabled: False (пока отключить))
Должно получиться так:

Теперь нужно создать обработку событий в исходном коде программы.
Понадобятся три процедуры:
1. Кнопка Запуск procedure TForm1.Button1Click(Sender: TObject);
На форме совершить двойное нажатие на кнопку Button1
2. Кнопка procedure TForm1.Button2Click(Sender: TObject);
На форме совершить двойное нажатие на кнопку Button2
3. Сработал таймер procedure TForm1.Timer1Timer(Sender: TObject);
На форме совершить двойное нажатие на элемент Timer1.
Данные функции автоматически создаются средой.
Для реализации функций подключения к звуковой плате, можно воспользоваться готовыми функциями из библиотеки drkb.ru:
1. Воспроизведение звука
procedure MakeSound(Frequency {Hz}, Duration {mSec}: integer; Volume:integer);
Frequency — задает частоту сигнала целочисленной переменной в Гц,
Duration — задает длительность импульса целочисленной переменной в мсек,
Volume — задает громкость целочисленной переменной.
2. Установка громкости каждого канала
procedure SetVolume(const volL, volR: Word);
volL — задает амплитуду левого канала целочисленной переменной,
volR — задает амплитуду правого канала целочисленной переменной.
Они используют модуль MMSystem, поэтому его нужно добавить в раздел uses.
Получаем следующий шаблон в разделе implementation
implementation
{$R *.lfm}
uses
MMSystem;
// Проигрывание звука
procedure MakeSound(Frequency {Hz}, Duration {mSec}: integer; Volume:integer);
{writes tone to memory and plays it}
var
WaveFormatEx: TWaveFormatEx;
MS: TMemoryStream;
i, TempInt, DataCount, RiffCount: integer;
SoundValue: byte;
w: double; // omega ( 2 * pi * frequency)
const
stereo:Word = $0002;
SampleRate: Integer = 44100; // 8000, 11025, 22050, or 44100
RiffId: string = 'RIFF';
WaveId: string = 'WAVE';
FmtId: string = 'fmt ';
DataId: string = 'data';
begin
if Frequency > (0.6 * SampleRate) then
begin
ShowMessage(Format('Sample rate of %d is too Low to play a tone of %dHz',
[SampleRate, Frequency]));
Exit;
end;
with WaveFormatEx do
begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := stereo;
nSamplesPerSec := SampleRate;
wBitsPerSample := $0008;
nBlockAlign := (nChannels * wBitsPerSample) div 8;
nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
cbSize := 0;
end;
MS := TMemoryStream.Create;
with MS do
begin
{Calculate length of sound data and of file data}
DataCount := (Duration * SampleRate) div 1000; // sound data
RiffCount := Length(WaveId) + Length(FmtId) + SizeOf(DWORD) +
SizeOf(TWaveFormatEx) + Length(DataId) + SizeOf(DWORD) + DataCount;
// file data
{write out the wave header}
Write(RiffId[1], 4); // 'RIFF'
Write(RiffCount, SizeOf(DWORD)); // file data size
Write(WaveId[1], Length(WaveId)); // 'WAVE'
Write(FmtId[1], Length(FmtId)); // 'fmt '
TempInt := SizeOf(TWaveFormatEx);
Write(TempInt, SizeOf(DWORD)); // TWaveFormat data size
Write(WaveFormatEx, SizeOf(TWaveFormatEx)); // WaveFormatEx record
Write(DataId[1], Length(DataId)); // 'data'
Write(DataCount, SizeOf(DWORD)); // sound data size
{calculate and write out the tone signal}// now the data values
w := 2 * Pi * Frequency; // omega
for i := 0 to DataCount - 1 do
begin
SoundValue := 127 + trunc(Volume * sin(i * w / SampleRate));
// wt = w * i / SampleRate
Write(SoundValue, SizeOf(Byte));
end;
{now play the sound}
sndPlaySound(MS.Memory, SND_MEMORY or SND_SYNC);
MS.Free;
end;
end;
// Установка громкости правого и левого канала
procedure SetVolume(const volL, volR: Word);
var
hWO: HWAVEOUT;
waveF: TWAVEFORMATEX;
vol: DWORD;
begin
// init TWAVEFORMATEX
FillChar(waveF, SizeOf(waveF), 0);
// open WaveMapper = std output of playsound
waveOutOpen(@hWO, WAVE_MAPPER, @waveF, 0, 0, 0);
vol := volL + volR shl 16;
// set volume
waveOutSetVolume(hWO, vol);
waveOutClose(hWO);
end;
{ TForm1 }
//Кнопка Запуск
procedure TForm1.Button1Click(Sender: TObject);
begin
end;
//Кнопка Остановка
procedure TForm1.Button2Click(Sender: TObject);
begin
end;
//Сработал таймер
procedure TForm1.Timer1Timer(Sender: TObject);
begin
end; Для хранения параметров импульсов необходимо задать глобальные переменные в в разделе var. Можно обойтись без них, но для удобства чтения кода оставим так.
var Form1: TForm1; Frq: Integer; // Частота ImpLen: Integer;// Длительность импульса Vol: Integer; // Амплитуда импульса
Теперь нужно определить алгоритм работы. Например, программа после нажатия кнопки запуск будет запускать таймер и через определенный момент времени он будет получать параметры импульса из полей на форме и воспроизводить его.
Задаем алгоритм Запуска:
//Кнопка Запуск procedure TForm1.Button1Click(Sender: TObject); begin // Установка введенных параметров генерации Timer1.Interval:=TrackBar1.Position; // Установка периода следования импульсов Timer1.Enabled:= True; // Запуск прерывания таймера end;
Задаем алгоритм Остановки:
//Кнопка Остановка
procedure TForm1.Button2Click(Sender: TObject);
begin
Timer1.Enabled:=False; // Отключение прерывания таймера
end; Задаем алгоритм обработки таймера:
//Сработал таймер
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Frq:=SpinEdit1.Value; // Установка частоты сигнала
ImpLen:= SpinEdit2.Value;// Установка длительности импульсов
Vol := SpinEdit3.Value; //Установка амплитуды звука
SetVolume(SpinEdit5.Value, SpinEdit4.Value); // Установка громкости левого и правого каналов
Timer1.Interval:=TrackBar1.Position; // Изменение периода следования импульсов
MakeSound(Frq, ImpLen, Vol); // Воспроизвести импульс
end; Проверяем программу:
Задаем параметры на форме:

Подключаем наушники и к левому микрофон. (можно и без него) Запускаем программу записи и визуализации звука и нажимаем кнопку Запуск.
Можно наблюдать следующий сигнал генерации:

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

Можно менять значения во время работы и получить амплитудные и частотные модуляция сложной формы:

При возникновении вопросов, пишите в комментариях здесь или в сообществе ВК!
(с) Роман Исаков
