#!/bin/rc # audio player for use with the following plumbing rules: # wdir is /mnt/♫ # plumb to audio # dst is audio # plumb to audio # plumb client window playlist # data matches .+\.(pls|mp3|ogg|oga|flac|wav|opus|it|aac|m4a|au|snd|mid|mod|raw|pcm) # arg isfile $data # data set $file # plumb to audio # plumb client window playlist # data matches ♫(https?://.+) # data set $1 # plumb to audio # plumb client window playlist if(! test -f /mnt/plumb/audio){ echo /mnt/plumb/audio not found! >[1=2] exit 'no plumb port for audio' } rfork en fn redraw{ if(! test -f tmp) sed -n '/^⏶/q;p' /dev/text >tmp mv tmp /dev/text echo '⏶ ⏷ ⏪ ⏩ 🔀 🔁 [pls] [exit]' $state echo ☞ $cur switch($cur){ case *.mod <$cur{ syscall -o read 0 buf 20 echo for(i in `{seq 20 30 920}){ syscall -o pread 0 buf 22 $i echo } }>[2]/dev/null case *.xm syscall -o pread 0 buf 20 17 <$cur >[2]/dev/null echo case *.^(mp3 ogg oga flac wav opus it aac m4a) if(test -x /bin/audio/readtags) audio/readtags $cur } } fn sighup sigint sigexit ⏹{ if(! ~ $decpid '' && test -d /proc/$decpid){ @{echo kill >/proc/$decpid/notepg}& wait $decpid } decpid = '' } fn ⏵{ dec = '' if(! test -w /dev/audio) state = (stopped: /dev/audio busy) if not if(~ $cur http://* https://*){ state = ⏹ switch(`{hget -r 'User-Agent: play' -r 'Icy-MetaData: 0' $cur >[2]/dev/null | read -c 512 | file -m}){ case audio/mpeg dec = 'hget -r ''User-Agent: play'' -r ''Icy-MetaData: 0'' $cur | audio/mp3dec' case audio/ogg dec = 'hget -r ''User-Agent: play'' -r ''Icy-MetaData: 0'' $cur | audio/oggdec' case * state = (stopped: no decoder for stream) } } if not if(! ~ $#cur 1 || ! test -s $cur || ! test -r $cur) state = (stopped: nothing to play) if not{ switch($cur){ case *.mp3 dec = 'audio/mp3dec -s $secs < $cur' case *.ogg *.oga dec = 'audio/oggdec -s $secs < $cur' case *.flac dec = 'audio/flacdec -s $secs < $cur' case *.wav dec = 'audio/wavdec -s $secs < $cur' case *.opus dec = 'audio/opusdec -s $secs < $cur' case *.xm *.it *.s3m *.mod dec = 'audio/moddec -s $secs < $cur' case *.aac *.m4a secs = 0 dec = 'audio/aacdec < $cur' case *.au *.snd secs = 0 dec = 'audio/sundec < $cur' case *.mid secs = 0 dec = 'games/midi -c < $cur' case *.raw *.pcm dec = 'cat $cur' case * plumb -w/ $cur state = (stopped: no decoder for that: plumbed elsewhere) } if(! ~ $dec ''){ ⏱ = `{date -u $secs} state = ⏸ ⏱ = `{date -n} } } redraw if(! ~ $dec ''){ {eval $dec >/dev/audio; plumb ⏭}& decpid = $apid } } fn jumptodata{ if(! ~ $data ⏵ $cur){ {if(! ~ $cur '') echo $cur; cat /dev/text} | awk ' /^⏶/{exit} found{print} $0 == ENVIRON["data"]{found = 1} !found{l[n++] = $0} END{for(i = 0; i < n; i++) print l[i]} ' >tmp cur = $data secs = 0 } ⏹ ⏵ } ramfs -um /mnt/♫ cd /mnt/♫ echo -n /mnt/♫ >/dev/wdir echo -n ♫ >/dev/label echo -n scroll >/dev/wctl ifs = ' ' cur = '' secs = 0 state = (stopped: plumb a track to start) >tmp redraw /dev/null{ read # src read # dst read # wdir read # type attr = `{read} ndata = `{read} }){ if(~ $attr *'action=showdata'*){ read -c $ndata >tmp data = `{file -m tmp | tr / .} switch($data){ case audio.flac data = `{sum tmp | read -c 8}^.flac case audio.mpeg data = `{sum tmp | read -c 8}^.mp3 case audio.ogg data = `{sum tmp | read -c 8}^.ogg case audio.basic data = `{sum tmp | read -c 8}^.au case audio.midi data = `{sum tmp | read -c 8}^.mid case audio.wave data = `{sum tmp | read -c 8}^.wav case * data = `{sum tmp | read -c 8}^.^$data } mv tmp $data {if(! ~ $cur '') echo $cur; sed -n '/^⏶/q;p' /dev/text} >tmp cur = $data cat tmp >/dev/text ⏹ secs = 0 ⏵ } if not{ data = `{read -c $ndata} switch($data){ case ⏶ awk '/master/{print $2 + 3; exit}' /dev/volume >/dev/volume case ⏷ awk '/master/{print $2 - 3; exit}' /dev/volume >/dev/volume case ⏪ ⏹ secs = `{echo '[0pq]sx' $secs '30-d0>xp' | dc} ⏵ skip 60- case ⏩ ⏹ secs = `{echo '[0pq]sx' $secs '30+d0>xp' | dc} ⏵ case ⏭ cur = `{awk ' BEGIN{prev = ENVIRON["cur"]} /^⏶/{print prev; exit} {print prev >>"tmp"; prev = $0} ' /dev/text} ⏹ secs = 0 ⏵ case 🔀 awk ' /^⏶/{exit} length{l[n++] = $0} END{ srand while(n){ i = int(rand() * --n) print l[i] l[i] = l[n] } }' /dev/text >tmp redraw case 🔁 sed -n '/^⏶/q;p' /dev/text | sort -r | uniq | awk ' $0 < ENVIRON["cur"] $0 > ENVIRON["cur"]{l[n++] = $0} END{for(i = 0; i < n; i++) print l[i]} ' >tmp redraw case '[pls]' if(! ~ `{sed -n '/^⏶/q;/^[^\/]/p' /dev/text} '' || ~ $cur [~/]*) echo warning: temporary files in playlist! echo -n 'write playlist: ' echo -n  >/dev/kbdin where = `{read /dev/cons} if(~ $where /*) {sed -n '/^⏶/q;p' /dev/text; if(! ~ $cur '') echo $cur} | tail -r >$where if not echo need an absolute path! case '[exit]' echo -n  >/dev/kbdin exit case ⏹ ⏹ secs = 0 state = ⏵ redraw case ⏸ ⏹ secs = `{echo $secs `{date -n} $⏱ -+p | dc} ⏱ = `' '{date -u $secs} state = (⏵ $⏱(4)) redraw case [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ⏹ secs = `{awk 'BEGIN{split(ENVIRON["data"], t, ":"); print 60*60*t[1] + 60*t[2] + t[3]}'} ⏵ case image awk '/^image +image\/[^ ]+ +[0-9]+ +[0-9]+/{system("syscall -o pread 0 buf "$4" "$3" <"ENVIRON["cur"]" >[2]/dev/null | plumb -idimage");exit}' /dev/text case /*.pls if(test -r $data){ { if(! ~ $cur '') echo $cur sed -n '/^⏶/q;p' /dev/text cat $data | {cur = `{read}; tail -r} }>tmp ⏹ secs = 0 ⏵ } if not echo could not read playlist $data case ⏵ /* http://* https://* jumptodata case [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].* if(test -f $data){ echo `{{walk -es $data; echo 1048576/1+p}|dc}^MB $data^: echo -n 'hit enter to play or type a path to write to disk: ' echo -n  >/dev/kbdin where = `{read /dev/cons} if(~ $where '') jumptodata if(~ $where /* && mv $data $where){ if(~ $data $cur){ cur = $where ⏹ ⏵ } if not{ awk ' /^⏶/{exit} { if($0 == ENVIRON["data"]) print ENVIRON["where"] else print } ' /dev/text >tmp redraw } } } case * echo -n received bad plumb: $data } } }