Fix sound options and panel controls
This commit is contained in:
+11
-3
@@ -85,13 +85,21 @@ var EFFECTS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "sound", label: "Sound", icon: "",
|
type: "sound", label: "Sound", icon: "",
|
||||||
subtitle: "Plays a chime",
|
subtitle: "Plays a selected chime; Fit in 0 uses its natural speed",
|
||||||
rows: [
|
rows: [
|
||||||
|
{ key: "duration", label: "Fit in", min: 0, max: 30, step: 0.1, fallback: 0, unit: " s" },
|
||||||
{ key: "intensity", label: "Volume", min: 0, max: 2, step: 0.01, fallback: 1, unit: "" },
|
{ key: "intensity", label: "Volume", min: 0, max: 2, step: 0.01, fallback: 1, unit: "" },
|
||||||
{ key: "speed", label: "Repeat", min: 1, max: 50, step: 1, fallback: 1, unit: " ×" }
|
{ key: "repeat", label: "Times", min: 1, max: 50, step: 1, fallback: 1, unit: " ×" }
|
||||||
],
|
],
|
||||||
options: [
|
options: [
|
||||||
{ key: "file", label: "File", type: "text", fallback: "", placeholder: "empty: the default chime · or a path to a sound file" }
|
{ key: "sound", label: "Sound", type: "enum", fallback: "message",
|
||||||
|
values: [
|
||||||
|
{ value: "message", label: "Message" },
|
||||||
|
{ value: "bell", label: "Bell" },
|
||||||
|
{ value: "warning", label: "Warning" },
|
||||||
|
{ value: "complete", label: "Complete" },
|
||||||
|
{ value: "phone", label: "Phone" }
|
||||||
|
] }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ or resume every effect. A first rule is there to start: any notification that
|
|||||||
mentions **deliveroo** shakes the screen.
|
mentions **deliveroo** shakes the screen.
|
||||||
|
|
||||||
Runtime dependencies, all part of a stock Omarchy: `jq`, `busctl` (systemd),
|
Runtime dependencies, all part of a stock Omarchy: `jq`, `busctl` (systemd),
|
||||||
`hyprctl`, `notify-send`, `pw-play` (for the sound). `./install.sh` is an
|
`hyprctl`, `notify-send`, `pw-play` (for the sound), and `ffmpeg`/`ffprobe`
|
||||||
|
(when sound repeats must fit a duration). `./install.sh` is an
|
||||||
optional helper for a checkout somewhere else: it links the plugin into
|
optional helper for a checkout somewhere else: it links the plugin into
|
||||||
`~/.config/omarchy/plugins`, puts the `attention-required` command on your
|
`~/.config/omarchy/plugins`, puts the `attention-required` command on your
|
||||||
PATH and enables the bell. `omarchy plugin add` never runs it.
|
PATH and enables the bell. `omarchy plugin add` never runs it.
|
||||||
@@ -87,7 +88,7 @@ that does not set the option itself.
|
|||||||
| `airplane` | A plane bobs across the screen, trailing exhaust, towing the message on a rippling flag. | flight seconds (7) | size (1) | | `altitude` 0..1 (0.2), `direction`: ltr, rtl; `text` |
|
| `airplane` | A plane bobs across the screen, trailing exhaust, towing the message on a rippling flag. | flight seconds (7) | size (1) | | `altitude` 0..1 (0.2), `direction`: ltr, rtl; `text` |
|
||||||
| `confetti` | Confetti in the theme's colours. | seconds (1) | amount (1) | launch power (1) | `style`: cannons (bottom corners, up), burst (centre), rain (top) |
|
| `confetti` | Confetti in the theme's colours. | seconds (1) | amount (1) | launch power (1) | `style`: cannons (bottom corners, up), burst (centre), rain (top) |
|
||||||
| `blink` | The screen dims and comes back. | seconds (1) | darkness 0..1 (0.6) | blinks per second (2) | |
|
| `blink` | The screen dims and comes back. | seconds (1) | darkness 0..1 (0.6) | blinks per second (2) | |
|
||||||
| `sound` | Plays a chime with `pw-play`. | | volume (1) | times (1) | `file` |
|
| `sound` | Plays a selected chime with `pw-play`. | seconds to fit repeats; 0 is natural speed (0) | volume (1) | times (1) | Message, bell, warning, complete, or phone |
|
||||||
| `focus` | Brings the sending app's window to the front. | | | | `window`: a class or title instead |
|
| `focus` | Brings the sending app's window to the front. | | | | `window`: a class or title instead |
|
||||||
| `command` | Runs a shell command. | | | | `run`; sees `AR_APP`, `AR_SUMMARY`, `AR_BODY`, `AR_RULE`, every option as `AR_OPT_<NAME>` |
|
| `command` | Runs a shell command. | | | | `run`; sees `AR_APP`, `AR_SUMMARY`, `AR_BODY`, `AR_RULE`, every option as `AR_OPT_<NAME>` |
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ Values outside a range are clamped when the effect runs. Every effect has
|
|||||||
| `airplane` | flight seconds 0.5..120 (7) | size 0.2..10 (1) | | `altitude` 0..1 from the top (0.2); `direction`: `ltr`, `rtl`; `text` template |
|
| `airplane` | flight seconds 0.5..120 (7) | size 0.2..10 (1) | | `altitude` 0..1 from the top (0.2); `direction`: `ltr`, `rtl`; `text` template |
|
||||||
| `confetti` | seconds it keeps coming 0.1..60 (1) | amount 0.05..20 (1) | launch power 0.1..10 (1) | `style`: `cannons` (bottom corners, up), `burst` (centre), `rain` (top) |
|
| `confetti` | seconds it keeps coming 0.1..60 (1) | amount 0.05..20 (1) | launch power 0.1..10 (1) | `style`: `cannons` (bottom corners, up), `burst` (centre), `rain` (top) |
|
||||||
| `blink` | seconds 0.1..30 (1) | darkness 0.01..1 (0.6) | blinks per second 0.2..30 (2) | |
|
| `blink` | seconds 0.1..30 (1) | darkness 0.01..1 (0.6) | blinks per second 0.2..30 (2) | |
|
||||||
| `sound` | | volume 0..2 (1) | times played 1..50 (1) | `file`: path to a sound file (default: freedesktop's new-message chime) |
|
| `sound` | seconds to fit repeats 0..30; 0 is natural speed (0) | volume 0..2 (1) | repeat 1..50 (1) | `sound`: `message`, `bell`, `warning`, `complete`, or `phone` |
|
||||||
| `focus` | | | | `window`: a window class or title to focus instead of the sending app |
|
| `focus` | | | | `window`: a window class or title to focus instead of the sending app |
|
||||||
| `command` | | | | `run`: a shell command, run as the user with `AR_APP`, `AR_SUMMARY`, `AR_BODY`, `AR_RULE`, `AR_KEY`, `AR_URGENCY` and every effect option as `AR_OPT_<NAME>` in the environment |
|
| `command` | | | | `run`: a shell command, run as the user with `AR_APP`, `AR_SUMMARY`, `AR_BODY`, `AR_RULE`, `AR_KEY`, `AR_URGENCY` and every effect option as `AR_OPT_<NAME>` in the environment |
|
||||||
|
|
||||||
|
|||||||
+35
-10
@@ -25,7 +25,7 @@ Panel {
|
|||||||
implicitWidth: button.implicitWidth
|
implicitWidth: button.implicitWidth
|
||||||
implicitHeight: button.implicitHeight
|
implicitHeight: button.implicitHeight
|
||||||
|
|
||||||
readonly property var svc: bar && bar.shell ? bar.shell.serviceFor("alanfortlink.attention-required") : null
|
property var svc: null
|
||||||
readonly property bool paused: svc ? !svc.enabled : false
|
readonly property bool paused: svc ? !svc.enabled : false
|
||||||
readonly property var cfg: svc ? svc.rawConfig : ({})
|
readonly property var cfg: svc ? svc.rawConfig : ({})
|
||||||
readonly property var rules: svc ? svc.rawRules : []
|
readonly property var rules: svc ? svc.rawRules : []
|
||||||
@@ -40,6 +40,25 @@ Panel {
|
|||||||
readonly property int labelW: Style.space(96)
|
readonly property int labelW: Style.space(96)
|
||||||
readonly property var catalog: Catalog.EFFECTS
|
readonly property var catalog: Catalog.EFFECTS
|
||||||
|
|
||||||
|
function bindService() {
|
||||||
|
var host = bar && bar.shell ? bar.shell : null
|
||||||
|
if (!host || typeof host.serviceFor !== "function") return
|
||||||
|
var service = host.serviceFor("alanfortlink.attention-required")
|
||||||
|
if (service) root.svc = service
|
||||||
|
}
|
||||||
|
|
||||||
|
onBarChanged: {
|
||||||
|
root.svc = null
|
||||||
|
root.bindService()
|
||||||
|
}
|
||||||
|
Component.onCompleted: root.bindService()
|
||||||
|
Timer {
|
||||||
|
interval: 200
|
||||||
|
running: root.svc === null
|
||||||
|
repeat: true
|
||||||
|
onTriggered: root.bindService()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- navigation ----------
|
// ---------- navigation ----------
|
||||||
property string page: "list" // list | rule | effect
|
property string page: "list" // list | rule | effect
|
||||||
property int current: -1 // the rule the deeper pages are about
|
property int current: -1 // the rule the deeper pages are about
|
||||||
@@ -185,27 +204,33 @@ Panel {
|
|||||||
spacing: Style.space(10)
|
spacing: Style.space(10)
|
||||||
|
|
||||||
PanelHero {
|
PanelHero {
|
||||||
|
id: hero
|
||||||
width: parent.width
|
width: parent.width
|
||||||
title: "Attention Required"
|
title: "Attention Required"
|
||||||
meta: root.armedLabel()
|
meta: root.armedLabel()
|
||||||
detail: root.svc && root.svc.configError !== "" ? root.svc.configError : ""
|
detail: !root.svc ? "Service unavailable" : (root.svc.configError !== "" ? root.svc.configError : "")
|
||||||
foreground: root.fg
|
foreground: root.fg
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
|
readonly property bool attentionPaused: root.paused
|
||||||
|
readonly property var attentionService: root.svc
|
||||||
|
function toggleAttention() {
|
||||||
|
if (attentionService) attentionService.setEnabled(attentionPaused)
|
||||||
|
}
|
||||||
iconComponent: Component {
|
iconComponent: Component {
|
||||||
Text {
|
Text {
|
||||||
text: root.paused ? "" : ""
|
text: hero.attentionPaused ? "" : ""
|
||||||
color: root.fg
|
color: hero.foreground
|
||||||
opacity: root.paused ? 0.5 : 1
|
opacity: hero.attentionPaused ? 0.5 : 1
|
||||||
font.family: root.fontFamily
|
font.family: hero.fontFamily
|
||||||
font.pixelSize: Style.font.display
|
font.pixelSize: Style.font.display
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trailingControl: Component {
|
trailingControl: Component {
|
||||||
ToggleSwitch {
|
ToggleSwitch {
|
||||||
checked: !root.paused
|
checked: !hero.attentionPaused
|
||||||
foreground: root.fg
|
foreground: hero.foreground
|
||||||
cursorPad: root.trailInset
|
cursorPad: Style.space(6)
|
||||||
onToggled: if (root.svc) root.svc.setEnabled(root.paused)
|
onToggled: hero.toggleAttention()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
+55
-5
@@ -3,14 +3,23 @@
|
|||||||
# Plays a sound.
|
# Plays a sound.
|
||||||
#
|
#
|
||||||
# Options (set on the effect in rules.json):
|
# Options (set on the effect in rules.json):
|
||||||
# file path to a sound file; default is the freedesktop "new message" chime
|
# 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)
|
# intensity volume, 0.0 .. 1.0, default 1 (`volume` works too)
|
||||||
# speed how many times to play it, default 1 (`repeat` works too)
|
# repeat how many times to play it, default 1 (`speed` works for old rules)
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
file=${AR_OPT_FILE:-/usr/share/sounds/freedesktop/stereo/message-new-instant.oga}
|
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}}
|
volume=${AR_OPT_INTENSITY:-${AR_OPT_VOLUME:-1}}
|
||||||
times=${AR_OPT_SPEED:-${AR_OPT_REPEAT:-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
|
[[ $volume =~ ^[0-9]*\.?[0-9]+$ ]] || volume=1
|
||||||
[[ $times =~ ^[0-9]+$ ]] || times=1
|
[[ $times =~ ^[0-9]+$ ]] || times=1
|
||||||
(( times > 50 )) && times=50
|
(( times > 50 )) && times=50
|
||||||
@@ -31,6 +40,47 @@ play() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
play_repeatedly() {
|
||||||
for ((i = 0; i < times; i++)); do
|
for ((i = 0; i < times; i++)); do
|
||||||
play || exit 1
|
play || return 1
|
||||||
done
|
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
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ const assert = require("assert")
|
|||||||
const src = fs.readFileSync(path.join(__dirname, "..", "Rules.js"), "utf8").replace(/\/\/ Not a .pragma library[^\n]*/, "")
|
const src = fs.readFileSync(path.join(__dirname, "..", "Rules.js"), "utf8").replace(/\/\/ Not a .pragma library[^\n]*/, "")
|
||||||
const R = {}
|
const R = {}
|
||||||
new Function("exports", src + "\n" + ["normalizeConfig", "matchingRules", "stripTags", "describeRule", "normalizeEffect", "withDefaults", "renderTemplate"].map(n => `exports.${n} = ${n}`).join("\n"))(R)
|
new Function("exports", src + "\n" + ["normalizeConfig", "matchingRules", "stripTags", "describeRule", "normalizeEffect", "withDefaults", "renderTemplate"].map(n => `exports.${n} = ${n}`).join("\n"))(R)
|
||||||
|
const catalogSrc = fs.readFileSync(path.join(__dirname, "..", "EffectCatalog.js"), "utf8")
|
||||||
|
const C = {}
|
||||||
|
new Function("exports", catalogSrc + "\nexports.effects = EFFECTS")(C)
|
||||||
|
const settingsSrc = fs.readFileSync(path.join(__dirname, "..", "Settings.qml"), "utf8")
|
||||||
|
|
||||||
const cfg = R.normalizeConfig({
|
const cfg = R.normalizeConfig({
|
||||||
rules: [
|
rules: [
|
||||||
@@ -58,4 +62,13 @@ assert.deepStrictEqual(all.rules[1].effects, [])
|
|||||||
assert.strictEqual(R.renderTemplate("", { summary: "Hi", body: "<b>x</b>" }, { name: "r" }), "Hi")
|
assert.strictEqual(R.renderTemplate("", { summary: "Hi", body: "<b>x</b>" }, { name: "r" }), "Hi")
|
||||||
assert.strictEqual(R.renderTemplate("{rule}: {summary} / {body} ({app})", { app: "A", summary: "Hi", body: "<b>x</b>" }, { name: "r" }), "r: Hi / x (A)")
|
assert.strictEqual(R.renderTemplate("{rule}: {summary} / {body} ({app})", { app: "A", summary: "Hi", body: "<b>x</b>" }, { name: "r" }), "r: Hi / x (A)")
|
||||||
|
|
||||||
|
const sound = C.effects.find(effect => effect.type === "sound")
|
||||||
|
assert.ok(sound.rows.some(row => row.key === "duration" && row.min === 0 && row.max === 30 && row.fallback === 0))
|
||||||
|
assert.ok(sound.rows.some(row => row.key === "repeat" && row.min === 1 && row.max === 50))
|
||||||
|
assert.strictEqual(sound.options.length, 1)
|
||||||
|
assert.deepStrictEqual(sound.options[0].values.map(value => value.value), ["message", "bell", "warning", "complete", "phone"])
|
||||||
|
assert.ok(settingsSrc.includes("function bindService()"))
|
||||||
|
assert.ok(settingsSrc.includes("running: root.svc === null"))
|
||||||
|
assert.ok(settingsSrc.includes("onToggled: hero.toggleAttention()"))
|
||||||
|
|
||||||
console.log("rules: all tests passed")
|
console.log("rules: all tests passed")
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Verifies that the repeat setting reaches the player, including when a saved
|
||||||
|
# legacy speed value is present alongside it.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "$0")/.." && pwd)
|
||||||
|
log=$(mktemp)
|
||||||
|
trap 'rm -f -- "$log"' EXIT
|
||||||
|
|
||||||
|
pw-play() { printf '%s\n' "$*" >> "$AR_SOUND_LOG"; }
|
||||||
|
export -f pw-play
|
||||||
|
|
||||||
|
# Every UI choice must reach a different system sound through ar-effect.
|
||||||
|
sounds=(message bell warning complete phone)
|
||||||
|
files=(message-new-instant.oga bell.oga dialog-warning.oga complete.oga phone-incoming-call.oga)
|
||||||
|
for i in "${!sounds[@]}"; do
|
||||||
|
: > "$log"
|
||||||
|
payload="{\"effect\":{\"type\":\"sound\",\"sound\":\"${sounds[i]}\",\"repeat\":1,\"duration\":0},\"notification\":{},\"rule\":{\"name\":\"test\"}}"
|
||||||
|
AR_SOUND_LOG="$log" bash "$repo/bin/ar-effect" sound "$payload"
|
||||||
|
grep -Fqx -- "--volume=1 /usr/share/sounds/freedesktop/stereo/${files[i]}" "$log"
|
||||||
|
done
|
||||||
|
|
||||||
|
# An explicit repeat count wins over a saved legacy speed value.
|
||||||
|
: > "$log"
|
||||||
|
payload='{"effect":{"type":"sound","sound":"phone","repeat":3,"speed":1,"duration":0},"notification":{},"rule":{"name":"test"}}'
|
||||||
|
AR_SOUND_LOG="$log" bash "$repo/bin/ar-effect" sound "$payload"
|
||||||
|
|
||||||
|
[[ $(wc -l < "$log") -eq 3 ]]
|
||||||
|
[[ $(grep -Fxc -- "--volume=1 /usr/share/sounds/freedesktop/stereo/phone-incoming-call.oga" "$log") -eq 3 ]]
|
||||||
|
|
||||||
|
# Each repeat is separately tempo-adjusted when a duration is selected.
|
||||||
|
ffprobe() { printf '1\n'; }
|
||||||
|
ffmpeg() {
|
||||||
|
printf 'ffmpeg %s\n' "$*" >> "$AR_SOUND_LOG"
|
||||||
|
printf 'wav'
|
||||||
|
}
|
||||||
|
pw-play() {
|
||||||
|
cat >/dev/null
|
||||||
|
printf 'pw-play %s\n' "$*" >> "$AR_SOUND_LOG"
|
||||||
|
}
|
||||||
|
export -f ffprobe ffmpeg pw-play
|
||||||
|
: > "$log"
|
||||||
|
AR_OPT_SOUND=bell \
|
||||||
|
AR_OPT_DURATION=1 \
|
||||||
|
AR_OPT_REPEAT=3 \
|
||||||
|
AR_SOUND_LOG="$log" \
|
||||||
|
bash "$repo/effects/sound"
|
||||||
|
|
||||||
|
[[ $(grep -Fc -- 'ffmpeg -nostdin -v error -i /usr/share/sounds/freedesktop/stereo/bell.oga -filter:a atempo=2,atempo=1.50000000 -f wav -' "$log") -eq 3 ]]
|
||||||
|
[[ $(grep -Fxc -- 'pw-play --volume=1 -' "$log") -eq 3 ]]
|
||||||
|
|
||||||
|
# A longer target duration slows each repetition by the corresponding factor.
|
||||||
|
: > "$log"
|
||||||
|
AR_OPT_SOUND=warning \
|
||||||
|
AR_OPT_DURATION=4 \
|
||||||
|
AR_OPT_REPEAT=1 \
|
||||||
|
AR_SOUND_LOG="$log" \
|
||||||
|
bash "$repo/effects/sound"
|
||||||
|
|
||||||
|
grep -Fq -- '-filter:a atempo=0.5,atempo=0.50000000 -f wav -' "$log"
|
||||||
|
grep -Fqx -- 'pw-play --volume=1 -' "$log"
|
||||||
|
|
||||||
|
echo "sound: all tests passed"
|
||||||
Reference in New Issue
Block a user