Capturing Audio [COMPLETE SOURCE]

Share your advanced PureBasic knowledge/code with the community.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Capturing Audio [COMPLETE SOURCE]

Post by chris319 »

Sometimes you need a low-tech solution.

It's not perfect but it's improved. Changed the management of user settings (bit depth, sampling frequency and channels), hard coded buffer parameters, improved the window appearance. dropped the unimplemented volume control icon and the "LCD" display to give the CPU and graphics engine a break.

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
    ;  04/02/2010 : DrGolf for PB 4.50
    ;  01/20/2011 : Vitor_Boss® -- Fixed clamping
    ;  6/8/2012 -- revised again by chris319 on PB 4.61

    ;- REC GLOBAL
    ;
Global QuitRec.l
    ;
;- REC CONSTANTES
    ;
#MinWidth      = 320
#MinHeight     = 240

#MONO = 1
#STEREO = 2


;- REC INITIALISATION

Enumeration 0 ; #CHANNEL
  #CHANNEL_LEFT
  #CHANNEL_RIGHT
EndEnumeration

Enumeration 0 ; #GADGET
  #gadFrame1
  #gadFrame2
  #gadStart
  #gadStop
  #gadSndVol
  #gadFile
  #gadChannel
  #gadResolution
  #gadFrequence
  #gadDevice
  #gadText1
  #gadText2
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.snd) ;chris319 6/7/12
      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) ;chris319 6/7/12
;      nHertz.l            ; Capturing Frequency  (Hertz) ;chris319 6/7/12
;      nChannel.l          ; Capturing Channels number (Mono/Stereo) ;chris319 6/7/12
     
      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$) ;chris319 6/7/12
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.snd")
      Config\SndVol = ReadPreferenceString("SndVol","sndvol32")
     
      PreferenceGroup("Capture")
      Config\nDev     = ReadPreferenceLong("Device",0)
      Config\format\nChannels = ReadPreferenceLong("Channel", 1)
      Config\format\nSamplesPerSec   = ReadPreferenceLong("Frequency",44100)
      Config\format\wBitsPerSample = ReadPreferenceLong("Resolution",16)
      Config\nBuf = 8 ;ReadPreferenceLong("BufferNum",8) ;chris319 6/7/12
      Config\lBuf = 2048 ;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\format\nChannels - 1)

