Audio v2: body-resonance, breath-coupled bed, session arc, eyes-closed mode (DAR-377)#6
Open
abiassi wants to merge 18 commits into
Open
Audio v2: body-resonance, breath-coupled bed, session arc, eyes-closed mode (DAR-377)#6abiassi wants to merge 18 commits into
abiassi wants to merge 18 commits into
Conversation
Pre-commit per project convention before shipping the bundled audio v2 changes (DAR-378 through DAR-386). Hypothesis: bundled quality fixes, body-resonance layer, session-arc evolution, breath-coupled noise bed, and opt-in eyes-closed mode lift engaged minutes + return rate without breaking existing embed flows. Pre-committed read 2026-05-31, verdict 2026-06-14.
Previously the first getCueReverb call cached an IR built from the first
caller's params and silently reused it for all subsequent calls — so
Box (0.35s/3.2), Relax (0.7s/2.5), and Coherent (0.55s/2.8) preset
reverbs all collapsed to whichever fired first in a session. Switching
modes mid-session also returned the stale convolver.
Cache by `${duration}:${decay}` key (2 decimal places) so each preset
gets its own convolver. Bounded since the preset table is small.
Drone (4 osc) + binaural (2 osc) + pink noise + cue (tone + noise + reverb tail) stacks at phase transitions could push peaks above 0 dBFS on loud presets — especially mobile speakers with EQ boost. Insert a DynamicsCompressorNode (threshold -6, knee 30, ratio 4, attack 0.01, release 0.25) followed by a -1 dBFS gain trim between masterGain and ctx.destination. Centralized the chain construction in buildOutputChain() so Safari's "interrupted → closed → recreated" lifecycle rebuilds it correctly. Also clear cueNoiseBuffer + cueReverbCache when ctx is rebuilt — those nodes belong to the old ctx and would be unusable otherwise.
Previously only 4 hex colors mapped to root notes; everything else (WimHof orange, NadiShodhana violet, Belly amber, time-of-day teals, AI-suggested colors, etc.) silently fell back to A2. Walk the spectrum into 7 hue zones spanning the C major / A minor neighborhood so cues stay tonally consonant: red→C3, orange→D3, amber→E3, green→G3, teal/cyan→A3, blue→C3, violet→A2, magenta→C3. Saturation <0.15 (greys) → A2 fallback so desaturated palettes get a deliberate root rather than a hue-noise artifact. Now every BREATHING_PATTERNS color, every time-of-day override, and every AI-suggested color gets a deliberate root.
Pink-noise bed (Relax + Coherent) was static. Add a BiquadFilter between the source and gain stages, and a new updatePinkNoisePhase method driven from the animate rAF loop with the existing per-phase progress value. Inhale: cutoff swells 480→2400 Hz (texture opens, like ocean toward the shore). Exhale: cutoff sweeps back down (wave receding). Hold: no ramp. setTargetAtTime with τ=0.08s smooths the tracking without zipper noise. Wim Hof loop doesn't start pink noise so no wire-up needed there; the method is a no-op when noiseNode is null.
Add a sub-bass layer one octave below the drone root (e.g. C2 at 65.4Hz when drone is C3). Single sine, omnidirectional (no HRTF — sub-bass isn't directional anyway), low gain (~0.18 of music volume) and a 0.05Hz LFO so it breathes too. Pairs with both drone modes (every pattern except Relax/Coherent) AND pink-noise modes — both branches in handleTogglePlay start it, all cleanup paths (pause, stop, auto-complete, Wim Hof protocol-complete, fadeOutAndSuspend) stop it. On phone speakers that can't reproduce ~65Hz this is harmless; on headphones / decent speakers it adds chest resonance. The compressor from DAR-382 keeps cumulative peaks safe.
A 30s session and a 10min session sounded identical from the drone's perspective — startDrone set partials once and never touched them. Add a slow, imperceptible-per-frame ramp over the first 4 minutes: - Root drifts down a perfect 5th (×2/3) — opens a deeper feel - Per-partial LFO rates slow to ~50% — texture settles - 8D orbit speed slows by ~40% — less restless motion Captured baseline (root, LFO rates, orbit speed) when startDrone fires; ramps via setTargetAtTime with τ=8s so frame-to-frame change is invisible. Wired into BOTH animate and animateProtocol via a new sessionSecondsRef so Wim Hof sessions get the arc too. stopDrone cancels frequency ramps + LFO scheduled values before the gain ramp to 0, so pause/stop lands cleanly.
Add a Binaural beats toggle to the Resonance Settings panel that persists to resonance_settings.binauralEnabled and round-trips via ?binaural=0|1 URL param (matches the existing ?duration= pattern). Default ON (preserves current behavior). Live response: stop binaural mid-session if toggled off. GA4 event binaural_toggled. parseBoolParam helper accepts 1/0, true/false, on/off, yes/no for embedder forgiveness. URL param wins over storage; storage wins over default. Embed generator UI: new "Sound" section with both binaural and eyes-closed checkboxes so embedders can pre-set their visitors' defaults. The build-url helper only emits non-default values so existing short snippets stay short. Also pre-wires the eyes-closed toggle (UI + URL plumbing) which the next commit (DAR-379) builds on for the actual fade-to-black behavior.
Eyes-closed mode (DAR-379): - New toggle (already plumbed in the previous commit) now actually dims the particle background + main visualizer area to 18% opacity over ~1.8s when running. Header chrome (incl. Settings gear) stays full opacity so users can always toggle back without hunting. - Tap-to-pause gesture on the orb is preserved — the orb is still in the DOM, just dimmed, so the existing click handler still fires. Phase-length sonic envelope (DAR-386): - New continuous tonal layer (sine an octave above drone root, lowpass filter) whose gain and cutoff ride breath progress with eased curves (sin/cos so the swell feels organic). Inhale opens up, exhale decays. - GATED behind eyes-closed mode per advisor recommendation — earns its keep most when visuals are dimmed, and gating gives us measurement isolation for the v2 read. - Wired into BOTH animate and animateProtocol rAF loops so Wim Hof sessions also get the envelope. - Mid-session toggle: useEffect on (eyesClosed, isRunning) starts/stops the envelope live, no need to restart the session. Stop calls added to all 4 cleanup paths (pause, stop, auto-complete, Wim Hof protocol-complete, fadeOutAndSuspend).
binauralFromUrl/eyesClosedFromUrl are intentionally captured at mount in the mount-only effect — dedicated useEffects below handle live URL changes. Comment explains.
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Two advisor-flagged tuning issues caught before review: 1. Phase envelope was at root × 2 — exactly the drone's 2nd partial. Different detune meant audible beating in eyes-closed mode, AND the beat rate would shift as the session-arc drifted the drone root (envelope captures freq at start; drone drifts later). Move to root × 3 (perfect 12th) so it sits outside the partial set and stays consonant. 2. Compressor was -6 / 4:1 / 10ms attack — loud-rock territory that audibly squeezes the cue swells on every phase transition. Soften to -12 / 3:1 / 20ms — transparent for ambient, still catches the worst cumulative peaks. Both are 2-line tuning edits, no API change, no test impact.
May 18 checkpoint: mobile abandonment worsened from 74.3% → 82.0%, pause rate dropped from 25.7% → 18.0%. Hint is visual noise. Reverts 42f1fc3.
Two reported issues + product call to park one feature.
1. Clipping on homepage (Box default).
Root cause: the "limiter" was just a -1dBFS gain trim. Cue noise
transients (~15ms attack) escaped the slow 20ms compressor. Replace
the trim with a real brick-wall peak limiter (DynamicsCompressor
with -3dB threshold, 20:1 ratio, 1ms attack, 50ms release), then add
a -3dB safety trim after that. Compressor stays transparent for
ambient (-14 / 3:1 / 20ms).
Also drop sub-bass gain from 0.18 to 0.10 — was muddy on the
homepage stack.
2. Synth occasionally dissonant against the cues.
Root cause: hue-derived root mapping (DAR-384) + perfect-5th session
arc (DAR-380) drifted the drone root into intervals where the FIXED
per-mode cue Hz hit tritones. Cues need to become root-relative for
a wandering root to work safely.
- Revert hue mapping to a curated per-color table that picks roots
known to be consonant with each mode's cue preset.
- Reduce session arc drift from a perfect 5th to a whole tone (8/9).
Same arc shape, much smaller pitch travel = much less dissonance
risk against the fixed-Hz cues.
Comment in both spots flags the follow-up: make cues root-relative
to safely re-enable wider drift / hue range.
3. Park eyes-closed mode (UI only).
Feature isn't useful without voice narration in each locale (visual
cues are removed, users need an audio guide). Hide the Settings
toggle and the embed-generator checkbox, but keep all the wiring
(URL param, state, phase envelope, mid-session live toggle) so
?eyesClosed=1 still works for internal testing once narration ships.
The 'static' on drone-using techniques (box, sigh, wim-hof) was the drone layer: warm themes use triangle oscillators whose high harmonics bleed into the 6-16kHz hiss band, plus a smaller HRTF panner zipper from per-frame position writes. Slow modes use the pink-noise bed instead, which is why they never hissed. Route all drone partials through one shared post-panner lowpass (2000Hz, Q0.5) before the master bus. Mechanism-agnostic: removes both the triangle harmonics and the panner zipper while keeping the warm low pad (roots 87-165Hz). Verified via layer-isolation HF measurement: fixed build measures >14kHz below the noise floor and >8kHz floored on wim-hof + box. Diagnosis tooling and evidence in deepbreathing tools/orb-video.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bundled implementation of the Resonance audio v2 epic (DAR-377). Ten sub-issues land in nine focused commits so any one can be reverted cleanly without touching the others.
What changes from the user's perspective
Always-on quality lift (no UI surface, just sounds better):
Two new opt-in toggles in the Settings panel (also URL-driven for embeds):
URL params (additive — existing embeds keep working)
?duration=?theme=light|dark?binaural=0/1(alsoon/off,true/false)1?eyesClosed=0/10Embed generator UI has a new "Sound" section exposing both toggles. Default values are NOT emitted into the URL so existing short snippets stay short.
Files
src/components/resonance/services/audioService.ts— main engine (sub-bass, phase envelope, breath-coupled noise filter, session arc, hue→root mapping, compressor, reverb cache fix)src/components/resonance/Resonance.tsx— toggles, URL plumbing, mid-session live response, eyes-closed visual fadesrc/app/embed/embed-generator.tsx— UI for the two new paramsdocs/PRODUCT-EXPERIMENTS.md— pre-committed experiment entry (read 2026-05-31, verdict 2026-06-14)Surface coverage
Single
<Resonance />component is shared across 11+ surfaces (pattern pages, use-case pages, pSEO combo pages, dedicated/breathing-visualizer, home hero, embed route). All of them automatically pick up the v2 changes — no per-page wiring needed.What's NOT in this PR
Linear
Closes:
Part of epic DAR-377.
Test plan
/breathing-visualizer, hit play, listen for sub-bass presence (headphones)/breathe/4-7-8, hit play, listen for the pink-noise bed breathing with you (filter swelling on inhale)/embed/box?eyesClosed=1&binaural=0, verify the iframe lands in the configured state/embed, generate a snippet with the new toggles, verify the iframe preview updates