diff --git a/.gitignore b/.gitignore
index d515d75..2060ead 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,8 @@ simulator/sample_media/**/*.m4a
# Local-only candidate review galleries (re-buildable; not shipped content).
simulator/static/review.html
simulator/static/review_audio.html
+simulator/static/review_binaural.html
+simulator/static/review_music.html
# Editor annotation/comment-thread metadata (not project content)
.threads/
diff --git a/docs/audio-candidate-pool.md b/docs/audio-candidate-pool.md
index 140c3b4..6073fa1 100644
--- a/docs/audio-candidate-pool.md
+++ b/docs/audio-candidate-pool.md
@@ -50,7 +50,13 @@ recordings (NASA/NOAA) keep the "machines reshaping perception" thesis literal.
| π coast | `coast/waves` | Sea waves + tern calls | BigSoundBank #0267 β https://bigsoundbank.com/sea-waves-and-seagulls-s0267.html (`/UPLOAD/mp3/0267.mp3`) | 57s | CC0 |
| π³ abyss | `abyss/whale` | NE Pacific blue-whale "AB call" | NOAA PMEL β https://www.pmel.noaa.gov/acoustics/multimedia/NETS_bluWhale.wav | 13s | PD (NOAA) |
-## Music (the Audio-dropdown "Music" option, shipped 2026-07-01)
+## Music (the Audio-dropdown "Music" option)
+
+> **SUPERSEDED 2026-07-02:** the single ethereal loop below was replaced by a
+> **rotating crossfading playlist of mellow drumless synth** (Kevin MacLeod, CC BY) β
+> see `docs/music-candidate-pool.md`. The ethereal-loop record is kept for history.
+
+### (history) ethereal loop β shipped 2026-07-01, then replaced
**One** ethereal ambient loop, played the same at every altitude (altitude-independent β
NOT the per-altitude idea the "cosmos/music" / "Frozen Star" rows above reserved; those
diff --git a/docs/binaural-candidate-pool.md b/docs/binaural-candidate-pool.md
new file mode 100644
index 0000000..6e7e1bd
--- /dev/null
+++ b/docs/binaural-candidate-pool.md
@@ -0,0 +1,110 @@
+# Binaural / entrainment candidate pool β stress-relief "Music" tracks (2026-07-02)
+
+> **PARKED (2026-07-02):** the operator dropped the binaural direction in favor of
+> **mellow chordal synth music** β see `docs/music-candidate-pool.md`. This file is kept
+> as the record of the binaural exploration (and its NC-licensing dead-ends).
+
+
+Candidates for evolving the **Music** audio option into a stress-relief set of
+**binaural-beat / brainwave-entrainment** tracks that **crossfade between each
+other** (not a single loop). Operator brief: "binaural beats for stress relief."
+
+**All 11 below are CC0** (public domain β no attribution legally required; we credit
+anyway as courtesy), **all stereo**, all sourced from freesound.org and license-
+verified by fetching each page. Review gallery (gitignored, local-only, like
+`review_audio.html`): `simulator/static/review_binaural.html` β open
+`/review_binaural.html` with the sim server running. It streams each track's public
+HQ preview so you can audition + tick keepers.
+
+## Caveats (operator, read before picking)
+
+- **True binaural (#1β7) needs headphones.** The beat *is* the L/R frequency
+ difference, so on speakers it collapses to a steady carrier tone (no entrainment).
+ **#8β11** are ambient sub-bass / pulsing drones that keep the calm feel on speakers.
+- **Band coverage:** heavy on **delta (0.5β1.5 Hz)** + one clean **theta (4 Hz, #2)**.
+ **No CC0 alpha (8β12 Hz)** track surfaced β widen to CC-BY (FMA/ccMixter) if wanted.
+- **No clean CC0/CC-BY isochronic or monaural** (the speaker-correct entrainment type)
+ was found β every hit was NC/SA or unlicensed (see rejects). For speaker playback,
+ lean on #8β11 for the "beat" feel.
+- **Previews vs originals:** the `cdn.freesound.org/β¦-hq.mp3` URLs are public streamable
+ previews (verified live, HTTP 206). The **originals** (WAV/AIF/full-MP3) need a free
+ freesound login; CC0 permits self-hosting, so for keepers download the originals and
+ host on R2 β do not hot-link the CDN in production.
+
+## Candidates
+
+| # | id | title | artist | source page | license | dur | type | beat / carrier |
+|---|---|---|---|---|---|---|---|---|
+| 1 | `fs75309` | 60-64-delta | iantm | https://freesound.org/people/iantm/sounds/75309/ | CC0 | 0:30 | true-binaural | 4 Hz Β· 60/64 Hz |
+| 2 | `fs457654` | Binaural Beats (Theta Waves 4 Hz) | Fabrizio84 | https://freesound.org/people/Fabrizio84/sounds/457654/ | CC0 | 30:00 | true-binaural | 4 Hz theta |
+| 3 | `fs803742` | 1.23456789 Hz binaural beat (69L/70.23R) | itchytooth | https://freesound.org/people/itchytooth/sounds/803742/ | CC0 | 60:00 | true-binaural | 1.23 Hz delta Β· 69/70.23 Hz |
+| 4 | `fs414271` | Binaural Delta 440/440.5 Hz | pbabin | https://freesound.org/people/pbabin/sounds/414271/ | CC0 | 10:00 | true-binaural | 0.5 Hz Β· 440/440.5 Hz |
+| 5 | `fs414272` | Binaural Delta 500/501.5 Hz | pbabin | https://freesound.org/people/pbabin/sounds/414272/ | CC0 | 10:00 | true-binaural | 1.5 Hz Β· 500/501.5 Hz |
+| 6 | `fs414273` | Binaural Delta 52/52.5 Hz | pbabin | https://freesound.org/people/pbabin/sounds/414273/ | CC0 | 10:00 | true-binaural | 0.5 Hz Β· 52/52.5 Hz |
+| 7 | `fs414274` | Binaural Delta 60/61.5 Hz | pbabin | https://freesound.org/people/pbabin/sounds/414274/ | CC0 | 10:00 | true-binaural | 1.5 Hz Β· 60/61.5 Hz |
+| 8 | `fs856225` | Delta Pulse Subharmonic Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/856225/ | CC0 | 5:00 | ambient+beats (speaker-ok) | delta sub-bass (procedural) |
+| 9 | `fs854874` | Void Monolith Subharmonic Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/854874/ | CC0 | 5:00 | ambient (speaker-ok) | dark sub-drone |
+| 10 | `fs860122` | Expanding & Vibrating Stereo Pulsating Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/860122/ | CC0 | 2:00 | ambient+beats (speaker-ok) | slow stereo pulsation |
+| 11 | `fs493524` | Pulse Breath *(lower pref)* | phylobates | https://freesound.org/people/phylobates/sounds/493524/ | CC0 | 1:40 | ambient binaural | octaves of G / Gβ |
+
+Preview MP3s (for the review page) follow `https://cdn.freesound.org/previews/
/_-hq.mp3`
+β exact URLs are embedded in `review_binaural.html`.
+
+## Round 2 β musical / evolving (the "not boring" set, 2026-07-02)
+
+Operator found the pure-tone set (#1β7) too plain. **Reality:** truly *musical* tracks
+with a **confirmed** binaural layer are almost all NC (see rejects). Best legitimate
+options β pick for how they *sound*; treat binaural as a bonus:
+
+| id | title | artist | source | license | dur | binaural? |
+|---|---|---|---|---|---|---|
+| `fs856223` | Theta Gateway Sacred Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/856223/ | CC0 | 5:00 | capable (theta band; not certified on render) |
+| `fs856221` | Raga of Stillness Sacred Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/856221/ | CC0 | 5:00 | capable (Ξ/ΞΈ/Ξ±/Ξ² optional) |
+| `fs856220` | Ice Cathedral Sacred Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/856220/ | CC0 | 5:00 | capable |
+| `fs856224` | Vowel of the Deep Sacred Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/856224/ | CC0 | 5:00 | capable |
+| `fs856222` | Stellar Vowel Sacred Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/856222/ | CC0 | 5:00 | capable |
+| `fs856329` | Harmonic Chamber High-Frequency Drone (MANTICE) | bassimat | https://freesound.org/people/bassimat/sounds/856329/ | CC0 | 5:00 | capable |
+| `fs854438` | Immersive Ethereal Generative Ambient (Floating Harmonics) | bassimat | https://freesound.org/people/bassimat/sounds/854438/ | CC0 | 11:33 | no (richest single piece) |
+| `fma_cosmic` | Cosmic Waves | HoliznaCC0 | https://freemusicarchive.org/music/holiznacc0/space-sleep-meditation/cosmic-waves/ | CC0 | 33:04 | no (long-form melodic pads) |
+| `fma_dream` | DreamScape | HoliznaCC0 | https://freemusicarchive.org/music/holiznacc0/space-sleep-meditation/dreamscape/ | CC0 | 21:59 | no |
+| `fma_rain` | Rain / Sleep / Meditation | HoliznaCC0 | https://freemusicarchive.org/music/holiznacc0/space-sleep-meditation/rain-sleep-meditation/ | CC0 | 14:35 | no (rain + pads) |
+| `ccm_shagrugge` | How to Get Rid of a Dream (real imagination mix) | shagrugge | http://dig.ccmixter.org/files/shagrugge/12629 | CC BY 3.0 | ~ | yes-ish (binaural-filtered vocals; experimental) |
+
+- The MANTICE **Sacred Drone** series is the composed extension of Round-1 #8β10 (same
+ generator; the generator *does* binaural entrainment across Ξ/ΞΈ/Ξ±/Ξ², but the individual
+ freesound renders don't certify the beat is on β bassimat could confirm/render on request).
+- **HoliznaCC0** (FMA, CC0) = the strongest pure-*music* options; no binaural layer.
+- FMA `files.freemusicarchive.org/...` URLs verified live (206) β safe to self-host.
+ The **ccMixter** shagrugge URL is **hotlink-protected** (403) β download in a browser and self-host.
+
+### Opt-in (outside the CC0/CC-BY rule β needs operator OK)
+
+- **"Hang Binaural" β Mindseye** β https://freemusicarchive.org/music/MindsEye/single/hang-binaural
+ β **CC BY-SA 4.0**. The single best musical+*confirmed*-binaural fit (hang-drum music +
+ real binaural beats, 3:36, stereo), but ShareAlike copyleft. Add only if SA is acceptable.
+
+## Rejected / could not verify
+
+- **archive.org "Binaural Beats β Alpha/Beta/Theta/Delta"** & **"Isochronic Tones &
+ Binaural Beats Combined"** (loopool / Jean-Paul Garnier) β **CC BY-NC-SA 3.0** (NC).
+ The most promising isochronic set β rejected for the NC clause.
+- **archive.org "Om Alpha Brainwave Entrainment Loop"** (Entraino / KS Chauhan;
+ freesound chauhans/100370) β **CC BY-NC 3.0**. Rejected (NC).
+- **archive.org "1hr 30 Min Theta Binaural Beat (7 Hz)"** (Chari G) β no license shown.
+ Rejected (unverifiable).
+- **freesound "deadthetawaves.wav"** (NoiseCollector/43295) β license fine (**CC BY 3.0**)
+ but "evil sounding loop of the living dead" β rejected for *fit*, not license. Back
+ pocket only if an unsettling deep-scale track is ever wanted.
+- **TunePocket / dialedggsound / miraclefrequencies / audio.com "Theta 7Hz"** β
+ "royalty-free"/commercial pages, murky redistribution, no per-track CC license.
+- **Round 2 NC drops** (great content, non-commercial licence): "Fear Nothing (Relaxation
+ Mix)" β Jeris (CC BY-NC), "On the Earth" β Quarkstar (CC BY-NC), "Timeless (Binaural
+ Beatmix)" β duckett (CC BY-NC), "Ambient Meditation" β Scott Holmes (CC BY-NC). ccMixter's
+ binaural tag is overwhelmingly NC; only shagrugge (CC BY) survived.
+
+## Next
+
+Operator auditions `review_binaural.html`, ticks keepers (Copy picks). Then we wire the
+playback: the **Music** source becomes a multi-track set that crossfades between the kept
+tracks (architecture decision β replace vs. join the ethereal loop β deferred to that
+step; see [[audio-music-option]]).
diff --git a/docs/music-candidate-pool.md b/docs/music-candidate-pool.md
new file mode 100644
index 0000000..fc42edc
--- /dev/null
+++ b/docs/music-candidate-pool.md
@@ -0,0 +1,129 @@
+# Music candidate pool β mellow chordal synth (2026-07-02)
+
+The chosen direction for the **Music** audio option: **mellow, soothing synth music with
+real chord movement** ("many chords" β progressions that evolve, not static drones or
+binaural tones). Supersedes the binaural exploration ([[binaural-candidate-pool]] β parked).
+We'll use **several** tracks and **crossfade between them** (not loop one).
+
+All **CC0 or CC-BY** (verified by fetching each page + a live HTTP 206 check on every audio
+URL). Local review gallery (gitignored, like the other review pages):
+`simulator/static/review_music.html` β open `/review_music.html` with the sim server running.
+
+**Honesty flag:** licenses + URLs are verified; the "harmony" notes are inferred from
+genre/reputation, not a by-ear check β audition before final selection.
+
+## FINAL selection β WIRED 2026-07-02
+
+After a Round-4 drumless pass, the operator's **final Music set = 5 Kevin MacLeod tracks**
+(CC BY 4.0), now **shipped as a rotating crossfading playlist**:
+
+**Music for Manatees Β· Dewdrop Fantasy Β· Eastminster Β· Overheat Β· Concentration**
+
+Wiring: `app.js` `MUSIC_PLAYLIST` β the Music source plays one track and every
+`MUSIC_ROTATE_MS` (5 min) crossfades to a random OTHER track (`pickNextIndex`, never an
+immediate repeat), altitude-independent, on the two `#aud`/`#aud-b` elements. Files
+loudness-normalized + transcoded under the 25MB LFS limit by `build_audio_media.py`
+(`MUSIC`), committed via LFS; the single ethereal loop was **retired**. Credited on
+`credits.html` (CC BY attribution). Earlier candidates below kept for record.
+
+*(History: the operator first kept Dream Culture + Silver Blue Light, then β after the
+drumless Round-4 pass β chose the 5 long-form/floaty tracks above instead.)*
+
+## Two license families
+
+- **HoliznaCC0 = CC0** β zero attribution burden (simplest for the credits page).
+- **Kevin MacLeod = CC BY 4.0** β must credit, e.g. *"Chill Wave by Kevin MacLeod β
+ incompetech.com β Licensed under Creative Commons: By Attribution 4.0."* The credits page
+ already exists, so this is fine.
+- **Self-host everything** on R2 for the install β the `files.freemusicarchive.org` and
+ `incompetech.com` URLs are live/hot-linkable now but shouldn't be depended on in prod.
+
+## Round 4 β drumless, chord-moving (the refined set, 2026-07-02)
+
+All **Kevin MacLeod, CC BY 4.0**, **drumless confirmed from his per-track `pieces.json`
+instrument metadata** (tonal instruments only β no drum kit/percussion), verified live (206).
+This is the authoritative candidate list; the lo-fi below (beat-driven) is dropped.
+
+*(mp3 host `https://incompetech.com/music/royalty-free/mp3-royaltyfree/.mp3`; source page
+`https://incompetech.com/music/royalty-free/index.html?isrc=`.)*
+
+**Best matches β metered, chords clearly move** (safest bets, like the two keepers):
+| title | isrc | dur | instruments | harmony |
+|---|---|---|---|---|
+| Impact Lento | USUAN1100619 | 3:31 | Synths, Choir | "several striking chord progressions" |
+| Dreams Become Real | USUAN1500027 | 6:50 | Piano, Synths | dreamy, layered, evolving (closest to Dream Culture) |
+| Aretes | USUAN1100325 | 4:36 | Piano, Synths, Choir | warm major, clear motion |
+| When The Wind Blows | USUAN1100717 | 4:26 | Guitar, Synths | lilting, calm (Silver-Blue-Light recipe) |
+| Reawakening | USUAN1400017 | 3:34 | Piano, Cello | warm resolving progression |
+| Almost New | USUAN1800008 | 3:23 | Piano, Basses | gentle moving harmony (documented) |
+
+**Long-form / floaty β drumless but slower-moving** (audition for "boring" risk):
+| title | isrc | dur | instruments | note |
+|---|---|---|---|---|
+| Music for Manatees | USUAN1400009 | 17:36 | Piano, EP, Strings, Synth | slow consonant shifts |
+| Clean Soul | USUAN1300033 | 5:07 | Electric Piano, Bass | spacious, sparser |
+| Soaring | USUAN1600040 | 6:20 | Synths | evolving pads, mystical-calm |
+| Dewdrop Fantasy | USUAN1700001 | 34:58 | Synths, Rain | slowly changing, lower density |
+| Eastminster | USUAN1100719 | 6:45 | Plucked Strings, Synths | drone + koto, some tension β least chordal |
+| Overheat | USUAN1100630 | 7:03 | Flute, Synth, Violin | trance-y minimalism |
+| Concentration | β | 29:53 | Synths, EP | floaty, lower density |
+
+Reference: the kept **Dream Culture** is tagged "Piano, **Percussion**" (subtle beat); **Silver Blue
+Light** is "Guitar, Synths" (drumless). The Round-4 picks are strictly tonal-only. A ~40-track
+filtered pool of more drumless-calm-chordal KM tracks exists if a bigger rotation is wanted.
+
+**Rejected this round (percussion / not soothing):** Angel Share (Drums), Dreamer (Percussion,
+bpm 100), Lightless Dawn (Percussion + unnerving), Anamalie (Percussion + dark), Chill Wave &
+all HoliznaCC0 lo-fi (beat-driven).
+
+## Candidates β earlier mellow-synth round (lo-fi DROPPED, kept for record)
+
+Ranked by chord movement.
+
+### Lo-fi / dreamy β HoliznaCC0 (CC0)
+
+| id | title | source page | mp3 (verified 206) | dur | harmony |
+|---|---|---|---|---|---|
+| `tokyo` | Tokyo Sunset | https://freemusicarchive.org/music/holiznacc0/public-domain-lofi/tokyo-sunset-lofi-peaceful-soft/ | .../Xnd9Hr5AVzB68IlWcImKtXPlwCePD2G2m8ZFSVj4.mp3 | 2:18 | jazzy extended-chord progression |
+| `reverie` | Dreamy Reverie | https://freemusicarchive.org/music/holiznacc0/public-domain-lofi/dreamy-reverie-lofi-nostalgic-chill/ | .../Bo2XWazV4kHpnPksjJGRRLr16EmOE44NZnMITZaB.mp3 | 2:23 | moving 7th/9th chords |
+| `complic` | Complicated Feelings | https://freemusicarchive.org/music/holiznacc0/public-domain-lofi/complicated-feelings-lofi-dreamy-relaxed/ | .../jEFkzWNxxtz76vjPoUOjZALHvdK3HkVMQAnGpgTn.mp3 | 2:39 | bittersweet changes |
+| `drift` | Peaceful Drift | https://freemusicarchive.org/music/holiznacc0/public-domain-lofi/peaceful-drift-lofi-nostalgic-calm/ | .../SQvtLguk6S1VSthv0oXWycoB6ipUS0pt8jzAxxPq.mp3 | 2:08 | calm nostalgic progression |
+| `currents`| Calm Currents | https://freemusicarchive.org/music/holiznacc0/public-domain-lofi/calm-currents-lofi-relax-calm/ | .../4rKapZUMNnNSPAOvpjlfSH6B5Ib8rgEWdvjnM7C6.mp3 | 2:27 | flowing chords |
+| `moon` | Moon Unit | https://freemusicarchive.org/music/holiznacc0/public-domain-lofi/moon-unit-lofi-reflection-dreamy/ | .../CbNZO1QUuJq1f50RHzZ5kykNj1hdqT04UaWOYSNf.mp3 | 2:52 | reflective dreamy chords |
+| `mist` | Into The Mist | https://freemusicarchive.org/music/holiznacc0/public-domain-lofi/into-the-mist-lofi-calm-relaxed/ | .../cI5iv9Xga0OOvGsS7mGZn9BlTshZdzAqLW7bmArA.mp3 | 2:09 | calm progression |
+| `wave` | Wave Maker | https://freemusicarchive.org/music/holiznacc0/public-domain-lofi/wave-maker-lofi-dreamy-retro/ | .../SbPJTWMnBmT5yBsXP4jLfp6RsvWLhfOnbw2PxXol.mp3 | 3:05 | warm analog retro chords |
+
+*(mp3 host: `https://files.freemusicarchive.org/storage-freemusicarchive-org/tracks/`)*
+
+### Calming synth β Kevin MacLeod (CC BY 4.0 β credit required)
+
+| id | title | source page | mp3 (verified 206) | dur | harmony |
+|---|---|---|---|---|---|
+| `chillwave` | Chill Wave | https://archive.org/details/ChillWave_781 | https://incompetech.com/music/royalty-free/mp3-royaltyfree/Chill%20Wave.mp3 | 3:57 | clear repeating 4-chord cycle |
+| `enchanted` | Enchanted Journey | https://freemusicarchive.org/music/Kevin_MacLeod/Calming | https://incompetech.com/music/royalty-free/mp3-royaltyfree/Enchanted%20Journey.mp3 | 4:29 | many changes, warm major |
+| `dreamcult` | Dream Culture | https://freemusicarchive.org/music/Kevin_MacLeod/Calming | https://incompetech.com/music/royalty-free/mp3-royaltyfree/Dream%20Culture.mp3 | 3:34 | evolving chord sequence |
+| `silverblue` | Silver Blue Light | https://freemusicarchive.org/music/Kevin_MacLeod/Calming | https://incompetech.com/music/royalty-free/mp3-royaltyfree/Silver%20Blue%20Light.mp3 | 5:50 | slow, deliberate changes |
+
+### Spares
+
+- **Clean Soul** β Kevin MacLeod β CC BY β 5:06 β `https://incompetech.com/music/royalty-free/mp3-royaltyfree/Clean%20Soul.mp3`
+- **Old Age** β HoliznaCC0 β CC0 β 3:33 β https://freemusicarchive.org/music/holiznacc0/only-in-the-milky-way-part-1/old-age/
+- **Deja Vu** β HoliznaCC0 β CC0 β 2:04 β https://freemusicarchive.org/music/holiznacc0/only-in-the-milky-way-part-1/deja-vu/
+- More Kevin MacLeod (all CC BY 4.0, incompetech): Dreamer, Reawakening, Lightless Dawn,
+ Almost New, Angel Share. The whole **HoliznaCC0 "Public Domain Lofi"** album is 42 CC0
+ tracks in this vibe β a ready-made rotation if wanted.
+
+## Rejected / not used
+
+- **Kevin MacLeod baroque/classical "public-domain" pieces** β the *composition* is PD but
+ incompetech states the *recording* is CC BY 4.0; harpsichord/orchestral, off-brief anyway.
+- **NC finds** (great but non-commercial): Scott Holmes "Ambient Meditation" (CC BY-NC),
+ ccMixter chordal ambient (Jeris/Quarkstar/duckett β CC BY-NC).
+- **"Hang Binaural" β Mindseye** β CC BY-SA (ShareAlike), excluded per the CC0/CC-BY rule.
+
+## Next
+
+Operator auditions `review_music.html`, ticks keepers (Copy picks). Then wire the **Music**
+source to a multi-track set that crossfades between the kept tracks (replaces the single
+ethereal loop; see [[audio-music-option]]). Kept CC-BY tracks get a credit line on
+`credits.html`; all kept files self-hosted on R2.
diff --git a/simulator/build_audio_media.py b/simulator/build_audio_media.py
index fafc287..0d8ba75 100644
--- a/simulator/build_audio_media.py
+++ b/simulator/build_audio_media.py
@@ -1,42 +1,62 @@
-"""Production pass for the 5 per-altitude soundtracks + the single Music loop
-(audio spec Β§5.3; Music layer per docs/audio-candidate-pool.md). The audio
-analogue of build_pool_manifest.py --media: read the sourced ambience clips
-(re-downloadable per docs/audio-candidate-pool.md), and make each a seamless
-loudness-normalized loop.
+"""Production pass for the 5 per-altitude soundtracks + the Music playlist
+(audio spec Β§5.3; Music per docs/music-candidate-pool.md). The audio analogue of
+build_pool_manifest.py --media: read the sourced clips (re-downloadable per the
+candidate-pool docs) and make each web-ready.
-Media is gitignored; this script is the reproducible record. Outputs land under
-simulator/sample_media/audio//.loop.mp3, served at /media/audio/...
-Run: python simulator/build_audio_media.py
+- Soundtracks: seamless loudness-normalized loops -> audio//.loop.mp3.
+- Music: the mellow drumless synth playlist (Kevin MacLeod, CC BY 4.0) β NOT looped
+ (the client crossfades between tracks). Each is loudness-normalized and transcoded
+ to a bitrate that keeps it under the ~25MB git-LFS/nginx limit (ambient tolerates
+ it) -> audio/music/.mp3. Put the downloaded original at audio/music/.src.mp3
+ (URLs below) first; this transcodes it.
-(White-noise is deferred β the live Audio control is a simple on/off soundtrack
-toggle. `tools.pipeline.audio_run.generate_white_noise` remains for when it's
-wanted, but no noise bed is built here.)
+Media is committed via git-LFS; this script is the reproducible record.
+Run: python -m simulator.build_audio_media
"""
from __future__ import annotations
+import subprocess
from pathlib import Path
from tools.pipeline.audio_run import process_soundtrack
+from tools.pipeline.run import resolve_ffmpeg
AUDIO = Path(__file__).parent / "sample_media" / "audio"
# scale -> (sourced raw file, production output file), both under audio//.
# Sources per docs/audio-candidate-pool.md (the "β KEEP" picks).
-#
-# The `music` entry is the Audio-dropdown "Music" layer (docs/audio-candidate-pool.md
-# Β§ Music): ONE ethereal ambient loop played the same at every altitude β not a
-# per-altitude soundtrack. It lands at audio/music/ethereal.loop.mp3, served at
-# /media/audio/music/ethereal.loop.mp3 (client fallback: app.js MUSIC_FALLBACK).
SOURCES: dict[str, tuple[str, str]] = {
"cosmos": ("pillars.mp3", "pillars.loop.mp3"),
"orbit": ("spaceamb.mp3", "spaceamb.loop.mp3"),
"coast": ("waves.mp3", "waves.loop.mp3"),
"reef": ("soundscape.mp3", "soundscape.loop.mp3"),
"abyss": ("whale.wav", "whale.loop.mp3"),
- "music": ("ethereal.mp3", "ethereal.loop.mp3"),
}
+# Music playlist (docs/music-candidate-pool.md Β§ Round 4): name -> (incompetech source
+# URL, target bitrate). Kevin MacLeod, CC BY 4.0 β credited on credits.html. Drumless,
+# chord-moving ambient; the operator's picks. Longer tracks get a lower bitrate to stay
+# under the LFS size limit. Client playlist: app.js MUSIC_PLAYLIST.
+_KM = "https://incompetech.com/music/royalty-free/mp3-royaltyfree/"
+MUSIC: dict[str, tuple[str, str]] = {
+ "manatees": (_KM + "Music%20for%20Manatees.mp3", "96k"),
+ "dewdrop": (_KM + "Dewdrop%20Fantasy.mp3", "80k"),
+ "eastminster": (_KM + "Eastminster.mp3", "128k"),
+ "overheat": (_KM + "Overheat.mp3", "128k"),
+ "concentration": (_KM + "Concentration.mp3", "80k"),
+}
+
+
+def _transcode(src: Path, dst: Path, bitrate: str, ff: str) -> Path:
+ """Loudness-normalize + transcode to mp3 at `bitrate` (no looping)."""
+ subprocess.run(
+ [ff, "-y", "-hide_banner", "-loglevel", "error", "-i", str(src),
+ "-af", "loudnorm", "-c:a", "libmp3lame", "-b:a", bitrate, "-ar", "44100", str(dst)],
+ check=True,
+ )
+ return dst
+
def main() -> None:
for scale, (raw, out) in SOURCES.items():
@@ -47,6 +67,15 @@ def main() -> None:
dst = process_soundtrack(src, AUDIO / scale / out)
print(f" produced {dst.relative_to(AUDIO.parent)}")
+ ff = resolve_ffmpeg()
+ for name, (url, bitrate) in MUSIC.items():
+ src = AUDIO / "music" / f"{name}.src.mp3"
+ if not src.exists():
+ print(f" SKIP music/{name}: source missing ({src}) β download {url}")
+ continue
+ dst = _transcode(src, AUDIO / "music" / f"{name}.mp3", bitrate, ff)
+ print(f" produced {dst.relative_to(AUDIO.parent)} @ {bitrate}")
+
if __name__ == "__main__":
main()
diff --git a/simulator/e2e/tests/audio-source.spec.ts b/simulator/e2e/tests/audio-source.spec.ts
index 9df942b..631f188 100644
--- a/simulator/e2e/tests/audio-source.spec.ts
+++ b/simulator/e2e/tests/audio-source.spec.ts
@@ -2,7 +2,7 @@ import { test, expect, Page } from "@playwright/test";
// The Audio-source feature (docs/superpowers/specs/2026-07-01-audio-music-option-design.md):
// the old 0β10 "Audio" slider becomes "Volume", and a new Audio