748 lines
12 KiB
Bash
748 lines
12 KiB
Bash
#!/usr/bin/env bash
|
|
set -u
|
|
|
|
P=${0##*/}
|
|
V=1.2.0
|
|
CFG=${FFX_CONFIG_FILE:-"$HOME/.config/ffx/helpers.sh"}
|
|
OS=unix
|
|
case "$(uname -s 2>/dev/null || printf unknown)" in
|
|
MINGW*|MSYS*|CYGWIN*) OS=windows ;;
|
|
esac
|
|
|
|
case "${NO_COLOR:-}" in
|
|
1|true|yes) R= G= Y= B= D= Z= ;;
|
|
*)
|
|
if [ -t 1 ]; then
|
|
R=$(printf '\033[31m')
|
|
G=$(printf '\033[32m')
|
|
Y=$(printf '\033[33m')
|
|
B=$(printf '\033[34m')
|
|
D=$(printf '\033[1m')
|
|
Z=$(printf '\033[0m')
|
|
else
|
|
R= G= Y= B= D= Z=
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
i(){ printf '%s%s→%s %s\n' "$B" "$D" "$Z" "$*"; }
|
|
o(){ printf '%s%s✓%s %s\n' "$G" "$D" "$Z" "$*"; }
|
|
w(){ printf '%s%s!%s %s\n' "$Y" "$D" "$Z" "$*" >&2; }
|
|
e(){ printf '%s%s✗%s %s\n' "$R" "$D" "$Z" "$*" >&2; }
|
|
x(){ e "$*"; exit 1; }
|
|
h(){ command -v "$1" >/dev/null 2>&1; }
|
|
n(){ printf '%s' "$1" | grep -Eq '^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$'; }
|
|
|
|
usage(){
|
|
cat <<EOF
|
|
$P $V
|
|
|
|
Usage:
|
|
$P <command> [arguments]
|
|
$P --add-helper-cmd
|
|
$P --remove-helper-cmd
|
|
$P -h|--help
|
|
$P <command> -h|--help
|
|
|
|
Commands:
|
|
bass INPUT PERCENT [FINE_DB] [OUTPUT]
|
|
treble INPUT PERCENT [FINE_DB] [OUTPUT]
|
|
volume INPUT DB [OUTPUT]
|
|
distort INPUT [DRIVE_0_TO_10] [OUTPUT]
|
|
trim INPUT START_SECONDS [END_SECONDS] [OUTPUT]
|
|
tempo INPUT SPEED_0.125_TO_8 [OUTPUT]
|
|
pitch INPUT SEMITONES_-36_TO_36 [OUTPUT]
|
|
install
|
|
help
|
|
|
|
Options:
|
|
--add-helper-cmd Install short shell helper commands.
|
|
--remove-helper-cmd Remove installed helper commands.
|
|
--no-color Disable colored output.
|
|
-h, --help Show help.
|
|
-V, --version Show version.
|
|
|
|
Examples:
|
|
$P bass song.mp3 25 1 bass.mp3
|
|
$P tempo song.mp3 1.5 fast.mp3
|
|
$P trim song.wav 10 30 clip.wav
|
|
EOF
|
|
}
|
|
|
|
chelp(){
|
|
case "$1" in
|
|
bass|treble)
|
|
cat <<EOF
|
|
Usage:
|
|
$P $1 INPUT PERCENT [FINE_DB] [OUTPUT]
|
|
|
|
PERCENT range: -200..200
|
|
FINE_DB range: -60..60
|
|
33 percent = 16.5 dB
|
|
66 percent = 33 dB
|
|
100 percent = 50 dB
|
|
EOF
|
|
;;
|
|
volume)
|
|
cat <<EOF
|
|
Usage:
|
|
$P volume INPUT DB [OUTPUT]
|
|
|
|
DB range: -120..120
|
|
EOF
|
|
;;
|
|
distort)
|
|
cat <<EOF
|
|
Usage:
|
|
$P distort INPUT [DRIVE_0_TO_10] [OUTPUT]
|
|
|
|
DRIVE range: 0..10
|
|
EOF
|
|
;;
|
|
trim)
|
|
cat <<EOF
|
|
Usage:
|
|
$P trim INPUT START_SECONDS [END_SECONDS] [OUTPUT]
|
|
EOF
|
|
;;
|
|
tempo)
|
|
cat <<EOF
|
|
Usage:
|
|
$P tempo INPUT SPEED_0.125_TO_8 [OUTPUT]
|
|
|
|
Changes speed while preserving pitch.
|
|
EOF
|
|
;;
|
|
pitch)
|
|
cat <<EOF
|
|
Usage:
|
|
$P pitch INPUT SEMITONES_-36_TO_36 [OUTPUT]
|
|
|
|
Changes pitch while preserving duration.
|
|
EOF
|
|
;;
|
|
*)
|
|
usage
|
|
;;
|
|
esac
|
|
}
|
|
|
|
home(){
|
|
case "$1" in
|
|
"~/"*) printf '%s/%s\n' "$HOME" "${1#~/}" ;;
|
|
*) printf '%s\n' "$1" ;;
|
|
esac
|
|
}
|
|
|
|
dir(){
|
|
case "$1" in
|
|
*/*) printf '%s\n' "${1%/*}" ;;
|
|
*\\*) printf '%s\n' "${1%\\*}" ;;
|
|
*) printf '.\n' ;;
|
|
esac
|
|
}
|
|
|
|
base(){ printf '%s\n' "${1##*[\\/]}" ;}
|
|
|
|
stem(){
|
|
local a
|
|
a=$(base "$1")
|
|
case "$a" in
|
|
*.*) printf '%s\n' "${a%.*}" ;;
|
|
*) printf '%s\n' "$a" ;;
|
|
esac
|
|
}
|
|
|
|
ext(){
|
|
local a
|
|
a=$(base "$1")
|
|
case "$a" in
|
|
*.*) printf '%s\n' "${a##*.}" ;;
|
|
*) printf '\n' ;;
|
|
esac
|
|
}
|
|
|
|
rid(){
|
|
if [ -r /dev/urandom ] && h od; then
|
|
od -An -N5 -tx1 /dev/urandom 2>/dev/null | tr -d ' \n'
|
|
else
|
|
printf '%s%s%s' "$$" "${RANDOM:-0}" "$(date +%s)"
|
|
fi
|
|
}
|
|
|
|
out(){
|
|
local a=$1 t=$2 d s e
|
|
d=$(dir "$a")
|
|
s=$(stem "$a")
|
|
e=$(ext "$a")
|
|
[ -n "$e" ] || e=wav
|
|
printf '%s/%s_%s_%s.%s\n' "$d" "$s" "$t" "$(rid)" "$e"
|
|
}
|
|
|
|
abs(){
|
|
if h realpath; then
|
|
realpath -m -- "$1" 2>/dev/null
|
|
else
|
|
local d b
|
|
d=$(dir "$1")
|
|
b=$(base "$1")
|
|
(
|
|
cd -P -- "$d" 2>/dev/null &&
|
|
printf '%s/%s\n' "$(pwd)" "$b"
|
|
)
|
|
fi
|
|
}
|
|
|
|
same(){
|
|
[ "$(abs "$1")" = "$(abs "$2")" ]
|
|
}
|
|
|
|
probe(){
|
|
h ffprobe || return 0
|
|
ffprobe -v error \
|
|
-select_streams a:0 \
|
|
-show_entries stream=codec_type \
|
|
-of default=noprint_wrappers=1:nokey=1 \
|
|
-- "$1" 2>/dev/null | grep -qx audio
|
|
}
|
|
|
|
pm(){
|
|
if [ "$OS" = windows ]; then
|
|
h winget && { printf '%s' winget; return; }
|
|
h choco && { printf '%s' choco; return; }
|
|
h scoop && { printf '%s' scoop; return; }
|
|
fi
|
|
|
|
for a in apt-get dnf pacman zypper apk; do
|
|
h "$a" && { printf '%s' "$a"; return; }
|
|
done
|
|
}
|
|
|
|
install(){
|
|
h ffmpeg && {
|
|
o "ffmpeg is already installed."
|
|
return
|
|
}
|
|
|
|
local m
|
|
m=$(pm)
|
|
|
|
[ -n "$m" ] ||
|
|
x "No supported package manager found. Install ffmpeg manually."
|
|
|
|
i "Installing ffmpeg."
|
|
|
|
case "$m" in
|
|
apt-get)
|
|
sudo apt-get update &&
|
|
sudo apt-get install -y ffmpeg
|
|
;;
|
|
dnf)
|
|
sudo dnf install -y ffmpeg
|
|
;;
|
|
pacman)
|
|
sudo pacman -Sy --noconfirm ffmpeg
|
|
;;
|
|
zypper)
|
|
sudo zypper --non-interactive install ffmpeg
|
|
;;
|
|
apk)
|
|
sudo apk add ffmpeg
|
|
;;
|
|
winget)
|
|
winget install --id Gyan.FFmpeg -e
|
|
;;
|
|
choco)
|
|
choco install ffmpeg -y
|
|
;;
|
|
scoop)
|
|
scoop install ffmpeg
|
|
;;
|
|
esac || x "ffmpeg installation failed."
|
|
|
|
h ffmpeg || x "ffmpeg is still unavailable."
|
|
}
|
|
|
|
req(){
|
|
h awk || x "awk is required."
|
|
h ffmpeg && return
|
|
|
|
if [ -t 0 ]; then
|
|
printf 'ffmpeg is missing. Install now? [Y/n] '
|
|
read -r a
|
|
case "${a:-Y}" in
|
|
y|Y|yes|YES) install ;;
|
|
*) exit 1 ;;
|
|
esac
|
|
else
|
|
x "ffmpeg is missing. Run: $P install"
|
|
fi
|
|
}
|
|
|
|
prep(){
|
|
local a=$1 d
|
|
d=$(dir "$a")
|
|
|
|
[ -d "$d" ] ||
|
|
mkdir -p -- "$d" ||
|
|
x "Cannot create directory: $d"
|
|
|
|
[ -w "$d" ] ||
|
|
x "Directory is not writable: $d"
|
|
|
|
[ -e "$a" ] || return
|
|
|
|
if [ -t 0 ]; then
|
|
printf '%sOutput exists: %s. Overwrite? [y/N] %s' \
|
|
"$Y" "$a" "$Z" >&2
|
|
read -r q
|
|
case "$q" in
|
|
y|Y|yes|YES) ;;
|
|
*) x "Output was not overwritten." ;;
|
|
esac
|
|
else
|
|
x "Output exists; refusing overwrite: $a"
|
|
fi
|
|
}
|
|
|
|
run(){
|
|
local in=$1 f=$2 op=$3 d s e tmp log rc
|
|
local -a c
|
|
|
|
req
|
|
|
|
in=$(home "$in")
|
|
op=$(home "$op")
|
|
|
|
[ -f "$in" ] &&
|
|
[ -r "$in" ] ||
|
|
x "Input is missing or unreadable: $in"
|
|
|
|
[ -n "$f" ] ||
|
|
x "Missing audio filter."
|
|
|
|
same "$in" "$op" &&
|
|
x "Input and output must differ."
|
|
|
|
probe "$in" ||
|
|
x "No readable audio stream found: $in"
|
|
|
|
d=$(dir "$op")
|
|
s=$(stem "$op")
|
|
e=$(ext "$op")
|
|
|
|
[ -n "$e" ] ||
|
|
x "Output requires an extension."
|
|
|
|
case "$(printf '%s' "$e" | tr '[:upper:]' '[:lower:]')" in
|
|
mp3) c=(-c:a libmp3lame -q:a 2) ;;
|
|
wav) c=(-c:a pcm_s16le) ;;
|
|
flac) c=(-c:a flac) ;;
|
|
ogg) c=(-c:a libvorbis -q:a 6) ;;
|
|
opus) c=(-c:a libopus -b:a 192k) ;;
|
|
m4a) c=(-c:a aac -b:a 256k -movflags +faststart) ;;
|
|
aac) c=(-c:a aac -b:a 256k) ;;
|
|
mka) c=(-c:a flac) ;;
|
|
*) x "Unsupported output extension: .$e" ;;
|
|
esac
|
|
|
|
prep "$op"
|
|
|
|
tmp="$d/.${s}.ffx-partial.$$.$(rid).$e"
|
|
log="$tmp.log"
|
|
|
|
i "Processing: $(base "$in")"
|
|
i "Output: $op"
|
|
|
|
rm -f -- "$tmp" "$log"
|
|
|
|
ffmpeg \
|
|
-hide_banner \
|
|
-nostdin \
|
|
-y \
|
|
-v warning \
|
|
-i "$in" \
|
|
-map 0:a:0 \
|
|
-vn \
|
|
-af "$f" \
|
|
"${c[@]}" \
|
|
"$tmp" 2>"$log"
|
|
|
|
rc=$?
|
|
|
|
if [ "$rc" -eq 0 ] && [ -s "$tmp" ]; then
|
|
mv -f -- "$tmp" "$op" ||
|
|
x "Could not finalize output."
|
|
rm -f -- "$log"
|
|
o "Done: $op"
|
|
return
|
|
fi
|
|
|
|
[ -s "$log" ] && cat "$log" >&2
|
|
rm -f -- "$tmp" "$log"
|
|
x "Processing failed."
|
|
}
|
|
|
|
db(){
|
|
n "$1" &&
|
|
n "${2:-0}" ||
|
|
return 1
|
|
|
|
awk -v p="$1" -v f="${2:-0}" '
|
|
BEGIN {
|
|
if (p < -200 || p > 200 || f < -60 || f > 60) exit 1
|
|
printf "%.2f", p * .5 + f
|
|
}'
|
|
}
|
|
|
|
tempo_filter(){
|
|
local r=$1 z c=
|
|
n "$r" || return 1
|
|
|
|
awk -v n="$r" \
|
|
'BEGIN { exit !(n >= .125 && n <= 8) }' ||
|
|
return 1
|
|
|
|
z=$r
|
|
|
|
while awk -v n="$z" 'BEGIN { exit !(n > 2) }'; do
|
|
c="${c}${c:+,}atempo=2"
|
|
z=$(awk -v n="$z" 'BEGIN { printf "%.12f", n / 2 }')
|
|
done
|
|
|
|
while awk -v n="$z" 'BEGIN { exit !(n < .5) }'; do
|
|
c="${c}${c:+,}atempo=0.5"
|
|
z=$(awk -v n="$z" 'BEGIN { printf "%.12f", n * 2 }')
|
|
done
|
|
|
|
printf '%s%satempo=%s\n' "$c" "${c:+,}" "$z"
|
|
}
|
|
|
|
boost(){
|
|
local t=$1 in p f=0 op g
|
|
shift
|
|
|
|
[ "$#" -ge 2 ] ||
|
|
x "Usage: $P $t INPUT PERCENT [FINE_DB] [OUTPUT]"
|
|
|
|
in=$1
|
|
p=$2
|
|
|
|
if [ "$#" -ge 3 ]; then
|
|
if n "$3"; then
|
|
f=$3
|
|
op=${4:-}
|
|
else
|
|
op=$3
|
|
fi
|
|
fi
|
|
|
|
g=$(db "$p" "$f") ||
|
|
x "PERCENT must be -200..200; FINE_DB must be -60..60."
|
|
|
|
run "$in" \
|
|
"$t=g=$g:f=$([ "$t" = bass ] && printf 110 || printf 4000):w=$([ "$t" = bass ] && printf .6 || printf .5)" \
|
|
"${op:-$(out "$in" "$t")}"
|
|
}
|
|
|
|
volume(){
|
|
local in=${1:-} d=${2:-} op=${3:-}
|
|
|
|
[ -n "$in" ] &&
|
|
[ -n "$d" ] ||
|
|
x "Usage: $P volume INPUT DB [OUTPUT]"
|
|
|
|
n "$d" &&
|
|
awk -v n="$d" \
|
|
'BEGIN { exit !(n >= -120 && n <= 120) }' ||
|
|
x "DB must be between -120 and 120."
|
|
|
|
run "$in" "volume=${d}dB" \
|
|
"${op:-$(out "$in" volume)}"
|
|
}
|
|
|
|
distort(){
|
|
local in=${1:-} d=3 op=pg
|
|
|
|
[ -n "$in" ] ||
|
|
x "Usage: $P distort INPUT [DRIVE_0_TO_10] [OUTPUT]"
|
|
|
|
if [ "$#" -ge 2 ]; then
|
|
if n "$2"; then
|
|
d=$2
|
|
op=${3:-}
|
|
else
|
|
op=$2
|
|
fi
|
|
fi
|
|
|
|
n "$d" &&
|
|
awk -v n="$d" \
|
|
'BEGIN { exit !(n >= 0 && n <= 10) }' ||
|
|
x "DRIVE must be between 0 and 10."
|
|
|
|
pg=$(awk -v d="$d" \
|
|
'BEGIN { printf "%.3f", 1 + d * .85 }')
|
|
|
|
run "$in" \
|
|
"volume=$pg,alimiter=limit=.82:attack=5:release=50:level=disabled" \
|
|
"${op:-$(out "$in" distort)}"
|
|
}
|
|
|
|
trim(){
|
|
local in=${1:-} a=${2:-} b= op f
|
|
|
|
[ -n "$in" ] &&
|
|
[ -n "$a" ] ||
|
|
x "Usage: $P trim INPUT START_SECONDS [END_SECONDS] [OUTPUT]"
|
|
|
|
n "$a" &&
|
|
awk -v n="$a" \
|
|
'BEGIN { exit !(n >= 0) }' ||
|
|
x "START_SECONDS must be zero or greater."
|
|
|
|
if [ "$#" -ge 3 ]; then
|
|
if n "$3"; then
|
|
b=$3
|
|
op=${4:-}
|
|
else
|
|
op=$3
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$b" ]; then
|
|
awk -v a="$a" -v b="$b" \
|
|
'BEGIN { exit !(b > a) }' ||
|
|
x "END_SECONDS must be greater than START_SECONDS."
|
|
f="atrim=start=$a:end=$b,asetpts=PTS-STARTPTS"
|
|
else
|
|
f="atrim=start=$a,asetpts=PTS-STARTPTS"
|
|
fi
|
|
|
|
run "$in" "$f" "${op:-$(out "$in" trim)}"
|
|
}
|
|
|
|
speed(){
|
|
local in=${1:-} v=${2:-} op=${3:-} f
|
|
|
|
[ -n "$in" ] &&
|
|
[ -n "$v" ] ||
|
|
x "Usage: $P tempo INPUT SPEED_0.125_TO_8 [OUTPUT]"
|
|
|
|
f=$(tempo_filter "$v") ||
|
|
x "SPEED must be between 0.125 and 8."
|
|
|
|
run "$in" "$f" "${op:-$(out "$in" tempo)}"
|
|
}
|
|
|
|
pitch(){
|
|
local in=${1:-} s=${2:-} op=${3:-} r q f
|
|
|
|
[ -n "$in" ] &&
|
|
[ -n "$s" ] ||
|
|
x "Usage: $P pitch INPUT SEMITONES_-36_TO_36 [OUTPUT]"
|
|
|
|
n "$s" &&
|
|
awk -v n="$s" \
|
|
'BEGIN { exit !(n >= -36 && n <= 36) }' ||
|
|
x "SEMITONES must be between -36 and 36."
|
|
|
|
r=$(awk -v s="$s" \
|
|
'BEGIN { printf "%.12f", 2 ^ (s / 12) }')
|
|
|
|
q=$(awk -v r="$r" \
|
|
'BEGIN { printf "%.12f", 1 / r }')
|
|
|
|
f=$(tempo_filter "$q") ||
|
|
x "Cannot safely compensate duration."
|
|
|
|
run "$in" \
|
|
"asetrate=44100*$r,aresample=44100,$f" \
|
|
"${op:-$(out "$in" pitch)}"
|
|
}
|
|
|
|
block(){
|
|
cat <<EOF
|
|
# ffx helper commands
|
|
ffbass(){ $P bass "\$@"; }
|
|
fftreble(){ $P treble "\$@"; }
|
|
ffvolume(){ $P volume "\$@"; }
|
|
ffdistort(){ $P distort "\$@"; }
|
|
fftrim(){ $P trim "\$@"; }
|
|
fftempo(){ $P tempo "\$@"; }
|
|
ffpitch(){ $P pitch "\$@"; }
|
|
EOF
|
|
}
|
|
|
|
shell_rc(){
|
|
case "${SHELL##*/}" in
|
|
zsh) printf '%s/.zshrc\n' "$HOME" ;;
|
|
bash) printf '%s/.bashrc\n' "$HOME" ;;
|
|
*) printf '%s/.profile\n' "$HOME" ;;
|
|
esac
|
|
}
|
|
|
|
add(){
|
|
local rc="$1" b
|
|
mkdir -p -- "$(dirname "$CFG")" ||
|
|
x "Cannot create configuration directory."
|
|
|
|
if [ -f "$CFG" ] &&
|
|
grep -Fqx 'ffbass(){' "$CFG" 2>/dev/null; then
|
|
o "Helper commands are already installed: $CFG"
|
|
else
|
|
b=$(block)
|
|
{
|
|
[ -f "$CFG" ] && cat "$CFG"
|
|
printf '\n%s\n' "$b"
|
|
} > "$CFG.$$" ||
|
|
x "Cannot write helper configuration."
|
|
|
|
mv -f -- "$CFG.$$" "$CFG" ||
|
|
x "Cannot install helper configuration."
|
|
fi
|
|
|
|
if [ ! -f "$rc" ] ||
|
|
! grep -Fq "$CFG" "$rc" 2>/dev/null; then
|
|
printf '\n[ -f "%s" ] && . "%s"\n' "$CFG" "$CFG" >> "$rc" ||
|
|
x "Cannot update $rc."
|
|
fi
|
|
|
|
o "Installed helper commands."
|
|
i "Open a new terminal or run: . \"$rc\""
|
|
}
|
|
|
|
remove(){
|
|
[ -f "$CFG" ] ||
|
|
x "Helper configuration does not exist: $CFG"
|
|
|
|
awk '
|
|
BEGIN { skip = 0 }
|
|
/^# ffx helper commands$/ { skip = 1; next }
|
|
skip && /^ffpitch\(\)\{/ { skip = 0; next }
|
|
!skip { print }
|
|
' "$CFG" > "$CFG.$$" ||
|
|
x "Cannot update helper configuration."
|
|
|
|
mv -f -- "$CFG.$$" "$CFG" ||
|
|
x "Cannot remove helper commands."
|
|
|
|
o "Removed helper commands from $CFG."
|
|
}
|
|
|
|
main(){
|
|
case "${1:-}" in
|
|
-h|--help|help)
|
|
if [ "$#" -gt 1 ]; then
|
|
chelp "$2"
|
|
else
|
|
usage
|
|
fi
|
|
;;
|
|
|
|
-V|--version)
|
|
printf '%s %s\n' "$P" "$V"
|
|
;;
|
|
|
|
--no-color)
|
|
NO_COLOR=1
|
|
R= G= Y= B= D= Z=
|
|
shift
|
|
main "$@"
|
|
;;
|
|
|
|
--add-helper-cmd)
|
|
add "$(shell_rc)"
|
|
;;
|
|
|
|
--remove-helper-cmd)
|
|
remove
|
|
;;
|
|
|
|
install)
|
|
install
|
|
;;
|
|
|
|
bass)
|
|
shift
|
|
if [ "${1:-}" = "-h" ] ||
|
|
[ "${1:-}" = "--help" ]; then
|
|
chelp bass
|
|
else
|
|
boost bass "$@"
|
|
fi
|
|
;;
|
|
|
|
treble)
|
|
shift
|
|
if [ "${1:-}" = "-h" ] ||
|
|
[ "${1:-}" = "--help" ]; then
|
|
chelp treble
|
|
else
|
|
boost treble "$@"
|
|
fi
|
|
;;
|
|
|
|
volume)
|
|
shift
|
|
if [ "${1:-}" = "-h" ] ||
|
|
[ "${1:-}" = "--help" ]; then
|
|
chelp volume
|
|
else
|
|
volume "$@"
|
|
fi
|
|
;;
|
|
|
|
distort)
|
|
shift
|
|
if [ "${1:-}" = "-h" ] ||
|
|
[ "${1:-}" = "--help" ]; then
|
|
chelp distort
|
|
else
|
|
distort "$@"
|
|
fi
|
|
;;
|
|
|
|
trim)
|
|
shift
|
|
if [ "${1:-}" = "-h" ] ||
|
|
[ "${1:-}" = "--help" ]; then
|
|
chelp trim
|
|
else
|
|
trim "$@"
|
|
fi
|
|
;;
|
|
|
|
tempo)
|
|
shift
|
|
if [ "${1:-}" = "-h" ] ||
|
|
[ "${1:-}" = "--help" ]; then
|
|
chelp tempo
|
|
else
|
|
speed "$@"
|
|
fi
|
|
;;
|
|
|
|
pitch)
|
|
shift
|
|
if [ "${1:-}" = "-h" ] ||
|
|
[ "${1:-}" = "--help" ]; then
|
|
chelp pitch
|
|
else
|
|
pitch "$@"
|
|
fi
|
|
;;
|
|
|
|
"")
|
|
usage
|
|
;;
|
|
|
|
*)
|
|
e "Unknown command: $1"
|
|
usage
|
|
exit 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@" |