Page 1 of 1

24-Bit WAV Files

Posted: Sun Feb 12, 2012 3:38 am
by chris319
Here is a little utility for checking the read and write integrity of monaural 24-bit wav files under PureBasic.

You can use any monaural 24-bit wav file which has the old-fashioned wav header as the input file. I created a sine-wave file as my input file. This made it easy to verify that the output file was an exact replica of the data contained in the input file. The output file is a raw sound file with the extension .snd. There is no wav file header. I leave it to the reader to add one if he wants, or to add multichannel support. I use the program Goldwave to read the .snd file. The program reads the PCM data from the file into a buffer and then reads the data from the buffer and writes it back into the .snd file. It also reports the time this takes. You can also do multiple iterations for extended time testing by enabling the For/Next loop which is currently commented out. There are some hard-coded values for reading the wav file contents which is not the best way to do it; that's another project for the reader.

Code: Select all

;24-bit test.pb
;CHECKS INTEGRITY OF 24-BIT READING AND WRITING ROUTINES
;UPDATED ON 3/5/12

OpenConsole()

Global format.WAVEFORMATEX: Global offset.l, sample.l
Global Dim sampleData.d(1)

filename$ = ".\24 bit test.wav"

result = ReadFile(1, filename$)
If result = 0
  MessageRequester("Error", "Unable to open file " + fileName$)
  End
EndIf

FileSeek(1, 24)
sampleRate = ReadLong(1)

FileSeek(1, 34)
format\wBitsPerSample = ReadWord(1)
If format\wBitsPerSample <> 24
  MessageRequester("Error", "Incorrect bit depth. Must be 24 bits.")
  End
EndIf

format\nBlockAlign = format\wBitsPerSample / 8

Repeat ;GO PAST HEADER
  seek$ = LCase(Right(seek$ + Chr(ReadByte(1)), 4))
Until seek$ = "data"

subChunk2Size = ReadLong(1) ;SIZE OF AUDIO DATA IN BYTES
*soundBuffer = AllocateMemory(subChunk2Size)
bytes_read = ReadData(1, *soundbuffer, subChunk2Size)
CloseFile(1)
CreateFile(1, ".\24 bit output.snd")

bufferEnd = (*soundBuffer - 0) + subChunk2Size

now = ElapsedMilliseconds()
For ct2 = 1 To 10

;THERE ARE TWO DIFFERENT WAYS TO READ 24-BIT SAMPLES FROM THE BUFFER. METHOD #1 IS FASTER.

*offset = *soundBuffer - 1 ;USE FOR METHOD #1
;*offset = *soundBuffer ;USE FOR METHOD #2

Repeat
;METHOD #1
  sample.l = PeekL(*offset) >> 8

;METHOD #2
;  lsb.u = PeekU(*offset) ;USE UNSIGNED PUREBASIC TYPE "UNICODE"
;  msb.b = PeekB(*offset + 2) ;THIS BYTE MUST BE SIGNED
;  sample = msb << 16 | lsb

  WriteData(1, @sample, 3)
  *offset + format\nBlockAlign
Until *offset >= bufferEnd

Next

PrintN(Str(ElapsedMilliseconds() - now))

CloseFile(1)
FreeMemory(*soundbuffer)

PrintN(Chr(10) + "Press any key to exit.")

While Inkey() = "": Wend

Re: 24-Bit WAV Files

Posted: Mon Mar 05, 2012 8:48 pm
by chris319
This code has been updated based on subsequent testing and research.

The fastest way to read a 24-bit sound sample from a buffer is to read a long (32-bit) variable from an address which is one byte lower than the address of the desired sample, then to shift the long variable to the right by eight bits. Reading a sample in this way places it in the 24 most significant bits of the variable. The right shift moves the sample to the 24 least significant bits.

Re: 24-Bit WAV Files

Posted: Tue Mar 06, 2012 6:14 am
by wilbert
chris319 wrote:from an address which is one byte lower
You have to be careful with this approach since you start reading from an address outside of the memory you have allocated.

Re: 24-Bit WAV Files

Posted: Tue Mar 06, 2012 5:25 pm
by Thorium
Would be even faster if you dont use PeekL and use a pointer instead.

Re: 24-Bit WAV Files

Posted: Tue Mar 06, 2012 10:18 pm
by chris319
wilbert wrote:
chris319 wrote:from an address which is one byte lower
You have to be careful with this approach since you start reading from an address outside of the memory you have allocated.
Why would that be a problem? The bits from that address will be shifted off anyway.

Re: 24-Bit WAV Files

Posted: Tue Mar 06, 2012 10:30 pm
by Thorium
chris319 wrote:
wilbert wrote:
chris319 wrote:from an address which is one byte lower
You have to be careful with this approach since you start reading from an address outside of the memory you have allocated.
Why would that be a problem? The bits from that address will be shifted off anyway.
Because it could be that this memory address is not allocated at all and that would result in a invalid memory access = crash.

Re: 24-Bit WAV Files

Posted: Wed Mar 07, 2012 12:48 am
by chris319
Thorium wrote:
chris319 wrote:
wilbert wrote:
chris319 wrote:from an address which is one byte lower
You have to be careful with this approach since you start reading from an address outside of the memory you have allocated.
Why would that be a problem? The bits from that address will be shifted off anyway.
Because it could be that this memory address is not allocated at all and that would result in a invalid memory access = crash.
Then I will allocate it.

