1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
|
#!/bin/sh
# RADISH
# a new and improved RADIO that can play local files and noise.
# XXX: WIP
usage() {
cat <<EOF
RADISH: radio, music, static
USAGE: radish [-h|-k|-r|-s|-S]
radish -l [NAME]
radish [STATION]
FLAGS:
-h Show this help and exit.
-s Show radish's status and exit.
-S Show radish's status indefinitely.
-k Kill the currently-playing radish invocation.
-r Replay most recently-played station.
OPTIONS:
-l [NAME] List available stations.
If NAME is given, narrow the list to those matching it.
PARAMETERS:
STATION Which configured station to play.
Stations are defined in the \$RADISH_STATION_FILE
(default: $RADISH_STATION_FILE).
If STATION is not present, or if it matches
more than one station, radish will present a menu.
EOF
exit ${1:-0}
}
config() {
: "${RADISH_STATION_FILE:=${XDG_CONFIG_HOME:-$HOME/.config}/radish/stations}"
RADISH_PID_FILE=/tmp/radish.pid
RADISH_STATUS_FILE=/tmp/radish.status
RADISH_LP_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/radish.lp"
}
main() {
config
while getopts :hkrsSl: opt; do
case "$opt" in
h) usage ;;
k) radish_kill ;;
r) set -- "$(cat "$RADISH_LP_FILE")" ;;
s) radish_status ;;
S) radish_status -follow ;;
l) radish_list "$OPTARG" ;;
:)
case "$OPTARG" in
l) radish_list ;;
*)
echo >&2 "Option -$OPTARG requires an argument"
usage 1
;;
esac
;;
\?)
echo >&2 "Unknown option: -$OPTARG"
usage 1
;;
esac
done
shift "$((OPTIND - 1))"
radish_play "$@"
}
cleanup() {
rm /tmp/radish.*
}
### "Private" functions
_radish_player() {
command -v "${1:-}" ||
command -v "${RADISH_PLAYER:-}" ||
command -v mpv ||
command -v vlc ||
{
echo >&2 "No suitable player found."
echo >&2 "Set \$RADISH_PLAYER."
exit 3
}
}
_radish_stations() {
if [ -f "$RADISH_STATION_FILE" ]; then
sed -e '/^#/d' "$RADISH_STATION_FILE"
else
echo noise:
fi
}
_radish_kill_impl() {
[ -f "$RADISH_PID_FILE" ] || return 1
x=
while xargs -a "$RADISH_PID_FILE" kill 2>/dev/null; do
case "$x" in
xxx*)
printf .
x=
;;
*) x=x$x ;;
esac
done
echo
}
_radish_desc() {
cut -f2 "${1:--}" 2>/dev/null
}
_radish_url() {
cut -f1 "${1:--}" 2>/dev/null
}
_radish_tags() {
cut -f3 "${1:--}" 2>/dev/null
}
_radish_not_running() {
echo >&2 "Running radish not found."
exit 2
}
echo() { printf '%s\n' "$*"; }
### Main functionality
radish_kill() {
if [ -f "$RADISH_PID_FILE" ]; then
printf >&2 '%s' "Killing radish..."
_radish_kill_impl
else
_radish_not_running
fi
cleanup
exit
}
radish_status() {
trap 'echo;exit 0' INT
if [ -n "${1:-}" ]; then
follow=-f
else
follow=
fi
tail $follow "$RADISH_STATUS_FILE" 2>/dev/null || _radish_not_running
echo
exit
}
radish_list() {
_radish_stations | grep -i "${1:-}" | awk -F '\t' '{
desc = $'$_schema_desc'
url = $'$_schema_url'
tags = $'$_schema_tags'
printf "%-23s |", substr(desc,1,20) (length(desc)>20?"...":"")
printf "%-23s |", substr(tags,1,20) (length(tags)>20?"...":"")
printf "%-23s\n", substr(url,1,20) (length(url)>20?"...":"")
}'
exit
}
radish_choose_station() {
cands="$(_radish_stations | grep -i "$1")"
[ -z "$cands" ] && cands="$(_radish_stations)"
if [ "$(echo "$cands" | wc -l)" -gt 1 ]; then
echo "$cands" | _radish_desc | cat -n -
while true; do
printf "Radish> "
read station
if (echo "$station" | grep -qE '^[0-9]+$'); then
station="$(echo "$cands" | sed -n "${station}p;${station}q")"
if [ -z "$station" ]; then
echo >&2 "No station with that number."
continue
else
break
fi
fi
echo >&2 "Please input a station number."
done
else
station="$(echo "$cands")"
fi
echo >&2 "Selected: $(echo "$station" | _radish_desc)"
station="$(echo "$station" | _radish_url)"
}
### Player functions
radish_play() {
case "${1:-}" in
https:* | http:*)
_radish_kill_impl || :
name="$(grep "$1" "$RADISH_STATION_FILE" | cut -f2)"
echo >&2 "Streaming ${name:-$1}..."
radish_play_web "$1"
;;
noise:*)
_radish_kill_impl || :
echo >&2 "Playing noise: ${1#noise:}"
radish_play_noise "${1:-}"
;;
shuf:*)
_radish_kill_impl || :
echo >&2 "Shuffling from ${1#shuf:}"
SHUF=1 radish_play_file "$1" 2>&1
;;
file:*)
_radish_kill_impl || :
echo >&2 "Playing from ${1#file:}"
SHUF=0 radish_play_file "$1" 2>&1
;;
*)
if [ -f "$RADISH_STATION_FILE" ]; then
radish_choose_station "${1:-}"
_radish_kill_impl || :
radish_play "$station"
else
_radish_kill_impl || :
radish_play_noise
fi
;;
esac
echo "${1:-}" | _radish_url >"$RADISH_LP_FILE"
exit
}
radish_play_web() {
"$(_radish_player)" "$1" >"$RADISH_STATUS_FILE" 2>&1 &
echo $! >"$RADISH_PID_FILE"
}
radish_play_file() {
dir="$(echo "$1" | sed 's@^[^:]*:\(//\)\?@@')"
find "$dir" -depth -print0 \
-iname '*flac' -o \
-iname '*mp3' -o \
-iname '*ogg' -o \
-iname '*opus' |
xargs -0 realpath |
case "$SHUF" in
1) shuf ;;
0) sort ;;
esac >/tmp/radish.m3u
player="$(_radish_player)"
case "$player" in
*vlc | *mpv) args="--no-video" ;;
*) args= ;;
esac
"$player" $args /tmp/radish.m3u "$1" >"$RADISH_STATUS_FILE" 2>&1 &
echo $! >"$RADISH_PID_FILE"
}
radish_play_noise() {
# REQUIRES PLAY (from SOX) -- based on https://gist.github.com/rsvp/1209835
play="$(command -v play)" || {
echo >&2 "Noise playback requires sox(1)."
exit 3
}
# URL format: noise:type/center?time=60;wave=0.033;volume=1
url_format='noise:\([^/]\+\)/\([^?]\+\)?\(.*\)'
noise_type="$(echo "${1:-}" | sed -n "s@${url_format}@\1@")"
noise_center="$(echo "${1:-}" | sed -n "s@${url_format}@\2@")"
noise_params="$(echo "${1:-}" | sed -n "s@${url_format}@\3@")"
time=60
wave=0.03333333
volume=1
if [ -z "$noise_type" ] && [ -z "$noise_center" ]; then
noise_type=brown
noise_center=1786
elif [ -z "$noise_type" ] || [ -z "$noise_center" ]; then
echo >&2 "URL format: 'noise:TYPE/CENTER?[PARAMS]"
echo >&2 "TYPE is one of 'brown','white','pink','tpdf'."
echo >&2 "CENTER is the center of the band-pass filter."
echo >&2 "PARAMS are TIME to generate noise;"
echo >&2 "WAVE, denoting volume variation; and VOLUME."
exit 4
else
eval "$noise_params"
fi
export RADISH_PLAYER=play
echo >&2 "$(_radish_player)" \
-c 2 --null synth "$time" "${noise_type}noise" \
band -n "$noise_center" 499 \
tremolo "$wave" 43 reverb 19 \
bass -11 treble -1 \
vol 14dB vol "$volume" \
repeat "$(expr $time - 1)"
"$(_radish_player)" \
-c 2 --null synth "$time" "${noise_type}noise" \
band -n "$noise_center" 499 \
tremolo "$wave" 43 reverb 19 \
bass -11 treble -1 \
vol 14dB vol "$volume" \
repeat "$(expr $time - 1)" >"$RADISH_STATUS_FILE" 2>&1 &
echo $! >"$RADISH_PID_FILE"
}
main "$@"
|