;      SetGadgetState(#gadFrequence,Config\format\nSamplesPerSec)
Select Config\format\nSamplesPerSec ;chris319 6/7/12
  Case  8000: SetGadgetState(#gadFrequence, 0)
  Case 11025: SetGadgetState(#gadFrequence, 1)
  Case 12000: SetGadgetState(#gadFrequence, 2)
  Case 16000: SetGadgetState(#gadFrequence, 3)
  Case 22050: SetGadgetState(#gadFrequence, 4)
  Case 24000: SetGadgetState(#gadFrequence, 5)
  Case 32000: SetGadgetState(#gadFrequence, 6)
  Case 44100: SetGadgetState(#gadFrequence, 7)
  Case 48000: SetGadgetState(#gadFrequence, 8)
EndSelect

;      SetGadgetState(#gadResolution,Config\format\wBitsPerSample)
Select Config\format\wBitsPerSample
  Case 8: SetGadgetState(#gadResolution, 0)
  Case 16: SetGadgetState(#gadResolution, 1)
EndSelect

      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\format\nChannels)
  WritePreferenceLong("Frequency",Config\format\nSamplesPerSec)
  WritePreferenceLong("Resolution",Config\format\wBitsPerSample)
 
  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()

;GET USER SETTINGS     
      Config\nDev = GetGadgetState(#gadDevice)
     
;      Config\format\nChannels = ;chris319 6/7/12 
      state = GetGadgetState(#gadChannel)
      Select state
        Case 0 : Config\format\nChannels = #MONO
        Case 1 : Config\format\nChannels = #STEREO
      EndSelect
      gui_Resize() ;chris319 6/7/12

;      Config\format\wBitsPerSample ;chris319 6/7/12
       state = GetGadgetState(#gadResolution)
       Select state
         Case 0 : Config\format\wBitsPerSample = 8
         Case 1 : Config\format\wBitsPerSample = 16
       EndSelect
    
;      Config\format\nSamplesPerSec ;chris319 6/7/12
      state = GetGadgetState(#gadFrequence)
      Select state
        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
  Config\format\cbSize = 0
    
      If #MMSYSERR_NOERROR = waveInOpen_(@Config\wave,#WAVE_MAPPER+Config\nDev,@Config\format,Config\hWindow,#Null,#CALLBACK_WINDOW|#WAVE_FORMAT_DIRECT)
        For i = 0 To 7 ;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_Background(*scope.SCOPE) chris319 6/7/12
;      
;       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_Scope()
      If Config\buffer

        StartDrawing(WindowOutput(0)) ; 04/02/2010  DrGolf
         ;StartDrawing(Config\output)

;          DRAW_Background(Config\LScope)
;          DRAW_Background(Config\RScope)
  *scope.SCOPE = Config\LScope
  Box (*scope\left, *scope\top, *scope\width, *scope\height, Config\cback)

          DRAW_Wave(Config\LScope)

          If Config\format\nChannels = 2
  *scope.SCOPE = Config\RScope
  Box (*scope\left, *scope\top, *scope\width, *scope\height, Config\cback)
            DRAW_Wave(Config\RScope)
          EndIf

        StopDrawing()

      EndIf
    EndProcedure

;     Procedure DRAW_LCD(*scope.SCOPE,lcd$) -- GIVE THE CPU A BREAK. THERE ARE BETTER WAYS TO DO THIS      
;       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\format\wBitsPerSample

        Case 8
          ;DRAW_LCD(*scope.SCOPE,"8 bits") ;chris319 6/7/12
          Select Config\format\nChannels
            Case #MONO : DRAW_Wave8M(*scope.SCOPE)
            Case #STEREO : DRAW_Wave8S(*scope.SCOPE)
          EndSelect

        Case 16
          ;DRAW_LCD(*scope.SCOPE,"16 bits") ;chris319 6//7/12
          Select Config\format\nChannels
            Case #MONO : DRAW_Wave16M(*scope.SCOPE)
            Case #STEREO : DRAW_Wave16S(*scope.SCOPE)
          EndSelect

      EndSelect
    EndProcedure
    ;
    Procedure DRAW_Wave8M(*scope.SCOPE)
      Protected Max_Y = (Config\RScope\height)/2
      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
        If yy > Max_Y : yy = Max_Y : EndIf
        If yy < -Max_Y : yy = -Max_Y : EndIf
        LineXY(oldx,*scope\middleY+oldy,xx,*scope\middleY+yy, Config\cwave)
        oldx=xx:oldy=yy
      Next
     
    EndProcedure
    ;
    Procedure DRAW_Wave8S(*scope.SCOPE)
      Protected Max_Y = (Config\RScope\height)/2
      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
        If yy > Max_Y : yy = Max_Y : EndIf
        If yy < -Max_Y : yy = -Max_Y : EndIf
        LineXY(oldx,*scope\middleY+oldy,xx,*scope\middleY+yy,Config\cwave)
        oldx=xx:oldy=yy
      Next
     
    EndProcedure
    ;
Procedure DRAW_Wave16M(*scope.SCOPE) ;16 BITS MONO
Config\cwave = $00ffff
  Protected Max_Y = (Config\RScope\height)/2

      oldx.l = *scope\left
      For i = 0 To (Config\size - 2) Step 2
        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
        If yy > Max_Y : yy = Max_Y : EndIf
        If yy < -Max_Y : yy = -Max_Y : EndIf
        LineXY(oldx,*scope\middleY+oldy,xx,*scope\middleY+yy,Config\cwave)
        oldx = xx: oldy = yy
      Next

  LineXY(oldx, *scope\middleY + oldy, xx, *scope\middleY + yy, Config\cwave)

oldx = xx: oldy = yy
 
EndProcedure
    ;
Procedure DRAW_Wave16S(*scope.SCOPE) ;chris319 -- 16 BITS STEREO
Config\cwave = $ddddff
      Protected Max_Y = (Config\RScope\height) / 2
    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
        If yy > Max_Y : yy = Max_Y : EndIf
        If yy < -Max_Y : yy = -Max_Y : 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
        If yy > Max_Y : yy = Max_Y : EndIf
        If yy < -Max_Y : yy = -Max_Y : 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,1)+" 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,"snd|(*.snd)",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)

      FreeMemory(pBuf) ;chris319
    
      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
        MessageRequester("Error", "Unable to open window.") ;chris319 -- 6/8/12
        End
 :    EndIf

      LoadFont(0, "Arial", 24)
      TextGadget(#gadText1, 0, 150, 24, 40, "L")
      TextGadget(#gadText2, 0, 430, 24, 40, "R")
      SetGadgetFont(#gadText1, FontID(0))
      SetGadgetFont(#gadText2, FontID(0))

      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) ;START RECORDING
      ToolBarStandardButton(#gadStop,#PB_ToolBarIcon_Delete) ;STOP RECORDING
      ToolBarStandardButton(#gadFile,#PB_ToolBarIcon_Save) ;SAVE FILE
      ToolBarSeparator()
;      ToolBarStandardButton(#gadSndVol,#PB_ToolBarIcon_Properties) ;VOLUME CONTROL -- NOT IMPLEMENTED
      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,20) ; 04/02/2010 DrGolf
      AddGadgetItem(#gadChannel,-1,"Mono")
      AddGadgetItem(#gadChannel,-1,"Stereo")
      SetGadgetState(#gadChannel,0)
     
      GUI_TBCombo(#gadResolution,1,1,55,20) ; 04/02/2010 DrGolf
      AddGadgetItem(#gadResolution,-1,"8 bits")
      AddGadgetItem(#gadResolution,-1,"16 bits")
      SetGadgetState(#gadResolution, 1)
     
      GUI_TBCombo(#gadFrequence,2,1,80,20) ; 04/02/2010 DrGolf
      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")
      SetGadgetState(#gadFrequence, 7) ;chris319 6/7/12
     
      GUI_TBCombo(#gadDevice,2,1,160,20) ; 04/02/2010 DrGolf
      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.0 "+"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     = 28
      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     = 28
      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): ;DRAW_Scope()

        Case #WM_COMMAND

          Select wParam & $FFFF
            Case #gadStart      : FILE_Create()
            Case #gadStop       : FILE_Close()
            Case #gadFile       : FILE_Select()
            ;Case #gadSndVol     : RunProgram(Config\SndVol,"","") chris319 6/7/12

            Case #gadFrequence  : CAPTURE_Start() ;chris319 6/7/12

;            Case #gadResolution : If EventType() = 6 : CAPTURE_Start() : EndIf
            Case #gadResolution : CAPTURE_Start() ;chris319 6/7/12

;            Case #gadChannel    :Debug EventType(): If EventType() = 6 : Debug "event 6" : CAPTURE_Start() : EndIf
            Case #gadChannel    :CAPTURE_Start() ;chris319 6/7/12

;            Case #gadDevice     : If EventType() = 6 : CAPTURE_Start() : EndIf
            Case #gadDevice     : CAPTURE_Start() ;chris319 6/7/12

          EndSelect

        Case #WM_GETMINMAXINFO
          *mmi.MINMAXINFO=lParam
          *mmi\ptMinTrackSize\x=#MinWidth
          *mmi\ptMinTrackSize\y=#MinHeight
      EndSelect
     
      ProcedureReturn Result
     
EndProcedure
    ;
    ;- REC MAIN

;START RECORDER chris319 6/7/12
;    Procedure StartRecorder()
      If GUI_Init()

        CONFIG_Load()

        CAPTURE_Start()

        Repeat
        Until WaitWindowEvent() = #WM_CLOSE Or QuitRec
        CAPTURE_Stop()
        CONFIG_Save()
      Else
        MessageRequester("Error", "Unable to initialize GUI.") ;chris319 6/7/12
        End
      EndIf
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Capturing Audio [COMPLETE SOURCE]

Post by chris319 »

Here is a major rewrite of this program. It puts the audio capture and level meter graphics in their own threads. Disk-writing and meter-drawing activities thus cannot block the audio stream. It also replaces the squiggly lines of the scope with a level meter. The old "scope" display was extremely graphics intensive as it drew a line for every incoming sample. This version uses a more conventional level meter which requires only two calls to Box() per buffer per channel (it sweeps each buffer to find the peak level). I have also hard coded some parameters which do not need to be under user control.

Source code revised! Removed 8-bit support.

Code: Select all

;/ Object     MME Audio Recorder 1.0 (a)
;/
;/ Date       August 2004
;/ Author     Philippe Carpentier
;/ Contact    flype@altern.org
;/ Info       MS Windows only - MME, winmm.lib - mmsystem.h

;  Bug fixes by chris319 on September 2, 2007
;  04/02/2010 : DrGolf for PB 4.50
;  01/20/2011 : Vitor_Boss® -- Fixed clamping
;  06/10/2012 : Multi-threaded version by chris319 on PB 4.61

;- REC CONSTANTES
    ;
#MinWidth      = 640
#MinHeight     = 480

#MONO = 1
#STEREO = 2

;- REC INITIALISATION

Enumeration 0 ; #CHANNEL
  #CHANNEL_LEFT
  #CHANNEL_RIGHT
EndEnumeration

Enumeration 0 ; #GADGET
  #gadFrame1
  #gadFrame2
  #gadStart
  #gadStop
  #gadSndVol
  #gadFile
  #gadChannel
  #gadFrequence
  #gadDevice
  #gadText1
  #gadText2
EndEnumeration

Enumeration 0 ; #WINDOW
  #Window
EndEnumeration

Enumeration 0 ; #TOOLBAR
  #ToolBar
EndEnumeration

Enumeration 0 ; #STATUS BAR
  #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.snd) ;chris319 6/7/12
      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
     
      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
Global Config.CONFIG, threadID1, threadID2
;*hWave.WAVEHDR
Global Dim inHdr.WAVEHDR(16), QuitRec.l, audioSemaphore, meterSemaphore, wpa, lpa, *meterBuffer, *audioBuffer
   ;
    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 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 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 CONFIG_Load()
     
      OpenPreferences("Recorder.ini")
     
      PreferenceGroup("Files")
      Config\File   = ReadPreferenceString("FileName","c:\unnamed.snd")
      Config\SndVol = ReadPreferenceString("SndVol","sndvol32")
     
      PreferenceGroup("Capture")
      Config\nDev     = ReadPreferenceLong("Device",0)
      Config\format\nChannels = ReadPreferenceLong("Channel", 1)
      Config\format\nSamplesPerSec   = ReadPreferenceLong("Frequency",44100)
      Config\format\wBitsPerSample = 16
      Config\nBuf = 8 ;ReadPreferenceLong("BufferNum",8) ;chris319 6/7/12
      Config\lBuf = 8192 ;ReadPreferenceLong("BufferLen", 2048) ;chris319

      PreferenceGroup("Scope")
      Config\ScopeTimer   = 20
      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\format\nChannels - 1)

;      SetGadgetState(#gadFrequence,Config\format\nSamplesPerSec)
Select Config\format\nSamplesPerSec ;chris319 6/7/12
  Case  8000: SetGadgetState(#gadFrequence, 0)
  Case 11025: SetGadgetState(#gadFrequence, 1)
  Case 12000: SetGadgetState(#gadFrequence, 2)
  Case 16000: SetGadgetState(#gadFrequence, 3)
  Case 22050: SetGadgetState(#gadFrequence, 4)
  Case 24000: SetGadgetState(#gadFrequence, 5)
  Case 32000: SetGadgetState(#gadFrequence, 6)
  Case 44100: SetGadgetState(#gadFrequence, 7)
  Case 48000: SetGadgetState(#gadFrequence, 8)
EndSelect

;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\format\nChannels)
  WritePreferenceLong("Frequency",Config\format\nSamplesPerSec)
 
  PreferenceGroup("Scope")
  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()

;GET USER SETTINGS     
      Config\nDev = GetGadgetState(#gadDevice)
     
;      Config\format\nChannels = ;chris319 6/7/12 
      state = GetGadgetState(#gadChannel)
      Select state
        Case 0 : Config\format\nChannels = #MONO
        Case 1 : Config\format\nChannels = #STEREO
      EndSelect
      gui_Resize() ;chris319 6/7/12

;      Config\format\nSamplesPerSec ;chris319 6/7/12
      state = GetGadgetState(#gadFrequence)
      Select state
        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
  Config\format\cbSize = 0
    
      If #MMSYSERR_NOERROR = waveInOpen_(@Config\wave,#WAVE_MAPPER+Config\nDev,@Config\format,Config\hWindow,#Null,#CALLBACK_WINDOW|#WAVE_FORMAT_DIRECT)
        For i = 0 To 7
          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

;  waveInStart_(Config\wave)

        If #MMSYSERR_NOERROR = waveInStart_(Config\wave)
          SetTimer_(Config\hWindow,0,Config\ScopeTimer,0)
        EndIf

      EndIf

If IsThread(threadID1) <> 0
  ResumeThread(threadID1)
EndIf
If IsThread(threadID2) <> 0
  ResumeThread(threadID2)
EndIf

EndProcedure
    ;
Procedure CAPTURE_Stop()
If IsThread(threadID1) <> 0
  PauseThread(threadID1)
EndIf
If IsThread(threadID2) <> 0
  PauseThread(threadID2)
EndIf

      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)
*hWave.WAVEHDR = lpWaveHdr
Config\buffer = *hWave\lpData
Config\size = *hWave\dwBytesRecorded
;dwBytesRecorded MUST BE SAVED BEFORE CALLING waveInAddBuffer -- chris319 

If Config\buffer <> 0
  CopyMemory(Config\buffer, *meterBuffer, Config\size)
  CopyMemory(Config\buffer, *audioBuffer, Config\size)
EndIf

waveInAddBuffer_(hWaveIn, *hWave.WAVEHDR, 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_Meter()
 If Config\buffer
 
 StartDrawing(WindowOutput(0))
 
 If Config\format\nChannels = 1
    incre = 2
    leftLimit = Config\lBuf - 2 ;2046
 Else
    incre = 4
    leftLimit = Config\lBuf - 4 ;2044
 EndIf
; 
 *scope.SCOPE = Config\LScope
 max = 0
 
 i = 0
 Repeat
    value.w = PeekW(*meterBuffer + i)
     If value < 0: value = -value: EndIf
     If value > max: max = value: EndIf
     i + incre
 Until i > leftLimit ;THIS IS CORRECT

maxDefl.f = *scope\width * (max / 32767)
Box(*scope\left, *scope\top, maxDefl, *scope\height, $ffff00)
Box(maxDefl + *scope\left, *scope\top, *scope\width, *scope\height, 0)

 If Config\format\nChannels = 2
*scope.SCOPE = Config\RScope
 max = 0
; 
 i = 2
 Repeat
  value.w = PeekW(*meterBuffer + i)
   If value < 0: value = -value: EndIf
   If value > max: max = value: EndIf
   i + incre
 Until i > Config\lBuf - 2 ;THIS IS CORRECT
; 
maxDefl.f = *scope\width * (max / 32767)
Box(*scope\left, *scope\top, maxDefl, *scope\height, $8888ff)
Box(maxDefl + *scope\left, *scope\top, *scope\width, *scope\height, 0)
; 
 EndIf
StopDrawing()
 EndIf

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(#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)
        WriteData(Config\FileId, *audioBuffer, Config\size)

;        StatusBarText(#StatusBar, 0,Str(Config\recorded) + " bytes",#PB_StatusBar_Center)
;        StatusBarText(#StatusBar, 1,StrF((GetTickCount_() - Config\Date)/1000,1)+" 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(#gadDevice,#False)
      EndIf

    EndProcedure
    ;
Procedure FILE_Select()
     
      File.s = SaveFileRequester("Select a file",Config\File,"snd|(*.snd)",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)

      FreeMemory(pBuf) ;chris319
    
      ProcedureReturn #True
     
    EndProcedure
    ;
Procedure GUI_Init()
      QuitRec = 0
      WndId.l = OpenWindow(0,100,100,800,600,"Recorder",#PB_Window_ScreenCentered|#PB_Window_SystemMenu|#PB_Window_SizeGadget)
      If WndId=#Null
        MessageRequester("Error", "Unable to open window.") ;chris319 -- 6/8/12
        End
 :    EndIf

      LoadFont(0, "Arial", 24)
      TextGadget(#gadText1, 0, 150, 24, 40, "L")
      TextGadget(#gadText2, 0, 430, 24, 40, "R")
      SetGadgetFont(#gadText1, FontID(0))
      SetGadgetFont(#gadText2, FontID(0))

      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) ;START RECORDING
      ToolBarStandardButton(#gadStop,#PB_ToolBarIcon_Delete) ;STOP RECORDING
      ToolBarStandardButton(#gadFile,#PB_ToolBarIcon_Save) ;SAVE FILE
      ToolBarSeparator()
;      ToolBarStandardButton(#gadSndVol,#PB_ToolBarIcon_Properties) ;VOLUME CONTROL -- NOT IMPLEMENTED
      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,20) ; 04/02/2010 DrGolf
      AddGadgetItem(#gadChannel,-1,"Mono")
      AddGadgetItem(#gadChannel,-1,"Stereo")
      SetGadgetState(#gadChannel,0)

      GUI_TBCombo(#gadFrequence,2,1,80,20) ; 04/02/2010 DrGolf
      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")
      SetGadgetState(#gadFrequence, 7) ;chris319 6/7/12
     
      GUI_TBCombo(#gadDevice,2,1,160,20) ; 04/02/2010 DrGolf
      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.0 "+"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     = 28
      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     = 28
      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 AudioLoop(null)
Repeat
  WaitSemaphore(audioSemaphore)
  CAPTURE_Read(wpa, lpa)
ForEver
EndProcedure

Procedure MeterLoop(null)
Repeat
  WaitSemaphore(meterSemaphore)
  DRAW_Meter()
ForEver
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: SignalSemaphore(meterSemaphore) ;DRAW_Meter()

        Case #MM_WIM_DATA: wpa = wParam: lpa = lParam: SignalSemaphore(audioSemaphore)

        Case #WM_COMMAND
          Select wParam & $FFFF
            Case #gadStart      : FILE_Create()
            Case #gadStop       : FILE_Close()
            Case #gadFile       : FILE_Select()
            Case #gadFrequence  : CAPTURE_Start() ;chris319 6/7/12
            Case #gadChannel    :CAPTURE_Start() ;chris319 6/7/12
            Case #gadDevice     : CAPTURE_Start() ;chris319 6/7/12
          EndSelect

        Case #WM_GETMINMAXINFO
          *mmi.MINMAXINFO=lParam
          *mmi\ptMinTrackSize\x=#MinWidth
          *mmi\ptMinTrackSize\y=#MinHeight
      EndSelect
     
  ProcedureReturn Result
EndProcedure

;- REC MAIN

If GUI_Init()

CONFIG_Load()

CAPTURE_Start()

*meterBuffer = AllocateMemory(32768)
*audioBuffer = AllocateMemory(32768)
audioSemaphore = CreateSemaphore(0)
meterSemaphore = CreateSemaphore(0)
threadID1 = CreateThread(@AudioLoop(), #Null) ;THREAD TO MONITOR CALLBACK FLAG
threadID2 = CreateThread(@MeterLoop(), #Null) ;THREAD TO MONITOR METER FLAG

Repeat: Until WaitWindowEvent() = #WM_CLOSE Or QuitRec

CAPTURE_Stop()
If IsThread(threadID1): KillThread(threadID1): EndIf
If IsThread(threadid2): KillThread(threadID2): EndIf
If IsWindow(0) <> 0: CloseWindow(0): EndIf
If *meterBuffer <> 0: FreeMemory(*meterBuffer): EndIf
If *audioBuffer <> 0: FreeMemory(*audioBuffer): EndIf
CONFIG_Save()
End
Else
  MessageRequester("Error", "Unable to initialize GUI.") ;chris319 6/7/12
  End
EndIf
Last edited by chris319 on Sun Jun 10, 2012 7:53 pm, edited 3 times in total.
User avatar
VB6_to_PBx
Enthusiast
Enthusiast
Posts: 627
Joined: Mon May 09, 2011 9:36 am

Re: Capturing Audio [COMPLETE SOURCE]

Post by VB6_to_PBx »

chris319 ,

your latest version has a Bug,
the program locks up if you click on the Menu choices too many times.

tested on Windows 7 64-bit

------------


your previous version works fine on my Computer.
 
PureBasic .... making tiny electrons do what you want !

"With every mistake we must surely be learning" - George Harrison
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Capturing Audio [COMPLETE SOURCE]

Post by chris319 »

VB6_to_PBx wrote:chris319 ,

your latest version has a Bug,
the program locks up if you click on the Menu choices too many times.
Are you clicking in rapid succession?

Changing modes on the fly requires pausing the threads and restarting them (it calls Capture_Stop() and Capture_Start()). It's not ideal but it's how the original program was written. Maybe there's a workaround for this, maybe by disabling the gadgets until the audio capture resumes.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Capturing Audio [COMPLETE SOURCE]

Post by chris319 »

Well, you know what I forgot to do? I disabled 8-bit support but forgot to remove the bit-depth gadget. If you tried to select between 8 and 16 bits it would probably have crashed. The source has now been updated.

If you really must record 8-bit audio, use one of the previous versions.
User avatar
VB6_to_PBx
Enthusiast
Enthusiast
Posts: 627
Joined: Mon May 09, 2011 9:36 am

Re: Capturing Audio [COMPLETE SOURCE]

Post by VB6_to_PBx »

Are you clicking in rapid succession?
If you tried to select between 8 and 16 bits it would probably have crashed.
i accidently clicked on the wrong Menu choice
so i quickly clicked on the correct Menu choice, thats when it locked up.

i did click in rapid succession that time , and probably on 8 or 16 bit choice as well .

Thanks for your great Code !
 
PureBasic .... making tiny electrons do what you want !

"With every mistake we must surely be learning" - George Harrison
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: Capturing Audio [COMPLETE SOURCE]

Post by chris319 »

See if you have the same problem with the new code.
User avatar
ar-s
Enthusiast
Enthusiast
Posts: 344
Joined: Sat Oct 06, 2007 11:20 pm
Location: France

Re: Capturing Audio [COMPLETE SOURCE]

Post by ar-s »

Thank you for this last code.
Could you tel me how to enable / disable the Line in device by a simple button ?

Thanks for helping.
~Ar-S~
My Image Hoster for PB users
My webSite (french) with PB apps : LDVMULTIMEDIA
PB - 3.x / 5.7x / 6 - W11 x64 - Ryzen 7 3700x / #Rpi4

Code: Select all

r3p347 : 7ry : un71l d0n3 = 1
Post Reply