diff --git a/docs/superpowers/specs/2026-07-01-audio-music-option-design.md b/docs/superpowers/specs/2026-07-01-audio-music-option-design.md new file mode 100644 index 0000000..a02f9df --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-audio-music-option-design.md @@ -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 +0–10 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 0–10 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 `
` (`simulator/static/index.html`) gains a dropdown above the +renamed slider: + +``` +Output + [ Video ] (toggle) + Audio [ Soundtrack ▾ ] ← NEW: ` 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/.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/.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 / 音楽) +- `