├── .config.sample ├── colours.bash ├── dispatch.bash ├── game.bash ├── maps.bash ├── maths.bash ├── music.bash ├── readme.md └── util.bash /.config.sample: -------------------------------------------------------------------------------- 1 | FPS=${FPS-60} 2 | mapselect=${mapselect-4} 3 | NTHR=${NTHR-8} 4 | UNBUFFERED=${UNBUFFERED-1} # any non empty string == enabled 5 | MINIMAP=${MINIMAP-asdf} # any non empty string == enabled 6 | 7 | # jemalloc usually makes bash go faster than the glibc malloc 8 | for jemalloc in /usr/lib{,64,/x86_64-linux-gnu}/libjemalloc.so; do 9 | [[ -e $jemalloc ]] && [[ ! $LD_PRELOAD = *$jemalloc* ]] && { 10 | LD_PRELOAD+=${LD_PRELOAD:+:}$jemalloc 11 | export LD_PRELOAD 12 | break 13 | } 14 | done 15 | 16 | # a full column can realistically exceed glibc's default buffering size 17 | # this is fine normally but not if our manual buffering is also disabled 18 | for stdbuf in /usr/lib{,64,exec,/*-linux-gnu}/coreutils/libstdbuf.so; do 19 | [[ -e $stdbuf ]] && [[ ! $LD_PRELOAD = *$stdbuf* ]] && { 20 | LD_PRELOAD+=${LD_PRELOAD:+:}$stdbuf 21 | export LD_PRELOAD _STDBUF_O=100000 22 | break 23 | } 24 | done 25 | -------------------------------------------------------------------------------- /colours.bash: -------------------------------------------------------------------------------- 1 | # colours are hard and i don't know what i'm doing 2 | wallsr=(0 32 34 105 107 155 238 232 202 175) 3 | wallsg=(0 223 201 195 114 30 85 116 136 229) 4 | wallsb=(0 20 135 230 230 235 196 123 34 32) 5 | 6 | wallcount=${#wallsr[@]} 7 | 8 | for ((i=0;i&"$fd" 9 | done 10 | for _ in "${oneshot[@]}"; do 11 | unset "state[$_]" 12 | done 13 | } 14 | listener () { 15 | tid=$1 16 | while read -r; do 17 | eval "$REPLY" 18 | done 19 | } 20 | declare -A state 21 | addstate () for _ do state[$_]=$_; done 22 | clearstate () for _ do unset "state[$_]"; done 23 | oneshot () { addstate "$@"; oneshot+=("$@"); } 24 | addstate sin cos mx my 25 | 26 | NTHR=${NTHR-4} 27 | 28 | run_listeners () { 29 | ((NTHR>1)) && 30 | for ((thread=0;thread>err; }; } 2>/dev/null 37 | notify[thread]=${tmp[0]} 38 | dispatch[thread]=${tmp[1]} 39 | done 40 | } 41 | -------------------------------------------------------------------------------- /game.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [[ -e .config ]] && source ./.config 4 | 5 | # bash calls REAP unconditionally after executing the command in every loop construct 6 | # https://git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd.c?id=c5c97b371044a44b701b6efa35984a3e1956344e#n3702 7 | # #define REAP() \ 8 | # do \ 9 | # { \ 10 | # if (job_control == 0 || interactive_shell == 0) \ 11 | # reap_dead_jobs (); \ 12 | # } \ 13 | # while (0) 14 | # 15 | # reap_dead_jobs calls mark_dead_jobs_as_notified 16 | # 17 | # result: if you've ever forked in this shell session, bash will block sigchld 18 | # after executing the body of every loop, and likely unblock it immediately after 19 | # 20 | # the only way to avoid this is to be in an interactive shell with job control 21 | # note: replacing loops with recursion makes things a *lot* slower 22 | # 23 | # this should be safe/correct, but i'm not fully sure 24 | # it is faster in benchmarks so it stays 25 | # 26 | # this is the dumbest thing i've ever written 27 | [[ $- = *i* && $- = *m* ]] || exec "$BASH" --norc --noediting --noprofile -im +H +o history ./game.bash 28 | 29 | mapselect=${mapselect-4} 30 | source ./maths.bash 31 | source ./maps.bash 32 | source ./util.bash 33 | source ./dispatch.bash 34 | 35 | 36 | LANG=C LC_ALL=C 37 | shopt -s extglob globasciiranges expand_aliases 38 | 39 | # for the basic bash game loop: https://gist.github.com/izabera/5e0cc5fcd598f866eb7c6cc955ef3409 40 | 41 | FPS=${FPS-30} 42 | 43 | gamesetup () { 44 | if [[ ! ( $TERM && -t 0 && -t 1 ) ]]; then 45 | echo you need a terminal to run this >&2 46 | exit 1 47 | fi 48 | 49 | stty -echo raw 50 | 51 | # this expects a bunch of modes to always be supported, and queries support for some less common ones 52 | printf %b%.b \ 53 | '\e[?1049h' 'alt screen on' \ 54 | '\e[?25l' 'cursor off' \ 55 | '\e[?1004h' 'report focus' \ 56 | '\e[m' 'reset colours' \ 57 | '\e[2J' 'erase screen' \ 58 | '\e[?u' 'kitty kbd proto' \ 59 | '\e[?2026$p' 'synchronised output' \ 60 | '\e[38;5;123m' '256 colour fg' \ 61 | '\e[38;2;45;67;89m' 'truecolor fg' \ 62 | '\eP$qm\x1b\\' 'decrqss m' \ 63 | '\e[9999;9999H' 'move to bottom right' 64 | 65 | # this used to query da1 as a flush, intended to get the terminal to reply 66 | # *something*, but alacritty 0.15 on windows sometimes replies with da1 67 | # before csi?u, which messes everything else up, so some some small delay 68 | # was added as a workaround. unfortunately it really just seems to be slow 69 | # at replying to csi?u. we can even query dsr multiple times after csi?u, 70 | # and get both replies before dsr, so the delay stays 71 | sleep .05 72 | 73 | printf %b%.b \ 74 | '\e[6n' 'query position' \ 75 | '\e[m' 'reset colours again' 76 | 77 | read -rdR 78 | # see tests in https://gist.github.com/izabera/3d1e5dfabbe80b3f5f2e50ec6f56eadb 79 | ! [[ $REPLY = *u* && ! $NOKITTY ]]; kitty=$? 80 | ! [[ $REPLY = *'2026;2'* ]]; sync=$? 81 | ! [[ $COLORTERM = *@(24bit|truecolor)* || $REPLY = *38*2*45*67*89*m* ]]; truecolor=$? 82 | 83 | # disambiguate 1 84 | # eventtypes 2 85 | # altkeys 4 86 | # allescapes 8 87 | # associatedtext 16 88 | ((kitty)) && printf '\e[>11u' 89 | 90 | exitfunc () { 91 | dispatch exit 92 | wait 93 | 94 | ((kitty)) && printf '\e[/dev/tty 95 | printf %b%.b >/dev/tty \ 96 | '\e[?1004l' 'focus off' \ 97 | '\e[?25h' 'cursor on' \ 98 | '\e[?1049l' 'alt screen off' 99 | 100 | stty echo sane 101 | dumpstats 102 | } 103 | trap exitfunc exit 104 | 105 | hblock=$'▀\e[D\e[B' # halfblock 106 | sblock=$' \e[D\e[B' # "space"block (yes i'm very good at naming things) 107 | hlen=${#hblock} 108 | 109 | declare -gA column 110 | # size-dependent vars 111 | update_sizes () { 112 | # see dumbdrawcol 113 | for ((i=1;i<=rows;i++)) do column[$i]=${column[$((i-1))]}$sblock; done 114 | } 115 | 116 | get_term_size() { 117 | __winch=0 118 | rows=${1%;*} cols=${1#*;} 119 | dispatch "${rows@A} ${cols@A}" 120 | update_sizes 121 | dispatch update_sizes 122 | } 123 | 124 | REPLY=${REPLY%%R*} REPLY=${REPLY##*$'\e['} 125 | get_term_size "$REPLY" 126 | trap __winch=1 WINCH 127 | 128 | declare -gA __keys=( 129 | [A]=UP [B]=DOWN [C]=RIGHT [D]=LEFT 130 | [1A]=UP [1B]=DOWN [1C]=RIGHT [1D]=LEFT # makes kitty slightly easier 131 | [' ']=SPACE [$'\t']=TAB 132 | [$'\n']=ENTER [$'\r']=ENTER 133 | [$'\177']=BACKSLASH [$'\b']=BACKSLASH 134 | ) 135 | for i in {32..126}; do 136 | printf -v oct %03o "$i" 137 | printf -v "__keys[${i}u]" "\\$oct" 138 | done 139 | declare -gA PRESSED=() 140 | FRAME=0 START=${EPOCHREALTIME/.} TOTALSKIPPED=0 FOCUS=1 141 | 142 | # somehow the least painful way to parse this stuff 143 | __kittyregex='^..([0-9]*)(;(([^:]*)(:([0-9]*))?))?(.)(.*)' 144 | # <--1---> key code 145 | # <--------2------------->? 146 | # <-------3-----------> 147 | # <--4--> modifier 148 | # <----5---->? 149 | # <---6--> event type 150 | # <7> final character 151 | # <8-> rest 152 | deltat=$((1000000/FPS)) 153 | nextframe() { 154 | local deadline now tmout tmp 155 | if ((__winch)); then printf '\e[9999;9999H\e[6n'; fi 156 | if ((SKIPPED=0,(now=${EPOCHREALTIME/.})>=(deadline=START+ ++FRAME*deltat))); then 157 | # you fucked up, your game logic can't run at $FPS 158 | ((deadline=START+(FRAME+=(SKIPPED=(now-deadline+deltat-1)/deltat))*deltat,TOTALSKIPPED+=SKIPPED)) 159 | fi 160 | while ((now<--ceiling--><--------wall-------><-------floor-------> 213 | alias drawcol='printf "\e[1;%sH\e[48;5;%sm%s\e[38;5;%s;48;5;%sm%s\e[38;5;%s;48;5;%sm%s"' 214 | ((truecolor)) && alias drawcol=${BASH_ALIASES[drawcol]//5/2} 215 | 216 | # dumb function that doesn't know where the horizon is 217 | # two versions because one case is painful 218 | # $1 column 219 | # $2 colour 220 | # $3 starting (half)row 221 | # $4 length 222 | dumbdrawcol () { 223 | # this does not deal correctly with height == 0, so make sure all walls are close by 224 | ((hihalf=$3%2,lohalf=($3+$4)%2, 225 | ceiling=$3/2, 226 | wall=($4-hihalf-lohalf)/2, 227 | floor=rows-($3/2+wall+hihalf+lohalf))) 228 | drawcol \ 229 | "$1" \ 230 | "$sky" "${column[$ceiling]}" \ 231 | "$sky" "$2" "${hblock[!hihalf]}${column[$wall]}" \ 232 | "$2" "$grass" "${hblock[!lohalf]}${column[$floor]}" 233 | } 234 | 235 | 236 | # the wall hit calculation is a horrible recursive expansion 237 | # it is a lot faster than a loop 238 | # when displaying colours, it also stores the right colour in the variable w 239 | hit='(side=sdxfar?far:dist))) 266 | 267 | # this is not at all how light works but it looks ok 268 | # dist 0 -> colour 100% 269 | # dist 11.5 -> colour 0% 270 | 271 | # depth map 272 | depthmap 256col dumbdrawcol "$((x+1))" "$((255-2*dist/scale))" "$(((rows*2-h)/2))" "$h" 273 | depthmap 24bit dumbdrawcol "$((x+1))" "$((z=255-22*dist/scale));$z;$z" "$(((rows*2-h)/2))" "$h" 274 | 275 | # wall colours 276 | nodepthmap 256col dumbdrawcol "$((x+1))" "$((16+wallsr[w]*fdist/far*6/256*36+wallsg[w]*fdist/far*6/256*6+wallsb[w]*fdist/far*6/256))" "$(((rows*2-h)/2))" "$h" 277 | nodepthmap 24bit dumbdrawcol "$((x+1))" "$((wallsr[w]*fdist/far));$((wallsg[w]*fdist/far));$((wallsb[w]*fdist/far))" "$(((rows*2-h)/2))" "$h" 278 | done 279 | } 280 | 281 | [[ $UNBUFFERED ]]; aliasing "$?" unbuffered buffered 282 | ((sync)); aliasing "$?" sync 283 | ((NTHR>1)); aliasing "$?" multithread singlethread 284 | 285 | # maybe this should be disabled if sync is off and we're in multithreaded mode 286 | [[ $MINIMAP ]]; aliasing "$?" minimap 287 | 288 | for i in "${!map[@]}"; do 289 | mapc[i*3+0]=${wallsr[mapt[i]]} 290 | mapc[i*3+1]=${wallsg[mapt[i]]} 291 | mapc[i*3+2]=${wallsb[mapt[i]]} 292 | done 293 | 294 | cellfmt=$'\e[38;2;%d;%d;%d;48;2;%d;%d;%dm▀' 295 | printf -v mapfmt '%*s' "$mapw" 296 | mapfmt=${mapfmt// /$cellfmt}$'\r\e[B' 297 | printf -v mapcache "$mapfmt" "${mapc[@]}" 298 | 299 | minimap='row=mx/scale,odd=row%2,row=row/2*2,col=my/scale, 300 | fgidx=row*mapw+col,bgidx=(row+1)*mapw+col, 301 | fgr=wallsr[odd?map[fgidx]:2],fgg=wallsg[odd?map[fgidx]:2],fgb=wallsb[odd?map[fgidx]:2], 302 | bgr=wallsr[odd?2:map[bgidx]],bgg=wallsg[odd?2:map[bgidx]],bgb=wallsb[odd?2:map[bgidx]]' 303 | minimapfmt="%s\e[%dA\e[%dC$cellfmt\e[m" 304 | 305 | exec {outfile}>"${OUTFILE-/dev/tty}" 306 | declare -A frametimes 307 | drawframe () { 308 | frame_start=${EPOCHREALTIME/.} 309 | sync printf '\e[?2026h' 310 | 311 | multithread buffered dispatch 'drawrays > buffered."$tid"; printf x' 312 | multithread unbuffered dispatch 'drawrays >&"$outfile"; printf x' 313 | 314 | minimap ((minimap)) 315 | 316 | multithread for ((t=0;t&"$outfile" 336 | ((FRAME--)) 337 | exit 338 | fi 339 | 340 | speed=0 rspeed=0 341 | 342 | 343 | bomb=4 344 | addstate walls{r,g,b}\[{"$bomb","$((wallcount+bomb))"}]{,} 345 | addstate fov 346 | 347 | collision='(map[mx/scale*mapw+my/scale]|1)==1' 348 | move='t=pos*speed*deltat/scale**2' 349 | smoothing='speed=speed*3**(deltat/15000)/4**(deltat/15000)' 350 | 351 | printf -v movement %s, \ 352 | "${move//pos/mx+cos}" "${collision/mx/t}&&(mx=t)" \ 353 | "${move//pos/my+sin}" "${collision/my/t}&&(my=t)" \ 354 | "$smoothing" "${smoothing//speed/rspeed}" 355 | movement=${movement%,} 356 | 357 | bombtimer='wallsg[bomb]=wallsg[bomb+wallcount]=(FRAME*deltat/2500)%255' 358 | bombtimer+=,${bombtimer//wallsg/wallsb} 359 | bombtimer+=,'wallsr[bomb]=200,wallsr[bomb+wallcount]=250' 360 | 361 | (( 362 | scale_2=scale/2, 363 | scale_5=scale/5, 364 | scale_10=scale/10, 365 | scale_100=scale/100, 366 | scale2=scale*2, 367 | scale5=scale*5, 368 | scale10=scale*10, 369 | scale100=scale*100 370 | )) 371 | while nextframe; do 372 | for k in "${INPUT[@]}"; do 373 | case $k in 374 | q) break 2 ;; 375 | LEFT) rspeed=$scale_5;; 376 | RIGHT) rspeed=-$scale_5;; 377 | UP) speed=$scale_2;; 378 | DOWN) speed=-$scale_2;; 379 | j) ((fovscale_5&&(fov=fov*95/100))); oneshot fov ;; 381 | esac 382 | done 383 | 384 | ((angle+=rspeed*deltat/scale,angle>=pi2&&(angle-=pi2),angle<0&&(angle+=pi2))) 385 | sincos "$angle" 386 | 387 | ((movement,bombtimer)) 388 | 389 | drawframe >&"$outfile" 390 | done 391 | -------------------------------------------------------------------------------- /maps.bash: -------------------------------------------------------------------------------- 1 | if ((mapselect==1)); then 2 | map=( 3 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 5 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 6 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 7 | 1 0 0 0 0 0 2 2 2 2 2 0 0 0 0 3 0 3 0 3 0 0 0 1 8 | 1 0 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 1 9 | 1 0 0 0 0 0 2 0 0 0 2 0 0 0 0 3 0 0 0 3 0 0 0 1 10 | 1 0 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 1 11 | 1 0 0 0 0 0 2 2 0 2 2 0 0 0 0 3 0 3 0 3 0 0 0 1 12 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 13 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 14 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 15 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 16 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 17 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 18 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 19 | 1 4 4 4 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 20 | 1 4 0 4 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 21 | 1 4 0 0 0 0 5 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 22 | 1 4 0 4 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 23 | 1 4 0 4 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 24 | 1 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 25 | 1 4 4 4 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 26 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 27 | ) 28 | mapw=24 maph=24 29 | mx=$((22*scale)) my=$((maph/2*scale)) 30 | angle=pi 31 | elif ((mapselect==2)); then 32 | 33 | map=( 34 | 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 6 6 6 6 6 6 6 6 6 5 5 5 5 6 6 6 6 6 6 6 35 | 5 0 0 0 0 0 0 0 0 6 6 6 0 6 0 6 0 6 5 0 0 0 0 0 6 0 0 0 6 6 6 6 5 0 0 0 0 7 6 6 6 6 6 6 36 | 5 0 8 0 8 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 8 8 0 0 6 0 0 0 6 6 6 6 7 0 8 0 0 7 0 0 0 0 0 6 37 | 5 0 0 0 0 0 0 0 0 6 6 6 6 6 0 6 0 6 8 8 8 0 0 0 6 0 7 0 6 6 6 7 0 0 8 8 0 7 0 0 0 0 0 6 38 | 5 0 8 0 8 0 8 0 0 0 0 0 0 6 0 6 0 6 6 6 8 0 0 6 6 0 7 0 6 6 7 0 0 0 0 0 0 7 0 0 0 0 0 6 39 | 5 0 0 0 0 0 0 0 0 5 5 5 0 6 6 6 0 0 6 6 8 0 0 0 0 0 7 0 6 5 0 0 0 7 5 5 5 6 6 6 0 6 6 6 40 | 5 0 8 0 8 0 8 0 0 5 0 5 0 6 6 6 7 0 7 6 6 5 5 5 5 5 7 0 6 7 0 0 7 5 0 0 0 6 6 6 0 6 6 6 41 | 5 0 0 0 0 5 0 0 0 5 0 0 0 6 6 7 0 0 0 7 6 6 6 6 5 0 0 0 6 0 0 7 6 6 7 7 0 6 6 6 0 6 6 6 42 | 5 5 5 0 5 5 6 6 0 5 5 5 0 6 7 0 0 0 0 0 7 6 6 6 5 0 6 6 6 0 0 5 6 6 6 7 0 6 6 0 0 6 6 6 43 | 6 6 8 0 8 6 6 6 0 0 0 0 0 6 7 0 0 8 0 0 7 6 6 6 5 0 0 0 0 0 7 6 6 6 6 7 0 6 0 0 0 6 6 6 44 | 6 7 0 0 0 7 7 6 6 6 6 6 6 6 6 7 0 0 0 7 6 6 6 6 5 0 0 0 0 7 6 6 6 6 6 7 0 0 0 0 6 6 6 6 45 | 6 7 0 0 0 0 7 6 6 6 6 6 6 6 6 6 5 5 5 6 6 6 6 6 6 7 7 7 0 0 5 6 6 6 6 7 0 0 0 6 6 6 6 6 46 | 7 0 0 0 0 0 0 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 5 0 0 5 6 6 6 7 0 0 6 6 6 6 6 6 47 | 7 0 0 7 7 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 6 5 0 0 5 6 6 7 0 6 6 6 6 6 6 6 48 | 6 7 7 7 6 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 6 6 5 0 0 7 6 7 0 6 6 6 6 6 6 6 49 | 6 6 6 6 0 0 0 5 0 0 8 0 0 0 0 0 0 0 0 0 8 0 0 6 6 6 6 6 6 6 6 5 0 5 6 7 0 6 6 6 6 6 6 6 50 | 6 0 0 0 0 8 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0 0 6 7 0 0 6 6 6 6 6 6 51 | 6 0 6 6 0 8 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 6 7 0 0 0 0 0 7 0 0 0 6 6 6 6 6 52 | 6 6 6 0 0 0 0 5 0 0 0 0 0 3 3 0 3 3 0 0 0 0 0 6 6 6 6 6 6 8 0 0 0 0 0 0 0 0 0 0 0 6 6 6 53 | 6 6 0 0 0 6 6 5 0 0 0 0 0 3 0 0 0 3 0 0 0 0 0 6 6 6 6 6 6 7 0 0 0 0 0 7 5 7 6 0 0 0 6 6 54 | 6 0 0 0 6 6 6 5 0 0 0 0 0 3 0 0 0 3 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0 0 6 6 6 6 6 0 0 0 6 6 55 | 6 0 6 6 6 6 6 5 0 0 0 0 0 3 0 0 0 3 0 0 0 0 0 6 6 6 6 6 6 6 6 5 0 5 6 5 5 6 6 6 0 6 6 6 56 | 6 0 6 6 0 0 0 5 0 0 0 0 0 3 3 3 3 3 0 0 0 0 0 6 6 6 5 6 6 6 0 5 0 5 5 0 5 6 6 6 0 6 6 6 57 | 6 0 6 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 5 5 0 5 5 6 0 0 0 0 0 0 5 6 6 6 0 6 6 6 58 | 6 0 6 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 5 5 0 0 0 5 5 6 6 6 6 0 6 6 6 59 | 6 0 0 0 0 0 0 5 0 0 8 0 0 0 0 0 0 0 0 0 8 0 0 6 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 0 0 0 6 6 60 | 6 5 5 5 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 6 6 0 0 0 5 6 6 6 6 6 0 6 6 6 61 | 6 5 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 5 5 0 5 5 6 6 6 0 0 0 5 6 6 6 6 0 6 6 6 62 | 6 5 0 0 0 0 0 5 6 6 6 6 6 6 8 0 8 6 6 6 6 6 6 6 6 5 0 5 6 6 6 6 5 6 5 6 6 6 6 6 0 6 6 6 63 | 6 5 0 0 0 6 6 6 6 6 6 6 6 6 8 0 8 6 6 6 6 6 6 5 5 5 0 5 5 5 5 5 5 5 5 6 6 6 6 6 0 6 6 6 64 | 5 0 0 0 6 6 6 6 6 6 6 6 6 6 8 0 8 6 6 6 6 6 6 0 0 0 0 0 0 0 0 0 0 0 5 6 6 6 6 0 0 0 6 6 65 | 7 0 7 6 6 6 6 6 6 6 6 6 6 6 8 0 8 6 6 6 6 6 6 0 0 0 0 0 0 0 0 0 0 0 5 6 6 6 0 0 0 0 0 6 66 | 7 0 7 6 6 6 6 6 6 6 6 6 6 6 8 0 8 6 6 6 6 6 6 0 0 0 0 0 0 0 0 0 0 0 0 5 6 6 0 0 0 0 0 6 67 | 7 0 7 6 6 6 6 6 6 6 6 6 6 6 8 0 8 6 6 6 6 6 6 0 0 0 0 0 0 0 0 0 0 0 0 5 6 6 0 0 0 0 0 6 68 | 7 0 7 6 0 0 0 0 0 0 0 0 6 6 6 0 6 6 6 6 6 6 6 0 0 0 0 0 0 0 0 0 0 0 0 5 6 6 6 0 0 0 6 6 69 | 7 0 7 6 0 0 0 8 8 8 8 8 6 6 6 0 6 6 6 6 6 6 6 5 5 5 5 5 0 0 0 0 0 0 0 5 6 6 6 6 0 6 6 6 70 | 7 0 7 3 0 0 0 8 0 0 0 8 6 6 6 0 6 6 6 6 6 6 6 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 6 71 | 7 0 0 0 0 0 0 0 0 0 0 8 6 6 6 0 6 6 6 6 6 6 6 0 0 0 0 5 0 0 0 0 0 0 0 5 6 6 6 8 0 8 6 6 72 | 7 7 7 3 0 0 0 8 0 0 0 8 6 6 6 0 6 6 6 6 6 6 6 0 0 0 0 5 0 0 0 0 0 0 0 5 6 6 8 0 0 0 8 6 73 | 6 6 7 6 0 0 0 8 8 8 8 8 6 6 6 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 5 6 8 0 0 0 0 0 8 74 | 6 6 7 6 0 0 0 0 0 0 0 0 6 6 6 6 6 6 6 6 6 6 5 0 0 0 0 0 0 0 0 0 0 0 7 6 6 8 0 0 0 0 0 8 75 | 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 3 7 7 7 7 7 7 6 6 6 8 0 0 0 0 0 8 76 | 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 8 0 0 0 8 6 77 | 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 8 8 8 6 6 78 | ) 79 | mapw=44 maph=44 80 | ((mx=375*scale/10)) 81 | ((my=95*scale/10)) 82 | angle=$((pi2-pi/2)) 83 | elif ((mapselect==3)); then 84 | map=( 85 | 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 86 | 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 87 | 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 88 | 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 89 | 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 90 | 9 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 9 91 | 9 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 9 92 | 9 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 9 93 | 9 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 9 94 | 9 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 9 95 | 9 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 9 96 | 9 0 0 0 0 0 0 0 0 0 0 7 0 0 0 0 0 9 97 | 9 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 9 98 | 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 99 | 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 100 | 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 101 | 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 102 | 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 103 | ) 104 | mapw=18 maph=18 105 | ((mx=scale,my=(maph-1)*scale)) 106 | angle=$((pi2-pi/4)) 107 | else 108 | map=( 109 | 2 2 2 3 3 4 4 5 6 7 7 7 7 7 9 9 9 9 9 9 9 9 9 110 | 2 0 0 0 0 0 0 0 6 0 0 0 0 8 0 0 9 9 0 0 0 0 9 111 | 2 2 2 0 0 4 0 0 6 0 7 0 0 8 8 8 8 9 0 0 0 0 9 112 | 9 0 3 0 0 4 0 0 0 0 7 0 0 8 0 0 0 0 0 0 0 0 9 113 | 9 0 3 0 0 4 0 0 6 0 7 0 0 0 0 0 8 9 0 0 0 0 9 114 | 9 0 3 0 0 4 0 0 6 0 7 0 0 8 8 8 8 9 0 0 0 0 9 115 | 3 3 3 0 0 0 0 0 6 0 0 0 0 8 0 0 0 9 0 0 0 0 9 116 | 3 0 3 0 0 0 0 0 6 7 7 7 7 8 0 0 0 9 9 9 9 9 9 117 | 3 0 3 3 5 5 6 6 6 7 7 0 0 0 0 0 0 0 0 0 0 0 9 118 | 3 0 4 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 9 119 | 3 0 0 0 0 0 0 0 6 0 7 0 0 0 0 0 0 0 0 0 0 0 9 120 | 4 4 4 4 5 5 6 6 6 0 7 0 0 0 0 0 0 0 0 0 0 0 9 121 | 9 0 0 0 0 0 0 0 8 0 8 0 0 0 0 0 0 0 0 0 0 0 9 122 | 9 0 0 0 0 0 0 0 8 0 8 0 0 0 0 0 0 0 0 0 0 0 9 123 | 9 0 0 0 0 0 0 8 8 0 8 8 0 0 0 0 0 0 0 0 0 0 9 124 | 9 0 0 0 0 0 0 9 0 0 0 9 0 0 0 0 0 0 0 0 0 0 9 125 | 9 0 0 0 0 0 0 9 0 0 0 9 0 0 0 0 0 0 0 0 0 0 9 126 | 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 127 | ) 128 | mapw=23 maph=18 129 | ((mx=my=scale*3/2)) 130 | angle=$((pi/2)) 131 | fi 132 | for ((i=0;i/dev/null; then 47 | mpv --no-video \ 48 | --demuxer=rawaudio \ 49 | --demuxer-rawaudio-format=s16le \ 50 | --demuxer-rawaudio-rate="$samples" \ 51 | --demuxer-rawaudio-channels=1 \ 52 | --no-terminal - 53 | elif command -v ffplay >/dev/null; then 54 | ffplay \ 55 | -v -8 -nodisp -nostats -hide_banner -autoexit \ 56 | -f s16le -ar "$samples" -ch_layout mono - 57 | elif command -v aplay >/dev/null; then 58 | aplay -f S16_LE -r "$samples" - 59 | fi 60 | else 61 | cat 62 | fi 63 | } 64 | 65 | scale=$((32*1024-1)) 66 | pi=102941 67 | pi2=205881 68 | pisq=10596760227 69 | pi_2=51470 70 | pi3_2=154411 71 | 72 | cosine='(pisq-4*y*y)*scale/(pisq+y*y)' 73 | 74 | # yes this is 5 quadrants because pi_2*4==pi2-1 75 | # tom's idea btw 76 | coscalc=(x pi-x x-pi x-pi2 x-pi2) 77 | cosmult=(1 -1 -1 1 1 ) 78 | cos=( 79 | "y=${coscalc[0]},${cosmult[0]%1}cosine" 80 | "y=${coscalc[1]},${cosmult[1]%1}cosine" 81 | "y=${coscalc[2]},${cosmult[2]%1}cosine" 82 | "y=${coscalc[3]},${cosmult[3]%1}cosine" 83 | "y=${coscalc[4]},${cosmult[4]%1}cosine" 84 | ) 85 | 86 | 87 | umax=$((64*1024)) 88 | 89 | declare -A binary 90 | for ((i=0;i>8))" 92 | binary[$i]=$tmp binary[$((i-umax))]=$tmp 93 | done 94 | 95 | # notes = ['c', 'cs', 'd', 'ds', 'e', 'f', 'fs', 'g', 'gs', 'a', 'as', 'b'] 96 | # scale = 32 * 1024 - 1 97 | # a4 = 440 * scale 98 | # a4idx = 12 * 4 + 9 99 | # for i in range(8*12): 100 | # print(f"[{notes[i%12]}{i//12}]={int(2 ** ((i-a4idx)/12) * a4)}") 101 | 102 | declare -A notes=( 103 | [c0]=535792 [cs0]=567652 [d0]=601407 [ds0]=637168 [e0]=675056 [f0]=715197 [fs0]=757725 [g0]=802782 [gs0]=850518 [a0]=901092 [as0]=954674 [b0]=1011442 104 | [c1]=1071585 [cs1]=1135305 [d1]=1202814 [ds1]=1274337 [e1]=1350113 [f1]=1430395 [fs1]=1515450 [g1]=1605564 [gs1]=1701036 [a1]=1802185 [as1]=1909348 [b1]=2022884 105 | [c2]=2143171 [cs2]=2270610 [d2]=2405628 [ds2]=2548674 [e2]=2700226 [f2]=2860790 [fs2]=3030901 [g2]=3211128 [gs2]=3402072 [a2]=3604370 [as2]=3818696 [b2]=4045768 106 | [c3]=4286342 [cs3]=4541221 [d3]=4811256 [ds3]=5097348 [e3]=5400453 [f3]=5721580 [fs3]=6061803 [g3]=6422257 [gs3]=6804144 [a3]=7208740 [as3]=7637393 [b3]=8091537 107 | [c4]=8572684 [cs4]=9082443 [d4]=9622513 [ds4]=10194697 [e4]=10800906 [f4]=11443161 [fs4]=12123607 [g4]=12844514 [gs4]=13608289 [a4]=14417480 [as4]=15274787 [b4]=16183074 108 | [c5]=17145369 [cs5]=18164886 [d5]=19245026 [ds5]=20389395 [e5]=21601812 [f5]=22886322 [fs5]=24247214 [g5]=25689028 [gs5]=27216578 [a5]=28834960 [as5]=30549575 [b5]=32366148 109 | [c6]=34290739 [cs6]=36329773 [d6]=38490053 [ds6]=40778791 [e6]=43203624 [f6]=45772645 [fs6]=48494428 [g6]=51378057 [gs6]=54433156 [a6]=57669920 [as6]=61099151 [b6]=64732296 110 | [c7]=68581479 [cs7]=72659546 [d7]=76980107 [ds7]=81557583 [e7]=86407249 [f7]=91545291 [fs7]=96988857 [g7]=102756115 [gs7]=108866312 [a7]=115339840 [as7]=122198303 [b7]=129464593 111 | ) 112 | 113 | 114 | # todo: make decay optional and calculate the right lengths on each note 115 | envelope=' 116 | j < attack_e ? j*scale/attack : 117 | j < decay_e ? scale+(j-attack_e)*decrease/decay : 118 | j < release_s ? sustain : 119 | sustain-(j-release_s)*sustain/release 120 | ' 121 | envelope=${envelope//[[:space:]]} 122 | 123 | 124 | (( 125 | attack = samples*adsr[0]/1000, 126 | decay = samples*adsr[1]/1000, 127 | sustain = scale *adsr[2]/100 , 128 | release = samples*adsr[3]/1000, 129 | 130 | attack_e = decay_s = attack, 131 | decay_e = sustain_s = decay + attack_e, 132 | 133 | decrease = sustain - scale, 134 | ssamples = samples * scale 135 | )) 136 | 137 | harmonics="sample+=cos[(x=(freq*pi2*t/ssamples)%pi2)/pi_2]" 138 | hnum=${hnum-4} 139 | for ((i=2;i<=hnum;i++)) do 140 | harmonics+=",sample+=cos[(x=($i*freq*pi2*t/ssamples)%pi2)/pi_2]/$((2**(i-1)))" 141 | done 142 | 143 | IFS=+ 144 | set -f 145 | 146 | for note in "${song[@]}"; do 147 | [[ $note =~ ([^:]*)(:(.*))? ]] 148 | 149 | freqs=() 150 | for note in ${BASH_REMATCH[1]}; do 151 | freqs+=(${notes[$note]}) 152 | done 153 | 154 | notel=$((samples*inter*${BASH_REMATCH[3]:-100}/100000)) \ 155 | release_s=$((notel-release)) 156 | 157 | for ((j=0;j}" 82 | 83 | affinity=$(taskset -pc "$$" 2>/dev/null) 84 | affinity=${affinity##*: } 85 | info 'cpu affinity' "${affinity:-unknown}" 86 | 87 | # sometimes useful but they don't really need to be accessed at runtime 88 | # { 89 | # comment=$(readelf -p .comment "$BASH") 90 | # cmdline=$(readelf -p .GCC.command.line "$BASH") 91 | # } 2>/dev/null 92 | # info '.comment' "${comment:-empty}" 93 | # info '.GCC.command.line' "${cmdline:-empty}" 94 | 95 | title 'terminal info' 96 | info '$TERM' "$TERM" 97 | info '$COLORTERM' "$COLORTERM" 98 | if [[ $DISPLAY ]] && type xprop &>/dev/null; then 99 | IFS=' ' read -r _ _ _ _ self _ < <(xprop -root _NET_ACTIVE_WINDOW) 100 | IFS='"' read -r _ _ _ class _ < <(xprop -id "$self" WM_CLASS) 101 | else 102 | class=unknown 103 | fi 104 | info 'wm class' "$class" 105 | 106 | colours=(256 truecolor) 107 | 108 | info colours "${colours[truecolor]}" 109 | info 'kitty keyboard proto support' "${tf[kitty]}" 110 | info 'synchronised output support' "${tf[sync]}" 111 | 112 | # r | g | b | rdx | gdx | bdx 113 | # ------------+-----------+-----------+-----+-----+---- 114 | # max | 0->max | 0 | 0 | 1 | 0 115 | # max->0 | max | 0 | -1 | 0 | 0 116 | # 0 | max | 0->max | 0 | 0 | 1 117 | # 0 | max->0 | max | 0 | -1 | 0 118 | # 0->max | 0 | max | 1 | 0 | 0 119 | # max | 0 | max->0 | 0 | 0 | -1 120 | 121 | # walk around an rgb cube on the edges that don't include black or white 122 | walkcube () { 123 | local max=$(($1-1)) fmt=$2 124 | local rdx=4 gdx=2 bdx=0 125 | local r=max g=0 b=0 126 | local i j 127 | 128 | for (( i = 0; i < 6; i++ )) do 129 | for (( j = 0; j < max; j++ )) do 130 | printf -v 'hues[i*max+j]' "$fmt" \ 131 | "$(( r += (rdx%6==2)-(rdx%6==5) ))" \ 132 | "$(( g += (gdx%6==2)-(gdx%6==5) ))" \ 133 | "$(( b += (bdx%6==2)-(bdx%6==5) ))" 134 | done 135 | (( rdx = (rdx+1) % 6, gdx = (gdx+1) % 6, bdx = (bdx+1) % 6 )) 136 | done 137 | } 138 | 139 | declare -ai hues 140 | walkcube 6 '16 + %d*6*6 + %d*6 + %d' 141 | 142 | printf '256 colour test: ' 143 | printf '\e[38;5;%s;48;5;%sm▌' "${hues[@]}" 144 | printf '\e[m\n' 145 | 146 | unset hues 147 | walkcube 256 '%d;%d;%d' 148 | 149 | h=${#hues[@]} 150 | for (( i = 0; i < h && h>cols; i++ )) do 151 | (( i % (h/cols) )) && unset 'hues[i]' 152 | done 153 | 154 | printf '24bit colour test: ' 155 | printf '\e[38;2;%s;48;2;%sm▌' "${hues[@]}" 156 | printf '\e[m\n' 157 | ((truecolor)) || note 'if the 24bit colour test looks ok, set COLORTERM=truecolor' 158 | } 159 | 160 | drawmsgs () { 161 | set -- "${msgs[@]:(${#msgs[@]}>5?-5:0):5}" 162 | printf '\e[m\e[%s;2H' "$((rows+2-$#))" 163 | printf "%.$((cols+5))s\r\e[B\e[C" "$@" 164 | } 165 | declare -A infos 166 | drawinfo () { 167 | ((${#infos[@]}))||return 168 | printf '\e[1;1H\e[m' 169 | printf '%s=%s\t' "${infos[@]@k}" 170 | } 171 | drawborder () { 172 | local i 173 | printf '\e[H' 174 | printf '+%s+\e[K\r\e[B' "${hspaces// /-}" 175 | for ((i=1;i<=rows;i++)) do 176 | printf '|\e[%sC|%d\e[K\r\e[B' "$cols" "$i" 177 | done 178 | printf '+%s+\e[K\r\e[B' "${hspaces// /-}" 179 | } 180 | 181 | infos=() 182 | msg= msgs=() 183 | error() { printf -v 'msgs[msg++]' '\e[31m%(%T)T [ERROR]: %s\e[m' -1 "$1"; } 184 | warn () { printf -v 'msgs[msg++]' '\e[33m%(%T)T [WARNING]: %s\e[m' -1 "$1"; } 185 | info () { printf -v 'msgs[msg++]' '\e[34m%(%T)T [INFO]: %s\e[m' -1 "$1"; } 186 | --------------------------------------------------------------------------------