Skip to content

Audio v2: body-resonance, breath-coupled bed, session arc, eyes-closed mode (DAR-377)#6

Open
abiassi wants to merge 18 commits into
mainfrom
audio-v2-overnight
Open

Audio v2: body-resonance, breath-coupled bed, session arc, eyes-closed mode (DAR-377)#6
abiassi wants to merge 18 commits into
mainfrom
audio-v2-overnight

Conversation

@abiassi

@abiassi abiassi commented May 17, 2026

Copy link
Copy Markdown
Collaborator

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):

    • Sub-bass body-resonance layer adds chest-feel on headphones
    • Pink-noise bed (Relax + Coherent) now breathes with the user — flat rain → ocean
    • Drone slowly drifts down a 5th + slows LFO + slows spatial orbit over the first 4 min
    • Hue-based root-note mapping so all 12 patterns get a deliberate tonality (most were falling back to A2)
    • Master compressor + limiter prevents clipping when layers stack on phase transitions
    • Reverb cache bug fix — each per-mode preset now gets its intended IR instead of the first-call winner
  • Two new opt-in toggles in the Settings panel (also URL-driven for embeds):

    • Eyes Closed mode — fades visuals to 18% opacity + adds a continuous phase-length tonal envelope that swells through the inhale and decays through the exhale. Settings gear stays full opacity so users can always toggle back.
    • Binaural beats opt-out — for users on speakers or who find binaurals disorienting. Default ON (preserves current behavior).

URL params (additive — existing embeds keep working)

Param Values Default Meaning
?duration= int seconds (max 600) 60 unchanged
?theme= light|dark system unchanged
?binaural= 0/1 (also on/off, true/false) 1 new
?eyesClosed= 0/1 0 new

Embed 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 fade
  • src/app/embed/embed-generator.tsx — UI for the two new params
  • docs/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

  • DAR-387 Mobile haptics (deferred — needs simulator verification, doesn't block web review)
  • Sub-bass / session-arc tuning is a first-pass; expect a tuning commit after Abi's listen.

Linear

Closes:

  • DAR-378 (pink-noise breath coupling)
  • DAR-379 (eyes-closed mode)
  • DAR-380 (session arc)
  • DAR-381 (sub-bass)
  • DAR-382 (master compressor)
  • DAR-383 (reverb cache bug)
  • DAR-384 (hue root mapping)
  • DAR-385 (binaural toggle)
  • DAR-386 (phase envelope)

Part of epic DAR-377.

Test plan

  • Open /breathing-visualizer, hit play, listen for sub-bass presence (headphones)
  • Open /breathe/4-7-8, hit play, listen for the pink-noise bed breathing with you (filter swelling on inhale)
  • Run a 5+ minute Box session, verify the drone drifts deeper over time (compare minute 1 vs minute 4)
  • Settings → toggle Eyes Closed on mid-session, verify ~1.8s fade + tonal envelope kicks in
  • Settings → toggle Binaural off mid-session, verify the L/R sine stops
  • Open /embed/box?eyesClosed=1&binaural=0, verify the iframe lands in the configured state
  • Open /embed, generate a snippet with the new toggles, verify the iframe preview updates
  • Run a Wim Hof session, verify the session arc + phase envelope (with eyes-closed) work in protocol mode too
  • Verify on iPhone Safari (audio context unlock, eyes-closed fade transition, sub-bass on speaker)

abiassi added 10 commits May 17, 2026 21:42
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.
@vercel

vercel Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
deepbreathing-tmmj Ready Ready Preview, Comment May 19, 2026 0:54am

Request Review

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.
abiassi added 2 commits May 18, 2026 14:38
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant