Load & Display WAVE file (.WAV)

Hari ini dapat PM di forum delphi-id yang menanyakan masalah mengambil data pada file WAVE. Jadinya, tulisan ini saya buat sekalian untuk membahas bagaimana langkah-langkah dalam mengambil data pada file WAVE dan membuat sedikit visualisasi .
hasil akhir
tampilan program

definisi tipe data bentukan
hal pertama yang perlu dilakukan adalah membuat tipe data dasar untuk mengambil informasi yang ada pada header file WAV.

type
  TWaveHeader = packed record
    { harus bernilai 'RIFF' }
    Marker_RIFF: array [0..3] of char;
    ChunkSize: cardinal;

    { harus bernilai 'WAVE' }
    Marker_WAVE: array [0..3] of char;

    { harus bernilai 'fmt ' }
    Marker_fmt: array [0..3] of char;
    SubChunkSize: cardinal;

    {
      Audio Format see mmsystem.pas
      1 : WAVE_FORMAT_PCM
    }
    FormatTag: word;

    { nChannels : 1 jika mono, 2 jika stereo }
    NumChannels: word;
    SampleRate: longint;
    BytesPerSecond: longint;
    BytesPerSample: word;
    BitsPerSample: word;

    { Harus bernilai 'data' }
    Marker_data: array [0..3] of char;

    { Jumlah seluruh sampel dalam byte }
    DataBytes: longint;
  end;

  TChannel = record
    {
      tiap sample dinyatakan sebagai signed integer
      berukuran 16-bit (2 byte).
      tipe data ini pada delphi adalah smallint
      yang memiliki nilai dalam jangkauan -32767..32768
    }
    Data : array of smallint;
  end;

tambahkan komponen-komponen pada form seperti menyerupai gambar dan tambahkan deklarasi data dan prosedur berikut:

TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    od: TOpenDialog;
    ScrollBox1: TScrollBox;
    Image1: TImage;
    Button2: TButton;
    Button3: TButton;
    Label1: TLabel;
    StaticText1: TStaticText;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    wavehdr : TWaveHeader;
    wavedata : array [0..1] of TChannel;
    numsamples : integer;
    { skala penggambaran }
    scale : double;
  public
    procedure LoadFromStream(Stream: TStream);
    procedure LoadFromFile(filename: string);
    procedure DisplayWAV;
  end;

Prosedur-prosedur berikut adalah kode yang dipanggil ketika pengguna menekan tombol Load.

procedure TForm1.LoadFromFile(filename: string);
var
  fs : TFileStream;
begin
  fs := TFileStream.Create(filename, fmOpenRead);
  LoadFromStream(fs);
  fs.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  { load }
  if od.Execute then begin
    LoadFromFile(od.FileName);
    DisplayWAV;
  end;
end;

Kode utama untuk membaca file WAV ada di prosedur LoadFromStream:

  • baca header (belum ada validasi)
  • baca data sesuai dengan deskripsi yang ada di header

procedure TForm1.LoadFromStream(Stream: TStream);
var
  tmpstr : string;
  i : integer;
