Get WAV Attributes (length, rate, ...)

Share your advanced PureBasic knowledge/code with the community.
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Get WAV Attributes (length, rate, ...)

Post by kenmo »

I'm surprised that the native PB sound library doesn't have any functions for analyzing a loaded sound...

So for my own purposes, I pulled together bits and pieces of code, and wrote:

CatchWavAttribute(*WAV.i, Attribute.i) and
LoadWavAttribute(File.s, Attribute.i)

(in the style of CatchSound and LoadSound). All constants and return values are described in the code.

Note: this currently works ONLY with files of the most basic PCM format. (No FLAC, OGG, etc.)

Code: Select all

; +-------------------+-------+
; | WavAttributes.pbi | kenmo |
; +-------------------+-------+
; |  5.02.2011 - Version 1.0
; |  6.01.     - Version 1.1 (removed a debug, fixed Samples attribute)

;-
;- WAV Constants

;   _Attribute      _Value   _Description            _Units
#WAV_BitDepth     =  $00    ; Sample depth *          bits  / sample
#WAV_BitRate      =  $01    ; Total data rate         bits  / second
#WAV_ByteDepth    =  $02    ; Sample depth *          bytes / sample
#WAV_ByteRate     =  $03    ; Total data rate         bytes / second
#WAV_Channels     =  $04    ; Number of channels      channels
#WAV_ChunkSize    =  $05    ; Total audio data size   bytes
#WAV_Format       =  $06    ; File format             PCM or Unknown
#WAV_Milliseconds =  $07    ; Total audio length      milliseconds
#WAV_SampleRate   =  $08    ; Sample rate *           samples / second
#WAV_Samples      =  $09    ; Number of samples *     samples
#WAV_Seconds      =  $0A    ; Total audio length      seconds
;                           * refers to one channel only

; Return Values
#WAV_PCM      =  0
#WAV_Unknown  = -1
#WAV_NoFile   = -2
#WAV_ReadFail = -3
#WAV_NoBuffer = -4

;-
;- WAV Procedures

Procedure.i CatchWavAttribute(*WAV.i, Attribute.i)
  Protected Value.i = #WAV_Unknown
  
  If (*Wav)
    If ((PeekL(*Wav) = 'FFIR') And (PeekL(*Wav + 12) = ' tmf') And (PeekL(*Wav + 36) = 'atad'))
      Select (Attribute)
        Case #WAV_BitDepth
          Value = PeekU(*WAV + 34)
        Case #WAV_BitRate
          Value = PeekL(*WAV + 28)<<3
        Case #WAV_ByteDepth
          Value = PeekU(*WAV + 34)>>3
        Case #WAV_ByteRate
          Value = PeekL(*WAV + 28)
        Case #WAV_Channels
          Value = PeekU(*WAV + 22)
        Case #WAV_ChunkSize
          Value = PeekL(*WAV + 40)
        Case #WAV_Format
          Value = #WAV_PCM
        Case #WAV_Milliseconds
          Value = PeekL(*WAV + 40) / (PeekL(*Wav + 28) / 100) * 10
        Case #WAV_Samples
          Value = PeekL(*WAV + 40) / PeekU(*Wav + 32)
        Case #WAV_SampleRate
          Value = PeekL(*WAV + 24)
        Case #WAV_Seconds
          Value = PeekL(*WAV + 40) / PeekL(*Wav + 28)
      EndSelect
    EndIf
  EndIf
  
  ProcedureReturn (Value)
EndProcedure

