Page 1 of 1

DirectSound Recording with WAVE FORMAT EXTENSIBLE

Posted: Fri Oct 01, 2010 4:58 pm
by chris319
Below is a demo program which makes a wave recording using DirectSound and WAVE FORMAT EXTENSIBLE. CAUTION: there may yet be some bugs in the file save part. It should do multi-channel (more than two channel) recording.

Code: Select all

;DIRECTSOUND RECORD PROGRAM 
;chris319
;UPDATED ON 10/1/2010 

;THIS PROGRAM RECORDS 2 SECONDS OF AUDIO
;FROM THE DEFAULT RECORDING DEVICE, THEN SAVES THE 
;AUDIO AS A WAV FILE NAMED "test.wav". YOU CAN FOOL 
;AROUND WITH CHANNELS, SAMPLE RATE AND BIT DEPTH.

OpenConsole()

#CHANNELS                 = 4
#SAMPLE_RATE              = 44100
#BIT_DEPTH                = 16 
#SECONDS                  = 2

#WAVE_FORMAT_PCM          = 1 
#WAVE_FORMAT_EXTENSIBLE   = $FFFE

#SPEAKER_DIRECTOUT                 = 0
#SPEAKER_FRONT_LEFT                = ($1)
#SPEAKER_FRONT_RIGHT            = ($2)
#SPEAKER_FRONT_CENTER            = ($4)
#SPEAKER_LOW_FREQUENCY        = ($8)
#SPEAKER_BACK_LEFT                = ($10)
#SPEAKER_BACK_RIGHT                = ($20)
#SPEAKER_FRONT_LEFT_OF_CENTER    = ($40)
#SPEAKER_FRONT_RIGHT_OF_CENTER = ($80)
#SPEAKER_BACK_CENTER            = ($100)
#SPEAKER_SIDE_LEFT                = ($200)
#SPEAKER_SIDE_RIGHT                = ($400)
#SPEAKER_TOP_CENTER                = ($800)
#SPEAKER_TOP_FRONT_LEFT        = ($1000)
#SPEAKER_TOP_FRONT_CENTER    = ($2000)
#SPEAKER_TOP_FRONT_RIGHT    = ($4000)
#SPEAKER_TOP_BACK_LEFT        = ($8000)
#SPEAKER_TOP_BACK_CENTER    = ($10000)
#SPEAKER_TOP_BACK_RIGHT        = ($20000)
#SPEAKER_MONO                        = (#SPEAKER_FRONT_CENTER)
#SPEAKER_STEREO                      = (#SPEAKER_FRONT_LEFT | #SPEAKER_FRONT_RIGHT) 
#SPEAKER_QUAD                        = (#SPEAKER_FRONT_LEFT | #SPEAKER_FRONT_RIGHT | #SPEAKER_BACK_LEFT  | #SPEAKER_BACK_RIGHT)
#SPEAKER_SURROUND                  = (#SPEAKER_FRONT_LEFT | #SPEAKER_FRONT_RIGHT | #SPEAKER_FRONT_CENTER | #SPEAKER_BACK_CENTER)
#SPEAKER_5POINT1                  = (#SPEAKER_FRONT_LEFT | #SPEAKER_FRONT_RIGHT | #SPEAKER_FRONT_CENTER | #SPEAKER_LOW_FREQUENCY | #SPEAKER_BACK_LEFT  | #SPEAKER_BACK_RIGHT)
#SPEAKER_7POINT1                  = (#SPEAKER_FRONT_LEFT | #SPEAKER_FRONT_RIGHT | #SPEAKER_FRONT_CENTER | #SPEAKER_LOW_FREQUENCY | #SPEAKER_BACK_LEFT | #SPEAKER_BACK_RIGHT | #SPEAKER_FRONT_LEFT_OF_CENTER | #SPEAKER_FRONT_RIGHT_OF_CENTER)
#SPEAKER_5POINT1_SURROUND    = (#SPEAKER_FRONT_LEFT | #SPEAKER_FRONT_RIGHT | #SPEAKER_FRONT_CENTER | #SPEAKER_LOW_FREQUENCY | #SPEAKER_SIDE_LEFT  | #SPEAKER_SIDE_RIGHT)
#SPEAKER_7POINT1_SURROUND    = (#SPEAKER_FRONT_LEFT | #SPEAKER_FRONT_RIGHT | #SPEAKER_FRONT_CENTER | #SPEAKER_LOW_FREQUENCY | #SPEAKER_BACK_LEFT | #SPEAKER_BACK_RIGHT | #SPEAKER_SIDE_LEFT | #SPEAKER_SIDE_RIGHT)

