#!/usr/bin/env bash # # Plays a sound. # # Options (set on the effect in rules.json): # sound message | bell | warning | complete | phone (default: message) # duration fit all repeats into this many seconds; 0 uses natural speed # intensity volume, 0.0 .. 1.0, default 1 (`volume` works too) # repeat how many times to play it, default 1 (`speed` works for old rules) set -uo pipefail case ${AR_OPT_SOUND:-message} in bell) file=/usr/share/sounds/freedesktop/stereo/bell.oga ;; warning) file=/usr/share/sounds/freedesktop/stereo/dialog-warning.oga ;; complete) file=/usr/share/sounds/freedesktop/stereo/complete.oga ;; phone) file=/usr/share/sounds/freedesktop/stereo/phone-incoming-call.oga ;; *) file=/usr/share/sounds/freedesktop/stereo/message-new-instant.oga ;; esac volume=${AR_OPT_INTENSITY:-${AR_OPT_VOLUME:-1}} times=${AR_OPT_REPEAT:-} [[ $times =~ ^[0-9]+$ ]] || times=${AR_OPT_SPEED:-1} duration=${AR_OPT_DURATION:-0} [[ $volume =~ ^[0-9]*\.?[0-9]+$ ]] || volume=1 [[ $times =~ ^[0-9]+$ ]] || times=1 (( times > 50 )) && times=50 file=${file/#\~/$HOME} [[ -r $file ]] || { echo "sound: cannot read $file" >&2; exit 1; } play() { if command -v pw-play >/dev/null 2>&1; then pw-play --volume="$volume" "$file" elif command -v paplay >/dev/null 2>&1; then paplay "$file" elif command -v mpv >/dev/null 2>&1; then mpv --no-video --really-quiet --volume="$(awk -v v="$volume" 'BEGIN { printf "%d", v * 100 }')" "$file" else echo "sound: no player found (pw-play, paplay or mpv)" >&2 return 1 fi } play_repeatedly() { for ((i = 0; i < times; i++)); do play || return 1 done } # ffmpeg changes the tempo of each chime so all repeats fit the chosen duration. # Each repetition gets its own playback stream, keeping every hit distinct. fit_duration() { command -v ffprobe >/dev/null 2>&1 || { echo "sound: ffprobe is required when Fit in is set" >&2; return 1; } command -v ffmpeg >/dev/null 2>&1 || { echo "sound: ffmpeg is required when Fit in is set" >&2; return 1; } command -v pw-play >/dev/null 2>&1 || { echo "sound: pw-play is required when Fit in is set" >&2; return 1; } local clip total tempo filter clip=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$file" 2>/dev/null) || return 1 [[ $clip =~ ^[0-9]*\.?[0-9]+$ ]] || return 1 total=$(awk -v c="$clip" -v n="$times" 'BEGIN { printf "%.8f", c * n }') tempo=$(awk -v t="$total" -v d="$duration" 'BEGIN { printf "%.8f", t / d }') [[ $tempo =~ ^[0-9]*\.?[0-9]+$ ]] || return 1 # atempo accepts one factor from 0.5 to 2. Chain factors for every value the # settings slider permits, preserving pitch while changing playback speed. filter= while awk -v v="$tempo" 'BEGIN { exit !(v > 2) }'; do filter+="atempo=2," tempo=$(awk -v v="$tempo" 'BEGIN { printf "%.8f", v / 2 }') done while awk -v v="$tempo" 'BEGIN { exit !(v < 0.5) }'; do filter+="atempo=0.5," tempo=$(awk -v v="$tempo" 'BEGIN { printf "%.8f", v * 2 }') done filter+="atempo=$tempo" for ((i = 0; i < times; i++)); do ffmpeg -nostdin -v error -i "$file" -filter:a "$filter" -f wav - \ | pw-play --volume="$volume" - || return 1 done } if [[ $duration =~ ^[0-9]*\.?[0-9]+$ ]] && awk -v d="$duration" 'BEGIN { exit !(d > 0) }'; then fit_duration || exit $? else play_repeatedly fi