Procedure.i LoadWavAttribute(File.s, Attribute.i)
  Protected Value.i   = #WAV_NoFile
  Protected FID.i     = #Null
  Protected *Buffer.i = #Null
  
  If (File)
    FID = ReadFile(#PB_Any, File)
    If (FID)
      *Buffer = AllocateMemory(44)
      If (*Buffer)
        If (ReadData(FID, *Buffer, 44) = 44)
          Value = CatchWavAttribute(*Buffer, Attribute)
        Else
          Value = #WAV_ReadFail
        EndIf
        FreeMemory(*Buffer)
      Else
        Value = #WAV_NoBuffer
      EndIf
      CloseFile(FID)
    Else
      Value = #WAV_ReadFail
    EndIf
  EndIf
  
  ProcedureReturn (Value)
EndProcedure

;-
;  EOF
and an example:

Code: Select all

XIncludeFile "WavAttributes.pbi"

File.s = OpenFileRequester("Open WAV", "", "WAV Files|*.wav|All Files|*.*", 0)

If (File)

  Debug "File: " + GetFilePart(File)
  Debug ""
  Format.i = LoadWavAttribute(File, #WAV_Format)
  If (Format = #WAV_Unknown)
    Debug "WAV_Format: Unknown"
  ElseIf (Format < 0)
    Debug "Load Error: " + Str(Format)
  Else
    Select (Format)
      Case #WAV_PCM
        Debug "WAV_Format: PCM"
    EndSelect
    Debug "WAV_SampleRate: "   + Str(LoadWavAttribute(File, #WAV_SampleRate  ))
    Debug "WAV_BitDepth: "     + Str(LoadWavAttribute(File, #WAV_BitDepth    ))
    Debug "WAV_ByteDepth: "    + Str(LoadWavAttribute(File, #WAV_ByteDepth   ))
    Debug "WAV_Channels: "     + Str(LoadWavAttribute(File, #WAV_Channels    ))
    Debug "WAV_BitRate: "      + Str(LoadWavAttribute(File, #WAV_BitRate     ))
    Debug "WAV_ByteRate: "     + Str(LoadWavAttribute(File, #WAV_ByteRate    ))
    Debug "WAV_ChunkSize: "    + Str(LoadWavAttribute(File, #WAV_ChunkSize   ))
    Debug "WAV_Samples: "      + Str(LoadWavAttribute(File, #WAV_Samples     ))
    Debug "WAV_Milliseconds: " + Str(LoadWavAttribute(File, #WAV_Milliseconds))
    Debug "WAV_Seconds: "      + Str(LoadWavAttribute(File, #WAV_Seconds     ))
  EndIf
EndIf

Should come in handy once in a while!

* edit: got rid of a Debug that shouldn't have been left in the procedure

* edit June 1, 2011: (fixed Total # Samples bug)
Last edited by kenmo on Thu Jun 02, 2011 6:50 pm, edited 4 times in total.
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Re: Get WAV Attributes (length, rate, ...)

Post by c4s »

kenmo wrote:Should come in handy once in a while!
Indeed!
Thank you.
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Re: Get WAV Attributes (length, rate, ...)

Post by kenmo »

Fixed a (important) bug (in calculating the total number of samples) in case anyone is using this!
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Re: Get WAV Attributes (length, rate, ...)

Post by c4s »

I'm going to use it soon, thanks!

Edit:
Is PeekL(*WAV + 40) / PeekU(*Wav + 32) correct or should it be PeekU(*WAV + 40) / PeekU(*Wav + 32)?
And I think Line 8 (";- WAV ConFileants") is wrong... ;-)
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Re: Get WAV Attributes (length, rate, ...)

Post by kenmo »

Whoops, a rogue find/replace changed that comment.

But the PeekL and PeekU are correct:
- the value at offset 40 is NumSamples * NumChannels * BytesPerSample (32-bit value)
- the value at offset 32 is NumChannels * BytesPerSample (16-bit value)
So the quotient is just NumSamples.

See (here), one of many useful references.

Also, a hint: For simplicity, the Load procedure opens/reads/closes a .wav file every time you call it. For efficiency (if you are reading many attributes on the same file) it would be better to read the first 44 bytes of the file into your own buffer, then call the Catch procedure on this pointer. The rest of the file (the actual audio data) doesn't need to be included.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Get WAV Attributes (length, rate, ...)

Post by chris319 »

Hi Kenmo -

As you will discover, properly analyzing a wav file is not a trivial undertaking. Unfortunately, the format given at stanford.edu is obsolete as it does not take into account files which use the WAVEFORMATEXTENSIBLE format.

A valid wav file will have a WAVEFORMATEX structure but it may or may not have the two-byte cbSize variable; thus, the WAVEFORMATEX structure itself may be 16 or 18 bytes in length. In addition, it may or may not have a WAVEFORMATEXTENSIBLE structure which is cbSize (usually 22) bytes in length, including a 16-byte GUID. This will throw off the location of the "data" string (subchunk2ID). For these reasons, my modified version only looks at the "RIFF" and "fmt " strings. In addition, I convert these strings to lower case as there is no requirement that I'm aware of that they be in a particular case. In a file you could encounter either case.

There is much more to do but this is a start.

Code: Select all

    ; +-------------------+-------+
    ; | WavAttributes.pbi | kenmo |
    ; +-------------------+-------+
    ; |  5.02.2011 - Version 1.0
    ; |  6.01.     - Version 1.1 (removed a debug, fixed Samples attribute)
    ; |  6.11.2011 - Version 1.2 (modified by chris319 to accomodate cbSize varaible and
    ;                             incorporated case conversion of "RIFF", "fmt " and
    ;                             "data" strings - this version is still incomplete)
    ;-
    ;- WAV Constants

    ;   _Attribute      _Value   _Description            _Units
    #WAV_BitDepth     =  $00    ; Sample depth *          bits  / sample
    #WAV_BitRate      =  $01    ; Total data rate         bits  / second
    #WAV_ByteDepth    =  $02    ; Sample depth *          bytes / sample
    #WAV_ByteRate     =  $03    ; Total data rate         bytes / second
    #WAV_Channels     =  $04    ; Number of channels      channels
    #WAV_ChunkSize    =  $05    ; Total audio data size   bytes
    #WAV_Format       =  $06    ; File format             PCM or Unknown
    #WAV_Milliseconds =  $07    ; Total audio length      milliseconds
    #WAV_SampleRate   =  $08    ; Sample rate *           samples / second
    #WAV_Samples      =  $09    ; Number of samples *     samples
    #WAV_Seconds      =  $0A    ; Total audio length      seconds
    ;                           * refers to one channel only

    ; Return Values
    #WAV_PCM      =  0
    #WAV_Unknown  = -1
    #WAV_NoFile   = -2
    #WAV_ReadFail = -3
    #WAV_NoBuffer = -4

    ;-
    ;- WAV Procedures

    Procedure.i CatchWavAttribute(*WAV.i, Attribute.i)
      Protected Value.i = #WAV_Unknown
     
      If (*Wav)
        riff$ = PeekS(*Wav, 4): fmt$ = PeekS(*Wav + 12, 4) ;: data1$ = PeekS(*Wav + 36, 4): data2$ = PeekS(*Wav + 38, 4)
        If LCase(riff$) = "riff" And LCase(fmt$) = "fmt " ;And (LCase(data1$) = "data" Or LCase(data2$) = "data")
          Select (Attribute)
            Case #WAV_BitDepth
              Value = PeekU(*WAV + 34)
            Case #WAV_BitRate
              Value = PeekL(*WAV + 28)<<3
            Case #WAV_ByteDepth
              Value = PeekU(*WAV + 34)>>3
            Case #WAV_ByteRate
              Value = PeekL(*WAV + 28)
            Case #WAV_Channels
              Value = PeekU(*WAV + 22)
            Case #WAV_ChunkSize
              Value = PeekL(*WAV + 40)
            Case #WAV_Format
              Value = #WAV_PCM
            Case #WAV_Milliseconds
              Value = PeekL(*WAV + 40) / (PeekL(*Wav + 28) / 100) * 10
            Case #WAV_Samples
              Value = PeekL(*WAV + 40) / PeekU(*Wav + 32)
            Case #WAV_SampleRate
              Value = PeekL(*WAV + 24)
            Case #WAV_Seconds
              Value = PeekL(*WAV + 40) / PeekL(*Wav + 28)
          EndSelect
        EndIf
      EndIf
     
      ProcedureReturn (Value)
    EndProcedure

    Procedure.i LoadWavAttribute(File.s, Attribute.i)
      Protected Value.i   = #WAV_NoFile
      Protected FID.i     = #Null
      Protected *Buffer.i = #Null
     
      If (File)
        FID = ReadFile(#PB_Any, File)
        If (FID)
          *Buffer = AllocateMemory(44)
          If (*Buffer)
            If (ReadData(FID, *Buffer, 44) = 44)
              Value = CatchWavAttribute(*Buffer, Attribute)
            Else
              Value = #WAV_ReadFail
            EndIf
            FreeMemory(*Buffer)
          Else
            Value = #WAV_NoBuffer
          EndIf
          CloseFile(FID)
        Else
          Value = #WAV_ReadFail
        EndIf
      EndIf
     
      ProcedureReturn (Value)
    EndProcedure

    ;-
    ;  EOF

Code: Select all

    XIncludeFile "WavAttributes.pbi"

    File.s = OpenFileRequester("Open WAV", "", "WAV Files|*.wav|All Files|*.*", 0)

    If (File)

      Debug "File: " + GetFilePart(File)
      Format.i = LoadWavAttribute(File, #WAV_Format)
      If (Format = #WAV_Unknown)
        Debug "WAV_Format: Unknown"
      ElseIf (Format < 0)
        Debug "Load Error: " + Str(Format)
      Else
        Debug "Valid wav file": Debug ""
        Select (Format)
          Case #WAV_PCM
            Debug "WAV_Format: PCM"
        EndSelect
        Debug "WAV_SampleRate: "   + Str(LoadWavAttribute(File, #WAV_SampleRate  ))
        Debug "WAV_BitDepth: "     + Str(LoadWavAttribute(File, #WAV_BitDepth    ))
        Debug "WAV_ByteDepth: "    + Str(LoadWavAttribute(File, #WAV_ByteDepth   ))
        Debug "WAV_Channels: "     + Str(LoadWavAttribute(File, #WAV_Channels    ))
        Debug "WAV_BitRate: "      + Str(LoadWavAttribute(File, #WAV_BitRate     ))
        Debug "WAV_ByteRate: "     + Str(LoadWavAttribute(File, #WAV_ByteRate    ))
        Debug "WAV_ChunkSize: "    + Str(LoadWavAttribute(File, #WAV_ChunkSize   ))
        Debug "WAV_Samples: "      + Str(LoadWavAttribute(File, #WAV_Samples     ))
        Debug "WAV_Milliseconds: " + Str(LoadWavAttribute(File, #WAV_Milliseconds))
        Debug "WAV_Seconds: "      + Str(LoadWavAttribute(File, #WAV_Seconds     ))
      EndIf
    EndIf
Here is some recommended reading on the WAVEFORMATEX and WAVEFORMATEXTENSIBLE structures:

http://msdn.microsoft.com/en-us/library ... 85%29.aspx

http://msdn.microsoft.com/en-us/library ... 85%29.aspx
doctornash
Enthusiast
Enthusiast
Posts: 130
Joined: Thu Oct 20, 2011 7:22 am

Re: Get WAV Attributes (length, rate, ...)

Post by doctornash »

might I request for this to be extended please to load the actual sample values of the .wav file into an array?
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Re: Get WAV Attributes (length, rate, ...)

Post by kenmo »

Sure, that is easy as long as you are working with bare-bones WAV files (as chris319 said, the format can get more complicated).

The samples are stored as integers (8 or 16 bit) but it's easy to normalize them to floats from -1 to +1 if you want. I can write an example for you.
doctornash
Enthusiast
Enthusiast
Posts: 130
Joined: Thu Oct 20, 2011 7:22 am

Re: Get WAV Attributes (length, rate, ...)

Post by doctornash »

Kenmo 'bare bones' is sufficient, so yes please an example would be fabulous! :D
User avatar
kenmo
Addict
Addict
Posts: 2047
Joined: Tue Dec 23, 2003 3:54 am

Re: Get WAV Attributes (length, rate, ...)

Post by kenmo »

This is quick'n'dirty (it's thrown together from some WAV programs I had) and it's not exactly what you asked for (doesn't load the samples into an array, instead they are stored in a memory block as doubles, but you can still step through them).........

It should get you started at loading / analyzing / editing / saving basic 16-bit WAV files.

Code: Select all

;
; Read/Create/Edit/Save WAV files
; by kenmo
; 3/16/2014
;

;-
;- Structures

Structure STDWAV
  ChunkID.l
  ChunkSize.l
  Format.l
  ;
  Subchunk1ID.l
  Subchunk1Size.l
  AudioFormat.u
  NumChannels.u
  SampleRate.l
  ByteRate.l
  BlockAlign.u
  BitsPerSample.u
  ;
  Subchunk2ID.l
  Subchunk2Size.l
EndStructure

Structure FULLWAVE
  NumChannels.u
  SampleRate.l
  BitsPerSample.u
  ;
  NumSamples.l
  MemorySize.l
EndStructure

;-
;- Procedures

Procedure.i ReadFileToMemory(File.s)
  Protected *Result = #Null
  
  Protected FN.i = ReadFile(#PB_Any, File)
  If (FN)
    Protected n.i = Lof(FN)
    If (n > 0)
      *Result = AllocateMemory(n)
      If (*Result)
        If (ReadData(FN, *Result, n) <> n)
          FreeMemory(*Result)
          *Result = #Null
        EndIf
      EndIf
    EndIf
    CloseFile(FN)
  EndIf
  
  ProcedureReturn (*Result)
EndProcedure

Procedure.i LoadStdWav(File.s)
  Protected *SW.STDWAV = #Null
  
  Protected n.i = FileSize(File)
  If (n > SizeOf(STDWAV))
    *SW = ReadFileToMemory(File)
    If (*SW)
      Protected Valid.i = #True
      Valid = Valid * Bool(*SW\ChunkID = 'FFIR')
      Valid = Valid * Bool(*SW\ChunkSize <= n - 8)
      Valid = Valid * Bool(*SW\Format = 'EVAW')
      Valid = Valid * Bool(*SW\Subchunk1ID = ' tmf')
      Valid = Valid * Bool(*SW\Subchunk1Size = 16)
      Valid = Valid * Bool(*SW\AudioFormat = 1)
      Valid = Valid * Bool(*SW\ByteRate = *SW\SampleRate * *SW\NumChannels * *SW\BitsPerSample / 8)
      Valid = Valid * Bool(*SW\BlockAlign = *SW\NumChannels * *SW\BitsPerSample / 8)
      Valid = Valid * Bool(*SW\BitsPerSample % 8 = 0)
      Valid = Valid * Bool(*SW\Subchunk2ID = 'atad')
      Valid = Valid * Bool(*SW\Subchunk2Size <= n - SizeOf(STDWAV))
      If (Not Valid)
        FreeMemory(*SW)
        *SW = #Null
      EndIf
    EndIf
  EndIf
  
  ProcedureReturn (*SW)
EndProcedure

Procedure.i CreateFullWave(NumChannels.i, SampleRate.i, BitsPerSample.i, NumSamples.i)
  Protected *FW.FULLWAVE = #Null
  
  If ((NumChannels >= 1) And (SampleRate > 0) And (BitsPerSample % 8 = 0) And (NumSamples > 0))
    Protected BlockAlign.i = NumChannels * (BitsPerSample / 8)
    Protected n.i = SizeOf(FULLWAVE) + BlockAlign * NumSamples * SizeOf(DOUBLE)
    *FW = AllocateMemory(n)
    If (*FW)
      *FW\NumChannels   = NumChannels
      *FW\SampleRate    = SampleRate
      *FW\BitsPerSample = BitsPerSample
      *FW\NumSamples    = NumSamples
      *FW\MemorySize    = n
    EndIf
  EndIf
  
  ProcedureReturn (*FW)
EndProcedure

Procedure.i LoadFullWave(File.s)
  Protected *FW.FULLWAVE = #Null
  
  Protected *SW.STDWAV = LoadStdWav(File)
  If (*SW)
    Protected NumSamples.l = *SW\Subchunk2Size / (*SW\NumChannels * *SW\BitsPerSample / 8)
    *FW = CreateFullWave(*SW\NumChannels, *SW\SampleRate, *SW\BitsPerSample, NumSamples)
    If (*FW)
      Protected Offset.d, Scaling.d
      Protected c.i, i.i
      Protected *Read, *Write
      Select (*SW\BitsPerSample)
        Case 16 ; 16-bit, -32768 to +32767
          Scaling =  1.0 / 32767.5
          Offset  =  0.5
          For c = 0 To *SW\NumChannels - 1
            *Read  = *SW + SizeOf(STDWAV)   + c * 2
            *Write = *FW + SizeOf(FULLWAVE) + c * NumSamples * SizeOf(DOUBLE)
            For i = 0 To NumSamples - 1
              PokeD(*Write, (PeekW(*Read) + Offset) * Scaling)
              *Read  + *SW\BlockAlign
              *Write + SizeOf(DOUBLE)
            Next i
          Next c
        Case 8 ; 8-bit, 0 to 255
          Scaling =  1.0 / 127.5
          Offset  = -127.5
          ;
        Default
          ;
      EndSelect
    EndIf
    FreeMemory(*SW)
  EndIf
  
  ProcedureReturn (*FW)
EndProcedure

Procedure.i FullWaveSamplePointer(*FW.FULLWAVE, Channel.i)
  Protected *Result = #Null
  If (*FW And (Channel >= 0) And (Channel < *FW\NumChannels))
    *Result = *FW + SizeOf(FULLWAVE) + Channel * (*FW\NumSamples * SizeOf(DOUBLE))
  EndIf
  ProcedureReturn (*Result)
EndProcedure

Procedure.i SaveWave(*FW.FULLWAVE, File.s)
  Protected Result.i = #False
  
  If (*FW And File)
    Protected FN.i = CreateFile(#PB_Any, File)
    If (FN)
      Protected BlockAlign.i = (*FW\NumChannels * *FW\BitsPerSample / 8)
      Protected SampleBytes.i = (*FW\NumSamples * BlockAlign)
      WriteLong(FN, 'FFIR')
      WriteLong(FN, 36 + SampleBytes)
      WriteLong(FN, 'EVAW')
      ;
      WriteLong(FN, ' tmf')
      WriteLong(FN, 16)
      WriteUnicodeCharacter(FN, 1)
      WriteUnicodeCharacter(FN, *FW\NumChannels)
      WriteLong(FN, *FW\SampleRate)
      WriteLong(FN, *FW\SampleRate * BlockAlign)
      WriteUnicodeCharacter(FN, BlockAlign)
      WriteUnicodeCharacter(FN, *FW\BitsPerSample)
      ;
      WriteLong(FN, 'atad')
      WriteLong(FN, SampleBytes)
      ;
      Protected Offset.d, Scaling.d
      Protected c.i, i.i
      Select (*FW\BitsPerSample)
        Case 16
          Scaling =  32767.5
          Offset  =  0.5
          For i = 0 To *FW\NumSamples - 1
            For c = 0 To *FW\NumChannels - 1
              WriteWord(FN, Int(Scaling * PeekD(*FW + SizeOf(DOUBLE) * (i + c * *FW\NumSamples)) - Offset))
            Next c
          Next i
        Case 8
          ;
        Default
          ;
      EndSelect
      CloseFile(FN)
      Result = #True
    EndIf
  EndIf
  
  ProcedureReturn (Result)
EndProcedure

Procedure.i DuplicateFullWave(*FWI.FULLWAVE)
  Protected *FWO.FULLWAVE = #Null
  
  If (*FWI)
    *FWO = AllocateMemory(*FWI\MemorySize)
    If (*FWO)
      CopyMemory(*FWI, *FWO, *FWI\MemorySize)
    EndIf
  EndIf
  
  ProcedureReturn (*FWO)
EndProcedure

;-
;- Example Program

Structure DOUBLEBUFFER
  d.d[0]
EndStructure

InFile.s = OpenFileRequester("Load WAV", GetHomeDirectory(), "WAV Files|*.wav", 0)
If (InFile)

  *Wave.FULLWAVE = LoadFullWave(InFile)
  If (*Wave)
    
    *Sample.DOUBLEBUFFER = FullWaveSamplePointer(*Wave, 0)
        
    Delta = *Wave\NumSamples / 1000
    i = 0
    While (i < *Wave\NumSamples)
      Debug "Sample " + Str(i) + ": " + StrD(*Sample\d[i], 5)
      i + Delta
    Wend
    
    FreeMemory(*Wave)
  Else
    Debug "WAV could not be loaded!"
  EndIf
EndIf

;-
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Get WAV Attributes (length, rate, ...)

Post by chris319 »

The samples are stored as integers (8 or 16 bit)
There can also be 24-bit samples. They are not contained within 32 bits, just three bytes together.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Get WAV Attributes (length, rate, ...)

Post by chris319 »

Hi Kenmo -

Your code fails on valid wav files.

A wav file checker is a good thing to have, but malformed wav files will not play or cannot be opened, so it is important that a file checker program get it right.

PureBasic already has WAVEFORMATEX and WAVEFORMATEXTENSIBLE structures built into it, so let's use them. Don't confuse the file header with these structures. They are not the same.

This code will get you started. The wav file format has been kludged to accomodate advances in audio. The WAVEFORMATEXTENSIBLE structure is for audio with more than 2 channels or samples larger than 16 bits.

To find the audio data, search for the subchunk2ID string "data", then advance 4 more bytes to get past subchunk2Size.

Code: Select all

    ; Read WAV files
    ; original code by kenmo
    ; fixes started by chris319 ; 6/5/2014
    
    
OpenConsole()
    
Structure FILEHEADER
ChunkID.s{4}
ChunkSize.l
Format.s{4}
Subchunk1ID.s{4}
Subchunk1Size.l
;START OF WAVEFORMATEX or WAVEFORMATEXTENSIBLE STRUCTURE
wFormatTag.w
nChannels.w
nSamplesPerSec.l
nAvgBytesPerSec.l
nBlockAlign.w
wBitsPerSample.w
cbSize.w
EndStructure  

myFH.FILEHEADER

InFile.s = OpenFileRequester("Load WAV", GetHomeDirectory(), "WAV Files|*.wav", 0)
If InFile = "": End: EndIf
ReadFile(0,InFile)
ReadData(0, @myFH, SizeOf(myFH))
CloseFile(0)

PrintN(GetFilePart(InFile) + Chr(10))
PrintN("ChunkID: " + myFH\chunkID)
;PrintN("ChunkSize: " + Str(myFH\ChunkSize))
PrintN("Format: " + myFH\Format)
PrintN("SubChunk1ID: " + myFH\Subchunk1ID)
Print("Chunk1Size: " + Str(myFH\Subchunk1Size))
If myFH\Subchunk1Size <> 18
  PrintN("  Old file format.")
EndIf

If myFH\cbSize = 22
  PrintN("WAVEFORMATEXTENSIBLE")
  PrintN("cbSize: " + Str(myFH\cbSize))
EndIf

PrintN("Channels: " + Str(myFH\nChannels))
PrintN("Samples per second: " + Str(myFH\nSamplesPerSec))
PrintN("Average bytes per second: " + Str(myFH\nAvgBytesPerSec))
PrintN("Block align: " + Str(myFH\nBlockAlign))
PrintN("Bits per sample: " + Str(myFH\wBitsPerSample))
If cbSize = 22: PrintN("cbSize: " + Str(myFH\cbSize)): EndIf

If myFH\nChannels > 2 Or myFH\wBitsPerSample > 16: PrintN("WAVEFORMATEXTENSIBLE" + Chr(10)): EndIf

Repeat
Delay(100)
ForEver
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Get WAV Attributes (length, rate, ...)

Post by chris319 »

This code reports an error if a file has > 2 channels or > 16-bit samples and does not contain a proper cbSize field or if subchunk1size is incorrect.

Code: Select all

; Read WAV files
        ; original code by kenmo
        ; fixes started by chris319 ; 6/5/2014
       
       
    OpenConsole()
       
    Structure WAVEFILEHEADER
    ChunkID.s{4}
    ChunkSize.l
    Format.s{4}
    Subchunk1ID.s{4}
    Subchunk1Size.l
    ;START OF WAVEFORMATEX or WAVEFORMATEXTENSIBLE STRUCTURE
    wFormatTag.w
    nChannels.w
    nSamplesPerSec.l
    nAvgBytesPerSec.l
    nBlockAlign.w
    wBitsPerSample.w
    cbSize.w
    EndStructure 

    myFH.WAVEFILEHEADER

    InFile.s = OpenFileRequester("Load WAV", GetHomeDirectory(), "WAV Files|*.wav", 0)
    If InFile = "": End: EndIf
    ReadFile(0,InFile)
    ReadData(0, @myFH, SizeOf(myFH))
    CloseFile(0)

    PrintN("File name: " + GetFilePart(InFile) + Chr(10))
    PrintN("ChunkID: " + myFH\chunkID)
    ;PrintN("ChunkSize: " + Str(myFH\ChunkSize))
    PrintN("Format: " + myFH\Format)
    PrintN("SubChunk1ID: " + myFH\Subchunk1ID)
    Print("Chunk1Size: " + Str(myFH\Subchunk1Size))
    
    If myFH\Subchunk1Size <> 18
      PrintN("  Obsolete wav file format.")
    EndIf

    If myFH\cbSize = 22
      PrintN("WAVEFORMATEXTENSIBLE")
      PrintN("cbSize: " + Str(myFH\cbSize))
    EndIf

    PrintN("Channels: " + Str(myFH\nChannels))
    PrintN("Samples per second: " + Str(myFH\nSamplesPerSec))
    PrintN("Average bytes per second: " + Str(myFH\nAvgBytesPerSec))
    PrintN("Block align: " + Str(myFH\nBlockAlign))
    PrintN("Bits per sample: " + Str(myFH\wBitsPerSample))
    If cbSize = 22: PrintN("cbSize: " + Str(myFH\cbSize)): EndIf

    If myFH\nChannels > 2 Or myFH\wBitsPerSample > 16: PrintN("WAVEFORMATEXTENSIBLE" + Chr(10)): EndIf
    
    If (myFH\nChannels > 2 Or myFH\wBitsPerSample > 16) And (myFH\cbSize <> SizeOf(WAVEFORMATEXTENSIBLE) - SizeOf(WAVEFORMATEX) Or (myFH\Subchunk1Size <> 18))
      PrintN("ERROR: This file is not compliant with wav file specifications.")
    EndIf
    
    Repeat
    Delay(100)
    ForEver
Post Reply