feat(audio): Audio source dropdown (None/Soundtrack/Music) + Volume rename

Split the single 0–10 'Audio' slider into two controls:
- Audio  — a dropdown selecting the source: None / Soundtrack / Music
- Volume — the renamed 0–10 slider (master gain for the active source)

Music is one ethereal, relaxing ambient loop played the same at every
altitude (no per-altitude crossfade), per the innerflo.me brief. Sourced
CC0: deadrobotmusic 'Ambient F Sharp Minor Ethereal Choir Pad' (freesound
#808032); added to build_audio_media.py, the candidate-pool doc, and the
public credits colophon. Media stays gitignored; run build_audio_media.py.

Routing lives in a pure, node-tested HEFScrub.audioPlan(source, level, frac);
app.js's restAudio/blendAudio consult it. None greys out Volume. The welcome
screen's mirror level slider is relabeled Volume too. i18n for all 4 langs.

Spec: docs/superpowers/specs/2026-07-01-audio-music-option-design.md
Tests: +5 node unit (audioPlan), +4 e2e (audio-source.spec.ts); 32 related
e2e green, 38 node unit green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-07-01 21:55:23 -07:00
parent 6816e4ce75
commit ee7190bb4a
9 changed files with 239 additions and 32 deletions
+36
View File
@@ -0,0 +1,36 @@
"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
const S = require("../static/scrub.js");
// audioPlan decides, purely, what the audio layer should do for a given source
// mode + master level + crossfade fraction. app.js's restAudio/blendAudio consult
// it for the mode branch; the physical <audio> element assignment stays in app.js.
test("audioPlan: None source is silent regardless of level", () => {
assert.deepEqual(S.audioPlan("none", 1, 0.5), { mode: "silent" });
});
test("audioPlan: level 0 is silent even for soundtrack/music", () => {
assert.deepEqual(S.audioPlan("soundtrack", 0, 0.5), { mode: "silent" });
assert.deepEqual(S.audioPlan("music", 0, 0), { mode: "silent" });
});
test("audioPlan: Music plays a single track at the master level, no blend", () => {
assert.deepEqual(S.audioPlan("music", 0.8, 0.5), { mode: "music", vol: 0.8 });
});
test("audioPlan: Music ignores altitude fraction (constant across the dial)", () => {
const a = S.audioPlan("music", 0.6, 0);
const b = S.audioPlan("music", 0.6, 0.9);
assert.deepEqual(a, b);
});
test("audioPlan: Soundtrack blends lo/hi by fraction, scaled by level", () => {
assert.deepEqual(S.audioPlan("soundtrack", 1, 0), { mode: "blend", lo: 1, hi: 0 });
assert.deepEqual(S.audioPlan("soundtrack", 1, 1), { mode: "blend", lo: 0, hi: 1 });
const mid = S.audioPlan("soundtrack", 0.5, 0.5);
assert.equal(mid.mode, "blend");
assert.ok(Math.abs(mid.lo - 0.25) < 1e-9);
assert.ok(Math.abs(mid.hi - 0.25) < 1e-9);
});