Capturing Audio [COMPLETE SOURCE]

Share your advanced PureBasic knowledge/code with the community.
User avatar
Flype
Addict
Addict
Posts: 1542
Joined: Tue Jul 22, 2003 5:02 pm
Location: In a long distant galaxy

Post by Flype »

chris319 wrote:Capturing audio works fine under Windows XP but does not work under Vista using either PB 4.03 or PB 4.10 Beta 3. It is based on code found here (see Flype's post dated Oct. 18, 2006):

http://www.purebasic.fr/english/viewtop ... c&start=15

(NOTE: In PB 4.10 it is no longer necessary to include the WAVEFORMATEX structure.)

Microsoft has done some weird and wacky things to the audio in Vista.
freak wrote:Why should this be our problem ?

Please talk to MS about problems in their operatingsystems.
This section is for bugreports about PB, not the Windows API.
chris319 wrote:If PB doesn't keep up with changes to the OS, be it Windows, Linux or OS X, it will eventually become useless. If PB is to keep up with other changes to Windows I don't see why audio capture should be an exception.
ts-soft wrote:This is no PB only source, the source uses API from the OS. This is not the
problem of the PB TEAM.
I also agree,
to make it works on Vista, the code might be modified.
that's not in anyway a purebasic incompatibility.
it's the programmer task to 'convert' his code to Vista if he decided to use API programming.
So i decided to move here this discussion.


I don't own Vista, so can't test - any Vista pros here :?:

But it might be a Vista 'path' access problem.

this snippet write the raw sound in 'c:\unnamed.raw'

the application is maybe not allowed to write in c:\
No programming language is perfect. There is not even a single best language.
There are only languages well suited or perhaps poorly suited for particular purposes. Herbert Mayer
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Post by chris319 »

I have extensively modified the code posted here so that PB allocates a large RAM buffer and copies the audio data there. The audio is not saved to disk until the user explicitly chooses to do so, at which time the program creates a wav header and writes it all to disk. So I don't think the problem is related to C: drive write permission.

This might be a clue:

http://forums.microsoft.com/MSDN/ShowPo ... 5&SiteID=1

I'd be surprised if Microsoft let this problem linger for so long, but then this is Microsoft we're dealing with.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Post by chris319 »

OK, I finally got the audio recorder to work under Vista, but in order to do so I had to use flype's code posted on Oct 18, 2006. For some reason that code works under Vista but the original code, modified by me and which works under XP, does not. Even flype's code had a bug preventing it from recording which is now fixed. Now I am modifying flype's code (extensively) with the emphasis on metering (level display) and on glitch-free recording (no dropped samples). Also, the metering code has been rewritten to be much less CPU intensive. I can make the code available when I'm a little further along. I really like flype's drop-down selection of audio devices.

Would anyone be terribly offended if 8-bit sampling were eliminated? I can't imagine anyone actually considering it "audio".

BTW, this program is currently being developed on a MacBook running Vista under Boot Camp and PB 4.10 Beta 3.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Post by chris319 »

Below is a bug-fixed version of flype's audio recorder. You can find my fixes by searching for "chris319". The fixes consist of:

4/5/2009:
- Fixed procedure DRAW_Wave16S() to draw stereo properly.
- Removed deprecated CreateGadgetList() for 4.30.
- Set default buffer size to 2048.
- Other fixes in wave-drawing procedures to not overrun audio buffer.

- Structure member Config\size was not being saved before calling waveInAddBuffer and thus always had a value of zero.

- Structure member Config\recorded was being set to zero prematurely; moved to procedure FILE_Create().

- Incorrect calculation of blockalign (saving to wav file) fixed. Third-party programs no longer complain when opening files created by this program.

- Procedure raw2wav() now obtains audio buffer size from variable Config\recorded.

The oscilloscope display is VERY CPU intensive. Pops and clicks could be heard in recordings, presumably due to dropped samples caused by CPU activity.

Code: Select all

;/ 
;/ Object     Audio Recorder 1.0 (a) 
;/ 
;/ Date       August 2004 
;/ Author     Philippe Carpentier 
;/ Contact    flype@altern.org 
;/ Info       MS Windows only - winmm.lib - mmsystem.h 
;/ 
;  Bug fixes by chris319 on September 2, 2007
; 
;- REC GLOBAL 
; 
Global QuitRec.l 
; 
;- REC CONSTANTES 
; 
#MinWidth      = 320 
#MinHeight     = 240 
; 
;- REC INITIALISATION 
; 
Enumeration 0 ; #CHANNEL 
  #CHANNEL_LEFT 
  #CHANNEL_RIGHT 
EndEnumeration 
Enumeration 0 ; #GADGET 
  #gadFrame1 
  #gadFrame2 
  #gadStart 
  #gadStop 
  #gadSndVol 
  #gadFile 
  #gadChannel 
  #gadResolution 
  #gadFrequence 
  #gadDevice 
EndEnumeration 
Enumeration 0 ; #WINDOW 
  #Window 
EndEnumeration 
Enumeration 0 ; #TOOLBAR 
  #ToolBar 
EndEnumeration 
Enumeration 0 ; #STATUSBAR 
  #StatusBar 
EndEnumeration 

;Structure WAVEFORMATEX -- NOT NEEDED IN PB 4.10 -- chris319
;  wFormatTag.w 
;  nChannels.w 
;  nSamplesPerSec.l 
;  nAvgBytesPerSec.l 
;  nBlockAlign.w 
;  wBitsPerSample.w 
;  cbSize.w 
;EndStructure 
Structure SCOPE 
  channel.b 
  left.l 
  top.l 
  width.l 
  height.l 
  middleY.l 
  quarterY.l 
EndStructure 
Structure CONFIG 
  
  hWindow.l           ; Window handle 
  hToolBar.l          ; ToolBar handle 
  hfont.l             ; Font handle 
  
  Date.l              ; Start date 
  size.l              ; Wave buffer size 
  buffer.l            ; Wave buffer pointer 
  output.l            ; WindowOutput() 
  wave.l              ; Address of waveform-audio input device 
  recorded.l          ; Number of bytes recorded 
  recording.b         ; Record is running... 
  
  File.s              ; Recording FileName (c:\unnamed.raw) 
  FileId.l            ; Recording FileId (#PB_Any) 
  SndVol.s            ; Microsoft Volume Control (sndvol32.exe) 

  format.WAVEFORMATEX ; Capturing WaveFormatEx 
  lBuf.l              ; Capturing Buffer size 
  nBuf.l              ; Capturing Buffer number 
  nDev.l              ; Capturing Device identifier 
  nBit.l              ; Capturing Resolution (8/16) 
  nHertz.l            ; Capturing Frequency  (Hertz) 
  nChannel.l          ; Capturing Channels number (Mono/Stereo) 
  
  LScope.SCOPE        ; Wave form display 
  RScope.SCOPE        ; Wave form display 
  ScopeTimer.l        ; Scope Timer (redraw every n times) 
  cback.l             ; Back color 
  cline.l             ; Line color 
  cwave.l             ; Wave color 
  ColRecWave.l        ; Scope Record Wave Color 
  ColRecBack.l        ; Scope Record Back Color 
  ColRecLine.l        ; Scope Record Line Color 
  ColCapWave.l        ; Scope Capture Wave Color 
  ColCapBack.l        ; Scope Capture Back Color 
  ColCapLine.l        ; Scope Capture Line Color 
  
EndStructure 

Global Config.CONFIG 
Global Dim inHdr.WAVEHDR(16) 

Config\hfont             = LoadFont(0,"Arial",7) 
Config\format\cbSize     = 0 
Config\format\wFormatTag = #WAVE_FORMAT_PCM 
Config\LScope\channel    = #CHANNEL_LEFT 
Config\RScope\channel    = #CHANNEL_RIGHT 
; 
;- REC DECLARATIONS 
; 
Declare Clamp(value.l,min.l,max.l) 

Declare CONFIG_Load() 
Declare CONFIG_Save() 

Declare CAPTURE_Stop() 
Declare CAPTURE_Start() 
Declare CAPTURE_Error(err.l) 
Declare CAPTURE_GetDevices(gadId.l) 
Declare CAPTURE_Read(hWaveIn.l,lpWaveHdr.l) 

Declare DRAW_Scope() 
Declare DRAW_Wave(*scope.SCOPE) 
Declare DRAW_Wave8M(*scope.SCOPE) 
Declare DRAW_Wave8S(*scope.SCOPE) 
Declare DRAW_Wave16M(*scope.SCOPE) 
Declare DRAW_Wave16S(*scope.SCOPE) 
Declare DRAW_LCD(*scope.SCOPE,lcd$) 
Declare DRAW_Background(*scope.SCOPE) 

Declare FILE_Close() 
Declare FILE_Append() 
Declare FILE_Create() 
Declare FILE_Select() 
Declare FILE_Recording(state.b) 
Declare FILE_raw2wav(File.s) 

Declare GUI_Init() 
Declare GUI_Resize() 
Declare GUI_TBCombo(Id,x,y,w,h) 
Declare GUI_CallBack(hWnd.l,Msg.l,wParam.l,lParam.l) 

; 
;- REC PROCEDURES 
; 
; 
Procedure Clamp(value.l,min.l,max.l) 
  If value<min : ProcedureReturn min : EndIf 
  If value>max : ProcedureReturn max : EndIf  
  ProcedureReturn value 
EndProcedure 
; 
Procedure CONFIG_Load() 
  
  OpenPreferences("Recorder.ini") 
  
  PreferenceGroup("Files") 
  Config\File   = ReadPreferenceString("FileName","c:\unnamed.raw") 
  Config\SndVol = ReadPreferenceString("SndVol","sndvol32") 
  
  PreferenceGroup("Capture") 
  Config\nDev     = ReadPreferenceLong("Device",0) 
  Config\nChannel = ReadPreferenceLong("Channel",1) 
  Config\nHertz   = ReadPreferenceLong("Frequency",7) 
  Config\nBit     = ReadPreferenceLong("Resolution",1) 
  Config\nBuf     = ReadPreferenceLong("BufferNum",8) 
  Config\lBuf     = ReadPreferenceLong("BufferLen", 2048) ;chris319
  
  PreferenceGroup("Scope") 
  Config\ScopeTimer   = ReadPreferenceLong("ScopeTimer",25) 
  Config\ColRecLine   = ReadPreferenceLong("ColRecLine",  $000035) 
  Config\ColRecWave   = ReadPreferenceLong("ColRecWave",  $1010E0) 
  Config\ColRecBack   = ReadPreferenceLong("ColRecBack",  $000050) 
  Config\ColCapLine   = ReadPreferenceLong("ColCapLine",  $004000) 
  Config\ColCapWave   = ReadPreferenceLong("ColCapWave",  $20E020) 
  Config\ColCapBack   = ReadPreferenceLong("ColCapBack",  $004900) 
  
  PreferenceGroup("Window") 
  pos.WINDOWPLACEMENT 
  pos\length=SizeOf(WINDOWPLACEMENT) 
  pos\ptMinPosition\x         = ReadPreferenceLong("MinPosX",0) 
  pos\ptMinPosition\y         = ReadPreferenceLong("MinPoxY",0) 
  pos\ptMaxPosition\x         = ReadPreferenceLong("MaxPosX",-1) 
  pos\ptMaxPosition\y         = ReadPreferenceLong("MaxPosY",-1) 
  pos\rcNormalPosition\left   = ReadPreferenceLong("PosX1",100) 
  pos\rcNormalPosition\top    = ReadPreferenceLong("PosY1",100) 
  pos\rcNormalPosition\right  = ReadPreferenceLong("PosX2",#MinWidth) 
  pos\rcNormalPosition\bottom = ReadPreferenceLong("PosY2",#MinHeight) 
  
  ClosePreferences() 
  
  Config\cback   = Config\ColCapBack 
  Config\cline   = Config\ColCapLine 
  Config\cwave   = Config\ColCapWave 
  
  StatusBarText(0,2,Config\File) 
  SetGadgetState(#gadDevice,Config\nDev) 
  SetGadgetState(#gadChannel,Config\nChannel) 
  SetGadgetState(#gadFrequence,Config\nHertz) 
  SetGadgetState(#gadResolution,Config\nBit) 
  SetWindowPlacement_(Config\hWindow,@pos) 
  ShowWindow_(Config\hWindow,#True) 
  
EndProcedure 
; 
Procedure CONFIG_Save() 
  
  pos.WINDOWPLACEMENT 
  pos\length = SizeOf(WINDOWPLACEMENT) 
  GetWindowPlacement_(Config\hWindow,@pos) 
  
  If CreatePreferences("Recorder.ini") 
    
    PreferenceGroup("Files") 
    WritePreferenceString("FileName",Config\File) 
    WritePreferenceString("SndVol",Config\SndVol) 
    
    PreferenceGroup("Capture") 
    WritePreferenceLong("Device",Config\nDev) 
    WritePreferenceLong("Channel",Config\nChannel) 
    WritePreferenceLong("Frequency",Config\nHertz) 
    WritePreferenceLong("Resolution",Config\nBit) 
    WritePreferenceLong("BufferNum",Config\nBuf) 
    WritePreferenceLong("BufferLen",Config\lBuf) 
    
    PreferenceGroup("Scope") 
    WritePreferenceLong("ScopeTimer",Config\ScopeTimer) 
    WritePreferenceLong("ColRecLine",Config\ColRecLine) 
    WritePreferenceLong("ColRecWave",Config\ColRecWave) 
    WritePreferenceLong("ColRecBack",Config\ColRecBack) 
    WritePreferenceLong("ColCapLine",Config\ColCapLine) 
    WritePreferenceLong("ColCapWave",Config\ColCapWave) 
    WritePreferenceLong("ColCapBack",Config\ColCapBack) 
    
    PreferenceGroup("Window") 
    WritePreferenceLong("MinPosX",pos\ptMinPosition\x) 
    WritePreferenceLong("MinPoxY",pos\ptMinPosition\y) 
    WritePreferenceLong("MaxPosX",pos\ptMaxPosition\x) 
    WritePreferenceLong("MaxPosY",pos\ptMaxPosition\y) 
    WritePreferenceLong("PosX1",pos\rcNormalPosition\left) 
    WritePreferenceLong("PosY1",pos\rcNormalPosition\top) 
    WritePreferenceLong("PosX2",pos\rcNormalPosition\right) 
    WritePreferenceLong("PosY2",pos\rcNormalPosition\bottom) 
    
    ClosePreferences() 
    ProcedureReturn #True 
    
  EndIf 
  
EndProcedure 
; 
Procedure CAPTURE_Error(err.l) 

  If err 
    text.s=Space(#MAXERRORLENGTH) 
    waveInGetErrorText_(err,text,#MAXERRORLENGTH) 
    MessageRequester("Error",text,#MB_ICONERROR) 
    CAPTURE_Stop() 
    End 
  EndIf 

EndProcedure 
; 
Procedure CAPTURE_Start() 
  
  CAPTURE_Stop() 
  
  Config\nDev = GetGadgetState(#gadDevice) 
  
  Config\nChannel = GetGadgetState(#gadChannel) 
  Select Config\nChannel 
    Case 0 : Config\format\nChannels = 1 
    Case 1 : Config\format\nChannels = 2 
  EndSelect 
  
  Config\nBit = GetGadgetState(#gadResolution) 
  Select Config\nBit 
    Case 0 : Config\format\wBitsPerSample = 8 
    Case 1 : Config\format\wBitsPerSample = 16 
  EndSelect 
  
  Config\nHertz = GetGadgetState(#gadFrequence) 
  Select Config\nHertz 
    Case 0 : Config\format\nSamplesPerSec =  8000 
    Case 1 : Config\format\nSamplesPerSec = 11025 
    Case 2 : Config\format\nSamplesPerSec = 12000 
    Case 3 : Config\format\nSamplesPerSec = 16000 
    Case 4 : Config\format\nSamplesPerSec = 22050 
    Case 5 : Config\format\nSamplesPerSec = 24000 
    Case 6 : Config\format\nSamplesPerSec = 32000 
    Case 7 : Config\format\nSamplesPerSec = 44100 
    Case 8 : Config\format\nSamplesPerSec = 48000 
  EndSelect 
  
  Config\format\nBlockAlign     = (Config\format\nChannels*Config\format\wBitsPerSample)/8 
  Config\format\nAvgBytesPerSec = Config\format\nSamplesPerSec*Config\format\nBlockAlign 
  
  If #MMSYSERR_NOERROR = waveInOpen_(@Config\wave,#WAVE_MAPPER+Config\nDev,@Config\format,Config\hWindow,#Null,#CALLBACK_WINDOW|#WAVE_FORMAT_DIRECT) 
    
    For i=0 To Config\nBuf-1 
      inHdr(i)\lpData=AllocateMemory(Config\lBuf) 
      inHdr(i)\dwBufferLength=Config\lBuf 
      waveInPrepareHeader_(Config\wave,inHdr(i),SizeOf(WAVEHDR)) 
      waveInAddBuffer_(Config\wave,inHdr(i),SizeOf(WAVEHDR)) 
    Next 
    
    If #MMSYSERR_NOERROR = waveInStart_(Config\wave) 
      SetTimer_(Config\hWindow,0,Config\ScopeTimer,0) 
    EndIf 
    
  EndIf 
  
EndProcedure 
; 
Procedure CAPTURE_Stop() 

  If Config\wave 
    waveInReset_(Config\wave) 
    waveInStop_(Config\wave) 
    For i=0 To Config\nBuf-1 
      If inHdr(i) 
        waveInUnprepareHeader_(Config\wave,inHdr(i),SizeOf(WAVEHDR)) 
      EndIf 
    Next 
    waveInClose_(Config\wave) 
  EndIf 
  
  KillTimer_(Config\hWindow,0) 
  
EndProcedure 
; 
Procedure CAPTURE_Read(hWaveIn.l,lpWaveHdr.l) 
;  waveInAddBuffer_(hWaveIn,lpWaveHdr,SizeOf(WAVEHDR)) 

  *hWave.WAVEHDR=lpWaveHdr 
  Config\buffer=*hWave\lpData 
  Config\size=*hWave\dwBytesRecorded 
;  dwBytesRecorded MUST BE SAVED BEFORE CALLING waveInAddBuffer -- chris319  
  waveInAddBuffer_(hWaveIn,lpWaveHdr,SizeOf(WAVEHDR)) 

  FILE_Append() 
  
EndProcedure 
; 
Procedure CAPTURE_GetDevices(gadId.l) 
  MMNumDevice.l = waveInGetNumDevs_() 
  If MMNumDevice 
    For MMDeviceId=#WAVE_MAPPER To MMNumDevice-1 
      MMResult.l = waveInGetDevCaps_(MMDeviceId,@Caps.WAVEINCAPS,SizeOf(WAVEINCAPS)) 
      If MMResult = #MMSYSERR_NOERROR 
        AddGadgetItem(gadId,-1,PeekS(@Caps\szPname,#MAXPNAMELEN)) 
      EndIf 
    Next 
  EndIf 
EndProcedure 
; 
Procedure DRAW_Scope() 
  If Config\buffer 
    StartDrawing(Config\output) 
      DRAW_Background(Config\LScope) 
      DRAW_Background(Config\RScope) 
      DRAW_Wave(Config\LScope) 
      If Config\nChannel 
        DRAW_Wave(Config\RScope) 
      EndIf 
    StopDrawing() 
  EndIf 
EndProcedure 
; 
Procedure DRAW_Background(*scope.SCOPE) 
  
  Box (*scope\left,*scope\top,*scope\width,*scope\height,Config\cback) 
  Line(*scope\left,*scope\middleY-*scope\quarterY,*scope\width,0,Config\cline) 
  Line(*scope\left,*scope\middleY+*scope\quarterY,*scope\width,0,Config\cline) 
  
EndProcedure 
; 
Procedure DRAW_LCD(*scope.SCOPE,lcd$) 
  
  DrawingMode(1) 
  FrontColor($00FFFF) 
  DrawingFont(Config\hfont) 
  Select *scope\channel 
    Case #CHANNEL_LEFT  : DrawText(*scope\left+1, *scope\top, "[L] "+lcd$) 
    Case #CHANNEL_RIGHT : DrawText(*scope\left+1, *scope\top, "[R] "+lcd$) 
  EndSelect 
  DrawingMode(0) 
  
EndProcedure 
; 
Procedure DRAW_Wave(*scope.SCOPE) 
  Select Config\nBit 
    Case 0 
      DRAW_LCD(*scope.SCOPE,"8 bits") 
      Select Config\nChannel 
        Case 0 : DRAW_Wave8M(*scope.SCOPE) 
        Case 1 : DRAW_Wave8S(*scope.SCOPE) 
      EndSelect 
    Case 1 
      DRAW_LCD(*scope.SCOPE,"16 bits") 
      Select Config\nChannel 
        Case 0 : DRAW_Wave16M(*scope.SCOPE) 
        Case 1 : DRAW_Wave16S(*scope.SCOPE) 
      EndSelect 
  EndSelect 
EndProcedure 
; 
Procedure DRAW_Wave8M(*scope.SCOPE) 
  
  oldx.l=*scope\left 
  For i = 0 To Config\size - 1 ;chris319
    value.b=PeekB(Config\buffer+i)+$7F 
    xx=Clamp(*scope\left+(i**scope\width)/(Config\size+1),*scope\left,*scope\width) 
    yy=(value**scope\height)/$FF 
    LineXY(oldx,*scope\middleY+oldy,xx,*scope\middleY+yy,Config\cwave) 
    oldx=xx:oldy=yy 
  Next 
  
EndProcedure 
; 
Procedure DRAW_Wave8S(*scope.SCOPE) 
  
  oldx.l=*scope\left 
  For i = 0 To (Config\size - 1) Step 2 ;chris319
    value.b=PeekB(Config\buffer+i+*scope\channel*2)+$7F 
    xx=Clamp(*scope\left+(i**scope\width)/(Config\size+1),*scope\left,*scope\width) 
    yy=(value**scope\height)/$FF 
    LineXY(oldx,*scope\middleY+oldy,xx,*scope\middleY+yy,Config\cwave) 
    oldx=xx:oldy=yy 
  Next 
  
EndProcedure 
; 
Procedure DRAW_Wave16M(*scope.SCOPE) 
  
  oldx.l=*scope\left 
  For i=0 To (Config\size -2) Step 2 ;chris319
    value.w=PeekW(Config\buffer+i) 
    xx=Clamp(*scope\left+(i**scope\width)/(Config\size+1),*scope\left,*scope\width) 
    yy=(value**scope\height)/$FFFF 
    LineXY(oldx,*scope\middleY+oldy,xx,*scope\middleY+yy,Config\cwave) 
    oldx=xx:oldy=yy 
  Next 
  
EndProcedure 
; 
Procedure DRAW_Wave16S(*scope.SCOPE) ;chris319

Select *scope\channel

Case 0 ;LEFT CHANNEL
  oldx.l = *scope\left
  For i = 0 To (Config\size - 2) Step 4
    value.w = PeekW(Config\buffer + i)
    xx = Clamp(*scope\left+(i**scope\width)/(Config\size+1),*scope\left,*scope\width)
;    yy = (value * *scope\height)/$FFFF
    yy = (value * *scope\height) / $FFF
    ayy = Abs(yy)
    If ayy > vmax: vmax = ayy: EndIf
    LineXY(oldx,*scope\middleY+oldy,xx,*scope\middleY+yy,Config\cwave)
    oldx = xx: oldy = yy
  Next

Case 1 ;RIGHT CHANNEL
  oldx.l = *scope\left
  For i = 2 To (Config\size - 2) Step 4
    value.w = PeekW(Config\buffer + i)
    xx=Clamp(*scope\left+(i * *scope\width)/(Config\size+1),*scope\left,*scope\width)
;    yy = (value * *scope\height)/$FFFF
    yy = (value * *scope\height) / $FFF
    ayy = Abs(yy)
    If ayy > vmax: vmax = ayy: EndIf
    LineXY(oldx,*scope\middleY + oldy, xx, *scope\middleY + yy, Config\cwave)
    oldx = xx: oldy = yy
  Next

EndSelect

EndProcedure 
; 
Procedure FILE_Create() 
  
  Config\recorded  = #Null ; -- MOVED HERE BY chris319
    
  If Config\File 
    Config\FileId = CreateFile(#PB_Any,Config\File) 
    If Config\FileId 
      DisableToolBarButton(#ToolBar, #gadStart,#True) 
      DisableToolBarButton(#ToolBar, #gadFile,#True) 
      DisableGadget(#gadChannel,#True) 
      DisableGadget(#gadFrequence,#True) 
      DisableGadget(#gadResolution,#True) 
      DisableGadget(#gadDevice,#True) 
      Config\Date = GetTickCount_() 
      FILE_Recording(#True) 
    Else 
      MessageRequester("Error","Can't create file",#MB_ICONERROR) 
    EndIf 
  EndIf 
  
EndProcedure 
; 
Procedure FILE_Append() 
  
  If Config\recording = #True 
    Config\recorded + Config\size 
    WriteData(Config\FileId, Config\buffer,Config\size) 
    StatusBarText(#StatusBar, 0,Str(Config\recorded)+" bytes",#PB_StatusBar_Center) 
    StatusBarText(#StatusBar, 1,StrF((GetTickCount_()-Config\Date)/1000,3)+" secs",#PB_StatusBar_Center) 
  EndIf 
  
EndProcedure 
; 
Procedure FILE_Recording(state.b) 
  
  If state 
    Config\cback   = Config\ColRecBack 
    Config\cline   = Config\ColRecLine 
    Config\cwave   = Config\ColRecWave 
  Else 
    Config\cback   = Config\ColCapBack 
    Config\cline   = Config\ColCapLine 
    Config\cwave   = Config\ColCapWave 
  EndIf 
  
  Config\recording = state 
;  Config\recorded  = #Null -- THIS LINE OF CODE MOVED TO FILE_create-- chris319

EndProcedure 
; 
Procedure FILE_Close() 

  If Config\recording 
    Config\Date = #Null 
    FILE_Recording(#False) 
    CloseFile(Config\FileId) 
    Delay(1000) 
    FILE_raw2wav(Config\File)    
    DisableToolBarButton(#ToolBar, #gadStart,#False) 
    DisableToolBarButton(#ToolBar, #gadFile,#False) 
    DisableGadget(#gadChannel,#False) 
    DisableGadget(#gadFrequence,#False) 
    DisableGadget(#gadResolution,#False) 
    DisableGadget(#gadDevice,#False) 
  EndIf 

EndProcedure 
; 
Procedure FILE_Select() 
  
  File.s = SaveFileRequester("Select a file",Config\File,"Raw Sound|(*.raw)",0) 
  
  If File 
    Config\File = File 
  EndIf 
  
  StatusBarText(0,2,Config\File) 
  
EndProcedure 
; 
Procedure FILE_raw2wav(File.s) 

  inId.l = ReadFile(#PB_Any,File) 
  
  If inId = #Null 
    MessageRequester("Error", "Unable to open file",#MB_ICONERROR) ; chris319
    ProcedureReturn #False 
  EndIf 
  
  lBuf.l = Lof(inId) 
  pBuf.l = AllocateMemory(lBuf) 
  If pBuf = #Null
    MessageRequester("Error", "Unable to allocate buffer",#MB_ICONERROR) ; chris319
    ProcedureReturn #False 
  EndIf 
  
  ReadData(inId, pBuf,lBuf) 
  CloseFile(inId) 
  
  subchunk2size.l = Config\recorded
  chunksize.l = 36 + subchunk2size
  
  f$ = GetFilePart(File) 
  x$ = GetPathPart(File)+Left(f$,Len(f$)-Len(GetExtensionPart(File))-1)+".wav" 
  
  outId.l = CreateFile(#PB_Any,x$)  
  If outId = #Null 
    MessageRequester("Error", "Unable to create file",#MB_ICONERROR) ; chris319
    ProcedureReturn #False 
  EndIf 
  
  b.w = Config\format\wBitsPerSample 
  c.w = Config\format\nChannels 
  h.l = Config\format\nSamplesPerSec 
 
  WriteString(outId, "RIFF") ; 4 bytes 

;  WriteLong(outId, lBuf+44) ; 4 bytes 
  WriteLong(outId, chunksize) ; 4 bytes -- chris319

  WriteString(outId, "WAVEfmt ") ; 8 bytes -- chris319
    ;WriteString(outId, "fmt ") 
  
  ;WriteLong(outId, 16)
  WriteLong(outId, b) ; 4 bytes -- chris319
  
   
  WriteWord(outId, 1) ; 2 bytes -- PCM 
  WriteWord(outId, c) ; 2 bytes
  WriteLong(outId, h) ; 4 bytes
  WriteLong(outId, c*h*(b/8)) ; 4 bytes
  
  ;WriteWord(outId, c*(h/8))
  WriteWord(outId, (c * b) / 8) ; -- blockalign 2 bytes -- chris319
   
  WriteWord(outId, b) ; 2 bytes
  WriteString(outId, "data") ; 4 bytes

  WriteLong(outId, lBuf) ; -- 4 bytes
  WriteData(outId, pBuf,lBuf) 
  
  CloseFile(outId) 
  
  ProcedureReturn #True 
  
EndProcedure 
; 
Procedure GUI_Init() 
  QuitRec = 0 
  WndId.l = OpenWindow(0,0,0,500,400,"Recorder",#PB_Window_ScreenCentered|#PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_Invisible) 
  If WndId=#Null : ProcedureReturn #False : EndIf 
  
  Config\hWindow=WindowID(0) 
  Config\output=WindowOutput(0) 
  
  Config\hToolBar=CreateToolBar(#ToolBar,Config\hWindow) 
  If Config\hToolBar=#Null : ProcedureReturn #False : EndIf 
  
  If CreateStatusBar(#StatusBar,Config\hWindow)=#Null 
    ProcedureReturn #False 
  EndIf 
  
  ToolBarStandardButton(#gadStart,#PB_ToolBarIcon_Open) 
  ToolBarStandardButton(#gadStop,#PB_ToolBarIcon_Delete) 
  
  ToolBarStandardButton(#gadFile,#PB_ToolBarIcon_Save) 
  ToolBarSeparator() 
  ToolBarStandardButton(#gadSndVol,#PB_ToolBarIcon_Properties) 
  ToolBarSeparator() 
  
  ToolBarToolTip(#ToolBar, #gadStart,"Record") 
  ToolBarToolTip(#ToolBar, #gadStop,"Stop") 
  ToolBarToolTip(#ToolBar, #gadFile,"Create a file") 
  ToolBarToolTip(#ToolBar, #gadSndVol,"Volume") 
  
  GUI_TBCombo(#gadChannel,0,1,58,200) 
  AddGadgetItem(#gadChannel,-1,"Mono") 
  AddGadgetItem(#gadChannel,-1,"Stereo") 
  SetGadgetState(#gadChannel,0) 
  
  GUI_TBCombo(#gadResolution,2,1,55,200) 
  AddGadgetItem(#gadResolution,-1,"8 bits") 
  AddGadgetItem(#gadResolution,-1,"16 bits") 
  
  GUI_TBCombo(#gadFrequence,2,1,80,200) 
  AddGadgetItem(#gadFrequence,-1,"8.0 kHz") 
  AddGadgetItem(#gadFrequence,-1,"11.025 kHz") 
  AddGadgetItem(#gadFrequence,-1,"12.0 kHz") 
  AddGadgetItem(#gadFrequence,-1,"16.0 kHz") 
  AddGadgetItem(#gadFrequence,-1,"22.05 kHz") 
  AddGadgetItem(#gadFrequence,-1,"24.0 kHz") 
  AddGadgetItem(#gadFrequence,-1,"32.0 kHz") 
  AddGadgetItem(#gadFrequence,-1,"44.1 kHz") 
  AddGadgetItem(#gadFrequence,-1,"48.0 kHz") 
  
  GUI_TBCombo(#gadDevice,2,1,160,200) 
  CAPTURE_GetDevices(#gadDevice) 
  SetGadgetState(#gadDevice,0) 
  
  Frame3DGadget(#gadFrame1,0,0,0,0,"",#PB_Frame3D_Double) 
  Frame3DGadget(#gadFrame2,0,0,0,0,"",#PB_Frame3D_Double) 
  
  AddStatusBarField( 100) 
  AddStatusBarField(  80) 
  AddStatusBarField(1000) 
  StatusBarText(0,0,"0 "+"bytes",#PB_StatusBar_Center) 
  StatusBarText(0,1,"0.000 "+"secs",#PB_StatusBar_Center) 
  
  SetWindowCallback(@GUI_CallBack()) 
  FILE_Recording(#False) 
  GUI_Resize() 
  
  ProcedureReturn #True 
  
EndProcedure 
; 
Procedure GUI_Resize() 
  
  WndW.l = ( WindowWidth(0)  -  4 ) 
  WndH.l = ( WindowHeight(0) - 60 ) >> 1 

  Config\LScope\left     = 2 
  Config\LScope\top      = 30 
  Config\LScope\width    = WndW 
  Config\LScope\height   = WndH 
  Config\LScope\quarterY = Config\LScope\height >> 2 
  Config\LScope\middleY  = Config\LScope\top + Config\LScope\height >> 1 
  ResizeGadget(#gadFrame1,Config\LScope\left-2,Config\LScope\top-2,Config\LScope\width+4,Config\LScope\height+4) 
  
  Config\RScope\left     = 2 
  Config\RScope\top      = Config\LScope\top + Config\LScope\height + 6 
  Config\RScope\width    = WndW 
  Config\RScope\height   = WndH 
  Config\RScope\quarterY = Config\RScope\height >> 2 
  Config\RScope\middleY  = Config\RScope\top + Config\RScope\height >> 1 
  ResizeGadget(#gadFrame2,Config\RScope\left-2,Config\RScope\top-2,Config\RScope\width+4,Config\RScope\height+4) 
  
EndProcedure 
; 
Procedure GUI_TBCombo(Id,x,y,w,h) 
  
  pos=SendMessage_(Config\hToolBar,#TB_BUTTONCOUNT,0,0) 
  ToolBarSeparator()        
  SendMessage_(Config\hToolBar,#TB_GETBUTTON,pos,@separator.TBBUTTON) 
  separator\iBitmap=x+w 
  SendMessage_(Config\hToolBar,#TB_DELETEBUTTON,pos,0) 
  SendMessage_(Config\hToolBar,#TB_INSERTBUTTON,pos,separator) 
  SendMessage_(Config\hToolBar,#TB_GETITEMRECT,pos,@rc.RECT)    
  UseGadgetList(Config\hToolBar) 
  ComboBoxGadget(Id,x+rc\left,y,w,h) 
  UseGadgetList(Config\hWindow) 
  
EndProcedure 
; 
Procedure GUI_CallBack(hWnd.l,Msg.l,wParam.l,lParam.l) 
  
  Result.l=#PB_ProcessPureBasicEvents 
  
  Select Msg 
    Case #WM_KEYDOWN 
      If GetAsyncKeyState_(#VK_ESCAPE) 
        QuitRec = 1 
      EndIf 
    Case #WM_SIZE     : GUI_Resize() 
    Case #WM_TIMER    : DRAW_Scope() 
    Case #MM_WIM_DATA : CAPTURE_Read(wParam,lParam) 
    Case #WM_COMMAND 
      Select wParam & $FFFF 
        Case #gadStart      : FILE_Create() 
        Case #gadStop       : FILE_Close() 
        Case #gadFile       : FILE_Select() 
        Case #gadSndVol     : RunProgram(Config\SndVol,"","") 
        Case #gadFrequence  : If EventType()=9 : CAPTURE_Start() : EndIf 
        Case #gadResolution : If EventType()=9 : CAPTURE_Start() : EndIf 
        Case #gadChannel    : If EventType()=9 : CAPTURE_Start() : EndIf 
        Case #gadDevice     : If EventType()=9 : CAPTURE_Start() : EndIf 
      EndSelect 
    Case #WM_GETMINMAXINFO 
      *mmi.MINMAXINFO=lParam 
      *mmi\ptMinTrackSize\x=#MinWidth 
      *mmi\ptMinTrackSize\y=#MinHeight 
  EndSelect 
  
  ProcedureReturn Result 
  
EndProcedure 
; 
;- REC MAIN 
; 
Procedure StartRecorder() 
  
  If GUI_Init() 
    CONFIG_Load() 
    CAPTURE_Start() 
    Repeat 
    Until WaitWindowEvent()=#WM_CLOSE Or QuitRec 
    CAPTURE_Stop() 
    CONFIG_Save() 
  EndIf 
  
EndProcedure 

StartRecorder()
Last edited by chris319 on Sun Apr 05, 2009 7:29 pm, edited 1 time in total.
User avatar
Flype
Addict
Addict
Posts: 1542
Joined: Tue Jul 22, 2003 5:02 pm
Location: In a long distant galaxy

Post by Flype »

so cool,

i will have a look at your changes :)

thank you chris319
No programming language is perfect. There is not even a single best language.
There are only languages well suited or perhaps poorly suited for particular purposes. Herbert Mayer
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Post by chris319 »

Flype -

You made a lot of changes since the first version.

Do you want to work on a DirectSound recorder/meter? Something that would go up to 24 bits/96000 Hz? This low-level interfacing to Windows confuses me.

I want to look into getting PB to support the new MMCSS in Vista. MMCSS has a category called "Pro Audio" which gives higher priority to real-time functions such as audio recording.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Post by chris319 »

You want to change a line in GUI_CallBack. "Case #WM_SIZE" should be "Case #WM_PAINT".
dna
User
User
Posts: 83
Joined: Wed Aug 30, 2006 12:07 am

Post by dna »

I get the error line 187:windowID():Incorrect Number of parameters

** Edit **

Now I get the error Line 84: Structure Not Found: WaveFormatEx

** Edit **

Now I'm getting Line 111:Structure Field Not Found:Format
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Post by chris319 »

Ah, should've mentioned that this was tested on PB 4.10 beta 3. Sorry for the inconvenience. The WAVEFORMATEX structure is still in there, only commented out.

That's the correct number of parameters for WindowID().
dna
User
User
Posts: 83
Joined: Wed Aug 30, 2006 12:07 am

Post by dna »

I see. I'll have to download that then.

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

Post by chris319 »

I'm not sure this program will work very well under Vista. After some testing over the weekend, the record level control seems to behave quite erratically and this directly affects metering. There is this from Larry Osterman's blog:
For capture, clipping happens whenever the volume control at the ADC (analog-digital converter) is set too high. That means that the only volume control that actually matters for capture is the master volume. The entire concept of per-application volume doesn't work for capture.

Needless to say, this was a bit embarrassing. Inside the audio engine, capture and render are essentially identical - the only difference between the two is the order in which the audio graph is built, so my internal mind-set treated them the same. I'd been so focused on rendering scenarios that I simply didn't think about how capture was basically different from render.
This tells me that Osterman/Microsoft were designing the new Vista audio stack around games and watching movies. Audio recording/capture were clearly an afterthought.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Post by chris319 »

The outlook for this program under Vista may not be as dire as first thought. There is a workaround for the erratic behavior of Vista audio.

Go to Control Panel -> Sound -> Recording and select one of the devices you plan to use. Click on the "Enhancements" tab and check the "Disable All Enhacements" box. Unless this box is explicitly checked, the volume control will behave erratically.
User avatar
electrochrisso
Addict
Addict
Posts: 989
Joined: Mon May 14, 2007 2:13 am
Location: Darling River

Post by electrochrisso »

I like your changes to this code chris319.
Works a treat, Thanks...
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

Or use OpenAL 1.1 which supports recording, and if a native OpenAL driver is available for your card then have fun, not entirely sure what happens if no native driver is available though, it should fall back to DirectSound or WaveIn in that case and no idea if the enhancements affect the recording in that case (when through OpenAL).
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Post by chris319 »

Flype is the real brains behind this program. I just make it look pretty :-)

OpenAL does not support 24-bit recording. If I were to change the audio platform it would have to be something which supports 24-bit recording. Also, come to learn that WMM does not recognize S/PDIF input. For that you need ASIO. You can see why I'm so confused :?:
Post Reply