# Audio Assist — design log

Hands-free audio to level something without watching the screen. Setting cycles
**OFF / TONES / VOICE** from the Level header. Plays on `USAGE_MEDIA` (so the volume slider
controls it) and never takes audio focus.

> **Source of truth for the current design is [`SOUND_DESIGN_HANDOFF.md`]**, from Jay's
> dedicated sound-design sessions. This log records the *journey* — especially rejected
> approaches — so they don't get rediscovered. Where the two ever disagree, the handoff wins.

## Chosen direction (current)

Two modes / two mechanisms, one sound identity (all derived from a 500 Hz blip):

**SURFACE (2-D) — TONES.** One vocabulary: **faster ticks = approaching, the bullseye sound =
centered right now.** Timing is split, not sound identity:

- *Outside the center zone:* `tick.wav` (60 ms) repeats faster as you near level — axis-agnostic
  "warmer/colder", rate is the message. No settle gate. Rate is exponential in the error,
  clamped **1.5/s (≥~5°) → 8/s (≤~0.4°)**, hard-capped. **Constant loudness** (acceleration is
  the signal). Scheduling is anchored to the last tick off a **monotonic clock**, due-time
  recomputed each frame from the current interval → immediate reaction to changing tilt, no
  drift, no catch-up burst, ≤1 tick per update.
- *Inside the center zone:* the ticks give way to the looping `level.wav` **immediately — no
  dwell, no velocity gate — even on a fast pass**, because a momentary alignment is real,
  locating information (that little zone needs the most help). It stops the instant you leave.
  Alignment uses **spatial hysteresis** (enter ≤0.2°, rearm only after leaving ≥0.35°) — not a
  time debounce, which would hide a legitimate quick re-crossing — and is measured from the
  **same hypot(stable pitch, stable roll)** that drives the bubble, so the sound can't lag the
  visual. `level.wav`'s stream id is retained and stopped before every restart / on exit.
- The stronger **"held level" confirmation — persistent lime, the "Flat within 0.35°" label,
  and the haptic — is decided separately** by the velocity-aware `LockDetector` (≤0.2° + ≤0.3°/s
  → 175 ms confirm). The locating *sound* never waits on it; a fast fly-through gets the bullseye
  cue but not the "you nailed it" confirmation.

**EDGE (1-D, future) — the "sonar staircase."** Direction + magnitude as a 4-blip pitch
contour (rising = raise it, falling = lower it, steepness = how far; level sounds flat), one
report per move→settle. Five pre-rendered assets staged in `sonar-staircase-v3/`; wired when
Edge mode is built.

**VOICE — deliberately separate, a SETTLED-CHANGE announcer (not a timer).** Speaks one
correction, then stays silent while moving; when it settles again it speaks only if the
dominant direction changed, it crossed coarse→fine, or it reached lock ("that's level"). A
settle still needing the same coarse nudge gets silence. No periodic repeat, no bed under it.

Implementation: pure `SurfaceTickPolicy` (monotonic deadline scheduler, exponential rate,
unit-tested) + `VoiceGuidancePolicy`; the SoundPool layer stays dumb (play a tick / play &
stop level). Sounds are Jay's SoundQ-derived blips (`tick.wav`, `level.wav`) downsampled to
mono 44.1 kHz in `res/raw`.

## Rejected approaches (and why)

1. **Two fixed proximity bands + lock ping** (original). Worked but coarse; the near/close jump
   was audible ("a lower ding when close").
2. **Continuous cadence** — beep interval shrinks smoothly toward center (parking-sensor).
   → "gets a bit much." Monotonous over a long adjustment.
3. **Rising-pitch discrete beeps.** Better, but still same-note-ish and busy.
4. **Continuous glide tone** — one warm tone, pitch low→treble by closeness, resolving into the
   aura. → "the constant sound works but it's too much; not clicking." A continuous tone gives
   the ear **no silence to rest on**, so it fatigues; it also bypassed the tested scheduler.
5. **Radar doublet on a per-band cadence** — two sonar pings ~110 ms apart, repeating on a
   1.3/0.9/0.6 s cadence while off-level. The literal two-ping "pew-pew" read as UI/radar beeps,
   not sonar, and the continued cadence was still a clock. Kept the bands/pitch, dropped the
   cadence in favor of one packet per settle.
6. **Continuous forcefield aura bed** (looping CC0 ambience while locked). Seamless on a PC
   media player but **clicked at the loop point through Android SoundPool**. Pulled; a gapless
   bed needs AudioTrack/ExoPlayer.
7. **Settle-gated two-ding "status packet"** (banded pitch + gap, one packet per move→settle,
   fuller arrival ding at lock). Built, unit-tested, on-device tested. Coherent, but on the
   bench the settle-then-report model felt sparse/laggy for the fiddly 2-D surface adjustment —
   you want live "warmer/colder" while you're actually moving it, not a report after you stop.
   Superseded by continuous ticking for Surface. (The settle-then-report shape lives on for
   VOICE and for future EDGE, where a discrete per-settle report fits the 1-D task.)

## Guiding principles (learned)

- Cue *character* decides tolerance more than raw rate. As **discrete report cues**, ~180 ms
  spacing felt alarm-like and ~600 ms felt calm — but as a **continuous proximity tick** an
  8/s (125 ms) near rate reads as pleasant acceleration, not an alarm (it's a Geiger/parking
  sensor, and the ear expects it to quicken as you close in). Judge by feel on the phone.
- **Volume** is a poor information channel (phone volume + room noise make loudness
  unreliable) — let rate/pitch carry the message and keep loudness constant.
- Keep the SoundPool layer dumb; put decisions in tested pure logic.
- The **centered** sound needs source loudness/spectrum presence, not just a bigger volume
  number.
- No reverb/comb tails on phone speakers — they smear and lengthen fatigue.
- Reuse the real lock state for the centered handoff; never invent a second audio threshold.