#DS_OK = 0
#LOOP_FLAG = 0 
#DSCBLOCK_ENTIREBUFFER = 1 

Structure WAVEFORMATEXTENSIBLE
  format.WAVEFORMATEX ;THE FAMILIAR WAVEFORMATEX STRUCTURE
  wValidBitsPerSample.w
  dwChannelMask.l
  SubFormat.GUID
EndStructure

Global my_wfe.WAVEFORMATEXTENSIBLE

If #CHANNELS > 2
  my_wfe\Format\wFormatTag = #WAVE_FORMAT_EXTENSIBLE
Else
  my_wfe\Format\wFormatTag = #WAVE_FORMAT_PCM
EndIf

Global *bufptr ;POINTER TO CAPTURE BUFFER 
Global bufsize.l ;SIZE OF CAPTURE BUFFER 

; Base part:
my_wfe\Format\nChannels       = #CHANNELS
my_wfe\Format\wBitsPerSample  = #BIT_DEPTH
my_wfe\Format\nSamplesPerSec  = #SAMPLE_RATE
my_wfe\Format\nBlockAlign     = my_wfe\Format\nChannels * (my_wfe\Format\wBitsPerSample / 8)
my_wfe\Format\nAvgBytesPerSec = my_wfe\Format\nSamplesPerSec * my_wfe\Format\nBlockAlign

; Extensible part:
my_wfe\wValidBitsPerSample    = #BIT_DEPTH ;All bits have Data
my_wfe\Format\cbSize          = 22 ;Size of Extensible part in bytes( SubFormat + wValidBitsPerSample + dwChannelMask)

; Choose channel mask:
Select #CHANNELS ;my_wfe\Format\nChannels
  Case 0
      my_wfe\dwChannelMask = #SPEAKER_DIRECTOUT
  Case 1
      my_wfe\dwChannelMask = #SPEAKER_MONO
  Case 2
      my_wfe\dwChannelMask = #SPEAKER_STEREO
  Case 3
      my_wfe\dwChannelMask = #SPEAKER_STEREO | #SPEAKER_FRONT_CENTER
  Case 4
      my_wfe\dwChannelMask = #SPEAKER_QUAD
  Case 5
      my_wfe\dwChannelMask = #SPEAKER_QUAD | #SPEAKER_FRONT_CENTER
  Case 6
      my_wfe\dwChannelMask = #SPEAKER_5POINT1_SURROUND
  Case 7
      my_wfe\dwChannelMask = #SPEAKER_5POINT1_SURROUND | #SPEAKER_BACK_CENTER
  Case 8
      my_wfe\dwChannelMask = #SPEAKER_7POINT1_SURROUND
EndSelect

;GUID
my_wfe\SubFormat\Data1    = #WAVE_FORMAT_PCM
my_wfe\SubFormat\Data2    = $00
my_wfe\SubFormat\Data3    = $10
my_wfe\SubFormat\Data4[0] = $80
my_wfe\SubFormat\Data4[1] = $00
my_wfe\SubFormat\Data4[2] = $00
my_wfe\SubFormat\Data4[3] = $aa
my_wfe\SubFormat\Data4[4] = $00
my_wfe\SubFormat\Data4[5] = $38
my_wfe\SubFormat\Data4[6] = $9b
my_wfe\SubFormat\Data4[7] = $71