What is the trick for reading this buffer with pointers? I'm fooling around with pointers and structures with no success.

Re: 24-Bit WAV Files

Posted: Wed Mar 07, 2012 12:55 am
by Demivec
chris319 wrote:What is the trick for reading this buffer with pointers? I'm fooling around with pointers and structures with no success.
Try something like this:

Code: Select all

For ct2 = 1 To 10
  
  ;THERE ARE TWO DIFFERENT WAYS TO READ 24-BIT SAMPLES FROM THE BUFFER. METHOD #1 IS FASTER.
  
  *offset.Long = *soundBuffer - 1 ;USE FOR METHOD #1
   
  Repeat
    ;METHOD #1
    sample.l = *offset\l >> 8
    
    WriteData(1, @sample, 3)
    *offset + format\nBlockAlign
  Until *offset >= bufferEnd
  
Next

Re: 24-Bit WAV Files

Posted: Wed Mar 07, 2012 1:19 am
by chris319
The pointer works as suggested, thanks.

Eliminating the disk writes, 100 iterations with pointers took 31.734 seconds. 100 iterations with PeekL() took 30.922 seconds. PeekL() is faster but by a rather small amount.

Re: 24-Bit WAV Files

Posted: Wed Mar 07, 2012 6:30 am
by chris319
It's a toss-up whether the pointer is faster than PeekL().

The example below allocates four more bytes for the sound buffer than needed. It then establishes a pointer for the sound buffer which is four bytes into the previously-allocated memory. The original pointer is used to deallocate the memory.

Code: Select all

;24-bit test.pb
;CHECKS INTEGRITY OF 24-BIT READING AND WRITING ROUTINES
;UPDATED ON 3/6/12

#FRAMES_PER_BUFFER = 3

OpenConsole()

Global format.WAVEFORMATEX: Global offset.l, sample.l
Global Dim sampleData.d(1)

filename$ = ".\24 bit test.wav"

result = ReadFile(1, filename$)
If result = 0
  MessageRequester("Error", "Unable to open file " + fileName$)
  End
EndIf

FileSeek(1, 24)
sampleRate = ReadLong(1)

FileSeek(1, 34)
format\wBitsPerSample = ReadWord(1)
If format\wBitsPerSample <> 24
  MessageRequester("Error", "Incorrect bit depth. Must be 24 bits.")
  End
EndIf

Repeat ;GO PAST HEADER
  seek$ = LCase(Right(seek$ + Chr(ReadByte(1)), 4))
Until seek$ = "data"

subChunk2Size = ReadLong(1) ;SIZE OF AUDIO DATA IN BYTES

;THIS TRICK IS TO PREVENT ACCESSING UNALLOCATED MEMORY
*bufferPtr = AllocateMemory(subChunk2Size + 4)
*soundBuffer = *bufferPtr + 4

bytes_read = ReadData(1, *soundbuffer, subChunk2Size)
CloseFile(1)
CreateFile(1, ".\24 bit output.snd")

bufferEnd = (*soundBuffer - 0) + subChunk2Size

now = ElapsedMilliseconds()
For ct2 = 1 To 10

;THERE ARE TWO DIFFERENT WAYS TO READ 24-BIT SAMPLES FROM THE BUFFER. METHOD #1 IS FASTER.

*offset.Long = *soundBuffer - 1 ;USE FOR METHOD #1
;*offset.l = *soundBuffer - 1 ;USE FOR METHOD #1
;*offset = *soundBuffer ;USE FOR METHOD #2

Repeat
;METHOD #1
;  sample.l = PeekL(*offset) >> 8
  sample.l = *offset\l >> 8

;METHOD #2
;  lsb.u = PeekU(*offset) ;USE PUREBASIC TYPE "UNICODE"
;  msb.b = PeekB(*offset + 2)
;  sample = msb << 16 | lsb

  WriteData(1, @sample, #FRAMES_PER_BUFFER)
  *offset + #FRAMES_PER_BUFFER
Until *offset >= bufferEnd

Next

PrintN(Str(ElapsedMilliseconds() - now))

CloseFile(1)
FreeMemory(*bufferPtr)
PrintN(Chr(10) + "Press any key to exit.")

While Inkey() = "": Wend
End

Re: 24-Bit WAV Files

Posted: Wed Mar 07, 2012 7:13 am
by wilbert
You could write your own Peek function (based on your method 2) that doesn't reed outside of the specified memory address like this

Code: Select all

Procedure Peek24(*MemoryBuffer)
  EnableASM
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    MOV edx, *MemoryBuffer
    MOVSX eax, byte [edx + 2]
    SAL eax, 16
    MOV ax, word [edx]
  CompilerElse
    MOV rdx, *MemoryBuffer
    MOVSX rax, byte [rdx + 2]
    SAL rax, 16
    MOV ax, word [rdx]
  CompilerEndIf
  DisableASM
  ProcedureReturn
EndProcedure
but your pointer approach probably will be faster.
What should be faster is to process more samples in a loop and write them to a file at once instead of one sample a time.

Re: 24-Bit WAV Files

Posted: Wed Mar 07, 2012 7:26 am
by chris319
The purpose of this exercise is to demonstrate that a sine wave could be read from a file into a buffer and that the samples could be written back to a different file and still be recongnizable as a sine wave. In practice one would write the entire buffer to disk using WriteData().