' RadioGram is a radio broadcast system for console Linux written by Jason ' Page at the end of 2020, starting on Thanks Giving day. ' Initially the program maintains three files to define the program's function: ' 1. Scheduler - Set times for when an item is added to queue ' 2. Active Rotation - Automated rotation of music / podcasts using Music Player Demean MPD/MPC ' 3. 0-9 Programmable Sound Pad - Live sound effects using MPV ' ' In addition the space bar de/activates the microphone input and pauses the ' rotation. ' Hiting keys 0-9 will produce a sound program to that macro number. ' [Shift] + (0-9) will allow programming a sound file to that macro number. ' [Enter] will prompt for date/time and sound file for that time to scheduler. ' [UP/DOWN] Change Output Device ' [LEFT/RIGHT] Change Input Device dim shared sch$(255,2) ' Schedule files: date/time, filename dim shared schcnt as integer ' Schedule entry count dim shared pad$(9) ' Pad files 0-9 dim shared playlist$(11) ' Clockwheel playlist 12 at a time; 11 displayed dim shared plcnt as integer ' Playlist entry count dim shared plindex as integer ' Current playlist index dim shared sdate$ ' Next scheduled date in queue dim shared stime$ ' Next scheduled time in queue dim shared sfile$ ' Next file in queue that is scheduled dim shared qfile$ ' Currently queued file dim shared pfile$ ' Active file/device currently playing dim shared bpfile$ ' Set to compare if file/device changed dim shared lfile$ ' Current Playlist file dim shared ext$ ' Value 1 is exit program, default 0 dim shared devce$ ' The Aux or Mic Device setup in Config dim shared onoff$ ' Mic ON/OFF status dim shared indev$(50) ' Input devices dim shared outdev$(50) ' Output devices dim shared indevcnt as integer ' Input device count dim shared outdevcnt as integer ' Output device count dim shared curindev as integer ' Current input device index dim shared curoutdev as integer ' Current output device index dim shared padspath$ ' Path to pads directory dim shared configlock$ ' Password for config lock dim shared locked as integer ' Config lock status dim shared streamurl$ ' Icecast/Shoutcast server URL dim shared streamport$ ' Stream port dim shared streampass$ ' Stream password dim shared streammount$ ' Stream mount point dim shared streambitrate$ ' Stream bitrate dim shared streamon as integer ' Stream active status dim shared encoderpid$ ' Encoder process ID dim shared musicfolder$ ' Root folder for music dim shared jinglefolder$ ' Folder for jingles dim shared commercialfolder$ ' Folder for commercials dim shared shufflelist$(500) ' Shuffled music playlist dim shared shufflecnt as integer ' Count of shuffle items dim shared shuffleindex as integer ' Current shuffle position dim shared jingleinterval as integer ' Play jingle every X songs dim shared commercialinterval as integer ' Play commercial every X songs dim shared playcounter as integer ' Counter for rotation dim shared jinglefile$ ' Current jingle file dim shared commercialfile$ ' Current commercial file dim shared jinglelist$(50) ' List of jingles dim shared jinglecnt as integer ' Count of jingles dim shared commerciallist$(100) ' List of commercials dim shared commercialcnt as integer ' Count of commercials dim shared shufflemode as integer ' Shuffle mode on/off dim shared sysmsg$ ' System message for errors/status dim shared clearmsgnext as integer ' Flag to clear message on next action dim shared mpdhost$ ' MPD server host dim shared mpdport$ ' MPD server port dim shared mpdmusicdir$ ' MPD music directory path ' Forward declarations declare sub initpadsdir declare sub cleanuperrorfiles declare sub initmpdconnection declare sub loadconfig declare sub saveconfig declare sub loadschedule declare sub saveschedule declare sub loadpads declare sub savepads declare sub loadplaylist declare sub updateplaylistfrommpd declare sub checknextschedule declare sub updatepads declare sub updateschedule declare sub updateplaylist declare sub updatedisplay declare sub setsysmsg(msg as string) declare sub clearsysmsg declare sub checkcmderror(errorfile as string, operation as string) declare sub miconoff(onoff$) declare sub plays(pfile$) declare sub playmic declare sub stopmic declare sub addschedule declare sub browsepadsfiles(padnum as integer) declare sub programpad(padnum as integer) declare sub playpad(padnum as integer) declare sub getnextplaylisttrack declare sub startencoder declare sub stopencoder declare sub checkencoder declare sub selectinputdevice declare sub selectoutputdevice declare sub configmenu declare sub configurempd declare sub configurestream declare sub setconfiglock declare sub buildshufflelist declare sub buildjinglelist declare sub buildcommerciallist declare sub getnextshuffle declare sub configurerotation declare sub previewpad(padnum as integer) declare sub enumaudiodevices declare sub io(sdate$,stime$,sfile$,pfile$,lfile$) declare function fileexists(filename as string) as integer declare function getinput(maxlen as integer, hidden as integer) as string declare function gethomedir as string declare function readandformat(filename as string) as string declare function confirmexit as integer declare function browsefolder(prompt as string, startpath as string) as string declare function getmpcenv as string ext$ = "0" onoff$ = "OFF" schcnt = 0 plcnt = 0 plindex = 0 curindev = 0 curoutdev = 0 locked = 0 streamon = 0 shufflemode = 0 shufflecnt = 0 shuffleindex = 0 jinglecnt = 0 commercialcnt = 0 jingleinterval = 5 commercialinterval = 3 playcounter = 0 configlock$ = "" streamurl$ = "" streamport$ = "8000" streampass$ = "" streammount$ = "/stream" streambitrate$ = "128" encoderpid$ = "" musicfolder$ = "" jinglefolder$ = "" commercialfolder$ = "" jinglefile$ = "" commercialfile$ = "" sysmsg$ = "" clearmsgnext = 0 mpdhost$ = "localhost" mpdport$ = "6600" mpdmusicdir$ = "" ' Initialize arrays dim i as integer for i = 0 to 9 pad$(i) = "" next i for i = 0 to 11 playlist$(i) = "" next i ' Initialize pads directory call initpadsdir ' Clean up old error files call cleanuperrorfiles ' Load configuration files call loadconfig call loadschedule call loadpads call enumaudiodevices ' Initialize MPD connection call initmpdconnection call loadplaylist ' Start encoder if configured if streamurl$ <> "" then call startencoder end if call io(sdate$,stime$,sfile$,pfile$,lfile$) function fileexists(filename as string) as integer dim f as integer dim testvar as string f = freefile fileexists = 0 on error goto notfound open filename for input as #f close #f fileexists = -1 on error goto 0 exit function notfound: fileexists = 0 on error goto 0 end function function getinput(maxlen as integer, hidden as integer) as string dim result as string dim k as string dim displaychar as string result = "" do k = inkey$ if k <> "" then select case k case chr$(13) ' Enter key - return result getinput = result print exit function case chr$(8) ' Backspace if len(result) > 0 then result = left$(result, len(result) - 1) print chr$(8); " "; chr$(8); end if case chr$(27) ' ESC key - return empty string getinput = "" print exit function case chr$(32) to chr$(126) ' Printable characters if len(result) < maxlen then result = result + k if hidden then print "*"; else print k; end if end if end select end if sleep 10 ' Small delay to prevent CPU hogging loop end function function gethomedir as string dim cmd as string dim f as integer dim result as string cmd = "echo $HOME > /tmp/radiogram_home.tmp" shell cmd f = freefile result = "" if fileexists("/tmp/radiogram_home.tmp") then open "/tmp/radiogram_home.tmp" for input as #f if not eof(f) then line input #f, result end if close #f end if gethomedir = rtrim$(result) end function sub initpadsdir dim homedir as string dim cmd as string homedir = gethomedir if homedir <> "" then padspath$ = homedir + "/Music/pads" if not fileexists(padspath$) then padspath$ = homedir + "/music/pads" end if if not fileexists(padspath$) then padspath$ = homedir + "/Music/pads" cmd = "mkdir -p " + chr$(34) + padspath$ + chr$(34) shell cmd end if else padspath$ = "./pads" if not fileexists(padspath$) then shell "mkdir -p ./pads" end if end if end sub sub cleanuperrorfiles ' Clean up old error files from previous runs shell "rm -f /tmp/radiogram_*.err 2>/dev/null" shell "rm -f /tmp/radiogram_encoder.log 2>/dev/null" end sub sub initmpdconnection dim cmd as string dim mpcenv as string ' Set up MPD connection environment if mpdhost$ = "" then mpdhost$ = "localhost" if mpdport$ = "" then mpdport$ = "6600" ' Export MPD_HOST and MPD_PORT for mpc commands mpcenv = "export MPD_HOST=" + mpdhost$ + " && export MPD_PORT=" + mpdport$ + " && " ' Test MPD connection cmd = mpcenv + "mpc status > /tmp/radiogram_mpd_test.out 2>/tmp/radiogram_mpd_test.err" shell cmd ' Check if MPD is accessible if fileexists("/tmp/radiogram_mpd_test.err") then dim errtext as string errtext = readandformat("/tmp/radiogram_mpd_test.err") if len(rtrim$(errtext)) > 0 then ' MPD connection failed - set system message sysmsg$ = "MPD connection failed: " + errtext clearmsgnext = -1 end if end if ' Update music directory if configured if mpdmusicdir$ <> "" then cmd = mpcenv + "mpc update > /tmp/radiogram_mpd_update.out 2>/tmp/radiogram_mpd_update.err" shell cmd end if end sub function getmpcenv as string ' Return environment variables for mpc commands getmpcenv = "export MPD_HOST=" + mpdhost$ + " && export MPD_PORT=" + mpdport$ + " && " end function sub enumaudiodevices dim cmd as string dim f as integer dim ln as string dim i as integer ' Enumerate input devices using arecord cmd = "arecord -L 2>/dev/null | grep -v '^ ' > /tmp/radiogram_in.tmp" shell cmd indevcnt = 0 if fileexists("/tmp/radiogram_in.tmp") then f = freefile open "/tmp/radiogram_in.tmp" for input as #f while not eof(f) and indevcnt < 50 line input #f, ln if len(rtrim$(ln)) > 0 then indev$(indevcnt) = rtrim$(ln) indevcnt = indevcnt + 1 end if wend close #f end if ' Enumerate output devices using aplay cmd = "aplay -L 2>/dev/null | grep -v '^ ' > /tmp/radiogram_out.tmp" shell cmd outdevcnt = 0 if fileexists("/tmp/radiogram_out.tmp") then f = freefile open "/tmp/radiogram_out.tmp" for input as #f while not eof(f) and outdevcnt < 50 line input #f, ln if len(rtrim$(ln)) > 0 then outdev$(outdevcnt) = rtrim$(ln) outdevcnt = outdevcnt + 1 end if wend close #f end if ' Set defaults if no devices found if indevcnt = 0 then indev$(0) = "default" indevcnt = 1 end if if outdevcnt = 0 then outdev$(0) = "default" outdevcnt = 1 end if end sub sub io(sdate$,stime$,sfile$,pfile$,lfile$) cls locate 1,1 print "| RADIOGRAM V1.0a - Written by Jason Page for Console Radio Management |" locate 2,1 print "|--------------------------------------------------------------------- |" locate 3,1 print "| Da-Pad-Gram |\ | Da-Schedule || date / time | Da-Playlist |" locate 4,1 print "| ------------- | /| ----------- || -------------- \___________________|" locate 5,1 print "| | 7 | 8 | 9 | |\ | *********** |< ********/**** |" locate 6,1 print "| | 4 | 5 | 6 | | /| *********** |< ********/**** |" locate 7,1 print "| | 1 | 2 | 3 | |\ | *********** |< ********/**** |" locate 8,1 print "| ------------- | /| *********** |< ********/**** |" locate 9,1 print "| Dis-Playing |\ | *********** |< ********/**** |" locate 10,1 print "| ************* | /| *********** |< ********/**** |" locate 11,1 print "| In-Da-Queue |\ | *********** |< ********/**** |" locate 12,1 print "| ************* | /| *********** |< ********/**** |" locate 13,1 print "| Next-In-Queue |\ | *********** |< ********/**** |" locate 14,1 print "| ************* | /| *********** |< ********/**** |" locate 15,1 print "|---------------|\ | *********** |< ********/**** |" locate 16,1 print "| |MIC|OFF| | /| *********** |< ********/**** |" locate 17,1 print "|-----------------------------------------------------------------------" locate 18,1 print "|SYS: ********************************************************************|" locate 19,1 print "|> [C]onfig IO |--| [S]et Lock |--| [H]elp |--| [Q]uit |--| [A]bout |--|" locate 20,1 print "|> [SPACEBAR]=Mic[ON:OFF] |-| [ENTER]=Schedule Add |-| [SHIFT(1-9)=Pad |" call updatepads call updateschedule call updateplaylist call updatedisplay end sub sub loadconfig dim f as integer dim i as integer dim ln as string f = freefile devce$ = "default" if fileexists("radiogram.cfg") then open "radiogram.cfg" for input as #f if not eof(f) then line input #f, devce$ if not eof(f) then line input #f, configlock$ if not eof(f) then line input #f, streamurl$ if not eof(f) then line input #f, streamport$ if not eof(f) then line input #f, streampass$ if not eof(f) then line input #f, streammount$ if not eof(f) then line input #f, streambitrate$ if not eof(f) then line input #f, ln curindev = val(ln) end if if not eof(f) then line input #f, ln curoutdev = val(ln) end if if not eof(f) then line input #f, musicfolder$ if not eof(f) then line input #f, jinglefolder$ if not eof(f) then line input #f, commercialfolder$ if not eof(f) then line input #f, ln jingleinterval = val(ln) end if if not eof(f) then line input #f, ln commercialinterval = val(ln) end if if not eof(f) then line input #f, mpdhost$ if not eof(f) then line input #f, mpdport$ if not eof(f) then line input #f, mpdmusicdir$ close #f end if end sub sub saveconfig dim f as integer dim i as integer f = freefile open "radiogram.cfg" for output as #f print #f, devce$ print #f, configlock$ print #f, streamurl$ print #f, streamport$ print #f, streampass$ print #f, streammount$ print #f, streambitrate$ print #f, rtrim$(str$(curindev)) print #f, rtrim$(str$(curoutdev)) print #f, musicfolder$ print #f, jinglefolder$ print #f, commercialfolder$ print #f, rtrim$(str$(jingleinterval)) print #f, rtrim$(str$(commercialinterval)) print #f, mpdhost$ print #f, mpdport$ print #f, mpdmusicdir$ close #f end sub sub loadschedule dim f as integer f = freefile schcnt = 0 if fileexists("radiogram.sch") then open "radiogram.sch" for input as #f while not eof(f) and schcnt < 255 line input #f, sch$(schcnt, 0) if not eof(f) then line input #f, sch$(schcnt, 1) schcnt = schcnt + 1 wend close #f end if call checknextschedule end sub sub saveschedule dim f as integer dim i as integer f = freefile open "radiogram.sch" for output as #f for i = 0 to schcnt - 1 print #f, sch$(i, 0) print #f, sch$(i, 1) next i close #f end sub sub loadpads dim f as integer dim i as integer f = freefile if fileexists("radiogram.pad") then open "radiogram.pad" for input as #f for i = 1 to 9 if not eof(f) then line input #f, pad$(i) end if next i close #f end if end sub sub savepads dim f as integer dim i as integer f = freefile open "radiogram.pad" for output as #f for i = 1 to 9 print #f, pad$(i) next i close #f end sub sub loadplaylist dim f as integer dim cmd as string dim mpcenv as string f = freefile plcnt = 0 mpcenv = getmpcenv if fileexists("radiogram.lst") then open "radiogram.lst" for input as #f if not eof(f) then line input #f, lfile$ close #f end if if lfile$ <> "" and fileexists(lfile$) then cmd = mpcenv + "mpc clear > /tmp/radiogram_mpc.out 2>/tmp/radiogram_mpc.err" shell cmd call checkcmderror("/tmp/radiogram_mpc.err", "MPC clear") cmd = mpcenv + "mpc add " + chr$(34) + lfile$ + chr$(34) + " > /tmp/radiogram_mpc.out 2>/tmp/radiogram_mpc.err" shell cmd call checkcmderror("/tmp/radiogram_mpc.err", "MPC add") cmd = mpcenv + "mpc play > /tmp/radiogram_mpc.out 2>/tmp/radiogram_mpc.err" shell cmd call checkcmderror("/tmp/radiogram_mpc.err", "MPC play") end if call updateplaylistfrommpd end sub sub updateplaylistfrommpd dim f as integer dim cmd as string dim temp as string dim mpcenv as string f = freefile mpcenv = getmpcenv cmd = mpcenv + "mpc playlist -f %file% > /tmp/radiogram_pl.tmp 2>/tmp/radiogram_mpc.err" shell cmd call checkcmderror("/tmp/radiogram_mpc.err", "MPC playlist") plcnt = 0 if fileexists("/tmp/radiogram_pl.tmp") then open "/tmp/radiogram_pl.tmp" for input as #f while not eof(f) and plcnt < 12 line input #f, playlist$(plcnt) plcnt = plcnt + 1 wend close #f end if end sub sub checknextschedule dim nowdate as string dim nowtime as string dim i as integer dim spos as integer nowdate = date$ nowtime = time$ sdate$ = "" stime$ = "" sfile$ = "" for i = 0 to schcnt - 1 if sch$(i, 0) >= nowdate + " " + nowtime then spos = instr(sch$(i, 0), " ") if spos > 0 then sdate$ = left$(sch$(i, 0), spos - 1) stime$ = mid$(sch$(i, 0), spos + 1) end if sfile$ = sch$(i, 1) exit for end if next i end sub sub updatepads dim i as integer dim row as integer dim col as integer for i = 1 to 9 row = 5 + ((9 - i) \ 3) col = 4 + ((i - 1) mod 3) * 4 locate row, col print rtrim$(str$(i)) next i end sub sub updateschedule dim i as integer dim row as integer dim entry as string for i = 0 to 10 row = 5 + i locate row, 19 if i < schcnt then entry = left$(sch$(i, 0), 11) print entry; else print space$(11); end if next i if sdate$ <> "" and stime$ <> "" then locate 5, 34 print left$(sdate$ + "/" + stime$, 14); end if end sub sub updateplaylist dim i as integer dim row as integer dim entry as string for i = 0 to 11 row = 5 + i locate row, 50 if i < plcnt then entry = left$(playlist$(i), 30) print entry; else print space$(30); end if next i end sub sub updatedisplay locate 10, 3 print left$(pfile$ + space$(13), 13); locate 12, 3 print left$(qfile$ + space$(13), 13); locate 14, 3 print left$(sfile$ + space$(13), 13); locate 16, 10 print onoff$; locate 16, 50 if streamon then print "[STREAM:ON] "; else print "[STREAM:OFF]"; end if if locked then locate 16, 65 print "[LOCKED]"; end if locate 18, 7 print left$(sysmsg$ + space$(68), 68); end sub sub setsysmsg(msg as string) sysmsg$ = msg clearmsgnext = -1 call updatedisplay end sub sub clearsysmsg if clearmsgnext then sysmsg$ = "" clearmsgnext = 0 call updatedisplay end if end sub function readandformat(filename as string) as string dim f as integer dim result as string dim ln as string dim firstline as integer result = "" firstline = -1 if fileexists(filename) then f = freefile open filename for input as #f while not eof(f) line input #f, ln ln = rtrim$(ln) if len(ln) > 0 then if firstline then result = ln firstline = 0 else result = result + " | " + ln end if end if wend close #f ' Clean up newlines, tabs, and excess whitespace dim i as integer dim cleaned as string cleaned = "" for i = 1 to len(result) dim ch as string ch = mid$(result, i, 1) if ch = chr$(9) or ch = chr$(10) or ch = chr$(13) then cleaned = cleaned + " " else cleaned = cleaned + ch end if next i ' Collapse multiple spaces do while instr(cleaned, " ") > 0 i = instr(cleaned, " ") cleaned = left$(cleaned, i - 1) + mid$(cleaned, i + 1) loop result = cleaned end if readandformat = result end function sub checkcmderror(errorfile as string, operation as string) dim errtext as string if fileexists(errorfile) then errtext = readandformat(errorfile) if len(rtrim$(errtext)) > 0 then call setsysmsg(operation + ": " + errtext) end if end if end sub function confirmexit as integer dim pass as string ' If no password is set, allow exit if configlock$ = "" then confirmexit = -1 exit function end if ' Password is set, require confirmation locate 21, 1 print space$(75); locate 21, 1 print "Enter password to exit: "; pass = getinput(50, -1) if pass = configlock$ then confirmexit = -1 else call setsysmsg("Incorrect password! Exit cancelled.") confirmexit = 0 end if locate 21, 1 print space$(75); end function sub miconoff(onoff$) if onoff$ = "ON " then onoff$ = "OFF" else onoff$ = "ON " end if locate 16, 10 print onoff$ locate 20, 1 end sub sub plays(pfile$) dim cmd as string locate 20, 1 cmd = "pkill -9 mpv 2>/tmp/radiogram_pkill.err" shell cmd call checkcmderror("/tmp/radiogram_pkill.err", "Stop playback") sleep 1 cmd = "mpv " + chr$(34) + pfile$ + chr$(34) + " > /tmp/radiogram_mpv.out 2>/tmp/radiogram_mpv.err &" shell cmd sleep 200 call checkcmderror("/tmp/radiogram_mpv.err", "MPV playback") call updatedisplay end sub sub playmic dim cmd as string cmd = "pkill -9 mpv 2>/tmp/radiogram_pkill.err" shell cmd call checkcmderror("/tmp/radiogram_pkill.err", "Stop for mic") sleep 1 cmd = "arecord -D " + devce$ + " -f cd - 2>/tmp/radiogram_arecord.err | mpv - > /tmp/radiogram_mpv.out 2>/tmp/radiogram_mpv.err &" shell cmd sleep 200 call checkcmderror("/tmp/radiogram_arecord.err", "Mic recording") call checkcmderror("/tmp/radiogram_mpv.err", "Mic playback") pfile$ = "MIC:" + devce$ call updatedisplay end sub sub stopmic dim cmd as string dim mpcenv as string mpcenv = getmpcenv cmd = "pkill -9 arecord 2>/tmp/radiogram_pkill.err" shell cmd call checkcmderror("/tmp/radiogram_pkill.err", "Stop mic recording") cmd = "pkill -9 mpv 2>/tmp/radiogram_pkill.err" shell cmd call checkcmderror("/tmp/radiogram_pkill.err", "Stop mic playback") cmd = mpcenv + "mpc play > /tmp/radiogram_mpc.out 2>/tmp/radiogram_mpc.err" shell cmd call checkcmderror("/tmp/radiogram_mpc.err", "Resume MPC") end sub sub addschedule dim newdate as string dim newtime as string dim newfile as string locate 20, 1 print space$(75); locate 20, 1 print "Enter date (MM-DD-YYYY): "; newdate = getinput(20, 0) locate 20, 1 print space$(75); locate 20, 1 print "Enter time (HH:MM:SS): "; newtime = getinput(20, 0) locate 20, 1 print space$(75); locate 20, 1 print "Enter filename: "; newfile = getinput(200, 0) if newdate <> "" and newtime <> "" and newfile <> "" then sch$(schcnt, 0) = newdate + " " + newtime sch$(schcnt, 1) = newfile schcnt = schcnt + 1 call saveschedule call checknextschedule end if call io(sdate$, stime$, sfile$, pfile$, lfile$) end sub sub browsepadsfiles(padnum as integer) dim cmd as string dim f as integer dim files(100) as string dim filecnt as integer dim selection as integer dim i as integer dim k as string dim offset as integer ' Get list of audio files from pads directory cmd = "find " + chr$(34) + padspath$ + chr$(34) + " -maxdepth 1 -type f \( -iname '*.mp3' -o -iname '*.wav' -o -iname '*.ogg' -o -iname '*.flac' -o -iname '*.m4a' \) > /tmp/radiogram_padslist.tmp 2>/tmp/radiogram_find.err" shell cmd call checkcmderror("/tmp/radiogram_find.err", "Find pads") filecnt = 0 if fileexists("/tmp/radiogram_padslist.tmp") then f = freefile open "/tmp/radiogram_padslist.tmp" for input as #f while not eof(f) and filecnt < 100 line input #f, files(filecnt) filecnt = filecnt + 1 wend close #f end if if filecnt = 0 then locate 20, 1 print "No audio files found in "; padspath$; " - Press any key"; sleep call io(sdate$, stime$, sfile$, pfile$, lfile$) exit sub end if selection = 0 offset = 0 do cls locate 1, 1 print "Select audio file for Pad "; rtrim$(str$(padnum)); " (Arrow keys to navigate, ENTER to select, ESC to cancel)" locate 2, 1 print string$(75, "-") for i = 0 to 15 if offset + i < filecnt then locate 3 + i, 1 if offset + i = selection then print "> "; mid$(files(offset + i), len(padspath$) + 2) else print " "; mid$(files(offset + i), len(padspath$) + 2) end if end if next i k = inkey$ select case k case chr$(0) + "H" ' Up if selection > 0 then selection = selection - 1 if selection < offset then offset = offset - 1 end if case chr$(0) + "P" ' Down if selection < filecnt - 1 then selection = selection + 1 if selection >= offset + 16 then offset = offset + 1 end if case chr$(13) ' Enter pad$(padnum) = files(selection) call savepads call io(sdate$, stime$, sfile$, pfile$, lfile$) exit sub case chr$(27) ' ESC call io(sdate$, stime$, sfile$, pfile$, lfile$) exit sub end select sleep 50 loop end sub sub programpad(padnum as integer) dim choice as string locate 20, 1 print space$(75); locate 20, 1 print "Pad "; rtrim$(str$(padnum)); " - [B]rowse files or [M]anual entry? "; do choice = ucase$(inkey$) if choice = "B" then call browsepadsfiles(padnum) exit sub elseif choice = "M" then dim newfile as string locate 20, 1 print space$(75); locate 20, 1 print "Enter filename for pad "; rtrim$(str$(padnum)); ": "; newfile = getinput(200, 0) if newfile <> "" then pad$(padnum) = newfile call savepads end if call io(sdate$, stime$, sfile$, pfile$, lfile$) exit sub elseif choice = chr$(27) then call io(sdate$, stime$, sfile$, pfile$, lfile$) exit sub end if sleep 50 loop end sub sub playpad(padnum as integer) if pad$(padnum) <> "" then qfile$ = pad$(padnum) call plays(qfile$) end if end sub sub getnextplaylisttrack dim cmd as string dim f as integer dim temp as string dim mpcenv as string mpcenv = getmpcenv cmd = mpcenv + "mpc current -f %file% > /tmp/radiogram_current.tmp 2>/tmp/radiogram_mpc.err" shell cmd call checkcmderror("/tmp/radiogram_mpc.err", "MPC current") f = freefile if fileexists("/tmp/radiogram_current.tmp") then open "/tmp/radiogram_current.tmp" for input as #f if not eof(f) then line input #f, pfile$ end if close #f end if end sub sub startencoder dim cmd as string dim audiodev as string if streamurl$ = "" or streampass$ = "" then exit sub end if ' Kill any existing encoder call stopencoder ' Get current output device if curoutdev < outdevcnt then audiodev = outdev$(curoutdev) else audiodev = "default" end if ' Start darkice or ffmpeg encoder for streaming ' Using ffmpeg to capture audio and stream to Icecast/Shoutcast cmd = "ffmpeg -f alsa -i " + audiodev + " -acodec libmp3lame -ab " + streambitrate$ + "k -f mp3 " cmd = cmd + "icecast://source:" + streampass$ + "@" + streamurl$ + ":" + streamport$ + streammount$ cmd = cmd + " > /tmp/radiogram_encoder.log 2>/tmp/radiogram_encoder.err &" shell cmd shell "echo $! > /tmp/radiogram_encoder.pid" ' Check for encoder startup errors sleep 500 call checkcmderror("/tmp/radiogram_encoder.err", "Encoder startup") ' Get encoder PID dim f as integer f = freefile if fileexists("/tmp/radiogram_encoder.pid") then open "/tmp/radiogram_encoder.pid" for input as #f if not eof(f) then line input #f, encoderpid$ end if close #f end if streamon = -1 end sub sub stopencoder dim cmd as string if encoderpid$ <> "" then cmd = "kill " + encoderpid$ + " 2>/tmp/radiogram_kill.err" shell cmd call checkcmderror("/tmp/radiogram_kill.err", "Stop encoder") encoderpid$ = "" end if shell "pkill -f 'ffmpeg.*icecast' 2>/tmp/radiogram_pkill.err" call checkcmderror("/tmp/radiogram_pkill.err", "Kill encoder") streamon = 0 end sub sub checkencoder dim cmd as string dim f as integer dim result as string if streamurl$ = "" or streamon = 0 then exit sub end if ' Check if encoder process is still running if encoderpid$ <> "" then cmd = "ps -p " + encoderpid$ + " > /tmp/radiogram_ps.out 2>/tmp/radiogram_ps.err && echo 'running' > /tmp/radiogram_enccheck.tmp || echo 'stopped' > /tmp/radiogram_enccheck.tmp" shell cmd f = freefile if fileexists("/tmp/radiogram_enccheck.tmp") then open "/tmp/radiogram_enccheck.tmp" for input as #f if not eof(f) then line input #f, result end if close #f if rtrim$(result) = "stopped" then ' Encoder died, try to restart ' Check encoder log for errors dim encerr as string encerr = readandformat("/tmp/radiogram_encoder.err") if len(rtrim$(encerr)) > 0 then call setsysmsg("Stream error: " + encerr + " - Reconnecting...") else call setsysmsg("Stream encoder disconnected - Reconnecting...") end if sleep 2000 call startencoder end if end if else ' No PID found, restart encoder call startencoder end if end sub sub selectinputdevice dim selection as integer dim offset as integer dim i as integer dim k as string if indevcnt = 0 then locate 20, 1 print "No input devices found! "; sleep 1500 exit sub end if selection = curindev offset = 0 if selection >= 16 then offset = selection - 15 do cls locate 1, 1 print "Select Input Device (Arrow keys to navigate, ENTER to select, ESC to cancel)" locate 2, 1 print string$(75, "=") for i = 0 to 15 if offset + i < indevcnt then locate 3 + i, 1 if offset + i = selection then print "> "; left$(indev$(offset + i), 70) else print " "; left$(indev$(offset + i), 70) end if end if next i k = inkey$ select case k case chr$(0) + "H" ' Up if selection > 0 then selection = selection - 1 if selection < offset then offset = offset - 1 end if case chr$(0) + "P" ' Down if selection < indevcnt - 1 then selection = selection + 1 if selection >= offset + 16 then offset = offset + 1 end if case chr$(13) ' Enter curindev = selection devce$ = indev$(curindev) call saveconfig call setsysmsg("Input device set to: " + devce$) exit sub case chr$(27) ' ESC exit sub end select sleep 50 loop end sub sub selectoutputdevice dim selection as integer dim offset as integer dim i as integer dim k as string if outdevcnt = 0 then locate 20, 1 print "No output devices found! "; sleep 1500 exit sub end if selection = curoutdev offset = 0 if selection >= 16 then offset = selection - 15 do cls locate 1, 1 print "Select Output Device (Arrow keys to navigate, ENTER to select, ESC to cancel)" locate 2, 1 print string$(75, "=") for i = 0 to 15 if offset + i < outdevcnt then locate 3 + i, 1 if offset + i = selection then print "> "; left$(outdev$(offset + i), 70) else print " "; left$(outdev$(offset + i), 70) end if end if next i k = inkey$ select case k case chr$(0) + "H" ' Up if selection > 0 then selection = selection - 1 if selection < offset then offset = offset - 1 end if case chr$(0) + "P" ' Down if selection < outdevcnt - 1 then selection = selection + 1 if selection >= offset + 16 then offset = offset + 1 end if case chr$(13) ' Enter curoutdev = selection call saveconfig call setsysmsg("Output device set to: " + outdev$(curoutdev)) ' Restart encoder if streaming to apply new output device if streamon then call stopencoder sleep 500 call startencoder end if exit sub case chr$(27) ' ESC exit sub end select sleep 50 loop end sub sub configmenu dim pass as string dim temp as string ' Check password if locked if locked = -1 and configlock$ <> "" then locate 20, 1 print space$(75); locate 20, 1 print "Enter config password: "; pass = getinput(50, -1) if pass <> configlock$ then locate 20, 1 print "Incorrect password! "; sleep 1500 call io(sdate$, stime$, sfile$, pfile$, lfile$) exit sub end if end if do cls locate 1, 1 print "Configuration Menu" print string$(75, "=") print print "[1] Stream Configuration" print "[2] Select Input Device (Microphone)" print "[3] Select Output Device (Speakers/Encoder)" print "[4] Rotation/Shuffle Configuration" print "[5] MPD Configuration" print print "Current Devices:" print " Input: "; indev$(curindev) print " Output: "; outdev$(curoutdev) print print "MPD Settings:" print " Host: "; mpdhost$; " Port: "; mpdport$ print " Music Dir: "; left$(mpdmusicdir$, 50) print print "[ESC] Return to Main Screen" print temp = inkey$ select case temp case "1" call configurestream exit do case "2" call selectinputdevice exit do case "3" call selectoutputdevice exit do case "4" call configurerotation exit do case "5" call configurempd exit do case chr$(27) exit do end select sleep 50 loop call io(sdate$, stime$, sfile$, pfile$, lfile$) end sub sub configurempd dim pass as string dim temp as string dim newfolder as string cls locate 1, 1 print "MPD Configuration" print string$(75, "=") print print "Current Settings:" print " Host: "; mpdhost$ print " Port: "; mpdport$ print " Music Directory: "; mpdmusicdir$ print print "Enter new values (press ENTER to keep current):" print locate 9, 1 print "MPD Host: "; temp = getinput(100, 0) if temp <> "" then mpdhost$ = temp locate 10, 1 print "MPD Port: "; temp = getinput(10, 0) if temp <> "" then mpdport$ = temp locate 11, 1 print "MPD Music Directory: "; temp = getinput(200, 0) if temp <> "" then mpdmusicdir$ = temp locate 13, 1 print "Save and reconnect to MPD? [Y/N]: "; do temp = ucase$(inkey$) if temp = "Y" then call saveconfig call initmpdconnection call setsysmsg("MPD configuration saved and reconnected") exit do elseif temp = "N" then call saveconfig call setsysmsg("MPD configuration saved (reconnect on restart)") exit do end if sleep 50 loop end sub sub configurestream dim pass as string dim temp as string cls locate 1, 1 print "Stream Configuration" print string$(75, "=") print print "Current Settings:" print " Server URL: "; streamurl$ print " Port: "; streamport$ print " Mount Point: "; streammount$ print " Bitrate: "; streambitrate$; "k" print " Password: "; string$(len(streampass$), "*") print print "Enter new values (press ENTER to keep current):" print locate 11, 1 print "Server URL: "; temp = getinput(100, 0) if temp <> "" then streamurl$ = temp locate 12, 1 print "Port: "; temp = getinput(10, 0) if temp <> "" then streamport$ = temp locate 13, 1 print "Mount Point: "; temp = getinput(50, 0) if temp <> "" then streammount$ = temp locate 14, 1 print "Bitrate (kbps): "; temp = getinput(10, 0) if temp <> "" then streambitrate$ = temp locate 15, 1 print "Password: "; temp = getinput(50, -1) if temp <> "" then streampass$ = temp locate 17, 1 print "Enable streaming now? [Y/N]: "; do temp = ucase$(inkey$) if temp = "Y" then call saveconfig call startencoder exit do elseif temp = "N" then call saveconfig exit do end if sleep 50 loop end sub sub setconfiglock dim pass1 as string dim pass2 as string locate 20, 1 print space$(75); locate 20, 1 if configlock$ = "" then print "Enter new config password: "; pass1 = getinput(50, -1) locate 20, 1 print space$(75); locate 20, 1 print "Re-enter password: "; pass2 = getinput(50, -1) if pass1 = pass2 and pass1 <> "" then configlock$ = pass1 locked = -1 call saveconfig locate 20, 1 print "Config password set! "; else locate 20, 1 print "Passwords do not match or empty! "; end if else print "Enter current password to remove lock: "; pass1 = getinput(50, -1) if pass1 = configlock$ then configlock$ = "" locked = 0 call saveconfig locate 20, 1 print "Config password removed! "; else locate 20, 1 print "Incorrect password! "; end if end if sleep 1500 call io(sdate$, stime$, sfile$, pfile$, lfile$) end sub function browsefolder(prompt as string, startpath as string) as string dim cmd as string dim f as integer dim folders(100) as string dim foldercnt as integer dim selection as integer dim i as integer dim k as string dim offset as integer dim currentpath as string if startpath = "" then currentpath = gethomedir else currentpath = startpath end if do ' Get list of directories cmd = "find " + chr$(34) + currentpath + chr$(34) + " -maxdepth 1 -type d 2>/tmp/radiogram_find.err | sort > /tmp/radiogram_folders.tmp" shell cmd call checkcmderror("/tmp/radiogram_find.err", "Browse folders") foldercnt = 0 if fileexists("/tmp/radiogram_folders.tmp") then f = freefile open "/tmp/radiogram_folders.tmp" for input as #f while not eof(f) and foldercnt < 100 line input #f, folders(foldercnt) foldercnt = foldercnt + 1 wend close #f end if if foldercnt = 0 then browsefolder = "" exit function end if selection = 0 offset = 0 do dim displayname as string cls locate 1, 1 print prompt locate 2, 1 print "Current: "; currentpath locate 3, 1 print string$(75, "-") locate 4, 1 print "[ENTER]=Select this folder [S]=Select and return [ESC]=Cancel" locate 5, 1 print string$(75, "-") for i = 0 to 15 if offset + i < foldercnt then locate 6 + i, 1 if offset + i = selection then if folders(offset + i) = currentpath then displayname = ". (current)" else displayname = mid$(folders(offset + i), len(currentpath) + 2) if displayname = "" then displayname = folders(offset + i) end if print "> "; left$(displayname, 70) else if folders(offset + i) = currentpath then displayname = ". (current)" else displayname = mid$(folders(offset + i), len(currentpath) + 2) if displayname = "" then displayname = folders(offset + i) end if print " "; left$(displayname, 70) end if end if next i k = inkey$ select case k case chr$(0) + "H" ' Up if selection > 0 then selection = selection - 1 if selection < offset then offset = offset - 1 end if case chr$(0) + "P" ' Down if selection < foldercnt - 1 then selection = selection + 1 if selection >= offset + 16 then offset = offset + 1 end if case chr$(13) ' Enter - navigate into folder if folders(selection) <> currentpath then currentpath = folders(selection) exit do end if case "S", "s" ' Select this folder browsefolder = currentpath exit function case chr$(27) ' ESC browsefolder = "" exit function end select sleep 50 loop loop end function sub buildshufflelist dim cmd as string dim f as integer dim i as integer dim j as integer dim temp as string dim rnd1 as integer dim rnd2 as integer shufflecnt = 0 if musicfolder$ = "" then exit sub end if ' Recursively find all audio files in music folder cmd = "find " + chr$(34) + musicfolder$ + chr$(34) cmd = cmd + " -type f \( -iname '*.mp3' -o -iname '*.wav' -o -iname '*.ogg'" cmd = cmd + " -o -iname '*.flac' -o -iname '*.m4a' \) > /tmp/radiogram_music.tmp 2>/tmp/radiogram_find.err" shell cmd call checkcmderror("/tmp/radiogram_find.err", "Find music files") if fileexists("/tmp/radiogram_music.tmp") then f = freefile open "/tmp/radiogram_music.tmp" for input as #f while not eof(f) and shufflecnt < 500 line input #f, shufflelist$(shufflecnt) shufflecnt = shufflecnt + 1 wend close #f end if ' Shuffle the list using Fisher-Yates algorithm randomize timer for i = shufflecnt - 1 to 1 step -1 j = int(rnd * (i + 1)) temp = shufflelist$(i) shufflelist$(i) = shufflelist$(j) shufflelist$(j) = temp next i shuffleindex = 0 end sub sub buildjinglelist dim cmd as string dim f as integer jinglecnt = 0 if jinglefolder$ = "" then exit sub end if cmd = "find " + chr$(34) + jinglefolder$ + chr$(34) cmd = cmd + " -type f \( -iname '*.mp3' -o -iname '*.wav' -o -iname '*.ogg'" cmd = cmd + " -o -iname '*.flac' -o -iname '*.m4a' \) > /tmp/radiogram_jingles.tmp 2>/tmp/radiogram_find.err" shell cmd call checkcmderror("/tmp/radiogram_find.err", "Find jingles") if fileexists("/tmp/radiogram_jingles.tmp") then f = freefile open "/tmp/radiogram_jingles.tmp" for input as #f while not eof(f) and jinglecnt < 50 line input #f, jinglelist$(jinglecnt) jinglecnt = jinglecnt + 1 wend close #f end if end sub sub buildcommerciallist dim cmd as string dim f as integer commercialcnt = 0 if commercialfolder$ = "" then exit sub end if cmd = "find " + chr$(34) + commercialfolder$ + chr$(34) cmd = cmd + " -type f \( -iname '*.mp3' -o -iname '*.wav' -o -iname '*.ogg'" cmd = cmd + " -o -iname '*.flac' -o -iname '*.m4a' \) > /tmp/radiogram_commercials.tmp 2>/tmp/radiogram_find.err" shell cmd call checkcmderror("/tmp/radiogram_find.err", "Find commercials") if fileexists("/tmp/radiogram_commercials.tmp") then f = freefile open "/tmp/radiogram_commercials.tmp" for input as #f while not eof(f) and commercialcnt < 100 line input #f, commerciallist$(commercialcnt) commercialcnt = commercialcnt + 1 wend close #f end if end sub sub getnextshuffle dim needjingle as integer dim needcommercial as integer if shufflecnt = 0 then exit sub end if ' Check if we need a jingle needjingle = 0 if jingleinterval > 0 and jinglecnt > 0 then if (playcounter mod jingleinterval) = 0 and playcounter > 0 then needjingle = -1 end if end if ' Check if we need a commercial needcommercial = 0 if commercialinterval > 0 and commercialcnt > 0 then if (playcounter mod commercialinterval) = 0 and playcounter > 0 then if needjingle = 0 then needcommercial = -1 end if end if end if ' Play jingle if needed if needjingle then dim jidx as integer jidx = int(rnd * jinglecnt) jinglefile$ = jinglelist$(jidx) pfile$ = jinglefile$ exit sub end if ' Play commercial if needed if needcommercial then dim cidx as integer cidx = int(rnd * commercialcnt) commercialfile$ = commerciallist$(cidx) pfile$ = commercialfile$ exit sub end if ' Play next music track pfile$ = shufflelist$(shuffleindex) shuffleindex = shuffleindex + 1 if shuffleindex >= shufflecnt then shuffleindex = 0 call buildshufflelist ' Reshuffle end if playcounter = playcounter + 1 end sub sub configurerotation dim pass as string dim temp as string dim newfolder as string ' Check password if locked if locked = -1 and configlock$ <> "" then locate 20, 1 print space$(75); locate 20, 1 print "Enter config password: "; pass = getinput(50, -1) if pass <> configlock$ then locate 20, 1 print "Incorrect password! "; sleep 1500 call io(sdate$, stime$, sfile$, pfile$, lfile$) exit sub end if end if cls locate 1, 1 print "Rotation/Shuffle Configuration" print string$(75, "=") print print "Current Settings:" print " Music Folder: "; musicfolder$ print " Jingle Folder: "; jinglefolder$ print " Commercial Folder: "; commercialfolder$ print " Jingle Interval: Every "; rtrim$(str$(jingleinterval)); " songs" print " Commercial Interval: Every "; rtrim$(str$(commercialinterval)); " songs" print print "[M] Set Music Folder" print "[J] Set Jingle Folder" print "[C] Set Commercial Folder" print "[I] Set Jingle Interval" print "[O] Set Commercial Interval" print "[S] Start Shuffle Mode" print "[ESC] Return" print do temp = ucase$(inkey$) select case temp case "M" newfolder = browsefolder("Select Music Folder:", musicfolder$) if newfolder <> "" then musicfolder$ = newfolder call saveconfig call buildshufflelist end if exit do case "J" newfolder = browsefolder("Select Jingle Folder:", jinglefolder$) if newfolder <> "" then jinglefolder$ = newfolder call saveconfig call buildjinglelist end if exit do case "C" newfolder = browsefolder("Select Commercial Folder:", commercialfolder$) if newfolder <> "" then commercialfolder$ = newfolder call saveconfig call buildcommerciallist end if exit do case "I" cls locate 10, 1 print "Enter jingle interval (play jingle every X songs): "; temp = getinput(10, 0) if val(temp) > 0 then jingleinterval = val(temp) call saveconfig end if exit do case "O" cls locate 10, 1 print "Enter commercial interval (play commercial every X songs): "; temp = getinput(10, 0) if val(temp) > 0 then commercialinterval = val(temp) call saveconfig end if exit do case "S" if musicfolder$ <> "" then call buildshufflelist call buildjinglelist call buildcommerciallist shufflemode = -1 ext$ = "4" ' Switch to shuffle mode locate 20, 1 print "Shuffle mode activated! "; sleep 1000 else locate 20, 1 print "Set music folder first! "; sleep 1500 end if exit do case chr$(27) exit do end select sleep 50 loop call io(sdate$, stime$, sfile$, pfile$, lfile$) end sub sub previewpad(padnum as integer) dim info as string locate 20, 1 print space$(75); locate 20, 1 if pad$(padnum) <> "" then info = "Pad " + rtrim$(str$(padnum)) + ": " + mid$(pad$(padnum), len(padspath$) + 2) print left$(info, 75); else print "Pad "; rtrim$(str$(padnum)); " is not programmed"; end if sleep 2000 call io(sdate$, stime$, sfile$, pfile$, lfile$) end sub ' Main Program do ' Capture Keyboard Events... dim k as string k = inkey$ ' Clear system message on user action if k <> "" then call clearsysmsg end if select case k case chr$(27), "Q", "q" ' [ESC] or Q key exit program if confirmexit then ext$ = "1" end if case chr$(32) ' [SPACE] key Mic On/Off call miconoff(onoff$) if onoff$ = "ON " then ext$ = "2" ' Turn Mic On Active call playmic else ext$ = "0" ' Resume Playlist Default Active call stopmic end if case chr$(13) ' [ENTER] key schedule program call addschedule case "1" call playpad(1) case "2" call playpad(2) case "3" call playpad(3) case "4" call playpad(4) case "5" call playpad(5) case "6" call playpad(6) case "7" call playpad(7) case "8" call playpad(8) case "9" call playpad(9) case "!" ' Program Pad 1 call programpad(1) case "@" ' Program Pad 2 call programpad(2) case "#" ' Program Pad 3 call programpad(3) case "$" ' Program Pad 4 call programpad(4) case "%" ' Program Pad 5 call programpad(5) case "^" ' Program Pad 6 call programpad(6) case "&" ' Program Pad 7 call programpad(7) case "*" ' Program Pad 8 call programpad(8) case "(" ' Program Pad 9 call programpad(9) case "A", "a" ' About call setsysmsg("RadioGram v1.0a by Jason Page - Console Radio Broadcast System") case "C", "c" ' Config call configmenu case "R", "r" ' Rotation/Shuffle config call configurerotation case "H", "h" ' Help call setsysmsg("SPACE=Mic ENTER=Sched 1-9=Pads SHIFT+1-9=Prog C=Config R=Rotation E=Encoder") case "S", "s" ' Set Lock call setconfiglock case "E", "e" ' Toggle encoder if streamon = 0 then if streamurl$ <> "" then call startencoder call setsysmsg("Stream encoder started!") sleep 1000 else call setsysmsg("Configure stream settings first (press C)!") sleep 1500 end if else call stopencoder call setsysmsg("Stream encoder stopped!") sleep 1000 end if call io(sdate$, stime$, sfile$, pfile$, lfile$) case chr$(0) + "M" ' Right arrow - next input device if indevcnt > 0 then curindev = (curindev + 1) mod indevcnt devce$ = indev$(curindev) call saveconfig call setsysmsg("Input device changed to: " + devce$) end if case chr$(0) + "H" ' Up arrow - previous output device if outdevcnt > 0 then curoutdev = curoutdev - 1 if curoutdev < 0 then curoutdev = outdevcnt - 1 call setsysmsg("Output device changed to: " + outdev$(curoutdev)) end if case chr$(0) + "P" ' Down arrow - next output device if outdevcnt > 0 then curoutdev = (curoutdev + 1) mod outdevcnt call setsysmsg("Output device changed to: " + outdev$(curoutdev)) end if case chr$(0) + "K" ' Left arrow - previous input device if indevcnt > 0 then curindev = curindev - 1 if curindev < 0 then curindev = indevcnt - 1 devce$ = indev$(curindev) call saveconfig call setsysmsg("Input device changed to: " + devce$) end if ' ALT+# preview keys case chr$(0) + chr$(120) ' ALT+1 call previewpad(1) case chr$(0) + chr$(121) ' ALT+2 call previewpad(2) case chr$(0) + chr$(122) ' ALT+3 call previewpad(3) case chr$(0) + chr$(123) ' ALT+4 call previewpad(4) case chr$(0) + chr$(124) ' ALT+5 call previewpad(5) case chr$(0) + chr$(125) ' ALT+6 call previewpad(6) case chr$(0) + chr$(126) ' ALT+7 call previewpad(7) case chr$(0) + chr$(127) ' ALT+8 call previewpad(8) case chr$(0) + chr$(128) ' ALT+9 call previewpad(9) end select ' Capture Input States.... select case ext$ case "0" ' 0=Active Rotation (MPC/MPD playlist) call getnextplaylisttrack if bpfile$ <> pfile$ then bpfile$ = pfile$ call updatedisplay end if call checknextschedule if sdate$ + " " + stime$ <= date$ + " " + time$ and sfile$ <> "" then ext$ = "3" ' Switch to scheduled content end if case "1" ' 1=Exit Program call stopencoder system case "2" ' 2=Mic Active ' Microphone is already playing, just maintain status case "3" ' 3=Scheduled Content qfile$ = sfile$ call plays(qfile$) ext$ = "0" ' Return to rotation after playing scheduled item call loadschedule ' Reload to get next scheduled item case "4" ' 4=Shuffle Mode if shufflemode then call getnextshuffle if bpfile$ <> pfile$ then call plays(pfile$) bpfile$ = pfile$ call updatedisplay end if call checknextschedule if sdate$ + " " + stime$ <= date$ + " " + time$ and sfile$ <> "" then ext$ = "3" ' Switch to scheduled content end if else ext$ = "0" ' Fall back to normal rotation end if end select ' Check encoder status and reconnect if needed call checkencoder sleep 100 ' Small delay to prevent CPU hogging loop