From 8af3bc9649aa8539e094ec343b4c256f3165ae64 Mon Sep 17 00:00:00 2001 From: visionmercer <62051836+visionmercer@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:57:46 +0200 Subject: [PATCH] bah --- cimp.bas | 1010 +++++++++++++++++++++++++++--------------------------- 1 file changed, 505 insertions(+), 505 deletions(-) diff --git a/cimp.bas b/cimp.bas index 449706b..9988033 100644 --- a/cimp.bas +++ b/cimp.bas @@ -1,505 +1,505 @@ -'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 i0 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 addedwidthmaxtitlewidth then exit while - visibletitle=visibletitle+nextchar - addedwidth=addedwidth+uwidth(nextchar) - idx=idx+1 - wend - - if addedwidth=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 +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