









In previous article, Sound Recording with Wave API, we have discussed how to utilize Wave API for sound recording. In this topic, we are going to discuss how to play WAV with Wave API. If you don't need access to wave data currently played and only concern about playing WAV file, information in this article may not suit your need. For playing WAV only, you can use PlaySound() or TMediaPlayer. If you need access to wave data, for example, to be able to change wave data by applying filter and effects to wave data, then this article is for you.
Wave playback functions in Wave API, using naming convention waveOut***, for example, waveOutOpen(), waveOutClose(), etc. We will discuss these functions and how to use them soon. They are declared in MMSystem.pas unit.
This is the first step you must do to do sound playback.
function waveOutOpen(lphWaveOut: PHWaveOut; uDeviceID: UINT;
lpFormat: PWaveFormatEx; dwCallback, dwInstance, dwFlags: DWORD): MMRESULT; stdcall;
lphWaveOut parameter is variable that will holds handle of output device, HWAVEOUT, that we will use to access waveform audio output device. If we set it to nil, dwFlags must be set. uDevice ID is identifier of waveform audio output device. If you don't know what device ID to use, simply use WAVE _MAPPER constant to let waveOutOpen choose it for us. lpFormat points to TWaveFormatEx data structure which define wave format we are going to play. dwCallback holds address of callback, event handle or window handle that will receive notification when something happened during playback. dwInstance is user data that will be passed into the callback function. dwFlags tells Wave API about callback type we want. You can use one of following flags:
Please note that, there are other flags available for waveoutOpen, but I don't discuss it here, because I think, it is not be used very often.
If we succeed, waveOutOpen would return MMSYSERR_NOERROR. Otherwise, it might return error code WAVERR_BADFORMAT for unsupported wave format and etc.
Wave format is defined with TWaveFormatEx dat structure which declared as following:
PWaveFormatEx = ^TWaveFormatEx;
{$EXTERNALSYM tWAVEFORMATEX}
tWAVEFORMATEX = packed record
wFormatTag: Word; { format type }
nChannels: Word; { number of channels (i.e. mono, stereo, etc.) }
nSamplesPerSec: DWORD; { sample rate }
nAvgBytesPerSec: DWORD; { for buffer estimation }
nBlockAlign: Word; { block size of data }
wBitsPerSample: Word; { number of bits per sample of mono data }
cbSize: Word; { the count in bytes of the size of }
end;
wFormatTag holds wave format type. To play PCM wave whic is wave for Windows, we set WAVE_FORMAT_PCM. nChannels holds number of channel, 1 for mono and 2 for stereo. nSamplesPerSec is number of samples per second in Hertz. For WAVE_FORMAT_PCM, typical values are 8000 Hz, 11025 Hz, 22050 Hz and 44100 Hz. nAvgBytesPerSec, holds average required data transfer. For WAVE_FORMAT_PCM, it is a product of nSamplesPerSec and nBlockAlign. nBlockAlign holds data block size.
There are many type of callback, I use function callback a lot. Function callback must be in the form of following format:
procedure WaveOutProc(Handle:HWAVEOUT;uMsg:UINT;
dwInstance:DWORD;
dwParam1,dwParam2:DWORD);stdcall;
Handle is wave out handle we get via waveOutOpen(). uMsg is type of message, i.e WOM_OPEN, WOM_CLOSE and WOM_DONE. We are going to use WOM_DONE very often. This message tells us that device driver is complete processing data. dwInstance is our user data passed when we called waveOutOpen(), dwParam1 and dwParam2 is parameters. When we receive WOM_DONE, dwParam1 will hold pointer to data structure being played.
Following snippet shows hwo to open device for playback with function callback where function's name is _WaveOutProc.
procedure open_wave;
var awaveFormat:TWaveFormatEx;
begin
//siapkan format wave
aWaveFormat.wFormatTag:=WAVE_FORMAT_PCM;
aWaveFormat.wBitsPerSample:=8;
awaveFormat.nChannels:=2;
aWaveFormat.nSamplesPerSec:=22050;
aWaveFormat.nBlockAlign:=awaveFormat.nChannels*aWaveFormat.wBitsPerSample div 8;
aWaveFormat.nAvgBytesPerSec:=aWaveFormat.nSamplesPerSec*aWaveFormat.nBlockAlign;
aWaveFormat.cbSize:=0;
if (WaveOutOpen(@FHandle,WAVE_MAPPER,
@awaveFormat,
cardinal(@_WaveOutProc),
cardinal(Self),
CALLBACK_FUNCTION)<>MMSYSERR_NOERROR) then
begin
raise Exception.Create('Gagal membuka sound device');
end;
end;
Following snippet tests whether wave format of 8 bit stereo, sample rate 22,05 KHz can be played by waveform audio output device.
function isSupportedFormat:boolean;
var awaveFormat:TWaveFormatEx;
begin
//siapkan format wave
aWaveFormat.wFormatTag:=WAVE_FORMAT_PCM;
aWaveFormat.wBitsPerSample:=8;
awaveFormat.nChannels:=2;
aWaveFormat.nSamplesPerSec:=22050;
aWaveFormat.nBlockAlign:=awaveFormat.nChannels*aWaveFormat.wBitsPerSample div 8;
aWaveFormat.nAvgBytesPerSec:=aWaveFormat.nSamplesPerSec*aWaveFormat.nBlockAlign;
aWaveFormat.cbSize:=0;
result:=(WaveOutOpen(nil,WAVE_MAPPER,
@awaveFormat,
0,
0,
WAVE_FORMAT_QUERY)=MMSYSERR_NOERROR);
end;
We need to set up buffers that will holds wave data. We are responsible for managing memory for this buffer. We must tell device driver about this buffers by using waveOutPrepareHeader() before we use it to send wave data to device driver.
function waveOutPrepareHeader(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr;
uSize: UINT): MMRESULT; stdcall;
hWaveOut is handle wave out, lpWaveOutHdr holds our buffers. uSize is size of wave header. Following code snippet is declaration of PWaveHdr.
type
PWaveHdr = ^TWaveHdr;
{$EXTERNALSYM wavehdr_tag}
wavehdr_tag = record
lpData: PChar; { pointer to locked data buffer }
dwBufferLength: DWORD; { length of data buffer }
dwBytesRecorded: DWORD; { used for input only }
dwUser: DWORD; { for client's use }
dwFlags: DWORD; { assorted flags (see defines) }
dwLoops: DWORD; { loop control counter }
lpNext: PWaveHdr; { reserved for driver }
reserved: DWORD; { reserved for driver }
end;
TWaveHdr = wavehdr_tag;
For WAV playback, the most important field islpData which holds buffer address. Size of buffer is kept in dwBufferLength. dwBytesRecorded is not used. It is only for recording. dwUser can be used to passed user data. dwFlags holds bufer status information.
Filling buffer with waveform data is our responsibility. To fill buffer, you can use data copying function like Move() or CopyMemory(). What you must remember, if size of data that we copy in buffer is smaller than actual buffer size, dwBufferLength must be set with the size of data. Please note that you must not change buffer size. If you need to change buffer size, you must tell this change to device driver. First you must call waveOutUnPrepareHeader() (we will discuss it later), change buffer size and call waveOutPrepareHeader() again with new buffer size.
After buffer filled with data, we are ready to play it. To play it, we call waveOutWrite().
function waveOutWrite(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr;
uSize: UINT): MMRESULT; stdcall;
lpWaveOutHdr is wave header that we have been prepared previously. uSize is wave header size. When we send first data block to device driver, playback is started. We can send all WAV data to device driver by calling waveOutWrite once, but please note that, on some soundcards (especially the old ones), maximum buffer size can be processed is 64 KB, so if you have waveform data bigger than 64 KB, waveOutWrite must be called more than once with smaller data blocks.
Other problem may rise regarding playing smaller data blocks is data supply to device driver must be done continously. Otherwise, sound will be jerky becuase of gap between speed of soundcard data processing and speed of our application data supply. From my experience, this gap may rise because of the use of too small buffer size. Small buffer makes soundcard finish processing data faster, much faster than our application data supply to device driver. For our class that we are going to develop, Wave data will be separated into small data blocks of size of 64 KB.
waveOutReset() we use to stop playback.
function waveOutReset(hWaveOut: HWAVEOUT): MMRESULT; stdcall;
To pause playback we use waveOutPause() and to resume playback we use waveOutRestart().
After we finish, before we free buffer make sure we call waveOutUnPrepareHeader() to let device driver knows that we are going to free it. By calling waveOutUnPrepareHeader(), device driver is told to not use it again. After unpreparing, buffer can be destroyed safely.
function waveOutUnprepareHeader(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr;
uSize: UINT): MMRESULT; stdcall;
Our applcation must be polite toWindows, resource we used we must return it back by closing waveform audio output device.
function waveOutClose(hWaveOut: HWAVEOUT): MMRESULT; stdcall;
Steps above are basic steps to play wave with Wave API. There ara some additional functions available regarding wave play back. I will discuss how to get playback progress, setting up volume and get error status.
Status playback position can be queried with waveOutGetPosition(). We only can get get playback position but we cannot change it.
function waveOutGetPosition(hWaveOut: HWAVEOUT; lpInfo: PMMTime; uSize: UINT): MMRESULT; stdcall;
lpInfo will be fiiled with playback position information, tehare are many format for playback position. Before callning this function we must set its format. uSize is size of lpInfo. Declaration of PMMTime is as following:
{ MMTIME data structure }
type
PMMTime = ^TMMTime;
{$EXTERNALSYM mmtime_tag}
mmtime_tag = record
case wType: UINT of { indicates the contents of the variant record }
TIME_MS: (ms: DWORD);
TIME_SAMPLES: (sample: DWORD);
TIME_BYTES: (cb: DWORD);
TIME_TICKS: (ticks: DWORD);
TIME_SMPTE: (
hour: Byte;
min: Byte;
sec: Byte;
frame: Byte;
fps: Byte;
dummy: Byte;
pad: array[0..1] of Byte);
TIME_MIDI : (songptrpos: DWORD);
end;
TMMTime = mmtime_tag;
{$EXTERNALSYM MMTIME}
MMTIME = mmtime_tag;
Field wType must be set. Valid value are TIME_BYTES to get playback position in unit of bytes .If we useTIME_BYTES data field cb will contain position information. TIME_MS to get playback position in unit of miliseconds. For this value field ms will contain data we need. For our classes, we only use these two values.
function waveOutGetErrorText(mmrError: MMRESULT; lpText: PChar; uSize: UINT): MMRESULT; stdcall;
Left and right speaker volume can be set via waveOutSetVolume(). To query volume information, we use waveOutGetVolume.
function waveOutGetVolume(hwo: HWAVEOUT; lpdwVolume: PDWORD): MMRESULT; stdcall;
function waveOutSetVolume(hwo: HWAVEOUT; dwVolume: DWORD): MMRESULT; stdcall;
Left and right volume are combined into one value i.e lpdwVolume dan dwVolume. Left volume is low word of dwVolume and right volume is high word.
TSoundPlayer is wrapper class for waveOut*** functionalities. This class is derived from TWaveObject (discussion of this class can be found in Sound Recording with Wave API article). Abstract method Open, Close, Start, Stop are overriden. Constructor and destructor are overriden with buffer allocation/deallocation code, we also add method to pause anda resume playback.
We add some properties to adjust left and right volume, playback position and event handler property which will be generated when class instance need more data to send to device driver.
TSoundPlayer is declared in same unit with TSoundRecorder class (Sound Recording with Wave API) i.e usound.pas
TSoundPlayer=class(TWaveObject)
private
FBuffer1,FBuffer2,FCurrentBuffer:PWaveHdr;
FPlaying: boolean;
FOnDataRequired: TDataRequiredEvent;
FLeftVolume,FRightVolume:word;
procedure SetPlaying(const Value: boolean);
procedure SwapBuffers;
procedure SetOnDataRequired(const Value: TDataRequiredEvent);
procedure WriteData;
function GetCurrentPosTime: cardinal;
function GetCurrentPosBytes: cardinal;
procedure SetLeftVolume(const Value: word);
function GetLeftVolume: word;
procedure SetRightVolume(const Value: word);
function GetRightVolume: word;
protected
procedure DoDataRequired(const Buffer:pointer;
const BufferSize:cardinal;
var BytesInBuffer:cardinal);virtual;
procedure WaveProc(const handle:THandle;
const msg:UINT;
const dwInstance:cardinal;
const dwParam1,dwParam2:cardinal);override;
public
constructor Create;override;
destructor Destroy;override;
procedure Open;override;
procedure Close;override;
procedure Start;override;
procedure Stop;override;
procedure Pause;
procedure Resume;
published
property CurrentPosTime:cardinal read GetCurrentPosTime;
property CurrentPosBytes:cardinal read GetCurrentPosBytes;
property LeftVolume:word read GetLeftVolume write SetLeftVolume;
property RightVolume:word read GetRightVolume write SetRightVolume;
property Playing:boolean read FPlaying write SetPlaying;
property OnDataRequired:TDataRequiredEvent read FOnDataRequired write SetOnDataRequired;
end;
Impelementation's code looks like following code:
const MAX_BUFFER_SIZE=4*1024;
PLAYBACK_BUFFER_SIZE=64*1024;
{ TSoundPlayer }
procedure TSoundPlayer.Close;
begin
if FHandle<>0 then
begin
Stop;
waveOutUnPrepareHeader(Handle,FBuffer1,Sizeof(TWaveHdr));
waveOutUnPrepareHeader(Handle,FBuffer2,Sizeof(TWaveHdr));
WaveOutClose(FHandle);
FHandle:=0;
end;
end;
procedure _WaveOutProc(Handle:HWAVEOUT;uMsg:UINT;
dwInstance:DWORD;
dwParam1,dwParam2:DWORD);stdcall;
begin
TSoundPlayer(dwInstance).WaveProc(handle,
uMsg,
dwInstance,
dwParam1,
dwParam2);
end;
constructor TSoundPlayer.Create;
begin
inherited;
new(FBuffer1);
ZeroMemory(FBuffer1,sizeOf(TWaveHdr));
GetMem(FBuffer1.lpData,PLAYBACK_BUFFER_SIZE);
FBuffer1.dwBufferLength:=PLAYBACK_BUFFER_SIZE;
new(FBuffer2);
ZeroMemory(FBuffer2,sizeOf(TWaveHdr));
GetMem(FBuffer2.lpData,PLAYBACK_BUFFER_SIZE);
FBuffer2.dwBufferLength:=PLAYBACK_BUFFER_SIZE;
end;
destructor TSoundPlayer.Destroy;
begin
Close;
FreeMem(FBuffer1.lpData,PLAYBACK_BUFFER_SIZE);
FreeMem(FBuffer2.lpData,PLAYBACK_BUFFER_SIZE);
dispose(FBuffer1);
dispose(FBuffer2);
inherited;
end;
procedure TSoundPlayer.DoDataRequired(const Buffer: pointer;
const BufferSize: cardinal; var BytesInBuffer: cardinal);
begin
if Assigned(FOnDataRequired) then
FOnDataRequired(self,Buffer,BufferSize,BytesInBuffer);
end;
function TSoundPlayer.GetCurrentPosBytes: cardinal;
var posInfo:TMMTime;
begin
if (Handle<>0) then
begin
ZeroMemory(@posInfo,sizeof(TMMTime));
PosInfo.wType:=TIME_BYTES;
waveOutGetPosition(Handle,@posInfo,sizeof(TMMTime));
result:=posInfo.cb;
end else
result:=0;
end;
function TSoundPlayer.GetCurrentPosTime: cardinal;
var posInfo:TMMTime;
begin
result:=0;
if Handle<>0 then
begin
PosInfo.wType:=TIME_MS;
waveOutGetPosition(Handle,@posInfo,sizeof(TMMTime));
result:=posInfo.ms;
end;
end;
function TSoundPlayer.GetLeftVolume: word;
var dwVolume:cardinal;
begin
waveOutGetVolume(FHandle,@dwVolume);
FLeftVolume:=LoWord(dwVolume);
FRightVolume:=HiWord(dwVolume);
result:=FLeftVolume;
end;
function TSoundPlayer.GetRightVolume: word;
var dwVolume:cardinal;
begin
waveOutGetVolume(FHandle,@dwVolume);
FLeftVolume:=LoWord(dwVolume);
FRightVolume:=HiWord(dwVolume);
result:=FRightVolume;
end;
procedure TSoundPlayer.Open;
var ahandle:HWAVEOUT;
status:MMResult;
statusStr:string;
begin
if Handle=0 then
begin
status:=WaveOutOpen(@aHandle,
WAVE_MAPPER,
@FWaveFormat,
cardinal(@_WaveOutProc),
cardinal(Self),
CALLBACK_FUNCTION);
FHandle:=aHandle;
if status<>MMSYSERR_NOERROR then
begin
setlength(statusStr,MAXERRORLENGTH);
waveOutGetErrorText(status,pChar(statusStr),
MAXERRORLENGTH);
raise ESndError.Create(statusStr);
end;
WaveOutPrepareHeader(Handle,FBuffer1,sizeof(TWaveHdr));
WaveOutPrepareHeader(Handle,FBuffer2,sizeof(TWaveHdr));
end;
end;
procedure TSoundPlayer.Pause;
begin
if FHandle<>0 then
WaveOutPause(FHandle);
end;
procedure TSoundPlayer.Resume;
begin
if FHandle<>0 then
WaveOutRestart(FHandle);
end;
procedure TSoundPlayer.SetLeftVolume(const Value: word);
var dwVolume:cardinal;
begin
FLeftVolume:=value;
dwVolume:=(FRightVolume shl 16) or FLeftVolume;
waveOutSetVolume(FHandle,dwVolume);
end;
procedure TSoundPlayer.SetOnDataRequired(const Value: TDataRequiredEvent);
begin
FOnDataRequired := Value;
end;
procedure TSoundPlayer.SetPlaying(const Value: boolean);
begin
Stop;
end;
procedure TSoundPlayer.SetRightVolume(const Value: word);
var dwVolume:cardinal;
begin
FRightVolume:=value;
dwVolume:=(FRightVolume shl 16) or FLeftVolume;
waveOutSetVolume(FHandle,dwVolume);
end;
procedure TSoundPlayer.Start;
begin
if Handle<>0 then
begin
Stop;
FCurrentBuffer:=FBuffer1;
//pake buffer 64KB, kalo buffer kecil misal 4KB
//suara terdengar putus-putus
FBuffer1.dwBufferLength:=PLAYBACK_BUFFER_SIZE;
FBuffer2.dwBufferLength:=PLAYBACK_BUFFER_SIZE;
WriteData;
FPlaying:=true;
end;
end;
procedure TSoundPlayer.Stop;
begin
FPlaying:=false;
if FHandle<>0 then
WaveOutReset(FHandle);
end;
procedure TSoundPlayer.SwapBuffers;
begin
if FCurrentBuffer=FBuffer1 then
FCurrentBuffer:=FBuffer2
else
FCurrentBuffer:=FBuffer1;
end;
procedure TSoundPlayer.WaveProc(const handle:THandle;
const msg:UINT;
const dwInstance:cardinal;
const dwParam1,dwParam2:cardinal);
begin
case msg of
WOM_DONE:begin
if FPlaying then
begin
//tukar buffer
SwapBuffers;
WriteData;
end;
end;
end;
end;
procedure TSoundPlayer.WriteData;
var ActBytesInBuffer:cardinal;
begin
ActBytesInBuffer:=0;
DoDataRequired(FCurrentBuffer.lpData,
FCurrentBuffer.dwBufferLength,
ActBytesInBuffer);
if ActBytesInBuffer=0 then
begin
FPlaying:=false;
exit;
end;
if ActBytesInBuffer<FCurrentBuffer.dwBufferLength then
begin
//data yang harus dimainkan sudah
//habis, isi panjang buffer dengan sisa data yang ada
FCurrentBuffer.dwBufferLength:=ActBytesInBuffer;
WaveOutWrite(Handle,FCurrentBuffer,sizeof(TWaveHdr));
FPlaying:=false;
end else
WaveOutWrite(Handle,FCurrentBuffer,sizeof(TWaveHdr));
end;
What I will discuss is WriteData() method. When it called, we assumed buffer is empty (actBytesInBuffer=0). WriteData will call DoDataRequired() to generate OnDataRequired event to application. Application is requested to copy data into buffer provided. Size of buffer is also sent to let application knows that is should not copy data more than buffer size. Actual size of data being copied must be returned to TSoundPlayer by setting ActBytesInBuffer value. If ActBytesInBuffer =0, it is assumed that there are no more data to play. Otherwise, we check whether actual data size is smaller than buffer size. If yes, we assume this is is the last block. We set dwBufferLength with value of ActBufferInBytes and then play it to device driver. If not, we play it. WriteData will be called repeatly until no more block to play. Application must decide when data block is finished.
Ok, now we have TSoundPlayer class. Let's create demo to utilize features offered by this class.
Create new apllication and drag drop control onto the form just like following figure:

Rename control names, Set Enabled property to false except Open button. Complete its code so it looks like following code:
unit ufrmMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,MMSystem,
uSound, ExtCtrls, ComCtrls;
type
TForm1 = class(TForm)
OpenDialog1: TOpenDialog;
btnOpen: TButton;
btnPlay: TButton;
btnStop: TButton;
Timer1: TTimer;
ProgressBar1: TProgressBar;
trkbrLeft: TTrackBar;
trkbrRight: TTrackBar;
Label1: TLabel;
Label2: TLabel;
procedure btnPlayClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
procedure btnOpenClick(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure trkbrLeftChange(Sender: TObject);
procedure trkbrRightChange(Sender: TObject);
private
SoundPlayer:TSoundPlayer;
FWaveStream:TMemoryStream;
procedure DataRequired(sender: TObject; const Buffer: pointer;
const BufferSize: cardinal; var BytesInBuffer: cardinal);
procedure LoadFormat(FMem: TMemoryStream; var FWaveFormatEx: TWaveFormatEx);
{ Private declarations }
public
constructor Create(AOwner:TComponent);override;
destructor Destroy;override;
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.DataRequired(sender:TObject;
const Buffer:pointer;
const BufferSize:cardinal;
var BytesInBuffer:cardinal);
begin
if FWaveStream.Position+BufferSize<FWaveStream.Size then
begin
BytesInBuffer:=BufferSize;
FWaveStream.ReadBuffer(Buffer^,BufferSize);
end else
begin
BytesInBuffer:=FWaveStream.Size-FWaveStream.Position;
FWaveStream.ReadBuffer(Buffer^,BytesInBuffer);
end;
end;
constructor TForm1.Create(AOwner: TComponent);
begin
inherited;
FWaveStream:=TMemoryStream.Create;
SoundPlayer:=TSoundPlayer.Create;
SoundPlayer.OnDataRequired:=DataRequired;
end;
destructor TForm1.Destroy;
begin
SoundPlayer.Free;
FWaveStream.Free;
inherited;
end;
procedure TForm1.btnPlayClick(Sender: TObject);
var dataOffset:integer;
begin
//hitung start data pada file WAV
dataOffset:=4+ SizeOf(DWORD)+
4 + 4 +SizeOf(DWORD)+
SizeOf(TWaveFormatEx) + 4 + SizeOf(DWORD);
//geser pointer ke posisi data block
FWaveStream.Seek(dataOffset,soFromBeginning);
ProgressBar1.Max:=FWaveStream.Size-dataOffset;
ProgressBar1.Min:=0;
ProgressBar1.Position:=0;
SoundPlayer.Start;
btnStop.Enabled:=true;
btnPlay.Enabled:=false;
Timer1.Enabled:=true;
end;
procedure TForm1.btnStopClick(Sender: TObject);
begin
SoundPlayer.Stop;
ProgressBar1.Position:=0;
btnPlay.Enabled:=true;
btnStop.Enabled:=false;
end;
procedure TForm1.LoadFormat(FMem: TMemoryStream;
var FWaveFormatEx:TWaveFormatEx);
var id:array[0..3] of char;
len:integer;
begin
FMem.Seek(0,soFromBeginning);
FMem.ReadBuffer(id[0],4);
if (id='RIFF') then
begin
FMem.Seek(4,soFromCurrent);
FMem.ReadBuffer(id[0],4);
if (id='WAVE') then
begin
FMem.ReadBuffer(id[0],4);
if (id='fmt ') then
begin
FMem.ReadBuffer(len,4);
if (len=Sizeof(TWaveFormatEx)) then
begin
FMem.ReadBuffer(FWaveFormatEx,len);
end else
if (len=Sizeof(TPCMWaveFormat)) then
begin
FMem.ReadBuffer(FWaveFormatEx,len);
FWaveFormatEx.cbSize:=0;
end else
begin
FMem.Clear;
raise Exception.Create('Format file WAV tidak disupport.');
end;
end else
begin
FMem.Clear;
raise Exception.Create('Format file WAV invalid.');
end;
end else
begin
FMem.Clear;
raise Exception.Create('Bukan format file WAV.');
end;
end else
begin
FMem.Clear;
raise Exception.Create('Bukan format file WAV.');
end;
end;
procedure TForm1.btnOpenClick(Sender: TObject);
var afile:TFileStream;
awaveFormat:TWaveFormatEx;
begin
SoundPlayer.Stop;
if OpenDialog1.Execute then
begin
afile:=TFileStream.Create(OpenDialog1.FileName,fmOpenRead);
try
FWaveStream.Clear;
FWaveStream.CopyFrom(afile,0);
LoadFormat(FWaveStream,awaveFormat);
SoundPlayer.Channel:=awaveFormat.nChannels;
SoundPlayer.SamplePerSec:=awaveFormat.nSamplesPerSec;
SoundPlayer.BitsPerSample:=awaveFormat.wBitsPerSample;
SoundPlayer.Open;
trkbrLeft.Position:=SoundPlayer.LeftVolume;
trkbrRight.Position:=SoundPlayer.RightVolume;
trkbrLeft.enabled:=true;
trkbrRight.enabled:=true;
btnPlay.Enabled:=true;
finally
afile.Free;
end;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if ProgressBar1.Position=ProgressBar1.Max then
begin
btnPlay.Enabled:=true;
btnStop.Enabled:=false;
Timer1.Enabled:=false;
ProgressBar1.Position:=0;
SoundPlayer.Stop;
end else
ProgressBar1.Position:=SoundPlayer.CurrentPosBytes;
end;
procedure TForm1.trkbrLeftChange(Sender: TObject);
begin
SoundPlayer.LeftVolume:=trkbrLeft.Position;
end;
procedure TForm1.trkbrRightChange(Sender: TObject);
begin
SoundPlayer.RightVolume:=trkbrRight.Position;
end;
end.
To play a WAV file, click Play button. To change left and right volume, slide trackbar to left or right.
Source code can be downloaded here.
Do you like this article? Help this website improve by donating. Any amounts is appreciated.
Or you can help by bookmarking this page.
Bookmark this on Delicious