begin
  FillChar(wavehdr, sizeof(wavehdr), 0);
  Stream.Read(wavehdr, sizeof(wavehdr));

  { Log Header data }
  with memo1.Lines do begin
    Add('-- WAV Filename : '+od.FileName);
    Add('Header size : '+inttostr(sizeof(wavehdr)));
    tmpstr := wavehdr.Marker_RIFF;
    Add('RIFF ID : '+tmpstr+''' ');
    Add('Chunk size : '+inttostr(wavehdr.ChunkSize));
    tmpstr := wavehdr.Marker_WAVE;
    Add('WAVE ID : '+tmpstr+''' ');
    tmpstr := wavehdr.Marker_fmt;
    Add('''fmt '' ID : '+tmpstr+''' ');
    Add('SubChunk size : '+inttostr(wavehdr.SubChunkSize));
    Add('Format : '+inttostr(wavehdr.FormatTag));
    Add('Num Channels : '+inttostr(wavehdr.NumChannels));
    Add('Sample rate : '+inttostr(wavehdr.SampleRate));
    Add('Bytes per second : '+inttostr(wavehdr.BytesPerSecond));
    Add('bits per sample : '+inttostr(wavehdr.BitsPerSample));
    Add('Data Length : '+inttostr(wavehdr.DataBytes));
  end;

  numsamples := (wavehdr.DataBytes div wavehdr.NumChannels) div wavehdr.BytesPerSample;
  case wavehdr.NumChannels of
      1:begin
        SetLength(wavedata[0].Data, numsamples);
        Stream.Read(wavedata[0].Data[0], numsamples);
      end;

      2:begin
        SetLength(wavedata[0].Data, numsamples);
        SetLength(wavedata[1].Data, numsamples);
        for i := 0 to high(wavedata[0].Data) do begin
          Stream.Read(wavedata[0].Data[i], 2);
          Stream.Read(wavedata[1].Data[i], 2);
        end;
      end;
  end;

  { inisialisasi ulang }
  scale := 0.025;
end;

Langkah berikutnya adalah membuat visualisasi dari data gelombang suara. Karena data gelombang suara biasanya besar, maka data yang ditampilkan hanya gambaran kecil (sebagian tapi menggambarkan keseluruhan).

procedure TForm1.DisplayWAV;
var
  i, n : integer;
  max, mid, pos : integer;
  b : TBitmap;
begin
  b := Image1.Picture.Bitmap;
  with b.Canvas do begin
    Lock;
    Brush.Color := clBlack;
    Pen.Color := clLime;
    Pen.Style := psClear;
    FillRect(Image1.ClientRect);
    Pen.Style := psSolid;
  end;

  { Atur lebar jendela penggambaran }
  b.Width := Round(Length(wavedata[0].Data) * scale);

  { proses penggambaran }
  case wavehdr.NumChannels of
    { kasus mono }
    1: begin
      max := b.Height div 2;
      mid := max div 2;
      n := Round(high(wavedata[0].Data) / b.Width);
      b.Canvas.MoveTo(0, mid);
      for i := 0 to b.Width-1 do begin
        pos := Round(wavedata[0].Data[i * n] * max div 32768) + mid;
        b.Canvas.LineTo(i, pos);
      end;
    end;

    { kasus stereo }
    2: begin
      max := b.Height div 2;
      mid := max div 2;
      n := Round(high(wavedata[0].Data) / b.Width);

      { Channel 1 }
      b.Canvas.MoveTo(0, mid);
      for i := 0 to b.Width-1 do begin
        pos := Round(wavedata[0].Data[i * n] * max div 32768) + mid;
        b.Canvas.LineTo(i, pos);
      end;

      { Channel 2 }
      b.Canvas.MoveTo(0, mid + max);
      for i := 0 to b.Width-1 do begin
        pos := Round(wavedata[1].Data[i * n] * max div 32768) + mid + max;
        b.Canvas.LineTo(i, pos);
      end;
    end;
  end;
  
  { update gambar }
  b.Canvas.Unlock;
end;

Agar komponen TImage dapat digunakan, properti Picture dari komponen ini harus diinisialisasi terlebih dahulu.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Image1.Picture.Bitmap := TBitmap.Create;
  with Image1.Picture.Bitmap do begin
    Canvas.Lock;
    Width := ScrollBox1.Width;
    Height := 320;
    PixelFormat := pf24bit;
    with Canvas do begin
      Brush.Color := clBlack;
      Pen.Color := clLime;
      Pen.Style := psClear;
      FillRect(Image1.ClientRect);
    end;
    Canvas.Unlock;
  end;
  scale := 0.025;
end;

Kode berikut melakukan perubahan skala penggambaran. Perubahan skala dibatasi agar tidak menyebabkan error.
procedure TForm1.Button2Click(Sender: TObject);
begin
  { zoom in }
  if (scale < 0.1) or (Image1.Width < ScrollBox1.Width) then begin
    scale := scale * 2;
    DisplayWAV;
    statictext1.Caption := floattostr(scale);
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  { zoom out }
  if (scale > 0.005) and (Image1.Width > (SCrollBox1.Width div 2)) then begin
    scale := scale / 2;
    DisplayWAV;
    statictext1.Caption := floattostr(scale);
  end;
end;

penutup
Sekian dari saya.

Wassalam,

21 comments

  1. nursyafriady · Agustus 14, 2007

    Mas Pebbie..mohon maaf lagi
    kalo sambil wavnya dimainkan apakah listing nya juga sama untuk mengambail data wavenya karena skripsi saya sambil dimainkan ketika di sembunyikan gambar bitmap yaitu:
    numsamples := (wavehdr.DataBytes div wavehdr.NumChannels) div wavehdr.BytesPerSample;
    case wavehdr.NumChannels of
    1:begin
    SetLength(wavedata[0].Data, numsamples);
    Stream.Read(wavedata[0].Data[0], numsamples);
    end;

    2:begin
    SetLength(wavedata[0].Data, numsamples);
    SetLength(wavedata[1].Data, numsamples);
    for i := 0 to high(wavedata[0].Data) do begin
    Stream.Read(wavedata[0].Data[i], 2);
    Stream.Read(wavedata[1].Data[i], 2);
    end;
    end;
    end;

    terima kasih mas pebbie..mohon penjelasannya

  2. pebbie · Agustus 22, 2007

    untuk playback Anda bisa membaca di sini

  3. yadi · Agustus 24, 2007

    Terima kasih mas pebbie atas penjelasannya…

  4. yadi · November 1, 2007

    Assalamualaikum Wr..Wb..
    Mas Peb mau tanya ni,,
    gimana di delphi untuk kita bikin file bertipe .WAV dari hasil proses..mau kita simpan ke file dl dalam PC misal di ‘C:test.wav ‘, karena saya mengambil hanya data wavenya setelah dilakukan proses Invers DCT.
    misal :
    hasil proses invers DCT datanya😦 1 3 4 6 7);
    kita mau menyimpan data tersebut ke dalam Tipe File .Wav.
    Ma kasih mas peb…
    WassWrWb..

  5. yadi · November 23, 2007

    Assalamu’alaikum…
    Mohon Maaf mas peb..
    bener ga ni mas peb..saya nambahain listing untuk mengambil data suara..saya tampung di variabel array dinamik (dat dan dat2)…..
    numsamples := (wavehdr.DataBytes div wavehdr.NumChannels) div wavehdr.BytesPerSample;
    case wavehdr.NumChannels of
    1:begin
    SetLength(wavedata[0].Data, numsamples);
    Stream.Read(wavedata[0].Data[0], numsamples);

    setlength(dat, length(wavedata[0].Data));
    for i := 0 to high(wavedata[0].Data) – 1 do
    begin
    dat[i] := strtofloat(floattostr(wavedata[0].Data[i]));
    end;
    result := dat;
    end;

    2:begin
    SetLength(wavedata[0].Data, numsamples);
    SetLength(wavedata[1].Data, numsamples);
    for i := 0 to high(wavedata[0].Data) do
    begin
    Stream.Read(wavedata[0].Data[i], 2);
    Stream.Read(wavedata[1].Data[i], 2);
    end;

    setlength(dat2,length(wavedata[0].Data));
    for i := 0 to high(wavedata[0].Data) – 1 do
    begin
    dat2[i] := strtofloat(floattostr(wavedata[0].Data[i]));
    end;
    Result := dat2;
    end;
    end;
    terima kasih mas peb….
    wassalam.

  6. suman17 · Juni 17, 2008

    tereimakasih, artikel anda sangat membantu. maaf, saya baru belajar tentang data wav. saya mencoba membuat seperti yang anda tulisan. yang saya tidak tahu, apa satuan yang diperoleh dari pembacaan sinyal wav? dalam dB-kah? saya mencoba merekam suara dengan mciSendString dan membaca data dengan cara anda, tetapi saya mendapatkan nilai data yang besar. mohon penjelasan. terimakasih

    regards,
    suman17

  7. pebbie · Juni 17, 2008

    @suman17: saya juga sebetulnya kurang mengerti tentang suara😀 yang jelas, representasi sinyal sampel dalam format signed integer (8 bit/16 bit) yang merepresentasikan intensitas yang masuk (skala relatif/sembarang bukan skala dB). sampel wave analog seperti pixel pada citra yang merepresentasikan intensitas minimum-maksimum dalam rentang 0-255.

    semoga membantu

  8. suman17 · Juli 25, 2008

    mas nanya, punya referensi tentang hardware soundcard atau proses pengalamatan soundcard g? trus… maap nih pertanyaan bodoh => soundcard itu jalan pada level tegangan berapa ya? apakah tiap2 soundcard punya level tegangan yang beda (+-12V, +5V, etc) atau punya standart yang sama untuk semua jenis soundcard (+5V kayak TTL). makasih

  9. Yohanes · Desember 1, 2008

    Saya sudah membuat aplikasi perekam suara menggunakan delphi. Akan tetapi saya bingung bagaimana coding untuk mendapatkan nilai tiap titik-titik diskret setelah saya mendapatkan sinyal digital dalam format WAV?Dalam hal ini saya menggunakan bit rate : 8 bit, dengan samplerate 8000Hz, jd kan ada 8000 titik tiap detik, gimana mengetahui nilai masing-masing titik? Kalau tidak salah range untuk tiap titik 0-255.
    Mohon bantuannya…….

  10. pebbie · Desember 1, 2008

    @Yohanes: merekamnya pakai library apa? biasanya ketika merekam, kita membuat buffer penyimpan sementara yang akan diisi oleh hardware via library yang dipakai (misal waveinXXX) dengan data dalam format (PCM) yang berisi sampel sinyal suara sesuai dengan konfigurasi rekaman atau langsung dalam file eksternal (.WAV) kalau menggunakan komponen TMediaPlayer sebagai komponen untuk merekam.

  11. Yohanes · Desember 5, 2008

    Mas, saya merekamnya pakai Multimedia API.Pada Procedure SaveWaveToStream, seperti ini:
    stream.WriteBuffer(RiffId[1], 4); // ‘RIFF’
    Stream.WriteBuffer(RiffCount, SizeOf(DWORD)); // file data size
    Stream.WriteBuffer(WaveId[1], Length(WaveId)); // ‘WAVE’
    Stream.WriteBuffer(FmtId[1], Length(FmtId)); // ‘fmt ‘
    TempInt := SizeOf(TWaveFormatEx);
    Stream.WriteBuffer(TempInt, SizeOf(DWORD)); // TWaveFormat data size
    Stream.WriteBuffer(WaveFormatEx, SizeOf(TWaveFormatEx)); //

    WaveFormatEx record
    Stream.WriteBuffer(DataId[1], Length(DataId)); // ‘data’
    Stream.WriteBuffer(datacount, SizeOf(DWORD)); // sound data size
    recorded.Seek(0,soFromBeginning);
    Stream.CopyFrom(recorded,dataCount);

    Gimana Mas untuk mendapatkan array data dinamic nya?
    Trims loh mas sebelumnya…

    • pebbie · Desember 6, 2008

      kalau saya tidak salah, data wave-nya sudah disimpan di stream recorded (itu TMemoryStream kan?)😉

  12. Yohanes · Desember 6, 2008

    Maaf ya Mas tanya lagi he…
    Iya, saya pikir juga begitu…tapi saya tidak ngerti gimana coding untuk menampilkan array data dinamic nya? Misalkan akan ditampilkan ke dalam memo.
    Mohon bantuannya ya Mas…

    • pebbie · Desember 6, 2008

      CMIIW, kode blum di tes:
      buat array dinamis sebanyak dataCount, misal wavearr


      var
      wavearr : array of word; //unsigned 16 bit (sesuaikan dengan format di WaveFmt)
      {...}
      SetLength(wavearr, dataCount);
      recorded.Seek(0,soFromBeginning);
      recorded.Read(wavearr[0], dataCount);
      {...done!}

  13. Yohanes · Desember 9, 2008

    Mas, he… tanya lagi,
    Saya udah coding seperti petunjuk Mas, hasil array data nya seperti ini (tidak saya tampilkan semua). Benar tidak ya? bukannya dengan sample rate=8000, bit rate= 8 dan channel=1, harusnya array data berada pada range 0-255 ? Maklumi ketidakpahaman saya ya Mas…thanks
    s[0]= 0
    s[1]= 0
    s[2]= 0

    s[62]= 0
    s[63]= 0
    s[64]= 0
    s[65]= 0
    ……..
    s[84]= 32896
    s[85]= 32896
    s[86]= 32896
    s[87]= 32896
    s[88]= 32640
    s[89]= 32640
    s[90]= 32639
    s[91]= 32640
    s[92]= 32640
    s[93]= 32640
    ……..
    s[144]= 32896
    s[145]= 32896
    s[146]= 32896
    s[147]= 32896
    …………

  14. Yohanes · Desember 9, 2008

    Mas Pebbie, kalau saya gunakan bit rate=16, hasil array datanya sepertinya benar,berada pada range 0-65535. Why? Kok kalau bit rate=8, hasilnya tidak pada range 0-255?
    Plizzz…

    • pebbie · Desember 9, 2008

      kalau bit nya 8 ya pake array of byte dong mas..

  15. Yohanes · Desember 9, 2008

    Thanks ya Mas…
    he…
    Lupa saya ubah ke byte he…
    Udah berhasil Mas…
    Terimakasih Mas Pebbie

  16. Fahlawi · Mei 11, 2009

    Mo nanya, Mas. Saya sedang membuat aplikasi yang berhubungan dengan file wav. saya membuka file wave, kemudian merubahnya kedalam bentuk biner dengan tipe data string. data biner (dalam string) ini kemudian saya edit. gimana cara menyimpannya kembali dalam bentuk file wav? saya sudah coba simpan langsung, diubah ke hexadesima (dalam bentuk string), atau diubah ke desimal hasilnya lain. kemudian saya coba ubah kedalam ascii, hasilnya udah lumayan, tapi ada masalah dalam pengkonversian dari bilangan desimal ke ascii (menggunakan chr), karena tidak semua bilangan desimal tersebut dapat dikonversi ke ASCII. mohon batuannya! trm ksh.

  17. reni · April 6, 2010

    selamat siang
    mas kalo file wav dari sound recording diexport ke delphi itu sama seperti program diatas?bisa tidak kalo diketahui kekuatan amplitudo dan frekuensinya?
    apa menggunakan program lain lagi?
    bantu aku ya mas…
    terima kasih

  18. wahyudi · Mei 15, 2014

    mas, kalo di java bagaimana bisa gak? mohon pencerahannya mas, bwat skripsi… mohon bantuannya. wahyuodiesam@gmail.com

Tinggalkan Balasan

Isikan data di bawah atau klik salah satu ikon untuk log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s