I borrowed some of the code from that post, (mostly the API stuff that I didn't know how to do).
Thanks to everyone that contributed to Flype's audio recorder.
I have included several features in PureAudioRecorder that you may find useful.
With PureAudioRecorder you can record audio from your computer's audio line in jack.
You can record mono or stereo and choose from several different sample rates.
All audio is recorded with 16 bit resolution.
PureAudioRecorder provides signal level meters for right and left channels to monitor the recording levels.
The levels must be adjusted using your computer's audio mixer control or adjust the signal externally at line in.
The level meters do not function during playback.
Recorded audio can be played back and saved to disk as a wave file.
I have included a pause button that may be used for either recording or playback.
A track bar gadget is provided to change the playback position.
It is possible to load other wave files you may have on your drive and play them.
edit 4/12/2017:
Here is a download link to the latest: Version 1.30
edit 7/5/2015:
download link to older: Version 1.28
code for the original version below.
Code: Select all
; Title: PureAudioRecorder.pb
; Author: BasicallyPure
; date: 5/15/2013
; PureBasic 5.11
; OS: Windows
; License: Free
EnableExplicit
Define version.f = 1.0
CompilerIf Not #PB_Compiler_Thread
MessageRequester("WARNING!", "Set compiler options: 'Create threadsafe executable'")
End
CompilerEndIf
;- constants
;{
#MainWin = 0
#Win_Color = $F0C9CD
#GUI_WIDTH = 510
#GUI_HEIGHT = 180
#StatusBar = 0
#ToolBar = 0
#Stat_Color_Def = $E1EDEE
#Stat_Color_Pla = $B4EDAF
#Stat_Color_Rec = $8A6BFF
#MONO = 1
#STEREO = 2
; images
Enumeration
#Img_Record
#img_Play
#Img_Stop
#Img_Pause
#img_Erase
EndEnumeration
; tools
Enumeration
#Tool_Load
#Tool_Save
#Tool_Record
#Tool_Play
#Tool_Stop
#Tool_Pause
#Tool_Erase
EndEnumeration
; gadgets
Enumeration
#Combo_Channel
#Combo_Frequency
#Combo_Device
#Canvas_L
#Canvas_R
#Text_L
#Text_R
#Playback_Pos
EndEnumeration
; timers
Enumeration 1
#PlaybackTimer
#MeterTimer
EndEnumeration
; Constants used to configure tool buttons for various recorder conditions.
; bit order MSB to LSB:
; Device, Frequency, Channel, Erase, Pause, Stop, Play, Record, Save, Open
; 1=enabled, 0=disabled
#TB_State_1 = %1110000101 ; initial (erased)
#TB_State_2 = %0000110000 ; recording
#TB_State_3 = %0001001010 ; stop
#TB_State_4 = %0000110000 ; playback
;}
;- procedure declarations
;{
Declare INIT_GUI()
Declare GUI_CALLBACK(hWnd.l, Msg.l, wParam.l, lParam.l)
Declare DRAW_CUSTOM_BUTTONS()
Declare TB_COMBO(Id, x, y, w, h)
Declare RECORD_START()
Declare PAUSE()
Declare STOP()
Declare WRITE_HEADER(outID)
Declare LOAD_AUDIO()
Declare SAVE_AUDIO()
Declare PLAY_AUDIO()
Declare POSITION_PLAYBACK()
Declare ERASE_AUDIO()
Declare ASK(querry.s)
Declare CONFIG_BUTTONS(pattern)
Declare LOAD_CONFIG()
Declare SAVE_CONFIG()
Declare GET_CAPTURE_DEVICES(gadId.l)
Declare INIT_CAPTURE()
Declare CAPTURE_STOP()
Declare PLAYBACK_UPDATE()
Declare AUDIO_LOOP(null)
Declare METER_LOOP(null)
Declare VERIFY(result, text.s)
;}
;- structures
;{
Structure meter
h.i ; height
w.i ; width
x.i ; x location
y.i[2] ; y location
EndStructure
Structure CONFIG
format.WAVEFORMATEX ; Capturing WaveFormatEx
Begin.l ; Start time reference
size.l ; Wave buffer size
buffer.l ; Wave buffer pointer
wave.l ; Address of waveform-audio input device
recorded.l ; Number of bytes recorded
recording.b ; Record is runing...
tempFile.s ; File path and name for temporary audio data
SavePath.s ; Path for saving recorded audio files
FileId.l ; Recording FileId (#PB_Any)
lBuf.l ; Capturing Buffer size
nBuf.l ; Capturing Buffer number
nDev.l ; Capturing Device identifier
MeterTimer.l ; Meter Timer (mS)
PlayBack.i ; Sound number of playback sound
EndStructure
;}
;- global variables
Global mtr.meter
Global Config.CONFIG
Config\format\cbSize = 0
Config\format\wFormatTag = #WAVE_FORMAT_PCM
Config\nBuf = 8
Config\tempFile = GetTemporaryDirectory() + "tempAudio.snd"
Config\recording = #False
Global winTitle.s = "Pure Audio Recorder v. " + StrF(version,2)
Global Dim inHdr.WAVEHDR(Config\nBuf - 1)
Global hWindow ; window handle
Global hToolBar ; toolbar handle
Global hStatus ; status bar handle
Global threadID1, threadID2, audioSemaphore, meterSemaphore, wpa, lpa, pause, saved
Global *audioBuffer = AllocateMemory(32768)
Global m$ = "Recorded audio has not been saved." + #CRLF$
Global playback_nSPS ; number of samples per second for playback
VERIFY(InitSound(), "InitSound()")
INIT_GUI()
LOAD_CONFIG()
INIT_CAPTURE()
audioSemaphore = CreateSemaphore(0)
meterSemaphore = CreateSemaphore(0)
threadID1 = CreateThread(@AUDIO_LOOP(), #Null) ; THREAD TO HANDLE CALLBACK
threadID2 = CreateThread(@METER_LOOP(), #Null) ; THREAD TO HANDLE METER
;- EVENT LOOP
; -----------------------------------------------------------------------------------
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
If config\recording = #True : STOP() : EndIf
If saved = #False
If ASK(m$ + "Quit anyway?") = #True : Break
EndIf
Else : Break : EndIf
Case #PB_Event_Gadget
Select EventGadget()
Case #Combo_Channel : INIT_CAPTURE()
Case #Combo_Frequency : INIT_CAPTURE()
Case #Combo_Device : INIT_CAPTURE()
Case #Playback_Pos : POSITION_PLAYBACK()
EndSelect
Case #PB_Event_Menu
Select EventMenu()
Case #Tool_Load : LOAD_AUDIO()
Case #Tool_Save : SAVE_AUDIO()
Case #Tool_Record : RECORD_START()
Case #Tool_Play : PLAY_AUDIO()
Case #Tool_Stop : STOP()
Case #Tool_Pause : PAUSE()
Case #Tool_Erase : ERASE_AUDIO()
EndSelect
Case #PB_Event_Timer
Select EventTimer()
Case #PlaybackTimer : PLAYBACK_UPDATE()
Case #MeterTimer : SignalSemaphore(meterSemaphore)
EndSelect
EndSelect
ForEver
; -----------------------------------------------------------------------------------
CAPTURE_STOP()
If IsThread(threadID1) : KillThread(threadID1) : EndIf
If IsThread(threadID2) : KillThread(threadID2) : EndIf
If *audioBuffer <> 0 : FreeMemory(*audioBuffer) : EndIf
DeleteFile(Config\tempFile)
SAVE_CONFIG()
End ; -------------------------------------------------------------------------------
;- MACROS
Macro SHOW_SIZE()
StatusBarText(#StatusBar, 0,StrF(Config\recorded / 1048576, 2) + " MB",#PB_StatusBar_Center)
EndMacro
Macro SHOW_TIME(SBF, mSEC)
; SBF = status bar field number
StatusBarText(#StatusBar, SBF, Str(mSEC/60000) + " : " +
RSet(Str(Second(mSEC/1000)), 2, "0"), #PB_StatusBar_Center)
EndMacro
Macro SHOW_PATH(text)
StatusBarText(#StatusBar, 2,text)
EndMacro
;- PROCEDURES -
Procedure INIT_GUI()
Protected null.w, Flags = #PB_Window_ScreenCentered | #PB_Window_SystemMenu | #PB_Window_MinimizeGadget
hWindow = VERIFY(OpenWindow(#MainWin, 0, 0, #GUI_WIDTH, #GUI_HEIGHT, winTitle, Flags), "OpenWindow")
SetWindowColor(#MainWin, #Win_Color)
StickyWindow(#MainWin, 1)
hStatus = VERIFY(CreateStatusBar(#StatusBar, WindowID(#MainWin)), "CreateStatusBar")
AddStatusBarField( 100)
AddStatusBarField( 80)
AddStatusBarField(1000)
StatusBarText(#StatusBar, 0, "", #PB_StatusBar_Center)
StatusBarText(#StatusBar, 1, "", #PB_StatusBar_Center)
; remove any XP theme from StatusBar
If OSVersion() >= #PB_OS_Windows_XP
SetWindowTheme_(hStatus, @null, @null)
EndIf
SendMessage_(hStatus, #CCM_SETBKCOLOR, 0, #Stat_Color_Def) ; set StatusBar bkgnd color
hToolBar = VERIFY(CreateToolBar(#ToolBar,WindowID(#MainWin)), "CreateToolBar")
DRAW_CUSTOM_BUTTONS()
ToolBarStandardButton(#Tool_Load, #PB_ToolBarIcon_Open)
ToolBarStandardButton(#Tool_Save, #PB_ToolBarIcon_Save)
ToolBarImageButton(#Tool_Record, ImageID(#img_Record))
ToolBarImageButton(#Tool_Play , ImageID(#img_Play))
ToolBarImageButton(#Tool_Stop , ImageID(#img_Stop))
ToolBarImageButton(#Tool_Pause, ImageID(#Img_Pause), #PB_ToolBar_Toggle)
ToolBarImageButton(#Tool_Erase, ImageID(#img_Erase))
ToolBarSeparator()
ToolBarToolTip(#ToolBar, #Tool_Record, "Record")
ToolBarToolTip(#ToolBar, #Tool_Pause, "Pause")
ToolBarToolTip(#ToolBar, #Tool_Play , "Play")
ToolBarToolTip(#ToolBar, #Tool_Stop , "Stop")
ToolBarToolTip(#ToolBar, #Tool_Load , "Load Audio")
ToolBarToolTip(#ToolBar, #Tool_Save , "Save Audio")
ToolBarToolTip(#ToolBar, #Tool_Erase, "Erase Buffer")
TB_COMBO(#Combo_Channel, 5, 1, 60, 20)
AddGadgetItem(#Combo_Channel, -1, "Mono")
AddGadgetItem(#Combo_Channel, -1, "Stereo")
SetGadgetState(#Combo_Channel, 0)
TB_COMBO(#Combo_Frequency,50,1,80,20)
AddGadgetItem(#Combo_Frequency,-1,"8.0 kHz")
AddGadgetItem(#Combo_Frequency,-1,"11.025 kHz")
AddGadgetItem(#Combo_Frequency,-1,"12.0 kHz")
AddGadgetItem(#Combo_Frequency,-1,"16.0 kHz")
AddGadgetItem(#Combo_Frequency,-1,"22.05 kHz")
AddGadgetItem(#Combo_Frequency,-1,"24.0 kHz")
AddGadgetItem(#Combo_Frequency,-1,"32.0 kHz")
AddGadgetItem(#Combo_Frequency,-1,"44.1 kHz") ; default
AddGadgetItem(#Combo_Frequency,-1,"48.0 kHz")
SetGadgetState(#Combo_Frequency, 7)
TB_COMBO(#Combo_Device,115,1,170,20)
GET_CAPTURE_DEVICES(#Combo_Device)
SetGadgetState(#Combo_Device,0)
; define meter size and location
mtr\w = #GUI_WIDTH - 70
mtr\h = 25
mtr\x = 45
mtr\y[0] = 10 + ToolBarHeight(#ToolBar)
mtr\y[1] = 50 + ToolBarHeight(#ToolBar)
VERIFY(CanvasGadget(#Canvas_L, mtr\x, mtr\y[0], mtr\w + 4, mtr\h + 4, #PB_Canvas_Border), "CanvasGadget")
VERIFY(CanvasGadget(#Canvas_R, mtr\x, mtr\y[1], mtr\w + 4, mtr\h + 4, #PB_Canvas_Border), "CanvasGadget")
TrackBarGadget(#Playback_Pos, 15, 15 + mtr\h + mtr\y[1], mtr\w + 32, 25, 0, 0)
DisableGadget(#Playback_Pos, #True)
GadgetToolTip(#Playback_Pos, "Playback Position")
LoadFont(0, "Arial", 20)
TextGadget(#Text_L, 15, mtr\y[0] - 2, 20, 32, "L")
TextGadget(#Text_R, 15, mtr\y[1] - 2, 20, 32, "R")
SetGadgetColor(#Text_L, #PB_Gadget_BackColor, #Win_Color)
SetGadgetColor(#Text_R, #PB_Gadget_BackColor, #Win_Color)
SetGadgetFont(#Text_L, FontID(0))
SetGadgetFont(#Text_R, FontID(0))
SetWindowCallback(@GUI_CALLBACK())
EndProcedure
Procedure GUI_CALLBACK(hWnd.l, Msg.l, wParam.l, lParam.l)
If Msg = #MM_WIM_DATA
wpa = wParam
lpa = lParam
SignalSemaphore(audioSemaphore)
EndIf
ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure
Procedure DRAW_CUSTOM_BUTTONS()
CreateImage(#img_Record, 16, 16, 32)
CreateImage(#img_Play , 16, 16, 32)
CreateImage(#img_Stop , 16, 16, 32)
CreateImage(#img_Pause , 16, 16, 32)
CreateImage(#img_Erase , 16, 16, 32)
StartDrawing(ImageOutput(#img_Record))
Box(0,0,16,16,#Gray)
DrawingMode(#PB_2DDrawing_Gradient)
CircularGradient(8,8,14)
FrontColor(#Blue)
BackColor(#Red)
Circle(7,7,6)
StopDrawing()
StartDrawing(ImageOutput(#img_Pause))
DrawingMode(#PB_2DDrawing_Default)
Box(0,0,16,16,#Gray)
Box(3,3,04,10,#Yellow)
Box(9,3,04,10,#Yellow)
StopDrawing()
StartDrawing(ImageOutput(#img_Stop))
Box(0,0,16,16,#Gray)
Box(3,3,10,10,#Cyan)
StopDrawing()
StartDrawing(ImageOutput(#img_Play))
Box(0,0,16,16,#Gray)
LineXY(03, 02, 13, 07, #Green)
LineXY(03, 13, 13, 07, #Green)
LineXY(03, 02, 03, 13, #Green)
FillArea(7, 7, #Green, #Green)
StopDrawing()
LoadFont(1, "Arial", 11, #PB_Font_Bold)
StartDrawing(ImageOutput(#img_Erase))
DrawingFont(FontID(1))
Box(0,0,16,16,0)
BackColor(0)
DrawText(3,0,"X",#Red)
StopDrawing()
EndProcedure
Procedure TB_COMBO(Id,x,y,w,h)
Protected separator.TBBUTTON
Protected rc.RECT
Protected pos = SendMessage_(hToolBar,#TB_BUTTONCOUNT,0,0)
SendMessage_(hToolBar,#TB_GETBUTTON,pos,@separator)
separator\iBitmap = x + w
SendMessage_(hToolBar,#TB_DELETEBUTTON,pos,0)
SendMessage_(hToolBar,#TB_INSERTBUTTON,pos,separator)
SendMessage_(hToolBar,#TB_GETITEMRECT,pos,@rc)
UseGadgetList(hToolBar)
ComboBoxGadget(Id,x+rc\left,y,w,h)
UseGadgetList(hWindow)
EndProcedure
Procedure RECORD_START()
If Config\recording = #True : ProcedureReturn 0 : EndIf
Config\recorded = #Null
Config\FileId = CreateFile(#PB_Any,Config\tempFile)
If Config\FileId
WRITE_HEADER(Config\FileId) ; add wave header to file
CONFIG_BUTTONS(#TB_State_2)
Config\Begin = ElapsedMilliseconds()
Config\recording = #True
SendMessage_(hStatus, #CCM_SETBKCOLOR, 0, #Stat_Color_Rec)
ProcedureReturn 1
Else
MessageRequester("Can't create file", Config\tempFile)
ProcedureReturn 0
EndIf
EndProcedure
Procedure PAUSE()
; used for both record and playback
pause = GetToolBarButtonState(#ToolBar,#Tool_Pause)
If Config\PlayBack <> 0 ; pause playback
If pause
PauseSound(Config\PlayBack)
Else
ResumeSound(Config\PlayBack)
EndIf
ProcedureReturn
EndIf
If Config\recording = #True ; pause recording
Config\Begin = ElapsedMilliseconds() - Config\Begin
Else
SetToolBarButtonState(#ToolBar,#Tool_Pause, 0)
EndIf
EndProcedure
Procedure STOP()
; used for both record and playback
If Config\PlayBack <> 0 ; stop playback
RemoveWindowTimer(#MainWin, #PlaybackTimer)
FreeSound(Config\PlayBack)
Config\PlayBack = 0
SetGadgetState(#Playback_Pos, 0)
SetToolBarButtonState(#ToolBar, #Tool_Pause, 0)
pause = #False
CONFIG_BUTTONS(#TB_State_3)
SHOW_SIZE()
ProcedureReturn
EndIf
If Config\recording = #True ; stop recording
Config\recording = #False
; update wave header with final size
FileSeek(Config\FileId, 4) ; position for chunk size location
WriteLong(Config\FileId, Config\recorded + 36) ; chunk size
FileSeek(Config\FileId, 40) ; position for byte count
WriteLong(Config\FileId, Config\recorded) ; audio byte count
CloseFile(Config\FileId)
SendMessage_(hStatus, #CCM_SETBKCOLOR, 0, #Stat_Color_Pla)
SetToolBarButtonState(#ToolBar, #Tool_Pause, 0)
DisableGadget(#Playback_Pos, #False)
CONFIG_BUTTONS(#TB_State_3)
SHOW_PATH("unnamed.wav")
If pause
Config\Begin = ElapsedMilliseconds() - Config\Begin
pause = #False
EndIf
SetGadgetAttribute(#Playback_Pos, #PB_TrackBar_Maximum, (ElapsedMilliseconds() - Config\Begin)/1000)
saved = #False
EndIf
EndProcedure
Procedure WRITE_HEADER(file.i)
; construct wave header at begining of file
Protected b.W = Config\format\wBitsPerSample
Protected c.W = Config\format\nChannels
Protected h.l = Config\format\nSamplesPerSec
; final values of buffLen and chunkSize are not known at this time.
; they must be updated when recording is stopped and final size can be determined.
Protected buffLen.q = Config\recorded ; placeholder for number of audio bytes
Protected chunkSize.q = Config\recorded + 36 ; placeholder for final chunksize.
FileSeek(file, 0)
; Master chunk (12 bytes)
WriteString(file, "RIFF") ; 4 bytes -- chunk ID
WriteLong(file, chunkSize) ; 4 bytes -- total file size - 8
WriteString(file, "WAVE") ; 4 bytes -- wave ID
; Format chunk (24 bytes)
WriteString(file, "fmt ") ; 4 bytes -- chunk ID
WriteLong(file, 16) ; 4 bytes -- this chunk size
WriteWord(file, 1) ; 2 bytes -- PCM flag
WriteWord(file, c) ; 2 bytes -- number of channels
WriteLong(file, h) ; 4 bytes -- samples per second
WriteLong(file, c*h*(b/8)) ; 4 bytes -- bytes per second
WriteWord(file, c * (b/8)) ; 2 bytes -- blockalign
WriteWord(file, b) ; 2 bytes -- bits per sample
; Begin data chunk (8 bytes)
WriteString(file, "data") ; 4 bytes -- chunk ID
WriteLong(file, buffLen) ; 4 bytes -- number of audio bytes
EndProcedure
Procedure LOAD_AUDIO()
Protected file.s, inId.i, waveID.s, samplesPerSecond.l
Protected blockAlign.w, nAudioBytes.l, PCMflag.w, seconds.i
Static pattern.s = "Wave (*.wav)|*.wav"
file = OpenFileRequester("Select wave file", Config\SavePath, pattern, 0)
If file
inId = ReadFile(#PB_Any, file)
If inId
FileSeek(inId, 8) : waveID = ReadString(inId, #PB_Ascii, 4)
FileSeek(inId, 20) : PCMflag = ReadWord(inId)
FileSeek(inId, 24) : samplesPerSecond = ReadLong(inId)
FileSeek(inId, 32) : blockAlign = ReadWord(inId)
FileSeek(inId, 40) : nAudioBytes = ReadLong(inId)
CloseFile(inId)
If waveID = "WAVE" And PCMflag = 1
If CopyFile(file, Config\tempFile)
Config\recorded = nAudioBytes
playback_nSPS = samplesPerSecond
Seconds = (nAudioBytes / blockAlign) / samplesPerSecond
SetGadgetAttribute(#Playback_Pos, #PB_TrackBar_Maximum, seconds)
SHOW_TIME(1, Seconds * 1000)
SHOW_SIZE()
SendMessage_(hStatus, #CCM_SETBKCOLOR, 0, #Stat_Color_Pla)
DisableGadget(#Playback_Pos, #False)
CONFIG_BUTTONS(#TB_State_3)
SHOW_PATH(GetFilePart(file))
Config\SavePath = GetPathPart(File)
ProcedureReturn 1
Else
ProcedureReturn 0
EndIf
Else
MessageRequester("Error!", "File format is not PCM WAVE.")
ProcedureReturn 0
EndIf
Else
MessageRequester("Error!", "Unable to open file.")
ProcedureReturn 0
EndIf
EndIf
EndProcedure
Procedure SAVE_AUDIO()
Protected buffLen.q, outId.i, *pBuf
Protected inId.i = ReadFile(#PB_Any, Config\tempFile)
Protected ChunkSize.i, File.s
Static pattern.s = "Wave (*.wav)|*.wav|All files|*.*"
If inId = #Null
MessageRequester("Unable to open file", Config\tempFile, #MB_ICONERROR)
ProcedureReturn -1
EndIf
buffLen = Lof(inId)
*pBuf = AllocateMemory(buffLen)
If *pBuf = #Null
MessageRequester("Error", "Unable to allocate buffer", #MB_ICONERROR)
ProcedureReturn -1
EndIf
ReadData(inId, *pBuf, buffLen)
CloseFile(inId)
File = OpenFileRequester("Choose filename", Config\SavePath, pattern, 0)
If File <> ""
If GetFilePart(File) = ""
File = GetPathPart(File) + "unnamed.wav"
EndIf
If GetExtensionPart(File) <> "wav"
RTrim(File,".")
File + ".wav"
EndIf
Config\SavePath = GetPathPart(File)
StatusBarText(#StatusBar, 2, Config\SavePath)
Else
ProcedureReturn #False
EndIf
outId = CreateFile(#PB_Any, File) ; create empty file
If outId = #Null
MessageRequester("Error", "Unable to create file",#MB_ICONERROR)
ProcedureReturn #False
EndIf
SHOW_PATH(GetFilePart(file))
WriteData(outId, *pBuf, buffLen) ; this is the audio data
CloseFile(outId)
FreeMemory(*pBuf)
saved = #True
ProcedureReturn #True
EndProcedure
Procedure PLAY_AUDIO()
If Config\PlayBack <> 0
ProcedureReturn 0
Else
Config\PlayBack = LoadSound(#PB_Any, Config\tempFile)
If Config\PlayBack = 0
ProcedureReturn 0
Else
DisableGadget(#Playback_Pos, #False)
SetToolBarButtonState(#ToolBar, #Tool_Pause, 0)
CONFIG_BUTTONS(#TB_State_4)
AddWindowTimer(#MainWin, #PlaybackTimer, 1000)
Delay(100)
PlaySound(Config\PlayBack)
POSITION_PLAYBACK()
EndIf
EndIf
EndProcedure
Procedure POSITION_PLAYBACK()
Protected pos.i = GetGadgetState(#Playback_Pos)
SHOW_TIME(0, pos * 1000)
If Config\PlayBack
SetSoundPosition(Config\PlayBack, pos * playback_nSPS, #PB_Sound_Frame)
EndIf
EndProcedure
Procedure ERASE_AUDIO()
If saved = #False
If ASK(m$ + "Erase anyway?") = #False : ProcedureReturn : EndIf
EndIf
If Config\PlayBack <> 0
FreeSound(Config\PlayBack)
Config\PlayBack = 0
RemoveWindowTimer(#MainWin, #PlaybackTimer)
EndIf
DeleteFile(Config\tempFile)
Config\recorded = #Null
SetGadgetState(#Playback_Pos, 0)
DisableGadget(#Playback_Pos, #True)
SetToolBarButtonState(#ToolBar,#Tool_Pause, 0)
SendMessage_(hStatus, #CCM_SETBKCOLOR, 0, #Stat_Color_Def)
CONFIG_BUTTONS(#TB_State_1)
SHOW_SIZE()
SHOW_TIME(1, 0)
SHOW_PATH(Config\SavePath)
playback_nSPS = Config\format\nSamplesPerSec
saved = #True
EndProcedure
Procedure ASK(querry.s)
If MessageRequester("WARNING!", querry, #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
ProcedureReturn #True
Else
ProcedureReturn #False
EndIf
EndProcedure
Procedure CONFIG_BUTTONS(pattern)
Protected tool.i, gadget.i
For tool = #Tool_Load To #Tool_Erase
DisableToolBarButton(#ToolBar, tool, (pattern & 1) ! 1)
pattern >> 1
Next tool
For gadget = #Combo_Channel To #Combo_Device
DisableGadget(gadget, (pattern & 1) ! 1)
pattern >> 1
Next gadget
EndProcedure
Procedure LOAD_CONFIG()
OpenPreferences(GetHomeDirectory() + "PureAudioRecorder.prefs")
PreferenceGroup("Files")
Config\SavePath = ReadPreferenceString("FileName", GetHomeDirectory())
PreferenceGroup("Capture")
Config\nDev = ReadPreferenceLong("Device",0)
Config\format\nChannels = ReadPreferenceLong("Channel", 1)
Config\format\nSamplesPerSec = ReadPreferenceLong("Frequency",44100)
Config\format\wBitsPerSample = 16
ClosePreferences()
StatusBarText(0,2,Config\SavePath)
SetGadgetState(#Combo_Device, Config\nDev)
SetGadgetState(#Combo_Channel, Config\format\nChannels - 1)
Select Config\format\nSamplesPerSec
Case 8000: SetGadgetState(#Combo_Frequency, 0)
Case 11025: SetGadgetState(#Combo_Frequency, 1)
Case 12000: SetGadgetState(#Combo_Frequency, 2)
Case 16000: SetGadgetState(#Combo_Frequency, 3)
Case 22050: SetGadgetState(#Combo_Frequency, 4)
Case 24000: SetGadgetState(#Combo_Frequency, 5)
Case 32000: SetGadgetState(#Combo_Frequency, 6)
Case 44100: SetGadgetState(#Combo_Frequency, 7)
Case 48000: SetGadgetState(#Combo_Frequency, 8)
EndSelect
EndProcedure
Procedure SAVE_CONFIG()
If CreatePreferences(GetHomeDirectory() + "PureAudioRecorder.prefs", #PB_Preference_GroupSeparator)
PreferenceGroup("Files")
WritePreferenceString("FileName",Config\SavePath)
PreferenceGroup("Capture")
WritePreferenceLong("Device",Config\nDev)
WritePreferenceLong("Channel",Config\format\nChannels)
WritePreferenceLong("Frequency",Config\format\nSamplesPerSec)
ClosePreferences()
ProcedureReturn #True
EndIf
EndProcedure
Procedure GET_CAPTURE_DEVICES(gadId.l)
Protected NumDevices.i = waveInGetNumDevs_()
Protected DeviceID.i, Result.i
Protected Caps.WAVEINCAPS
If NumDevices
For DeviceID = #WAVE_MAPPER To NumDevices - 1
Result = waveInGetDevCaps_(DeviceID,@Caps, SizeOf(WAVEINCAPS))
If Result = #MMSYSERR_NOERROR
AddGadgetItem(gadId, -1, PeekS(@Caps\szPname, #MAXPNAMELEN))
EndIf
Next
EndIf
EndProcedure
Procedure INIT_CAPTURE()
Protected state, i, lBufopt.f
If Config\recording = #True : ProcedureReturn : EndIf
CAPTURE_STOP()
CONFIG_BUTTONS(#TB_State_1)
SHOW_SIZE()
SHOW_TIME(1, 0)
; GET USER OPTIONS
Config\nDev = GetGadgetState(#Combo_Device)
state = GetGadgetState(#Combo_Channel)
Select state
Case 0 : Config\format\nChannels = #MONO
Case 1 : Config\format\nChannels = #STEREO
EndSelect
state = GetGadgetState(#Combo_Frequency)
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
playback_nSPS = Config\format\nSamplesPerSec
Config\MeterTimer = 85 ; approximate meter refresh rate in milliseconds
With Config\format
; calculate optimum buffer size using approximate MeterTimer rate
lBufopt = 2 * \nSamplesPerSec * (Config\MeterTimer / 1000) * \nChannels
; actual buffer size must be a power of 2
; this calculates the next larger power of 2 from lBufopt
Config\lBuf = Pow(2, Int(Log(lBufopt) / Log(2)) + 1)
; recalculate MeterTimer to optimum using actual buffer size
Config\MeterTimer = 1000.0 * Config\lBuf / (2 * \nSamplesPerSec * \nChannels) + 1
\nBlockAlign = (\nChannels * \wBitsPerSample) / 8
\nAvgBytesPerSec = \nSamplesPerSec * \nBlockAlign
\cbSize = 0
EndWith
If #MMSYSERR_NOERROR = waveInOpen_(@Config\wave,#WAVE_MAPPER+Config\nDev,@Config\format,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)
AddWindowTimer(#MainWin, #MeterTimer, Config\MeterTimer)
EndIf
EndIf
If IsThread(threadID1) <> 0 : ResumeThread(threadID1) : EndIf
If IsThread(threadID2) <> 0 : ResumeThread(threadID2) : EndIf
saved = #True
EndProcedure
Procedure CAPTURE_STOP()
Protected i
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)\lpData
waveInUnprepareHeader_(Config\wave,inHdr(i),SizeOf(WAVEHDR))
FreeMemory(inHdr(i)\lpData)
inHdr(i)\lpData = 0
EndIf
Next
waveInClose_(Config\wave)
EndIf
KillTimer_(hWindow, #MeterTimer)
EndProcedure
Procedure PLAYBACK_UPDATE()
Protected pos.i
If pause = #False
pos = GetSoundPosition(Config\PlayBack, #PB_Sound_Frame)
If pos = 0 : STOP() : EndIf
pos / playback_nSPS
SetGadgetState(#Playback_Pos, pos)
SHOW_TIME(0, pos * 1000)
EndIf
EndProcedure
Procedure AUDIO_LOOP(null)
Protected *hWave.WAVEHDR
Static upCounter.f
Repeat
WaitSemaphore(audioSemaphore)
*hWave = lpa
Config\buffer = *hWave\lpData
Config\size = *hWave\dwBytesRecorded
waveInAddBuffer_(wpa, *hWave, SizeOf(WAVEHDR))
If Config\buffer <> 0
CopyMemory(Config\buffer, *audioBuffer, Config\size)
If pause = #False
If Config\recording = #True
Config\recorded + Config\size
WriteData(Config\FileId, *audioBuffer, Config\size) ; file append
; reduce status bar flicker by less frequent updates
If upCounter > 0 : upCounter - 1
Else : upCounter = Config\MeterTimer / 10.0
SHOW_SIZE()
SHOW_TIME(1, (ElapsedMilliseconds() - Config\Begin))
EndIf
EndIf
EndIf
EndIf
ForEver
EndProcedure
Procedure METER_LOOP(null)
Protected maxL, maxR, ch, i, amp, inc, limit, value.w
Repeat
WaitSemaphore(meterSemaphore)
If Config\PlayBack = 0
; UPDATE METER
inc = Config\format\nChannels * 2
limit = Config\lBuf - inc
maxL = 0 : maxR = 0 : i = 0
For ch = 1 To Config\format\nChannels
Repeat
value = PeekW(*audioBuffer + i)
If value < 0 : value = -value : EndIf
If value > maxR : maxR = value : EndIf
i + inc
Until i > limit
If ch = 1 : Swap maxL, maxR : EndIf
i = 2
limit + 2
Next ch
Else
maxL = 0 : maxR = 0
EndIf
amp = mtr\w * (maxL / 32767.0)
For ch = #Canvas_L To #Canvas_R
StartDrawing(CanvasOutput(ch))
DrawingMode(#PB_2DDrawing_Gradient)
BackColor($00FFFF)
GradientColor(0.33, $00FF00)
GradientColor(0.66, $00FF80)
FrontColor($0000FF)
LinearGradient(0, 0, mtr\w, 0)
Box(0, 0 ,amp, mtr\h)
DrawingMode(#PB_2DDrawing_Default)
Box(amp, 0, mtr\w ,mtr\h, 0)
StopDrawing()
amp = mtr\w * (maxR / 32767.0)
Next ch
ForEver
EndProcedure
Procedure VERIFY(result, text.s)
If result = 0
MessageRequester("Fatal Error!", "Unable to execute --> " + text)
End
EndIf
ProcedureReturn result
EndProcedure