REM. Simple cross-platform WAV file audio recorder in 'BBC BASIC for SDL 2.0' REM. (C) Copyright Richard Russell, http://www.rtrussell.co.uk/, 18-Jun-2021 Version$ = "v1.3" INSTALL @lib$ + "dlglib" INSTALL @lib$ + "msgbox" INSTALL @lib$ + "filedlg" INSTALL @lib$ + "stringlib" INSTALL @lib$ + "sortlib" DIM Click%(2) ON MOUSE Click%() = @msg%,@wparam%,@lparam% : RETURN ON ERROR PROCcleanup : OSCLI "REFRESH ON" : MODE 3 : PRINT REPORT$ : END ON CLOSE PROCcleanup : QUIT *ESC OFF VDU 23,22,440;450;8,16,16,128+8 IF @platform% >= &2000500 SYS "SDL_SetWindowResizable", @hwnd%, 0, @memhdc% SYS "SDL_SetWindowTitle", @hwnd%, "Audio Recorder " + Version$, @memhdc% GUIfont$ = """" + @lib$ + "DejaVuSans"",10" OSCLI "FONT " + GUIfont$ OFF REM!WC Windows Constants: WS_GROUP = &20000 SDL_MESSAGEBOX_INFORMATION = &40 REM Find out if there are any audio capture devices: SYS "SDL_GetNumAudioDevices", 1, @memhdc% TO numDevices% IF numDevices% = 0 THEN IF FN_messagebox("Audio Recorder", \ \ "No audio capture devices are available", SDL_MESSAGEBOX_INFORMATION) QUIT ENDIF REM Get capture device names into an array: DIM deviceName$(numDevices%) FOR index% = 0 TO numDevices%-1 SYS "SDL_GetAudioDeviceName", index%, 1, @memhdc% TO pname%% IF @platform% AND &40 ELSE pname%% = !^pname%% deviceName$(index% + 1) = $$pname%% NEXT REM Create main form template: PROC_setdialogpalette COLOR 7+128 : CLS Dlg% = FN_newdialog("", 0, 0) StartButton$ = CHR$18 + CHR$0 + CHR$1 + CHR$25 + CHR$0 + CHR$4 + CHR$0 + CHR$-14 + CHR$-1 + \ \ CHR$25 + CHR$153 + CHR$16 + CHR$0 + CHR$0 + CHR$0 + \ \ CHR$18 + CHR$0 + CHR$0 + CHR$25 + CHR$0 + CHR$0 + CHR$0 + CHR$14 + CHR$0 + \ \ " Start recording" StopButton$ = CHR$18 + CHR$0 + CHR$4 + CHR$25 + CHR$0 + CHR$0 + CHR$0 + CHR$-26 + CHR$-1 + \ \ CHR$25 + CHR$97 + CHR$24 + CHR$0 + CHR$24 + CHR$0 + \ \ CHR$18 + CHR$0 + CHR$0 + CHR$25 + CHR$0 + CHR$0 + CHR$0 + CHR$4 + CHR$0 + \ \ " Stop recording" PROC_button(Dlg%, StartButton$, 1, 24, 24, 340, 72, 0) PROC_static(Dlg%, "Left channel", 106, 650, 156, 180, 50, 0) PROC_static(Dlg%, "Right channel", 107, 650, 210, 180, 50, 0) PROC_trackbar(Dlg%, "", 108, 20, 300, 600, 62, 0) PROC_static(Dlg%, "Level 100%", 109, 650, 312, 180, 50, 0) FOR index% = 1 TO numDevices% IF index% = 1 flags% = WS_GROUP ELSE flags% = 0 PROC_radiobutton(Dlg%, deviceName$(index%), 110 + index%, 30, 340 + index% * 80, 720, 64, flags%) NEXT REM Display dialogue and poll for user input: Outfile% = 0 Level% = 100 Source$ = "" Device% = 0 PROC_settrackbarpos(Dlg%, 108, Level%, 0, 200) PROC_checkdlgitem(Dlg%, 111, TRUE) PROC_refreshdialog(Dlg%) REPEAT FOR index% = 1 TO numDevices% IF FN_isdlgitemchecked(Dlg%, 110+index%) Source$ = deviceName$(index%) NEXT Level% = FN_gettrackbarpos(Dlg%, 108) PROCselectsource(Device%, Source$) PROCshowlevel(Level%) PROCshowtime(Outfile%) PROCacquire(Device%, Level%, Outfile%) R% = FN_polldialog(Dlg%, INKEY(1), Click%()) IF R% = 1 PROCtogglestate(Outfile%) UNTIL R% = 2 QUIT END DEF PROCacquire(D%, L%, F%) LOCAL B%, C%, N%, p%%, rmsL, rmsR, levelL, levelR, l%(), r%(), t%() PRIVATE peakL, peakR DIM l%(2047), r%(2047), t%(2047) SYS "SDL_DequeueAudio", D%, ^r%(0), 4*(DIM(l%(),1) + 1), @memhdc% TO N% IF N% = 0 THEN ENDPROC B% = 2 : C% = 2 REM Separate into left & right channels: l%() = r%() AND &FFFF EOR &8000 : l%() -= &8000 r%() DIV= &10000 REM Gain change (L% < 300): l%() *= L% : l%() DIV= 100 r%() *= L% : r%() DIV= 100 REM Saturate left channel: t%() = l%() DIV &8000 : REM 0 = OK, <0 = -ve overflow, >0 = +ve overflow IF SUM(t%()) B% = 1 : REM Change colour of bar to red t%() -= t%() DIV 2 : REM Change -2 to -1, +2 to +1 t%() *= &7FFF l%() AND= t%() - 1 l%() OR= t%() REM Saturate right channel: t%() = r%() DIV &8000 : REM 0 = OK, <0 = -ve overflow, >0 = +ve overflow IF SUM(t%()) C% = 1 : REM Change colour of bar to red t%() -= t%() DIV 2 : REM Change -2 to -1, +2 to +1 t%() *= &7FFF r%() AND= t%() - 1 r%() OR= t%() rmsL = MOD(l%()) / SQR(N% DIV 4) rmsR = MOD(r%()) / SQR(N% DIV 4) REM Combine to stereo: r%() *= &10000 r%() OR= l%() AND &FFFF IF F% THEN p%% = @hfile%(F%) PTR#F% = PTR#F% IF @platform% AND &40 THEN SYS ](p%%+24), p%%, ^r%(0), 1, N% ELSE SYS !(p%%+12), p%%, ^r%(0), 1, N% ENDIF ENDIF levelL = 20*LOG(rmsL+1) - 55 levelR = 20*LOG(rmsR+1) - 55 IF peakL > 1 peakL -= 1 ELSE peakL = 0 IF peakR > 1 peakR -= 1 ELSE peakR = 0 IF levelL > peakL peakL = levelL IF levelR > peakR peakR = levelR VDU 24,24;700-48;620;700+48; *REFRESH OFF GCOL 7+128 : CLG GCOL B% : RECTANGLE FILL 24, 700, peakL*20, 48 GCOL C% : RECTANGLE FILL 24, 700, peakR*20, -48 GCOL 8 : RECTANGLE 24,652,592,96 : LINE 24,700,612,700 ENDPROC DEF PROCtogglestate(RETURN F%) LOCAL p%%, s%%, wav{}, file$ DIM wav{riff%, total%, wave%, fmt%, fmtsize%, wFormatTag{l&,h&}, nChannels{l&,h&}, \ \ nSamplesPerSec%, nAvgBytesPerSec%, nBlockAlign{l&,h&}, wBitsPerSample{l&,h&}, \ \ data%, datasize%} wav.riff% = &46464952 : REM "RIFF" wav.wave% = &45564157 : REM "WAVE" wav.fmt% = &20746D66 : REM "fmt " wav.fmtsize% = 16 wav.wFormatTag.l& = 1 : REM WAVE_FORMAT_PCM wav.nChannels.l& = 2 : REM Stereo wav.nSamplesPerSec% = 44100 wav.wBitsPerSample.l& = 16 wav.nBlockAlign.l& = wav.nChannels.l& * wav.wBitsPerSample.l& DIV 8 wav.nAvgBytesPerSec% = wav.nSamplesPerSec% * wav.nBlockAlign.l& wav.data% = &61746164 : REM "data" IF F% THEN s%% = EXT#F% PTR#F% = 4 PROC4(F%, s%% - 8) PTR#F% = 40 PROC4(F%, s%% - 44) CLOSE #F% F% = 0 file$ = FN_filedlg("Select destination WAV file", "OK", "", "WAV files", ".wav", 0) IF file$ <> "" THEN IF FN_instrr(file$, "/", 0) >= FN_instrr(file$, ".", 0) file$ += ".wav" IF FN_instrr(file$, "\", 0) >= FN_instrr(file$, ".", 0) file$ += ".wav" ON ERROR LOCAL IF FALSE THEN F% = OPENIN(file$) IF F% THEN CLOSE #F% F% = 0 OSCLI "DELETE """ + file$ + """" ENDIF OSCLI "RENAME """ + @tmp$ + "recorder.tmp.wav"" """ + file$ + """" ELSE IF FN_messagebox("Audio Recorder", "Could not save to file", \ \ SDL_MESSAGEBOX_INFORMATION) ENDIF : RESTORE ERROR ENDIF PROC_setdlgitemtext(Dlg%, 1, StartButton$) ELSE F% = OPENOUT(@tmp$ + "recorder.tmp.wav") FOR p%% = wav{} TO wav{} + DIM(wav{}) - 1 : BPUT#F%,?p%% : NEXT PROC_setdlgitemtext(Dlg%, 1, StopButton$) ENDIF PROC_refreshdialog(Dlg%) ENDPROC DEF PROCselectsource(RETURN D%, s$) PRIVATE src$, want{} LOCAL have{} IF src$ = s$ ENDPROC src$ = s$ DIM want{freq%, format{l&,h&}, channels&, silence&, samples%, size%, callback%%, userdata%%} DIM have{} = want{} want.freq% = 44100 want.format.h& = &80 : REM AUDIO_S16LSB want.format.l& = &10 : REM AUDIO_S16LSB want.channels& = 2 IF D% THEN SYS "SDL_PauseAudioDevice", D%, 1, @memhdc% SYS "SDL_CloseAudioDevice", D%, @memhdc% D% = 0 ENDIF SYS "SDL_OpenAudioDevice", s$, 1, want{}, have{}, 0, @memhdc% TO D% IF D% = 0 ERROR 100, "Couldn't open audio device " + s$ SYS "SDL_PauseAudioDevice", D%, 0, @memhdc% ENDPROC DEF PROCshowlevel(L%) PRIVATE lev% IF lev% = L% ENDPROC lev% = L% PROC_setdlgitemtext(Dlg%, 109, "Level " + STR$Level% + "%") PROC_refreshdialog(Dlg%) ENDPROC DEF PROCshowtime(F%) LOCAL tot%, sec%, min%, hrs%, dur$ PRIVATE tim$ IF F% THEN tot% = (EXT#F% - 44) / (44100 * 4) sec% = tot% MOD 60 min% = (tot% DIV 60) MOD 60 hrs% = tot% DIV 3600 dur$ = RIGHT$("0"+STR$hrs%,2) + ":" + \ \ RIGHT$("0"+STR$min%,2) + ":" + \ \ RIGHT$("0"+STR$sec%,2) ELSE dur$ = "00:00:00" ENDIF IF dur$ = tim$ ENDPROC tim$ = dur$ OSCLI "FONT """ + @lib$ + "DejaVuSans.ttf"", 24" GCOL 0 : GCOL 7 + 128 *REFRESH OFF VDU 5,24,470;800;800;880;16,30 PRINT dur$; OSCLI "FONT " + GUIfont$ ENDPROC DEF PROC4(F%,N%) BPUT#F%,N% : BPUT#F%,N%>>8 : BPUT#F%,N%>>16 : BPUT#F%,N%>>24 ENDPROC DEF PROCcleanup LOCAL s%% Device% += 0 IF Device% THEN SYS "SDL_PauseAudioDevice", Device%, 1, @memhdc% SYS "SDL_CloseAudioDevice", Device%, @memhdc% Device% = 0 ENDIF Outfile% += 0 IF Outfile% THEN ON ERROR LOCAL IF FALSE THEN s%% = EXT#Outfile% PTR#Outfile% = 4 PROC4(Outfile%, s%% - 8) PTR#Outfile% = 40 PROC4(Outfile%, s%% - 44) CLOSE #Outfile% ENDIF ENDIF ENDPROC