Memainkan WAV dengan Wave API
September 19, 2006 at 1:51 pm | Posted in General | 8 CommentsPada artikel sebelumnya, Merekam suara dengan Wave API, kita telah membahas cara penggunaan Wave API untuk melakukan perekaman suara. Pada topik kali ini, kita akan membahas bagaimana
memainkan file WAV menggunakan Wave API. Jika anda tidak membutuhkan akses ke
data wave yang sedang dimainkan dan hanya ingin memainkan file WAV, artikel
ini mungkin tidak terlalu berguna bagi anda. Untuk sekedar memainkan suara WAV,
anda bisa menggunakan PlaySound() atau TMediaPlayer. Jika anda membutuhkan akses
ke data wave yang hendak dimainkan, misalnya untuk mengubah data wave dengan
menerapkan filter dan efek pada data wave, maka artikel ini tepat untuk anda.
WaveOut***
Fungsi-fungsi playback wave pada Wave API, menggunakan penamaan
waveOut***, contohnya waveOutOpen(), waveOutClose() dan lain-lain. Kita akan
membahas fungsi-fungsi ini dan cara penggunaannya segera. Deklarasi fungsi-fungsi
ini terletak pada unit MMSystem.pas.
Membuka Waveform Output Device.
Ini adalah langkah pertama yang harus kita jalankan untuk melakukan
playback suara.
function waveOutOpen(lphWaveOut: PHWaveOut; uDeviceID: UINT;
lpFormat: PWaveFormatEx;
dwCallback, dwInstance,
dwFlags: DWORD): MMRESULT; stdcall;
Parameter lphWaveOut adalah variabel yang akan menerima
handle HWAVEOUT yang akan dipergunakan untuk mengakses waveform audio output
device. Jika diisi nil, dwFlags harus diisi. uDevice ID adalah ID waveform audio
output device. Jika anda tidak tahu ID device yang hendak anda pergunakan, gunakan
saja WAVE _MAPPER agar waveOutOpen sendiri yang mengisinya untuk kita. lpFormat
berisi alamat ke struktur data TWaveFormatEx yang menentukan format wave yang
akan kita mainkan. dwCallback berisi alamat fungsi callback, event
handle atau handle window yang akan diberi notifikasi ketika terjadi event saat
playback. dwInstance adalah user data yang akan dikirimkan ke callback.
dwFlags memberitahukan tipe callback yang kita inginkan juga informasi
mengenai wave. Dapat diisi salah satu flag berikut:
- CALLBACK_NULL
Mekanisme callback tidak digunakan. - CALLBACK_FUNCTION
Callback menggunakan fungsi. Jika menggunakan flag ini, dwCallback harus
berisi alamat fungsi callback. Kita akan bahas fungsi callback segera. - CALLBACK_WINDOW
Callback menggunakan handle window. Jika menggunakan flag ini, dwCallback
harus berisi handle window. - CALLBACK_THREAD
Callback menggunakan thread. Jika menggunakan flag ini, dwCallback harus
berisi thread ID - CALLBACK_EVENT
Callback menggunakan handle event. Jika menggunakan flag ini, dwCallback
harus berisi handle event. - WAVE_FORMAT_QUERY
Jika menggunakan flag ini, kita menginstruksikan waveOutOpen untuk melakukan
pengecekan apakah output device sanggup memainkan format wave yang kita
tentukan, namun output device tidak dibuka untuk playback.
Perhatikan bahwa selain flag-flag di atas masih ada flag lain,
namun sengaja tidak dibahas karena menurut saya tidak terlalu sering dipergunakan.
Jika sukses waveOutOpen akan mengembalikan nilai MMSYSERR_NOERROR.
Jika gagal, kode kesalahan dapat berupa WAVERR_BADFORMAT untuk format wave yang
tidak didukung dan lain-lain.
Format Wave
Format wave kita definisikan menggunakan struktur data TWaveFormatEx yang deklarasinya seperti berikut :
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 berisi format wave, untuk memainkan wave standard milik
Windows kita isi denganWAVE_FORMAT_PCM. nChannels berisi jumlah channel
1 untuk mono dan 2 untuk stereo. nSamplesPerSec berisi jumlah sample
yang dimainkan per detik dalam satuan Hertz. Untuk WAVE_FORMAT_PCM, nilai yang
umum adalah 8000 Hz, 11025 Hz, 22050 Hz dan 44100 Hz. nAvgBytesPerSec,
berisi rata-rata laju data transfer yang diperlukan, untuk WAVE_FORMAT_PCM,
nilainya adalah hasil perkalian antara nSamplesPerSec dan nBlockAlign. nBlockAlign
berisi ukuran blok data
Callback
Callback terdiri atas beberapa, yang paling sering saya pergunakan
adalah callback berupa fungsi. Callback fungsi menggunakan funsgi dengan format berikut:
procedure WaveOutProc(Handle:HWAVEOUT;uMsg:UINT;
dwInstance:DWORD;
dwParam1,
dwParam2:DWORD);stdcall;
Handle adalah handle wave out yang diperoleh melalui
pemanggilan waveOutOpen(). uMsg adalah tipe kejadian yang terjadi yakni WOM_OPEN,
WO_CLOSE dan WOM_DONE. Yang paling kita perlukan adalah WOM_DONE, pesan ini
memberitahukan bahwa device driver telah selesai memproses data. dwInstance
adalah user data yang kita kirim pada saat pemanggilan waveOutOpen(), dwParam1
dan dwParam2 adalah parameter. Pada saat WOM_DONE, dwParam1 akan berisi pointer
ke struktur data yang dimainkan.
Contoh kode berikut ini membuka device untuk playback, menggunakan
fungsi callback dimana nama fungsinya adalah _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;
Contoh berikut menguji apakah wave format 8 bit stereo, sample
rate 22,05 KHz dapat dimainkan oleh 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;
Menyiapkan Buffer
Kita perlu menyiapkan buffer yang akan menampung data wave. Kita bertanggung
jawab atas lifetime manajemen memori buffer ini. Buffer ini kemudian wajib kita
beritahukan ke device driver menggunakan waveOutPrepareHeader() sebelum
kita menggunakannya untuk mengirimkan data wave ke device driver.
function waveOutPrepareHeader(hWaveOut: HWAVEOUT;
lpWaveOutHdr: PWaveHdr;
uSize: UINT): MMRESULT; stdcall;
hWaveOut adalah handle wave out, lpWaveOutHdr menyimpan informasi
tentang buffer kita. uSize adalah ukuran wave header. Berikut ini adalah
deklarasi 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;
Untuk keperluan kita memainkan WAV, field yang penting adalah lpData
berisi alamat memori buffer, panjang buffer disimpan di dwBufferLength.
dwBytesRecorded hanya dipergunakan untuk proses recording. dwUser
dapat kita pergunakan untuk menyimpan user data. dwFlags berisi flag
mengenai buffer.
Mengisi Buffer dengan Data
Proses mengisi buffer dengan data-data waveform sepenuhnya adalah tanggung
jawab kita. Untuk mengisi buffer anda bisa menggunakan fungsi-fungsi pengkopian
data seperti Move() atau CopyMemory(). Yang perlu kita ingat, jika jumlah data
yang kita kopi kedalam buffer lebih kecil dari ukuran buffer, dwBufferLength
perlu kita set sama dengan panjang data, harap diperhatikan bahwa ukuran buffer
tidak berubah. Jika kita perlu mengubah ukuran buffer, kita wajib memberitahukan
perubahan ini ke deice driver dengan terlebih dahulu memanggil waveOutUnPrepareHeader()
(fungsi ini akan kita bahas segera) mengubah ukuran buffer dan kemudian memanggil
lagi waveOutPrepareHeader().
Mengirimkan Buffer ke Device Driver
Setelah buffer terisi data, kita siap memainkannya. Untuk itu kita panggil
waveOutWrite().
function waveOutWrite(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr;
uSize: UINT): MMRESULT; stdcall;
lpWaveOutHdr adalah wave header yang sebelumnya telah kita prepare.
uSize adalah ukuran data wave header. Ketika kita mengirimkan blok
data pertama ke device driver maka playback akan dimulai. Kita dapat saja mengirimkan
seluruh data WAV ke device driver dengan menggunakan pemanggilan waveOutWrite
sekali saja, namun harap diperhatikan bahwa, pada beberapa soundcard (terutama
soundcard lama), maksimum ukuran buffer yang dapat diproses adalah 64 KB, sehingga
jika kita memiliki data waveform yang besarnya melebihi 64 KB, pemanggilan waveOutWrite
harus dilakukan lebih dari satu kali dengan menggunakan blok-blok data yang
lebih kecil.
Masalah lain yang dapat timbul berkaitan dengan proses memainkan blok-blok
data beruuran kecil adalah supply data ke dievice driver harus kontinu. Sedikit
saja ada kelambatan maka suara akan terdengar putus-putus akibat adanya gap
antara kecepatan soundcard memproses data dan kecepatan aplikasi kita menyuplai
data. Dari pengalaman saya, gap semacam ini biasanya timbul karena penggunaan
ukuran buffer yang terlalu kecil. Buffer kecil, mengakibatkan soundcard membutuhkan
waktu relatif cepat untuk memproses data, jauh lebih cepat dari waktu yang dibutuhkan
aplikasi kita mengirim data ke device driver. Untuk kelas yang akan kita bangun,
proses pengiriman data wave akan dipecah menjadi blok-blok kecil dengan ukuran
blok 64 KB.
Menghentikan Playback
Fungsi waveOutReset() kita pergunakan untuk menghentikan proses playback.
function waveOutReset(hWaveOut: HWAVEOUT): MMRESULT; stdcall;
Pause/Resume Playback
Untuk menghentikan sementara playback kita menggunakan waveOutPause()
dan untuk meresume playback yang dihentikan sementara kita gunakan waveOutRestart().
Membebaskan Buffer
Setelah kita, selesai memainkan data wave, sebelum membebaskan buffer pastikan
kita memanggil waveOutUnPrepareHeader() untuk memberitahukan bahwa
buffer akan dibebaskan. Dengan pemanggilan waveOutUnPrepareHeader(), device
driver diberitahu agar tidak lagi menggunakan buffer ini. Setelah proses unprepare,
buffer dapat kita free dengan aman.
function waveOutUnprepareHeader(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr;
uSize: UINT): MMRESULT; stdcall;
Menutup Device
Agar aplikasi kita terlihat sopan di mata Windows, resource yang sudah kita
pergunakan kita kembalikan lagi yakni dengan menutup waveform audio output device.
function waveOutClose(hWaveOut: HWAVEOUT): MMRESULT; stdcall;
Langkah-langkah di atas adalah dasar untuk memainkan wave dengan Wave API.
Ada beberapa fungsi tambahan berkaitan dengan urusan playback wave. Dua yang
akan saya bahas adalah mendapatkan informasi progress playback, mengatur volume
dan mendapatkan string status kesalahan.
Mendapatkan Progress Playback
Status posisi playback dapat diminta dengan memanggil waveOutGetPosition().
Perhatikan bahwa kita hanya dapat meminta informasi posisi, namun tidak dapat
mengubah posisi playback.
function waveOutGetPosition(hWaveOut: HWAVEOUT; lpInfo: PMMTime; uSize: UINT): MMRESULT; stdcall;
lpInfo akan diisi dengan informasi posisi playback, format informasinya
bermacam-macam. Sebelum memanggil fungsi ini format informasinya perlu kita
tentukan. uSize berisi ukuran data lpInfo. Deklarasi PMMTime
sendiri adalah sebagai berikut:
{ 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 harus kita tentukan. Nilai yang valid adalah TIME_BYTES
untuk mendapatkan posisi playback dalam satuan bytes yang telah diproses. Jika
menggunakan TIME_BYTES maka data field cb akan berisi informasi posisi.
TIME_MS untuk mengembalikan posisi playback dalam satuan milisecond, untuk tipe
posisi ini field ms akan berisi informasi yang kita butuhkan. Untuk
kelas kita nanti, kita hanya akan menggunakan dua macam tipe ini.
Mendapatkan Status Pesan Kesalahan
Untuk mendapatkan pesan kesalahan dari kode kesalahan yang dikembalikan fungsi
waveOut***, kita menggunakan waveOutGetErrorText(). lpText kita
isi dengan buffer yang akan menampung pesan kesalahan. uSize menentukan
ukuran buffer tersebut.
function waveOutGetErrorText(mmrError: MMRESULT; lpText: PChar; uSize: UINT): MMRESULT; stdcall;
Mengatur Volume Playback
Volume speaker kiri dan kanan dapat diatur menggunakan waveOutSetVolume().
Untuk meminta informasi volume, kita menggunakan waveOutGetVolume.
function waveOutGetVolume(hwo: HWAVEOUT; lpdwVolume: PDWORD): MMRESULT; stdcall;
function waveOutSetVolume(hwo: HWAVEOUT; dwVolume: DWORD): MMRESULT; stdcall;
Volume kiri dan kanan menjadi satu dalam lpdwVolume dan dwVolume.
Volume speaker kiri adalah low word dwVolume dan volume speaker kanan adalah
high word.
Membuat TSoundPlayer
Desain
TSoundPlayer adalah kelas yang akan membungkus fungsionalitas waveOut***. Kelas
ini akan diturunkan dari kelas TWaveObject (pembahasannya ada pada artikel Merekam
suara dengan Wave API). Prosedur abstrak Open, Close, Start, Stop akan kita
override. Konstructor dan destruktor diisi dengan kode alokasi dan dealokasi
buffer, juga ditambahkan metode untuk pause dan resume playback.
Kita juga menambahkan property untuk mengatur volume kiri dan kanan, property
posisi playback dan property event yang akan dibangkitkan ketika instance kelas
membutuhkan data untuk dikirim ke device driver.
Implementasi
TSoundPlayer dideklarasikan dalam unit yang sama dengan kelas TSoundRecorder
(Merekam suara dengan Wave
API) yakni 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;
Kode implementasinya adalah sebagai berikut:
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;
Yang sedikit saya bahas adalah metode WriteData(). Ketika dipanggil, kita asumsikan
data dalam buffer tidak ada (actBytesInBuffer=0). WriteData kemudian akan memanggil
DoDataRequired() untuk membangkitkan event OnDataRequired ke aplikasi. Aplikasi
akan diminta mengkopi data ke buffer yang sudah disediakan. Ukuran buffer juga
diberitahukan ke aplikasi agar aplikasi tidak mengkopi data lebih dari ukuran
buffer. Jumlah aktual data yang dikopi ke buffer harus dikembalikan ke TSoundPlayer
melalui variabel ActBytesInBuffer. Jika ActBytesInBuffer =0 diasumsikan tidak
ada data yang harus dimainkan. Jika tidak nol, kita cek apakah jumlah data yang
dikopi lebih kecil dari ukuran buffer. Jika ya, kita asumsikan bahwa blok data
ini adalah blok terakhir. dwBufferLength kita set sama dengan ActBufferInBytes
dan kita kirim data ke device driver. Jika lainnya, buffer langsung kita mainkan.
WriteData akan dipanggil berulang-ulang hingga tidak ada blok data yang harus
dimainkan. Yang bertanggung jawab menentukan kapan blok data habis adlah aplikasi
pemanggil.
Ok, kita sudah memiliki kelas TSoundPlayer. Mari kita buat aplikasi demo yang
akan memanfaatkan fitur-fitur kelas ini.
Membuat aplikasi demo
Buat aplikasi dan drag drop kontrol ke form sehingga menjadi seperti gambar
berikut:

Rename nama-nama kontrol, ubah property Enabled menjadi false kecuali
tombol Open dan lengkapi kodenya sehingga menjadi kode berikut:
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.
Untuk memainkan buka sebuah file WAV dan klik button Play. Untuk mengubah-ubah
volume speaker kiri dan kanan, geser-geser trackbar kiri dan kanan.
Source code bisa diambil disini
8 Comments »
RSS feed for comments on this post. TrackBack URI
Leave a Reply
Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.
Thx atas tutorialnya …
Mas klo ngerekam pake line in tp cuman left channel or right channel aja yg di rekam gimana mas ? so dlam 1 sound card line in bisa kita rekam 2 channel…
makasih B4
Comment by TTG— February 13, 2007 #
mohon maaf pak…
kalo kita mau mengakses sample data wavenya di listing programnya di bagian mana pak..ya
Terima kasih Pak
Comment by nursyafriady— August 14, 2007 #
TSoundPlayer yang saya buat diatas mengharuskan aplikasi yang mensupply data WAV. Tiap kali TSoundPlayer membutuhkan data, event OnDataRequired di generate. Aplikasi harus menyuplai data WAV dengan mengkopi data sample ke buffer milik TSoundPlayer. Alamat memorinya dan ukuran buffer disimpan di paramater buffer dan bufferSize (lihat contoh event handler TForm1.DataRequired). Jika Anda bermaksud mengubah-ubah data WAV sebelum dikirim ke device driver untuk dimainkan lakukan di event OnDataRequired. Namun usahakan prosesnya secepat mungkin lebih cepat dari waktu yang diperlukan oleh sound card untuk memainkan sample data, jika tidak, suara akan menjadi terputus-putus karena kontinuitas supplai data terlambat
Comment by Zamrony P Juhara— August 14, 2007 #
klo waveOutGetPitch dan waveOutSetPitch, kira2 gimana contoh codingnya ? trims.
Comment by hapra— January 18, 2008 #
keren!
Comment by Mus_— April 26, 2008 #
mas, mo tanya gimana play beberapa file wav di listbox secara random/repeat all?
saya pake perintah dibawah, hanya untuk satu file WAV :
SndPlaySound (‘C:\My Documents\suara.wav’,
SND_LOOP or SND_ASYNC);
TQ
Comment by hery— August 7, 2008 #
SndPlaySound() sederhana namun tidak cocok digunakan bila hendak digunakan untuk membuat audio player dengan playlist. Contoh Anda diatas akan memainkan suara.wav secara berulang-ulang dan asynchronous (kontrol akan dikembalikan ke aplikasi segera sebelum proses memainkan suara selesai).
Anda tidak tahu kapan suara tersebut selesai dimainkan. Solusinya gunakan TSoundPlayer di atas atau TMediaPlayer.
Bila menggunakan TSoundPlayer, pada event OnDataRequired. Bila jumlah data yang harus dimainkan lebih kecil dari ukuran buffer maka suara hampir mendekati selesai. lakukan persiapan untuk membaca file suara berikutnya.
Bila menggunakan TMediaPlayer, buat handler event OnNotify, set property Notify:=true dan cek apakah suara telah selesai, bila ya dan bila random pilih suara yang dimainkan secara random, bila tidak random, ambil suara berikutnya
Comment by Zamrony P. Juhara— August 7, 2008 #
meskipun ini tread udah lama, jika saya play sound trus diform yg sama juga ada running text. tp koq running textnya berhenti saat musiknya sedang play ya?
Comment by medor— October 26, 2010 #