Разработка синтезатора звука в среде визуального программирования Delphi. Программная реализация
Министерство
образования Республики Беларусь
Учреждение
образования
«Гомельский
государственный университет
имени
Франциска Скорины»
Факультет
математический
Кафедра
вычислительной математики и программирования
Разработка
синтезатора звука в среде визуального программирования Delphi. Программная
реализация
Курсовой
проект
Исполнитель:
студент группы
ПO-32
Дубовик Т. С.
Научный
руководитель:
к.т.н., доцент
кафедры
Цурганова Л. А.
Гомель
2014
Введение
Звук - физическое явление, представляющее собой
распространение в виде упругих волн механических колебаний в твёрдой, жидкой
или газообразной среде. В узком смысле под звуком имеют в виду эти колебания,
рассматриваемые по отношению к тому, как они воспринимаются органами чувств
животных и человека.
Как и любая волна, звук характеризуется
амплитудой и спектром частот. Обычно человек осознаёт колебания, передаваемые
по воздуху, в диапазоне частот от 16-20 Гц до 15-20 кГц. Звук ниже диапазона
слышимости человека называют инфразвуком; выше: до 1 ГГц, - ультразвуком, от 1
ГГц - гиперзвуком.
Восприятие посредством слуха создает
акустическое пространство, центр которого в каждый данный момент находится там,
где находится источник звука, - в отличие от визуального пространства, центром
которого является каждый воспринимающий посредством зрения человек.
Среди слышимых звуков следует особо выделить
фонетические, речевые звуки и фонемы (из которых состоит устная речь) и
музыкальные звуки (из которых состоит музыка).
В своём проекте я подробно рассмотрю генерацию
звуковых волн и создание собственного источника (генератора звука) с помощью
средства разработки Delphi.
Целью данного курсового проекта является
подробное ознакомление с принципами генерации звука и создание собственного
генератора звука.
1. Основные сведения о генераторе звука
Источниками звука могут стать любые явления, вызывающие
местное изменение давления или механическое напряжение. Широко распространены
источники звука в виде колеблющихся твёрдых тел (например, диффузоры
громкоговорителей и мембраны телефонов, струны и деки музыкальных инструментов;
в ультразвуковом диапазоне частот - пластинки и стержни из пьезоэлектрических
материалов или магнитострикционных материалов). Источниками звука могут служить
и колебания ограниченных объёмов самой среды (например, в органных трубах,
духовых музыкальных инструментах, свистках и т.п.).
Чистый звуковой тон представляет собой звуковую
волну, подчиняющуюся синусоидальному закону:
у =,
где - максимальная амплитуда синусоиды;
- частота (=);
- количество колебаний упругой
среды в секунду ();
- период;
- время (параметрическая
переменная).
Звук характеризуется частотой (f),
обычно измеряемой в герцах, т.е. количеством колебаний в секунду, и амплитудой
(у). Амплитуда звуковых колебаний определяет громкость звука.
Для монотонного звука (меандр.)
характерно постоянство амплитуды во времени.
Затухающие звуковые колебания
характеризуются уменьшением амплитуды с течением времени.
Человек воспринимает механические
колебания частотой 20 Гц - 20 КГц (дети - до 30 КГц) как звуковые. Колебания с
частотой менее 20 Гц называются инфразвуком, колебания с частотой более 20 КГц
- ультразвуком. Для передачи разборчивой речи достаточен диапазон частот от 300
до 3000 Гц.
Если несколько чистых синусоидальных
колебаний смешать, то вид колебания изменится - колебания станут
несинусоидальными.
Особый случай, когда смешиваются не
любые синусоидальные колебания, а строго определенные, частота которых
отличается в два раза (гармоники).
Основная гармоника имеет частоту, и
амплитуду а1; вторая гармоника - частоту f2 и амплитуду а2; третья гармоника
соответственно f3 и a3.
Причем f1<f2<f3,
а1>а2>а3,При бесконечном количестве таких гармоник образуется
периодический сигнал, состоящий из прямоугольных импульсов.
На слух всякое отклонение от
синусоиды приводит к изменению звучания. В IBM PC источником звуковых колебаний
является динамик (PC Speaker), воспроизводящий частоты приблизительно от 2 до 8
КГц. Для генерации звука в PC Speaker используются прямоугольные импульсы.
.1 Частотный синтез
Многоголосый частотный синтезатор
предназначен для генерации звуковых сигналов сложной формы. Существуют два
принципиально различных способа синтеза звуковых сигналов:
частотный синтез (FM - Fregueney
Modulation);
волновой синтез (WS - Ware
Synthesys).
Частотные синтезаторы (модуляторы)
генерируют звуковые колебания синусоидальной формы заданной частоты и
амплитуды, благодаря чему значительно улучшается качество звука (по сравнению с
попытками генерировать звук с помощью прямоугольных колебаний). Наличие
нескольких генераторов позволяет использовать эти устройства для синтеза
сложных звуковых сигналов, в том числе речи.
Тембр звука формируется воздействием
одной простой волны на другую с целью изменения ее частоты. Под воздейтсвием
частотной модуляции возникают более спектрально богатые и сложные звуки,
которые невозможно получить другими типами синтеза.
Впервые о применении частотной
модуляции для синтеза звука задумался в 1966 году американец John Chowning , в
будущем директор Центра компьютерных исследований музыки и акустики (CCRMA)
Стенфордского университета, а на тот момент времени - преподаватель композиции
кафедры электронной музыки.
В своём проекте я воспользовался
методом генерации путём частотной модуляции, потому что он является достаточно
эффективным и простым для реализации, к тому же спектр звуков, создаваемый с
помощью частотной модуляции, действительно огромен.
2. Технология разработки генератора
звука
Я разработал программу генератор
звука в среде Borland Delphi с использованием стандартных классов TMemoryStream
(для хранения звука в виде бинарных данных) и TStrings (для хранения
характеристик конкретной частоты). Также описал новый класс TFreqObj и
использовал достаточно нестандартный класс TWaveFormatEx для хранения формата
аудиоданных.
При запуске пользователь может
настроить громкость и частоту звука, также он должен выбрать качество звука и
время проигрывания в миллисекундах (0 - зациклить). После нажатия кнопки
“Старт” пользователь услышит синусоидальный звук соответствующей громкости и
частоты.
Сейчас я подробно опишу используемые
классы, а в дальнейшем подробно разберу процедуру MakeComplexSound.
.1 Класс TFreqObj
Я создал класс TFreqObj для удобства
хранения и последующего использования характеристик частоты звука, таких, как:
- максимальная амплитуда синусоиды;
- частота (=);
- количество колебаний упругой
среды в секунду ().
Также в нём описан простейший
конструктор с параметрами и функция приведения всех параметров в одну
форматированную строку.
.2 Класс TWaveFormatEx
Для хранения информации об
аудио-данных мы будем использовать класс TWaveFormatEx. Структура TWaveFormatEx
определена следующим образом:
{wFormatTag;nChannels;nSamplesPerSec;nAvgBytesPerSec;nBlockAlign;wBitsPerSample;
WORD cbSize;
} WAVEFORMATEX;
Описание полей следующее:указывает
тип аудио-данных. Нас интересуют только несжатые аудио-данные (PCM), поэтому
для нас нужно чтобы тут было значение 0x0001 (то есть 1).определяет количество
каналов.определяет норму отбора в секунду. Обычно используется значение 44100
Гц.чаще всего определяет среднюю скорость передачи байтов в секунду.определяет
выравнивание в байтах.определяет количество бит для выборки. Обычно равно 8
либо 16.определяет размер всей структуры WAVEFORMATEX. Что в итоге равняется 18
байтам.
3 Алгоритмы и программная реализация
генератора звука
Суть метода генерации состоит в
следующем: в текстовом виде хранятся характеристики конкретной частоты (длина
волны, частота и т.д.)в формате TFreqObj, затем создаётся экземпляр класса
TWaveFormatEx (он предназначен для приведения звуковой информации в понятный
аудиоплате вид), этот класс заполняется информацией о звуке и затем в него
добавляется сгенерированная в зависимости от характеристик конкретной частоты
информация (грубо говоря, звук в виде бинарных данных) и отсылается функцией
PlaySound на аудиоплату.
3.1 Процедура
MakeComplexSound
Данная процедура заполняет формат
аудиоданных TWaveFormatEx, затем его заполняет сначала необходимыми низкими
частотами (они нужны для корректного воспроизведения звука), а затем данными в
зависимости от выбранной пользователем частоты.
Заполнение формата
TWaveFormatEx:WaveFormatEx do:= WAVE_FORMAT_PCM;:= Mono;:= SampleRate;:=
$0008;:= (nChannels * wBitsPerSample) div 8;:= nSamplesPerSec * nBlockAlign; := 0;
end;
Заполнение объекта типа
TMemoryStream для последующего его отправления в процедуру PlaySound:
MS[n] :=
TMemoryStream.Create;MS[n] do:= (Duration * SampleRate) div 1000;:=
Length(WaveId) + Length(FmtId) + SizeOf(DWORD) +(TWaveFormatEx) +
Length(DataId) + SizeOf(DWORD) + DataCount;(RiffId[1], 4); // 'RIFF'(RiffCount,
SizeOf(DWORD)); // file data size(WaveId[1], Length(WaveId)); //
'WAVE'(FmtId[1], Length(FmtId)); // 'fmt ':= SizeOf(TWaveFormatEx);(TempInt,
SizeOf(DWORD)); // TWaveFormat data size(WaveFormatEx, SizeOf(TWaveFormatEx));
// WaveFormatEx record(DataId[1], Length(DataId)); // 'data'(DataCount,
SizeOf(DWORD)); // sound data
size:=TFreqObj(freqlist.objects[0]).ftemp;:=0;:=false;:=samplerate div 2;i := 0
to trunc(2/minfreq*samplerate) do:=0;j:=0 to freqlist.count-1 doTFreqObj(freqlist.objects[j])<>nil
thenTFreqObj(freqlist.objects[j]) doftemp>sampdiv2 then freqerror:=true;:= 2
* Pi *
Ftemp;:=p/pi;:=soundvalue+trunc(Volume*a/1000*sin(ph+i*w/SampleRate);;soundvalue>maxvalmaxval:=soundvalue;;i
:= 0 to DataCount - 1 do:=127;j:=0 to freqlist.count-1
doTFreqObj(freqlist.objects[j])<>nil thenTFreqObj(freqlist.objects[j])
doftemp< sampdiv2 then:=samplerate/Ftemp;j=0 then
setlength(imagedata,min(datacount,trunc(5*ptspercycle)));:=frac(i/ptspercycle+p/360);:=a/1000
;:= 2 * Pi * Ftemp;:=p/pi;:=soundvalue+trunc(Volume*amp*sin(ph+i*w /
SampleRate));;;maxval>127 then byteval:=soundvalue*127 div
maxvalbyteval:=soundvalue;(Byteval, SizeOf(Byte));i<=high(imagedata) then
imagedata[i]:=byteval;
end;;
3.2 Запись звука
в файл
Получившийся звук можно записать в
файл формата .WAV. Создавая поток для проигрывания нашей волны, мы уже задали в
самом его начале формат данных, все характеристики нашей звуковой волны, то
есть мы имеем заголовок и собственно звуковую волну в виде потока данных. Класс
TMemoryStream, которым мы пользуемся для хранения потока в памяти, содержит
метод SaveToFile. С помощью данного метода мы сохраняем наш поток в файл
формата .WAV длиной в 1 секунду. Данный файл может быть воспроизведён в любом
проигрывателе.
Ниже представлен код процедуры
записи звука в файл:
procedure
TForm1.Button1Click(Sender: TObject); программа генератор звукButton1.Caption='Запись в файл'
then.Click;.Caption:='Сохранить';if
Button1.Caption='Сохранить'
then(nil,0,SND_Purge);:=false;SaveDialog2.Execute
then[streaminuse].SaveToFile(SaveDialog2.FileName);.Caption:='Запись в файл';assigned
(ms[streaminuse]) then freeandnil(Ms[streaminuse]);
end;;;
Заключение
В ходе выполнения курсового проекта
были изучены основы программирования для работы со звуком: изучены основные
понятия, некоторые классы для работы со звуком, а также использование потоков в
создаваемом приложении. В качестве примера полученных навыков было разработано
приложение “Генератор звука”.
Результат работы может
использоваться на любом ПК для создания и изучения синусоидального звука.
Список использованных источников
Википедия
- свободная энциклопедия // URL: https://www.wikipedia.org.ru - крупнейший
ресурс в России о создании компьютерных игр // URL: https://www.gamedev.ru.ru -
программирование звука в среде Windows // URL: https://www.soundcoding.ru
Кинтцель,
Т. Руководство программиста по работе со звуком / Т. Кинтцель. - М.: ДМК, 2000.
- 432 с.
Приложение А
Скриншот работы программы
Приложение Б
Исходный кодU_SoundGen2;,
Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,, mmsystem,
StdCtrls, ComCtrls, Menus, CheckLst, ExtCtrls, shellAPI,
Buttons;=class(TObject),f,P,a,shape:INTEGER;:String;Create(newf, newP, newA,
newshape:integer);makestringrep;;= 0..127 ;= class(TForm): TPageControl;:
TTabSheet;: TOpenDialog;: TSaveDialog;: TTabSheet;: TLabel;: TLabel;: TLabel;:
TLabel;: TLabel;: TButton;: TButton;: TTrackBar;: TCheckListBox;: TTrackBar;:
TRadioGroup;: TEdit;: TEdit;: TEdit;: TRadioGroup;: TUpDown;: TLabel;: TLabel;:
TLabel;: TButton;: TSaveDialog;PlayBtnClick(Sender:
TObject);StopBtnClick(Sender: TObject);VolBarChange(Sender:
TObject);FormCreate(Sender: TObject);FreqbarChange(Sender:
TObject);RateRgrpClick(Sender: TObject);VolEditChange(Sender:
TObject);FreqEditChange(Sender: TObject);FreqEditKeyPress(Sender: TObject; var
Key: Char);Button1Click(Sender: TObject);
{ Private declarations }
{ Public declarations }: array[0..1]
of tmemorystream;:integer;:integer;, soundfilename
:string;:TStringList;:boolean;:boolean;:boolean;:array of
byte;MakeComplexSound(n:integer; freqlist:TStrings;{mSec}: Integer; Volume:
TVolumeLevel);MsPlaySound;savesound;LoadSound(name:string);checkplaying;;: TForm1;:
Integer = 11025; // 8000, 11025, 22050, or 44100
{$R *.DFM}math,
U_SetFreq;makesoundname(name:string):string;n:integer;:string;:=extractfilename(name);:=pos('.',s);n>0
then delete(s,n,length(s)-n+1);:=s;;shapenames:array[0..3] of
string=('Sine','Square','Sawtooth','Triangle');:string='DEFAULT.SND';TFreqObj.Create;create;:=newf;:=f;:=newp;:=newA;:=newSHAPE;;;TFreqObj.makestringrep;:=format('%5d
(F:%4d, P:%4d, A:%4d %S)',[ftemp,f,p,a,SHAPENAMES[SHAPE]]);;: Word = $0001;:
string = 'RIFF';: string = 'WAVE';: string = 'fmt ';: string =
'data';TForm1.MakeComplexSound(N:integer {stream # to use};:TStrings;{mSec}:
Integer;: TVolumeLevel);: TWaveFormatEx;,j, TempInt, RiffCount: integer;:
integer;:byte;, maxval:integer;,ph, amp: double;:boolean;:integer;:string;,x:extended;WaveFormatEx
do:= WAVE_FORMAT_PCM;:= Mono;:= SampleRate;:= $0008;:= (nChannels *
wBitsPerSample) div 8;:= nSamplesPerSec * nBlockAlign;:= 0;;[n] :=
TMemoryStream.Create;MS[n] do:= (Duration * SampleRate) div 1000;:=
Length(WaveId) + Length(FmtId) + SizeOf(DWORD) +(TWaveFormatEx) +
Length(DataId) + SizeOf(DWORD) + DataCount;(RiffId[1], 4); // 'RIFF'(RiffCount,
SizeOf(DWORD)); // file data size(WaveId[1], Length(WaveId)); //
'WAVE'(FmtId[1], Length(FmtId)); // 'fmt ':= SizeOf(TWaveFormatEx);(TempInt,
SizeOf(DWORD)); // TWaveFormat data size(WaveFormatEx, SizeOf(TWaveFormatEx));
// WaveFormatEx record(DataId[1], Length(DataId)); // 'data'(DataCount,
SizeOf(DWORD)); // sound data
size:=TFreqObj(freqlist.objects[0]).ftemp;:=0;:=false;:=samplerate div 2;i := 0
to trunc(2/minfreq*samplerate) do:=0;j:=0 to freqlist.count-1
doTFreqObj(freqlist.objects[j])<>nil thenTFreqObj(freqlist.objects[j])
doftemp>sampdiv2 then freqerror:=true;:= 2 * Pi * Ftemp;:=p/pi;:=
soundvalue+ trunc(Volume * a/1000* sin(ph+i * w / SampleRate));;soundvalue>maxvalmaxval:=soundvalue;;i
:= 0 to DataCount - 1 do:=127;j:=0 to freqlist.count-1
doTFreqObj(freqlist.objects[j])<>nil thenTFreqObj(freqlist.objects[j])
doftemp< sampdiv2 then:=samplerate/Ftemp;j=0 then setlength(imagedata,min(datacount,trunc(5*ptspercycle)));:=frac(i/ptspercycle+p/360);:=a/1000
;:= 2 * Pi * Ftemp;:=p/pi;:= soundvalue+ trunc(Volume * amp* sin(ph+i * w /
SampleRate));;;maxval>127 then byteval:=soundvalue*127 div
maxvalbyteval:=soundvalue;(Byteval, SizeOf(Byte));i<=high(imagedata) then
imagedata[i]:=byteval;;;;TForm1.msPlaySound;options:integer;:=SND_MEMORY or
SND_ASYNC;duration.position=0 then options:=options or
SND_LOOP;(MS[streaminuse].Memory, 0, options);;TForm1.PlayBtnClick(Sender:
TObject);:integer;:TStringlist;:integer;:integer;:=TStringlist.create;listbox1
doitems.count>0 theni:=0 to items.count-1 do if
checked[i]freqlist.addobject(items[i],items.objects[i]);:=(streaminuse+1) mod
2;freqlist.count>0 then:=duration.position;unitsgrp.itemindex=1 then dur:=dur*1000;dur=0
then dur:=5000;(nextstream, freqlist, dur,
Volbar.position);(sender);:=true;:=nextstream;;stopbtnclick(sender);.free;;;TForm1.StopBtnClick(Sender:
TObject);(nil,0,SND_Purge);assigned (ms[streaminuse]) then
freeandnil(Ms[streaminuse]);:=false;;TForm1.FormCreate(Sender:
TObject);:=1;.activepage:=SoundSheet;('-Default.snd');;TForm1.CheckPlaying;playing
then
playbtnclick(self)(self);(self);;;TForm1.savesound;:textfile;:integer;(ff,soundfilename);(ff);(ff,'D','
',duration.position,' ',unitsgrp.itemindex);i:=0 to listbox1.items.count-1
dolistbox1, TFreqObj(items.objects[i]) do writeln(ff,(checked[i]),' ', f,'
',p,' ',a,' ',shape,'
',stringrep);(ff);:=false;;TForm1.Loadsound(name:string);:textfile;:TFreqobj;:integer;:char;,units:integer;:=name;(ff,soundfilename);(ff);(ff,d);d='D'
then(ff,dur,
units);.position:=dur;.itemindex:=units;reset(ff);:=makesoundname(soundfilename);.clear;not
eof(ff) dolistbox1 do:=tfreqobj.create(0,0,0,0);t do readln(ff,chk,
f,p,a,shape,stringrep);.ftemp:=t.f;.AddObject(t.stringrep,t);[items.count-1]:=chk<>0;(ff);:=false;.position:=TFreqobj(listbox1.items.objects[0]).f;.position:=64;(self);;TForm1.FreqbarChange(Sender:
TObject);:extended;:integer;freqbar.position>0 thenlistbox1,items
docount>=1 then:=freqbar.position/TFreqobj(objects[0]).f;.text:=inttostr(freqbar.position);i:=0
to count-1 doobjects[i]<>nil thenTFreqObj(objects[i])
do:=round(f*scale);;[i]:=stringrep;;;;;;samprates:array[0..3] of
integer=(8000,11025,22050,44100);TForm1.RateRgrpClick(Sender:
TObject);:=samprates[rateRgrp.itemindex];;;TForm1.VolBarChange(Sender:
TObject);.text:=inttostr(Volbar.position);;;TForm1.VolEditChange(Sender:
TObject);n:integer;:=volbar.position;.position:=strtointdef(VolEdit.text,n);n<>volbar.position
then volbarchange(sender);;TForm1.FreqEditChange(Sender:
TObject);n:integer;:=freqbar.position;.position:=strtointdef(freqedit.text,n);n<>freqbar.position
then FreqBarchange(sender);;TForm1.FreqEditKeyPress(Sender: TObject; var Key:
Char);key=#13 then Freqeditchange(sender);;TForm1.Button1Click(Sender:
TObject);Button1.Caption='Запись в файл'
then.Click;.Caption:='Сохранить';if
Button1.Caption='Сохранить'
then(nil,0,SND_Purge);:=false;SaveDialog2.Execute
then[streaminuse].SaveToFile(SaveDialog2.FileName);.Caption:='Запись в файл';assigned
(ms[streaminuse]) then freeandnil(Ms[streaminuse]);
end;;;.