; 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 ; IDE Options = PureBasic 5.30 (Windows - x86) ; CursorPosition = 931 ; FirstLine = 876 ; Folding = ------ ; EnableThread ; EnableXP ; Executable = reccy.exe