about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--COPYING7
-rw-r--r--Makefile41
-rwxr-xr-xradish298
-rw-r--r--radish.stations15
4 files changed, 361 insertions, 0 deletions
diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..4d13a5b --- /dev/null +++ b/COPYING
@@ -0,0 +1,7 @@
1Copyright (C) 2022 Case Duckworth <acdw@acdw.net>
2
3Usage of the works is permitted provided that this instrument is retained with
4the works, so that any entity that uses the works is notified of this
5instrument.
6
7DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b2656d --- /dev/null +++ b/Makefile
@@ -0,0 +1,41 @@
1# Radish
2
3DESTDIR =
4PREFIX = /usr/local
5
6BIN = $(DESTDIR)$(PREFIX)/bin
7RADISH_SHARE = $(DESTDIR)$(PREFIX)/share/radish
8
9RADISH_BIN = $(BIN)/radish
10RADISH_STATIONS = $(RADISH_SHARE)/stations
11
12.PHONY: help
13help:
14 @echo "RADISH : Play online radio"
15 @echo "(C) 2022 Case Duckworth <acdw@acdw.net>"
16 @echo "Licensed under the Fair License; see COPYING for details."
17 @echo
18 @echo "TARGETS:"
19 @echo " install Install radish to $(DESTDIR)$(PREFIX)/bin/radish."
20 @echo " An example configuration is at $(DESTDIR)$(PREFIX)/share/radish/stations."
21 @echo " link Install radish using symlinks."
22 @echo " Probably only useful for development."
23 @echo " uninstall Uninstall radish-related files."
24
25$(BIN) $(RADISH_SHARE):
26 mkdir -p $@
27
28.PHONY: install
29install: radish radish.stations $(BIN) $(RADISH_SHARE)
30 install -D radish $(RADISH_BIN)
31 install -D radish.stations $(RADISH_STATIONS)
32
33.PHONY: link
34link: $(BIN) $(RADISH_SHARE)
35 ln -sf $(PWD)/radish $(RADISH_BIN)
36 ln -sf $(PWD)/radish.stations $(RADISH_STATIONS)
37
38.PHONY: uninstall
39uninstall:
40 rm $(RADISH_BIN)
41 rm -r $(RADISH_SHARE)
diff --git a/radish b/radish new file mode 100755 index 0000000..d8c55b1 --- /dev/null +++ b/radish
@@ -0,0 +1,298 @@
1#!/bin/sh
2# RADISH
3# a new and improved RADIO that can play local files and noise.
4# XXX: WIP
5
6usage() {
7 cat <<EOF
8RADISH: radio, music, static
9USAGE: radish [-h|-k|-r|-s|-S]
10 radish -l [NAME]
11 radish [STATION]
12
13FLAGS:
14 -h Show this help and exit.
15 -s Show radish's status and exit.
16 -S Show radish's status indefinitely.
17 -k Kill the currently-playing radish invocation.
18 -r Replay most recently-played station.
19
20OPTIONS:
21 -l [NAME] List available stations.
22 If NAME is given, narrow the list to those matching it.
23
24PARAMETERS:
25 STATION Which configured station to play.
26 Stations are defined in the \$RADISH_STATION_FILE
27 (default: $RADISH_STATION_FILE).
28 If STATION is not present, or if it matches
29 more than one station, radish will present a menu.
30EOF
31 exit ${1:-0}
32}
33
34config() {
35 : "${RADISH_STATION_FILE:=${XDG_CONFIG_HOME:-$HOME/.config}/radish/stations}"
36 RADISH_PID_FILE=/tmp/radish.pid
37 RADISH_STATUS_FILE=/tmp/radish.status
38 RADISH_LP_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/radish.lp"
39}
40
41main() {
42 config
43 while getopts :hkrsSl: opt; do
44 case "$opt" in
45 h) usage ;;
46 k) radish_kill ;;
47 r) set -- "$(cat "$RADISH_LP_FILE")" ;;
48 s) radish_status ;;
49 S) radish_status -follow ;;
50 l) radish_list "$OPTARG" ;;
51 :)
52 case "$OPTARG" in
53 l) radish_list ;;
54 *)
55 echo >&2 "Option -$OPTARG requires an argument"
56 usage 1
57 ;;
58 esac
59 ;;
60 \?)
61 echo >&2 "Unknown option: -$OPTARG"
62 usage 1
63 ;;
64 esac
65 done
66 shift "$((OPTIND - 1))"
67 radish_play "$@"
68}
69
70cleanup() {
71 rm /tmp/radish.*
72}
73
74### "Private" functions
75
76_radish_player() {
77 command -v "${1:-}" ||
78 command -v "${RADISH_PLAYER:-}" ||
79 command -v mpv ||
80 command -v vlc ||
81 {
82 echo >&2 "No suitable player found."
83 echo >&2 "Set \$RADISH_PLAYER."
84 exit 3
85 }
86}
87
88_radish_stations() {
89 if [ -f "$RADISH_STATION_FILE" ]; then
90 sed -e '/^#/d' "$RADISH_STATION_FILE"
91 else
92 echo noise:
93 fi
94}
95
96_radish_kill_impl() {
97 [ -f "$RADISH_PID_FILE" ] || return 1
98 x=
99 while xargs -a "$RADISH_PID_FILE" kill 2>/dev/null; do
100 case "$x" in
101 xxx*)
102 printf .
103 x=
104 ;;
105 *) x=x$x ;;
106 esac
107 done
108 echo
109}
110
111_radish_desc() {
112 cut -f2 "${1:--}" 2>/dev/null
113}
114_radish_url() {
115 cut -f1 "${1:--}" 2>/dev/null
116}
117_radish_tags() {
118 cut -f3 "${1:--}" 2>/dev/null
119}
120
121echo() { printf '%s\n' "$*"; }
122
123### Main functionality
124
125radish_kill() {
126 printf >&2 '%s' "Killing radish..."
127 _radish_kill_impl || {
128 echo >&2 "I don't think radish is running."
129 exit 2
130 }
131 cleanup
132 exit
133}
134
135radish_status() {
136 trap 'echo;exit 0' INT
137 if [ -n "${1:-}" ]; then
138 follow=-f
139 else
140 follow=
141 fi
142 tail $follow "$RADISH_STATUS_FILE"
143 echo
144 exit
145}
146
147radish_list() {
148 _radish_stations | grep -i "${1:-}" | awk -F '\t' '{
149 desc = $'$_schema_desc'
150 url = $'$_schema_url'
151 tags = $'$_schema_tags'
152 printf "%-23s |", substr(desc,1,20) (length(desc)>20?"...":"")
153 printf "%-23s |", substr(tags,1,20) (length(tags)>20?"...":"")
154 printf "%-23s\n", substr(url,1,20) (length(url)>20?"...":"")
155 }'
156 exit
157}
158
159radish_choose_station() {
160 cands="$(_radish_stations | grep -i "$1")"
161 [ -z "$cands" ] && cands="$(_radish_stations)"
162
163 if [ "$(echo "$cands" | wc -l)" -gt 1 ]; then
164 echo "$cands" | _radish_desc | cat -n -
165 while true; do
166 printf "Radish> "
167 read station
168 if (echo "$station" | grep -qE '^[0-9]+$'); then
169 station="$(echo "$cands" | sed -n "${station}p;${station}q")"
170 if [ -z "$station" ]; then
171 echo >&2 "No station with that number."
172 continue
173 else
174 break
175 fi
176 fi
177 echo >&2 "Please input a station number."
178 done
179 else
180 station="$(echo "$cands")"
181 fi
182 echo >&2 "Selected: $(echo "$station" | _radish_desc)"
183 station="$(echo "$station" | _radish_url)"
184}
185
186### Player functions
187
188radish_play() {
189 case "${1:-}" in
190 https:* | http:*)
191 _radish_kill_impl || :
192 echo >&2 "Streaming $1..."
193 radish_play_web "$1"
194 ;;
195 noise:*)
196 _radish_kill_impl || :
197 echo >&2 "Playing noise: ${1#noise:}"
198 radish_play_noise "${1:-}"
199 ;;
200 shuf:*)
201 _radish_kill_impl || :
202 echo >&2 "Shuffling from ${1#shuf:}"
203 SHUF=1 radish_play_file "$1" 2>&1
204 ;;
205 file:*)
206 _radish_kill_impl || :
207 echo >&2 "Playing from ${1#file:}"
208 SHUF=0 radish_play_file "$1" 2>&1
209 ;;
210 *)
211 if [ -f "$RADISH_STATION_FILE" ]; then
212 radish_choose_station "${1:-}"
213 _radish_kill_impl || :
214 radish_play "$station"
215 else
216 _radish_kill_impl || :
217 radish_play_noise
218 fi
219 ;;
220 esac
221
222 echo "${1:-}" | _radish_url >"$RADISH_LP_FILE"
223 exit
224}
225
226radish_play_web() {
227 "$(_radish_player)" "$1" >"$RADISH_STATUS_FILE" 2>&1 &
228 echo $! >"$RADISH_PID_FILE"
229}
230
231radish_play_file() {
232 dir="$(echo "$1" | sed 's@^[^:]*:\(//\)\?@@')"
233 find "$dir" -depth -print0 \
234 -iname '*flac' -o \
235 -iname '*mp3' -o \
236 -iname '*ogg' -o \
237 -iname '*opus' |
238 xargs -0 realpath |
239 case "$SHUF" in
240 1) shuf ;;
241 0) sort ;;
242 esac >/tmp/radish.m3u
243 player="$(_radish_player)"
244 case "$player" in
245 *vlc | *mpv) args="--no-video" ;;
246 *) args= ;;
247 esac
248 "$player" $args /tmp/radish.m3u "$1" >"$RADISH_STATUS_FILE" 2>&1 &
249 echo $! >"$RADISH_PID_FILE"
250}
251
252radish_play_noise() {
253 # REQUIRES PLAY (from SOX) -- based on https://gist.github.com/rsvp/1209835
254 play="$(command -v play)" || {
255 echo >&2 "Noise playback requires sox(1)."
256 exit 3
257 }
258 # URL format: noise:type/center?time=60;wave=0.033;volume=1
259 url_format='noise:\([^/]\+\)/\([^?]\+\)?\(.*\)'
260 noise_type="$(echo "${1:-}" | sed -n "s@${url_format}@\1@")"
261 noise_center="$(echo "${1:-}" | sed -n "s@${url_format}@\2@")"
262 noise_params="$(echo "${1:-}" | sed -n "s@${url_format}@\3@")"
263 time=60
264 wave=0.03333333
265 volume=1
266 if [ -z "$noise_type" ] && [ -z "$noise_center" ]; then
267 noise_type=brown
268 noise_center=1786
269 elif [ -z "$noise_type" ] || [ -z "$noise_center" ]; then
270 echo >&2 "URL format: 'noise:TYPE/CENTER?[PARAMS]"
271 echo >&2 "TYPE is one of 'brown','white','pink','tpdf'."
272 echo >&2 "CENTER is the center of the band-pass filter."
273 echo >&2 "PARAMS are TIME to generate noise;"
274 echo >&2 "WAVE, denoting volume variation; and VOLUME."
275 exit 4
276 else
277 eval "$noise_params"
278 fi
279
280 export RADISH_PLAYER=play
281 echo >&2 "$(_radish_player)" \
282 -c 2 --null synth "$time" "${noise_type}noise" \
283 band -n "$noise_center" 499 \
284 tremolo "$wave" 43 reverb 19 \
285 bass -11 treble -1 \
286 vol 14dB vol "$volume" \
287 repeat "$(expr $time - 1)"
288 "$(_radish_player)" \
289 -c 2 --null synth "$time" "${noise_type}noise" \
290 band -n "$noise_center" 499 \
291 tremolo "$wave" 43 reverb 19 \
292 bass -11 treble -1 \
293 vol 14dB vol "$volume" \
294 repeat "$(expr $time - 1)" >"$RADISH_STATUS_FILE" 2>&1 &
295 echo $! >"$RADISH_PID_FILE"
296}
297
298main "$@"
diff --git a/radish.stations b/radish.stations new file mode 100644 index 0000000..7ff60ef --- /dev/null +++ b/radish.stations
@@ -0,0 +1,15 @@
1URL Description Tags
2https://somafm.com/synphaera256.pls Synphaera soma
3https://somafm.com/bagel.pls BAGel Radio soma
4https://somafm.com/bootliquor320.pls Boot Liquor soma
5https://somafm.com/deepspaceone.pls Deep Space One soma
6https://somafm.com/fluid.pls Fluid soma
7https://somafm.com/u80s256.pls Underground 80s soma
8https://azuracast.tilderadio.org/radio/8000/radio.ogg tilderadio friends
9https://s2.radio.co/s2b2b68744/listen BadRadio: 24/7 PHONK phonk
10http://radio.plaza.one/opus Nightwave Plaza vaporwave
11https://www.scenesat.com/listen/normal/mid.m3u SceneSat demoscene
12http://nectarine.ers35.net:8000/necta192.mp3 Nectarine demoscene
13https://kexp-mp3-128.streamguys1.com/kexp128.mp3 KEXP fm college
14https://rainwave.cc/tune_in/5.ogg.m3u Rainwave chiptune videogame
15noise: brown noise noise