;DIRECTSOUND BUFFER 
Structure DSCBUFFERDESC 
  dwSize.l            ; Size of the structure, in bytes. This member must be initialized before the structure is used. 
  dwFlags.l           ; Flags specifying the capabilities of the buffer 
  dwBufferBytes.l     ; Size of capture buffer to create, in bytes. 
  dwReserved.l        ; Must be 0 
  *lpwfxFormat        ; Address of a WAVEFORMATEX or WAVEFORMATEXTENSIBLE structure specifying the waveform format for the buffer. 
EndStructure 

;SET UP CAPTURE BUFFER 
dscbd.DSCBUFFERDESC
;dscbd\dwSize        = SizeOf(DSCBUFFERDESC) ; Save structure size 
dscbd\dwSize        = SizeOf(dscbd) ; Save structure size 
dscbd\dwFlags       = 0                     ; It is the primary Buffer (see DSound.h) 
dscbd\dwBufferBytes = my_wfe\format\nAvgBytesPerSec * #SECONDS
dscbd\dwReserved    = 0 
dscbd\lpwfxFormat   = @my_WFE 

Procedure Delete(*obj.IUnknown) 
  ProcedureReturn *Obj\Release() 
EndProcedure 

Procedure Error_Msg(String.s) 
 MessageRequester("Error",String.s,0) 
 End 
EndProcedure 

Procedure File_Save() 
;SAVE BUFFER AS A WAV FILE
;CAUTION: THERE MAY BE SOME BUGS IN THIS PART

