declare library "terminkey" function terminkey%() sub echooff() sub echoon() function termwidth() end declare $console:only const KEY_UP = 1001 const KEY_DOWN = 1002 const KEY_RIGHT = 1003 const KEY_LEFT = 1004 $if WIN then Shell "chcp 65001 > nul" $end if redim file(0) as string if command$ = "" then print "please specify file to play." system end if echooff cursoroff chdir _startdir$ dim volume as single dim repeat as integer dim shuffle as integer dim nooutput as integer dim timevis as integer dim nyan as integer dim marqueeOffset as integer dim i as integer dim musichandle as long dim oldhandle as long dim keyin as integer dim playnext as integer dim state as string dim songname as string dim progress as string dim progressbar as string dim tw as integer dim fixedWidth as integer dim maxTitleWidth as integer dim visibleTitle as string dim currentSongWidth as integer dim paddedTitle as string dim paddedLength as integer dim idx as integer dim addedWidth as integer dim charIdx as integer dim nextChar as string dim marqueeFrame as integer volume=1 repeat=0 shuffle=0 nooutput=0 timevis=1 nyan=0 marqueeOffset=0 for i=1 to _commandcount select case command$(i) case "-v", "--volume" i=i+1 volume=val(command$(i)) / 100 case "-s","--shuffle" shuffle=-1 case "-r","--repeat" if command$(i+1)="1" then repeat=1 else repeat=-1 end if case "-n","--nooutput" nooutput=-1 case "-N","--nyan" nyan=-1 case else if _fileexists(command$(i)) then if lcase$(right$(command$(i), 4))=".m3u" then ParseM3U command$(i),file() else file(ubound(file))=command$(i) redim _preserve file(ubound(file)+1) end if end if end select next redim _preserve file(ubound(file)-1) if shuffle=-1 then shufflearray file() i=0 musichandle = _sndopen(file(i)) if musichandle = 0 then print "Error: could not open file "; file(i) system end if _sndvol musichandle, volume _sndplay musichandle state = "playing " songname = beforelast(".", afterlast("/", file(i))) while keyin <> 27 keyin = terminkey select case keyin case KEY_UP volume = volume + (0.01 + (volume / 10)) if volume > 1 then volume = 1 _sndvol musichandle, volume case KEY_DOWN volume = volume - (0.01 + (volume / 10)) if volume < 0 then volume = 0 _sndvol musichandle, volume case KEY_RIGHT if _sndgetpos(musichandle) + 5 < _sndlen(musichandle) then _sndsetpos musichandle, _sndgetpos(musichandle) + 5 else playnext = 1 end if case KEY_LEFT if _sndgetpos(musichandle) - 5 > 0 then _sndsetpos musichandle, _sndgetpos(musichandle) - 5 else playnext = -1 end if case asc("q") keyin = 27 case asc("z") if _sndgetpos(musichandle) > 2 then _sndsetpos musichandle, 0 else playnext = -1 end if case asc("x") if _sndplaying(musichandle) then _sndsetpos musichandle, 0 else _sndplay musichandle end if case asc("c"), asc(" ") if _sndplaying(musichandle) then _sndpause musichandle state = "paused " else _sndplay musichandle state = "playing " end if case asc("v") _sndstop musichandle state = "stopped " case asc("b") playnext = 1 case asc("t") timevis = -timevis case asc("s") shufflearray file() end select if _sndgetpos(musichandle) = _sndlen(musichandle) then playnext = 1 if playnext <> 0 then oldhandle = musichandle ' Check repeat settings before advancing the playlist index if repeat = 1 and playnext = 1 then ' Repeat current song: do not change index i playnext = 0 else ' Advance or go back in playlist i = i + playnext ' Check boundary conditions based on repeat settings if i > ubound(file) then if repeat = -1 then i = 0 ' Loop back to start if repeat playlist is enabled else goto quit ' Exit program if we reached the end of the files end if elseif i < lbound(file) then if repeat = -1 then i = ubound(file) ' Loop to the end if navigating backwards else i = lbound(file) ' Clamp to start if repeat is off end if end if end if ' Only change song if playnext wasn't canceled by a track-level repeat musichandle = _sndopen(file(i)) if musichandle <> 0 then _sndvol musichandle, volume _sndplay musichandle _sndstop oldhandle _sndclose oldhandle state = "playing " songname = beforelast(".", afterlast("/", file(i))) playnext = 0 else musichandle = oldhandle end if end if if timevis = 1 then progress = " -" + timeleft(musichandle) else progress = " " + timeelapsed(musichandle) end if if nooutput = 0 then tw = termwidth fixedWidth = UWidth(state) + UWidth(progress) maxTitleWidth = tw - fixedWidth - 2 currentSongWidth = UWidth(songname) if currentSongWidth > maxTitleWidth and maxTitleWidth > 4 then paddedTitle = songname + " " paddedLength = ulen(paddedTitle) visibleTitle = "" addedWidth = 0 idx = 0 while addedWidth < maxTitleWidth charIdx = ((marqueeOffset + idx) mod paddedLength) + 1 nextChar = umid(paddedTitle, charIdx, 1) if addedWidth + UWidth(nextChar) > maxTitleWidth then exit while visibleTitle = visibleTitle + nextChar addedWidth = addedWidth + UWidth(nextChar) idx = idx + 1 wend if addedWidth < maxTitleWidth then visibleTitle = visibleTitle + space$(maxTitleWidth - addedWidth) end if marqueeFrame = marqueeFrame + 1 if marqueeFrame mod 4 = 0 then marqueeOffset = marqueeOffset + 1 if marqueeOffset >= paddedLength then marqueeOffset = 0 end if else ' Terminal is wide enough, no scrolling needed visibleTitle = songname marqueeOffset = 0 end if ' Reset marquee offset if song changes if playnext <> 0 then marqueeOffset = 0 ' Print the text line if nyan = -1 then print termcolor(7); state; AnimatedRainbowText(visibleTitle); termcolor(7); progress; clearrest else print termcolor(7); state; termcolor(3); visibleTitle; termcolor(7); progress; clearrest end if ' Generate and print progress bar matching the layout width progressbar = bar(UWidth(state) + UWidth(visibleTitle) + UWidth(progress), (_sndgetpos(musichandle) / _sndlen(musichandle)) * 100, 11, 7) print progressbar; clearrest; cursorback; end if _limit 30 if _exit then goto quit wend quit: _sndclose musichandle print clearrest print clearrest; print cursorback; cursoron echoon system sub shufflearray (stringarray() as string) randomize timer dim n as long, j as long for n = ubound(stringarray) to 1 step -1 j = int(rnd * n) swap stringarray(n), stringarray(j) next end sub sub ParseM3U (filename$, array$()) dim i as long, f as long, count as long dim basePath$, l$, resolvedPath$ for i = len(filename$) to 1 step -1 if mid$(filename$, i, 1) = "/" or mid$(filename$, i, 1) = "\" then basePath$ = left$(filename$, i) exit for end if next f = freefile open filename$ for input as #f count = 0 do until eof(f) line input #f, l$ l$ = _trim$(l$) if len(l$) > 0 and left$(l$, 1) <> "#" then resolvedPath$ = l$ if not _fileexists(resolvedPath$) then resolvedPath$ = basePath$ + l$ end if if _fileexists(resolvedPath$) then array$(ubound(array$)) = resolvedPath$ redim _preserve array$(ubound(array$) + 1) end if end if loop close #f end sub function timeleft$ (handle&) dim seconds as integer seconds = _sndlen(handle&) - _sndgetpos(handle&) if seconds < 0 then seconds = 0 timeleft$ = right$("0" + ltrim$(str$(seconds \ 60)), 2) + ":" + right$("0" + ltrim$(str$(seconds mod 60)), 2) end function function timeelapsed$ (handle&) dim seconds as integer seconds = _sndgetpos(handle&) if seconds < 0 then seconds = 0 timeelapsed$ = right$("0" + ltrim$(str$(seconds \ 60)), 2) + ":" + right$("0" + ltrim$(str$(seconds mod 60)), 2) end function function termcolor$ (colorvalue as _unsigned long) select case colorvalue case 0 to 7 termcolor = chr$(27) + "[0;3" + _trim$(str$(colorvalue)) + "m" case 8 to 15 termcolor = chr$(27) + "[1;3" + _trim$(str$(colorvalue - 8)) + "m" case 16 to 255 termcolor = chr$(27) + "[38;5;" + _trim$(str$(colorvalue)) + "m" case is > 255 termcolor = chr$(27) + "[38;2;" + _trim$(str$(_red32(colorvalue))) + ";" + _trim$(str$(_green32(colorvalue))) + ";" + _trim$(str$(_blue32(colorvalue))) + "m" end select end function function cursorback$ () cursorback = chr$(27) + "[F" end function function clearline$ clearline = chr$(27) + "[2K" end function function clearrest$ clearrest = chr$(27) + "[K" end function sub cursoroff () print chr$(27); "[?25l"; end sub sub cursoron () print chr$(27); "[?25h"; end sub function bar$ (length as integer, percent as integer, color1 as long, color2 as long) dim done as string dim notdone as string dim i as integer for i = 1 to int((percent / 100) * length) done = done + "━" next i for i = 1 to length - int((percent / 100) * length) notdone = notdone + "━" next i bar$ = termcolor(color1) + done + termcolor(color2) + notdone end function function afterlast$ (delim as string, strng as string) afterlast = mid$(strng, _instrrev(strng, delim) + 1) end function function beforelast$ (delim as string, strng as string) beforelast = left$(strng, _instrrev(strng, delim) - 1) end function function AnimatedRainbowText$ (text$) static offset as double dim result as string dim L as long, i as long dim r as integer, g as integer, b as integer dim hue as double, f as double dim sector as integer, v as integer, p as integer, q as integer, t as integer dim rgbPart$ L = ulen(text$) if L = 0 then exit function offset = offset + 5.0 if offset >= 360 then offset = offset - 360 for i = 1 to L hue = MOD_Double(offset + ((i - 1) / L) * 360, 360) sector = int(hue / 60) f = (hue / 60) - sector v = 255: p = 0: q = 255 * (1 - f): t = 255 * f select case sector case 0: r = v: g = t: b = p case 1: r = q: g = v: b = p case 2: r = p: g = v: b = t case 3: r = p: g = q: b = v case 4: r = t: g = p: b = v case 5: r = v: g = p: b = q end select rgbPart$ = _trim$(str$(r)) + ";" + _trim$(str$(g)) + ";" + _trim$(str$(b)) result = result + chr$(27) + "[38;2;" + rgbPart$ + "m" + umid$(text$, i, 1) next i AnimatedRainbowText$ = result + chr$(27) + "[0m" end function function MOD_Double (value as double, m as double) MOD_Double = value - (m * int(value / m)) end function function ulen% (txt$) dim count%, i%, b% count% = 0 for i% = 1 to len(txt$) b% = asc(txt$, i%) if (b% and &H80) = 0 or (b% and &HC0) = &HC0 then count% = count% + 1 end if next ulen% = count% end function function umid$ (txt$, startChar%, numChars%) if startChar% < 1 or numChars% <= 0 or txt$ = "" then exit function dim byteIdx%, charCount%, startByte%, endByte%, b% byteIdx% = 1 charCount% = 0 while byteIdx% <= len(txt$) b% = asc(txt$, byteIdx%) if (b% and &H80) = 0 or (b% and &HC0) = &HC0 then charCount% = charCount% + 1 if charCount% = startChar% then startByte% = byteIdx% end if if startByte% > 0 then exit while byteIdx% = byteIdx% + 1 wend if startByte% = 0 then exit function byteIdx% = startByte% dim charsFound% charsFound% = 0 while byteIdx% <= len(txt$) b% = asc(txt$, byteIdx%) if (b% and &H80) = 0 or (b% and &HC0) = &HC0 then charsFound% = charsFound% + 1 end if if charsFound% > numChars% then exit while byteIdx% = byteIdx% + 1 wend umid$ = mid$(txt$, startByte%, byteIdx% - startByte%) end function function UWidth% (txt$) dim totalWidth%, i%, char$, cp& totalWidth% = 0 for i% = 1 to ulen(txt$) char$ = umid(txt$, i%, 1) cp& = GetCodePoint&(char$) if cp& > &H1100 then totalWidth% = totalWidth% + 2 else totalWidth% = totalWidth% + 1 end if next UWidth% = totalWidth% end function function GetCodePoint& (utf8Char$) dim lLength as integer lLength = len(utf8Char$) dim b1 as _unsigned _byte, b2 as _unsigned _byte dim b3 as _unsigned _byte, b4 as _unsigned _byte select case lLength case 1 GetCodePoint& = asc(utf8Char$, 1) case 2 b1 = asc(utf8Char$, 1): b2 = asc(utf8Char$, 2) GetCodePoint& = (b1 and &H1F) * 64 + (b2 and &H3F) case 3 b1 = asc(utf8Char$, 1): b2 = asc(utf8Char$, 2): b3 = asc(utf8Char$, 3) GetCodePoint& = (b1 and &H0F) * 4096 + (b2 and &H3F) * 64 + (b3 and &H3F) case 4 b1 = asc(utf8Char$, 1): b2 = asc(utf8Char$, 2): b3 = asc(utf8Char$, 3): b4 = asc(utf8Char$, 4) GetCodePoint& = (b1 and &H07) * 262144 + (b2 and &H3F) * 4096 + (b3 and &H3F) * 64 + (b4 and &H3F) end select end function