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:
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user