If CreateFile(1, "audio\test.wav") = 0 
  MessageRequester("Error", "Unable to create file.", #MB_ICONERROR) 
  End 
  ProcedureReturn 
EndIf 

subchunk1size.l = SizeOf(WAVEFORMATEX) 
subchunk2size.l = bufsize 
chunksize = 4 + (8 + SizeOf(WAVEFORMATEX)) + (8 + subchunk2size) 

samprate.l = my_wfe\format\nSamplesPerSec 
byterate.l = my_wfe\format\nSamplesPerSec * my_wfe\format\nAvgBytesPerSec ;IS THIS RIGHT? 
blockalign.w = my_wfe\format\nChannels * (my_wfe\format\wBitsPerSample / 8) 
bitspersample.w = my_wfe\format\wBitsPerSample 

;my_wfe\format\cbSize = 0

chunksize = chunksize + my_wfe\format\cbSize 

;my_wfe\format\wFormatTag = #WAVE_FORMAT_PCM

;WRITE WAV HEADER 
WriteString(1, "RIFF") ; 4 bytes 
WriteLong(1, chunksize) ; 4 bytes 
WriteString(1, "WAVE") ; 4 bytes 
WriteString(1, "fmt ") ; 4 bytes 
WriteLong(1, subchunk1size) ; 4 bytes 
WriteData(1, my_WFE, SizeOf(WAVEFORMATEX)) 
;END OF WAVEFORMATEX STRUCTURE 

WriteString(1, "data", #PB_Ascii) ; 4 bytes 
WriteLong(1, subchunk2size) ; 4 bytes 
;END OF FILE HEADER 

;WRITE AUDIO DATA AFTER WAV HEADER 
WriteData(1, *bufptr, bufsize) 
CloseFile(1) 

EndProcedure 

;CREATE DIRECTSOUND CAPTURE OBJECT 
*DirectSound.IDirectSoundCapture
result = DirectSoundCaptureCreate_(0, @*DirectSound, 0) 
If Result <> #DS_OK 
 Error_Msg("Can't do DirectSoundCaptureCreate: " + Right(Hex(Result), 8))
EndIf 

result = *DirectSound\CreateCaptureBuffer(@dscbd, @*pDSCB.IDirectSoundCaptureBuffer, 0) 
If result <> #DS_OK 
  Error_Msg("Can't set up DirectSound buffer: " + Right(Hex(Result), 8))
EndIf 

;START RECORDING 
result = *pDSCB.IDirectSoundCaptureBuffer\Start(#LOOP_FLAG) 
If result <> #DS_OK 
  Error_Msg("Can't start : " + Str(Result)) 
Else 
  PrintN("Recording started")
EndIf 

;RECORD FOR SPECIFIED NUMBER OF SECONDS 
Delay(#SECONDS * 1000) 

;STOP RECORDING 
result = *pDSCB.IDirectSoundCaptureBuffer\Stop() 
If result <> #DS_OK 
  Error_Msg("Can't stop : " + Str(Result)) 
Else 
  PrintN("Recording stopped")
EndIf 

;LOCK THE BUFFER BEFORE SAVING 
result = *pDSCB.IDirectSoundCaptureBuffer\Lock(0, my_wfe\format\nAvgBytesPerSec * #SECONDS, @*bufptr, @bufsize, 0, 0, #DSCBLOCK_ENTIREBUFFER) 
If result <> #DS_OK 
  Error_Msg("Can't lock : " + Str(Result)) 
EndIf 

;SAVE BUFFER CONTENTS AS A WAV FILE 
PrintN("Saving")
File_Save() 

;UNLOCK THE BUFFER 
result = *pDSCB.IDirectSoundCaptureBuffer\Unlock(*bufptr, bufsize, 0, 0) 
If result <> #DS_OK 
  Error_Msg("Can't unlock : " + Str(Result)) 
EndIf 

;PrintN("Normal program termination")
CloseConsole()

End

Re: DirectSound Recording with WAVE FORMAT EXTENSIBLE

Posted: Sat Oct 02, 2010 11:19 pm
by idle
thanks

Re: DirectSound Recording with WAVE FORMAT EXTENSIBLE

Posted: Sun Oct 03, 2010 4:52 pm
by utopiomania
Thanks from mee too :) But how do you set the recording level from PB to avoid clipping and noise?

Most recording programs I have seen, and use, have a level meter, or a way to automatically determine the recording level before recording?

Re: DirectSound Recording with WAVE FORMAT EXTENSIBLE

Posted: Sun Jan 09, 2011 5:27 pm
by infratec
Hi Chris,

since I tested your code, I found that the FileSave() procedure is not correct.
It fails if the #CHANNELS are higher than 2.

Here is a working code

Code: Select all

Procedure File_Save()
;SAVE BUFFER AS A WAV FILE
;CAUTION: THERE MAY BE SOME BUGS IN THIS PART

If CreateFile(1, "DirectRecording.wav") = 0
  MessageRequester("Error", "Unable to create file.", #MB_ICONERROR)
  End
  ProcedureReturn
EndIf

subchunk1size.l = SizeOf(WAVEFORMATEX)
If #CHANNELS <= 2  
  If subchunk1size > 16 : subchunk1size = 16 : EndIf
  cbSize = 0
Else
  cbSize = my_wfe\Format\cbSize
EndIf

subchunk2size.l = bufsize
chunksize = 4 + (8 + subchunk1size) + (8 + subchunk2size) + cbSize


;WRITE WAV HEADER
WriteString(1, "RIFF") ; 4 bytes
WriteLong(1, chunksize) ; 4 bytes
WriteString(1, "WAVE") ; 4 bytes
WriteString(1, "fmt ") ; 4 bytes
WriteLong(1, subchunk1size + cbSize) ; 4 bytes
WriteData(1, my_WFE, subchunk1size)
If #CHANNELS > 2
  WriteWord(1, my_wfe\format\wBitsPerSample)
  WriteLong(1, my_wfe\dwChannelMask)
  WriteData(1, my_wfe\SubFormat, SizeOf(GUID))
EndIf
;END OF WAVEFORMATEX STRUCTURE

WriteString(1, "data", #PB_Ascii) ; 4 bytes
WriteLong(1, subchunk2size) ; 4 bytes
;END OF FILE HEADER

;WRITE AUDIO DATA AFTER WAV HEADER
WriteData(1, *bufptr, bufsize)
CloseFile(1)

EndProcedure
I hope it is not to.... late.

Bernd

P.S.: Changed it a bit, that the max. 2 channel version is compatible to the first version of wav files.