Skip to content

[Feature]: Reactive glass-bar live dot (amplitude + capture status colors) #42

Description

@rosspeili

One-line summary

Replace the always-on green pulsing live dot with a status-aware, amplitude-reactive indicator (quiet → soft color; louder speech → greener / stronger pulse; yellow while waiting; red on capture error).

Problem / motivation

Today the glass-bar dot is binary and misleading:

  • It only appears when audioSourceId !== 'none' and audioStatus === 'active' (AvatarStage.jsxlipSyncLive).
  • Once shown, CSS always paints a bright green pulse (avatar-glass-bar__live-dot) — independent of loudness. Silence looks the same as talking.
  • There is no bar-level signal for starting / waiting, or for capture failure (those only show as text inside Voice).

useAudioSource already exposes level, speaking, status, and error. Mouth visemes already track amplitude; the bar indicator does not. Users (and docs) treat “green dot = lip sync working,” but a stuck-green silent capture is hard to tell from healthy audio.

Proposed solution

Keep the same 8px glass-bar slot left of the gear. Drive appearance from capture status + analyser level (no new audio pipeline).

Visibility

Condition Dot
Audio source Off Hidden (unchanged)
Source selected (any non-none) Visible — even before active, so waiting/error can show

Status colors (discrete)

Map audioStatus (and error) to a base state. Prefer semantic colors, not “always soft green”:

State When Look Hover / aria-label (draft)
Waiting idle / starting, or active with no usable signal yet Soft yellow / amber pastel (calm, not alarm) “Lip sync waiting for audio”
Live quiet active, level near silence Soft pastel green-grey / very light green; little or no pulse “Lip sync listening”
Live active active, level / speaking rising Greener fill + glow; pulse rate / scale track amplitude (louder → stronger / faster; quieter → soft settle) “Lip sync active”
Error error (permission denied, device gone, capture failed) Soft red / coral; optional slow pulse “Lip sync error — open Voice”

Recommendation: use yellow for waiting, not soft green — so “armed but quiet” and “actually receiving audio” stay distinct. Soft green = healthy capture with little/no voice.

Amplitude reactivity (continuous, while Live)

While status is live (active):

  • Feed existing level (0…1-ish from useAudioAnalyser) into CSS custom properties on the dot (e.g. --live-level, --live-pulse), updated from the same frame path that already drives lip sync — avoid setState every frame if it causes bar re-renders; prefer a ref + direct style / requestAnimationFrame on the span, or pass level only into a tiny child that owns the DOM node.
  • Map level → green intensity (pastel → saturated lime), glow strength, and pulse scale/opacity.
  • On sustained silence, ease back to Live quiet (soft green-grey), not full bright green.
  • Optional light hue shift within greens is fine; avoid rainbow / distracting neon on an always-on-top overlay.

Implementation sketch (non-binding)

  • Extend AvatarStageShell props: e.g. liveDotStatus + liveDotLevel (or one derived liveDot object) instead of boolean lipSyncLive.
  • Derive status in AvatarStage from audioStatus / audioError / level / speaking.
  • CSS: modifier classes .avatar-glass-bar__live-dot--waiting|quiet|active|error + variables for continuous green.
  • Retire the fixed 1.8s infinite pulse as the only motion; keep a gentle idle pulse for waiting if desired.
  • Docs: Using the app live-dot section, Lip sync, Audio sources; screenshot 11-bar-live-dot.png (and any GIF) when visuals land — may fold into the docs visual overhaul.

Accessibility / chrome

  • Do not rely on color alone: keep distinct title / aria-label per state; optional click → open Voice drawer later (out of scope unless cheap).
  • Respect glass-bar chrome contrast (light vs dark environments): pastels must stay readable on both; no heavy bloom that washes the bar.
  • Prefer reduced-motion: if prefers-reduced-motion: reduce, freeze pulse and use static color only.

Alternatives considered

Approach Why not / later
Keep binary green; only document “silence still green” Still misleading; Voice status text is easy to miss with the drawer closed
Full waveform / mini VU meter on the bar Too busy for an 8px companion chrome; scope creep
Soft green as the waiting state Collides with “healthy listening”; yellow reads clearer as pending
Red only inside Voice panel Failures stay invisible on the bar; users stare at a still mouth
Drive pulse from mouth viseme weights Coupled to VRM path; analyser level is the right source of truth

Primary surface

Glass bar / menus

Constraints you accept

  • Should stay usable offline / local-first
  • Browser-only mode may remain limited vs Electron

Mock / sketch / reference (optional)

Same glass-bar slot as today (docs/screenshots/11-bar-live-dot.png), four states in one row:

[ · yellow ] waiting[ · soft green ] quiet[ ● bright green pulse ] talking[ · red ] error

No new bar controls — color + motion only.

Out of scope (separate / later)

  • Click-dot-to-open Voice / restart capture
  • Per-source icons on the bar
  • Phoneme-accurate coloring
  • Configurable colors in Settings (defaults only for v1)

Acceptance ideas

  • Source Off → no dot
  • Source on + starting/idle → yellow waiting (not bright green)
  • active + silence → soft quiet green; no aggressive pulse
  • active + speech/music → green intensity and pulse track loudness; settle when quiet
  • Capture error → red + clear label
  • No measurable bar jank from per-frame React state
  • Docs + screenshot note updated; reduced-motion respected

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:glass-barGlass bar, gear menus, drawers, dismissarea:lip-syncVisemes, live green dot, mouth mappingarea:voiceAudio sources, capture, permissions, restartfeatureNew feature or improvement requestneeds designNeeds UX / visual decision before implementation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions