docs(spec): Audio source dropdown (None/Soundtrack/Music) + Volume rename

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-07-01 21:41:40 -07:00
parent 7ec8b607d2
commit 6816e4ce75
@@ -0,0 +1,113 @@
# Audio "Music" option + "Volume" rename — design
*Date: 2026-07-01 · Status: approved design, awaiting spec review*
## Goal
Give the listener a choice of what to hear. Today the Output panel has a single
010 slider labeled **Audio** that acts as the master gain for the per-altitude
ambience crossfade. We split that into two controls:
- **Audio** — a dropdown selecting the audio *source*: **None · Soundtrack · Music**
- **Volume** — the existing 010 slider (relabeled), master gain for whatever
source is active
**Music** is one ethereal, relaxing ambient loop (the innerflo.me vibe) that
plays continuously and does **not** change with altitude.
## Non-goals
- No per-altitude music set (Music is a single track, not a musical Soundtrack).
- No change to the Soundtrack behavior itself (per-altitude crossfade stays exactly as-is).
- No new audio elements — the existing `#aud` / `#aud-b` pair is reused.
## Control surface
The Output `<fieldset>` (`simulator/static/index.html`) gains a dropdown above the
renamed slider:
```
Output
[ Video ] (toggle)
Audio [ Soundtrack ▾ ] ← NEW: <select id="audio-source">
Volume ●──────── 2/10 ← existing #audio slider, label → "Volume"
Speed ●──────── 1.00×
🌐 [ Language ▾ ]
```
- The dropdown is a `<select id="audio-source">` with options `none` / `soundtrack`
/ `music`, default **`soundtrack`** (preserves today's launch experience).
- The slider keeps `id="audio"` (avoids touching every reference) but its visible
label becomes **Volume** (new i18n key `output.volume`). The `output.audio` key
("Audio") now labels the dropdown.
## Behavior by mode
| Mode | Behavior |
|---|---|
| **None** | Both `#aud`/`#aud-b` fade to silence — the existing `audioLevel()===0` path. The Volume slider is disabled (greyed). |
| **Soundtrack** | Unchanged: per-altitude ambience crossfading across the two elements as the dial moves (`blendAudio` / `restAudio`). |
| **Music** | The single music loop plays on `#aud` at master volume; `#aud-b` fades out. Altitude changes do **not** crossfade or reload audio. |
Volume = 0 remains a hard mute in every mode.
## Code shape
All in `simulator/static/app.js`, following the existing audio-layer seams
(`restAudio`, `blendAudio`, `ensurePlaying`, `scaleAudioUrl`):
- **`audioSource()`** — new helper reading `#audio-source``"none" | "soundtrack" | "music"`.
- **`musicUrl()`** — returns the resolved music loop url via `mediaBase()`
(mirrors `scaleAudioUrl`'s R2/local routing), e.g. `mediaBase()+"audio/music/<name>.loop.mp3"`.
- **`restAudio(index)`** branches:
- `none` (or `audioLevel()===0`) → existing fade-to-silence.
- `music``ensurePlaying(aud, musicUrl(), audioVol())`; `fadeVolume(audB, 0, …)`.
- `soundtrack` → unchanged per-altitude logic.
- **`blendAudio(lo, frac)`** returns early (no crossfade) when source is `music` or
`none`; only Soundtrack blends across the altitude boundary.
- **Dropdown wiring**: `#audio-source` `change` → reconcile synchronously in the
gesture (`applyAudio()`), same Safari-unlock discipline the slider already uses;
toggles the Volume slider's `disabled` when None.
- **`beginExperience()`**: unchanged except it snaps **Volume** (`#audio`) to the
start level 2; default source stays Soundtrack.
- **Diagnostic readout** (`audioStatusText`): prefix with the current source
(`none`/`soundtrack`/`music`) so a "no sound" report stays legible.
No server/engine change — audio source is a pure client concern.
## Music asset & licensing
- One **CC0 / public-domain** ethereal ambient loop (innerflo.me-style: soft,
evolving, no percussion, seamless loop). Sourced by the implementer; reviewed by
ear before ship.
- Stored at `simulator/.../media/audio/music/<name>.loop.mp3` alongside the
per-scale soundtracks, and registered in the credits manifest exactly like every
other audio asset (this project credits all CC/PD audio — see `credits.html`).
- Fallback path constant mirrors the `SCALE_AUDIO_FALLBACK` pattern so Music works
even against an older server process.
## i18n + a11y
- New key `output.volume` (en: "Volume", es: "Volumen", fr: "Volume", ja: "音量").
- Dropdown option labels localized in en/es/fr/ja:
- None (Ninguno / Aucun / なし)
- Soundtrack (Banda sonora / Bande-son / サウンドトラック)
- Music (Música / Musique / 音楽)
- `<select id="audio-source">` gets an `aria-label` tied to the Audio label.
- Reduced-motion behavior is unaffected (audio is orthogonal to motion).
## Tests
- **Node unit** (`simulator/unit/…`): `audioSource()` routing and the
`blendAudio`/`restAudio` branch selection — none→silent, music→single-element +
no crossfade on altitude change, soundtrack→unchanged.
- **E2E** (`simulator/e2e/…`): dropdown switches modes; Music produces a single
playing element that persists across an altitude change; None disables the Volume
slider and silences audio; the slider label reads "Volume".
## Decisions (confirmed with operator)
1. **Music = one continuous, altitude-independent loop** (not per-altitude, not ducked).
2. Default Audio source = **Soundtrack**; Music is opt-in.
3. **None** disables the Volume slider.
4. Music track is **operator-approved CC0** sourced by the implementer.