title: Playing PCM Wav File in Delphi
body: <p>I have written a simple code that reads the header of a Wav File and then starts playing it. this is my code:</p>
  
  <pre><code>unit Unit1;
  
  interface
  
  uses
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Generics.collections,
    Vcl.ExtCtrls, MMSystem;
  
  type
    TForm1 = class(TForm)
      Button1: TButton;
      OpenDialog1: TOpenDialog;
      Label1: TLabel;
      Label2: TLabel;
      Shape1: TShape;
      Image1: TImage;
      procedure Button1Click(Sender: TObject);
      procedure FormCreate(Sender: TObject);
      procedure FormClose(Sender: TObject; var Action: TCloseAction);
    private
      { Private declarations }
    public
      { Public declarations }
    end;
  
  type
    TWaveformSample = integer; // signed 32-bit; -2147483648..2147483647
    TWaveformSamples = packed array of TWaveformSample; // one channel
  
  var
    Form1: TForm1;
  
    myWavFile: file;
    DataBlock: array[0..3] of byte;
    Count: integer;
    NumOfChannels: integer;
    SampleRate: integer;
    BytesPerSecond: integer;
    ByesPerSample: integer;
    BitsPerSample: integer;
    CompressionCode: integer;
    CompressionDesc: string;
    BlockAlign: integer;
    ExtraFormatBytes: integer;
  
    CompressionCodes: TDictionary&lt;integer, string&gt;;
  
    BytesRead: integer;
  
    Samples: TWaveformSamples;
    fmt: TWaveFormatEx;
  
    PacketIsPlaying: Boolean;
  
  implementation
  
  {$R *.dfm}
  
  procedure InitAudioSys;
  begin
    with fmt do
    begin
      wFormatTag := WAVE_FORMAT_PCM;
      nChannels := NumOfChannels;
      nSamplesPerSec := SampleRate;
      wBitsPerSample := BitsPerSample;
      nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
      nBlockAlign := nChannels * wBitsPerSample div 8;
      cbSize := 0;
    end;
  end;
  
  
  procedure PlaySound;
  var
    wo: integer;
    hdr: TWaveHdr;
  begin
  
    if Length(samples) = 0 then
    begin
      Writeln('Error: No audio has been created yet.');
      Exit;
    end;
  
    if waveOutOpen(@wo, WAVE_MAPPER, @fmt, 0, 0, CALLBACK_NULL) = MMSYSERR_NOERROR then
      try
        PacketIsPlaying := True;
        ZeroMemory(@hdr, sizeof(hdr));
        with hdr do
        begin
          lpData := @samples[0];
          dwBufferLength := fmt.nChannels * Length(Samples) * sizeof(TWaveformSample);
          dwFlags := 0;
        end;
  
        waveOutPrepareHeader(wo, @hdr, sizeof(hdr));
        waveOutWrite(wo, @hdr, sizeof(hdr));
        //sleep(450);
  
        //while waveOutUnprepareHeader(wo, @hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
          //sleep(100);
  
      finally
        waveOutClose(wo);
        PacketIsPlaying := False;
      end;
  
  
  end;
  
  function ReadDataBlock(Size: integer): Boolean;
  begin
    try
      BlockRead(myWavFile, DataBlock, Size, Count);
      INC(BytesRead, Size);
      Result := True;
    except
      Result := False;
    end;
  end;
  
  function OpenWav(FileName: string): Boolean;
  begin
    try
      Assignfile(myWavFile, filename);
      Reset(myWavFile, 1);
      Result := True;
    except
      Result := False;
    end;
  end;
  
  function CloseWav: Boolean;
  begin
    try
      CloseFile(myWavFile);
      Result := True;
    except
      Result := False;
    end;
  end;
  
  function ValidateWav: Boolean;
  const
    RIFF: array[0..3] of byte = (82, 73, 70, 70);
    WAVE: array[0..3] of byte = (87, 65, 86, 69);
    _FMT: array[0..3] of byte = (102, 109, 116, 32);
    FACT: array[0..3] of byte = (102, 97, 99, 116);
    DATA: array[0..3] of byte = (100, 97, 116, 97);
    _DATA: array[0..3] of byte = (64, 61, 74, 61);
  var
    RiffChunkSize, FmtChunkSize, FactChunkSize, DataChunkSize, i, j, tmp, Freq: integer;
  
    omega,
    dt, t: double;
    vol: double;
  begin
  
    BytesRead := 0;
  
    //Check "RIFF"
    ReadDataBlock(4);
    if not CompareMem(@DataBlock, @RIFF, SizeOf(DataBlock)) then
      begin
        Result := False;
        Exit;
      end;
  
    //Get "RIFF" Chunk Data Size
    ReadDataBlock(4);
    Move(DataBlock, RiffChunkSize, 4);
  
    //Check "WAVE"
    ReadDataBlock(4);
    if not CompareMem(@DataBlock, @WAVE, SizeOf(DataBlock)) then
      begin
        Result := False;
        Exit;
      end;
  
    {FMT ---------------------------------------------------------------------}
  
    //Check "FMT"
    ReadDataBlock(4);
    if not CompareMem(@DataBlock, @_FMT, SizeOf(DataBlock)) then
      begin
        Result := False;
        Exit;
      end;
  
    //Get "FMT" Chunk Data Size
    ReadDataBlock(4);
    Move(DataBlock, FmtChunkSize, 4);
  
    BytesRead := 0;
  
    //Get Wav Compression Code
    ReadDataBlock(2);
    Move(DataBlock, CompressionCode, 2);
    if not CompressionCodes.TryGetValue(CompressionCode, CompressionDesc) then
      CompressionDesc := 'File Error!';
  
    //Get Number of Channels
    ReadDataBlock(2);
    Move(DataBlock, NumOfChannels, 2);
  
    //Get Sample Rate
    ReadDataBlock(4);
    Move(DataBlock, SampleRate, 4);
  
    //Get Average Bytes Per Second
    ReadDataBlock(4);
    Move(DataBlock, BytesPerSecond, 4);
  
    //Get Block Align
    ReadDataBlock(2);
    Move(DataBlock, BlockAlign, 2);
  
    //Get Bits Per Sample
    ReadDataBlock(2);
    Move(DataBlock, BitsPerSample, 2);
  
    //Extra Format Bytes
    if BytesRead &lt;= FmtChunkSize - 2 then
      begin
        ReadDataBlock(2);
        Move(DataBlock, ExtraFormatBytes, 2);
      end;
  
    //If it's not Uncompressed/PCM File, then we have Extra Format Bytes
    if CompressionCode &lt;&gt; 1 then
      begin
        //Skip Compression Data
        for i := 0 to FmtChunkSize - BytesRead - 1 do
          ReadDataBlock(1);
  
        Result := False;
        Exit;
      end;
  
    {FACT --------------------------------------------------------------------}
  
    {FactChunkSize := 0;
    //Check "FACT"
    ReadDataBlock(4);
    if CompareMem(@DataBlock, @FACT, SizeOf(DataBlock)) then
      begin
        //Get "FMT" Chunk Data Size
        ReadDataBlock(4);
        Move(DataBlock, FactChunkSize, 4);
  
        BytesRead := 0;
        for i := 0 to FactChunkSize - BytesRead - 1 do
          ReadDataBlock(1);
      end;   }
  
      {DATA ------------------------------------------------------------------}
  
      while BytesRead &lt; FmtChunkSize do
        ReadDataBlock(1);
  
      BytesRead := 0;
  
      //Skip bytes until "data" shows up
      while (not CompareMem(@DataBlock, @DATA, SizeOf(DataBlock))) and (not CompareMem(@DataBlock, @_DATA, SizeOf(DataBlock))) do
      begin
        ReadDataBlock(4);
      end;
  
      ReadDataBlock(4);
      Move(DataBlock, DataChunkSize, 4);
  
  
  
  
        Form1.Label1.Caption := 'Compression Code: ' + IntToStr(CompressionCode) + #10#13 +
                          'Compression Description: ' + CompressionDesc + #10#13 +
                          'Number of Channels: ' + IntToStr(NumOfChannels) + #10#13 +
                          'Sample Rate: ' + IntToStr(SampleRate) + #10#13 +
                          'Byes per Sample: ' + IntToStr(ByesPerSample) + #10#13 +
                          'Byes per Second: ' + IntToStr(BytesPerSecond) + #10#13 +
                          'Bits per Second: ' + IntToStr(BitsPerSample);
  
  
  
  
      tmp := FileSize(myWavFile) - DataChunkSize;
  
     { j := 0;
      Form1.Image1.Canvas.Rectangle(0, 0, Form1.Image1.Width, Form1.Image1.Height);
      for i := 0 to (DataChunkSize div 20) do
        begin
          //BlockRead(myWavFile, DataBlock, 76, Count);
          tmp := tmp + 76;
          Seek(myWavFile, tmp);
  
          ReadDataBlock(4);
  
          Move(DataBlock, Freq, 4);
  
          if i mod ((DataChunkSize div 80) div Form1.Image1.Width) = 0 then
          begin
            INC(J);
            Form1.Image1.Canvas.MoveTo(j, 121 div 2);
            Form1.Image1.Canvas.LineTo(j, (121 div 2) - Trunc((Freq / High(Integer)) * (121 div 2)));
          end;
  
          Application.ProcessMessages;
        end;
  
      Seek(myWavFile, FileSize(myWavFile) - DataChunkSize); }
  
      InitAudioSys;
      PacketIsPlaying := False;
  
      SetLength(Samples, fmt.nSamplesPerSec);
  
      while PacketIsPlaying = false do
        begin
          for i := 0 to fmt.nSamplesPerSec do
            begin
              ReadDataBlock(4);
              Move(DataBlock, Freq, 4);
  
              Samples[i] := Freq;
            end;
  
          PlaySound;
          Sleep(2000);
          Application.ProcessMessages;
        end;
  
  
  
  
    Result := True;
  
  end;
  
  procedure TForm1.Button1Click(Sender: TObject);
  var
    f: file;
    b: array[0..3] of byte;
    count: integer;
  begin
  
    with opendialog1 do
    if execute then
      begin
        Form1.Image1.Canvas.Rectangle(0, 0, Form1.Image1.Width, Form1.Image1.Height);
        Label1.Font.Color := clBlack;
  
        OpenWav(FileName);
  
        if ValidateWav = False then
          begin
            Label1.Caption := 'Invalid File Data!';
            Label1.Font.Color := clRed;
            Exit;
          end;
  
  
  
        CloseWav;
      end;
  
  end;
  
  procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  begin
    CompressionCodes.Destroy;
  end;
  
  procedure TForm1.FormCreate(Sender: TObject);
  begin
    Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height);
  
    CompressionCodes := TDictionary&lt;integer, string&gt;.Create;
  
    CompressionCodes.Add(0, 'Unknown');
    CompressionCodes.Add(1, 'PCM/Uncompressed');
    CompressionCodes.Add(2, 'Microsoft ADPCM');
    CompressionCodes.Add(6, 'ITU G.711 a-law');
    CompressionCodes.Add(7, 'ITU G.711 Âµ-law');
    CompressionCodes.Add(17, 'IMA ADPCM');
    CompressionCodes.Add(20, 'ITU G.723 ADPCM (Yamaha)');
    CompressionCodes.Add(49, 'GSM 6.10');
    CompressionCodes.Add(64, 'ITU G.721 ADPCM');
    CompressionCodes.Add(80, 'MPEG');
    CompressionCodes.Add(85, 'ISO/MPEG');
    CompressionCodes.Add(65536, 'Experimental');
  
  
  end;
  
  end.
  </code></pre>
  
  <p>The Code needs a TLabel, a Tbutton and an OpenFileDialog on the form.</p>
  
  <p>I have problem with the File Playback. currently I create arrays of samples with the length of <code>SamplesPerSecond</code> and play them one after another with the delay of 2000 (delays less than 2000ms will raise error).
  What I want now is how can I Read samples and play them one after another smoothly and without delay. and Also I want to be able to visualize every few samples on a graph as the file is being played.</p>