[research] Mic level meter in the call bar while unmuted — what exists today? #146

Open
opened 2026-09-17 16:51:58 -04:00 by jared · 1 comment
Owner

Inventory: does the EC footer show a local mic level (upstream has a speaking indicator on the self tile); does cinny's call bar / PiP show anything; does io.lotus.call_state carry a level or only a boolean speaking? Then propose the smallest addition (likely: a 3-bar glyph on the cinny mute button fed by a level field, only while unmuted). Decide after the write-up.

Inventory: does the EC footer show a local mic level (upstream has a speaking indicator on the self tile); does cinny's call bar / PiP show anything; does `io.lotus.call_state` carry a level or only a boolean `speaking`? Then propose the smallest addition (likely: a 3-bar glyph on the cinny mute button fed by a `level` field, only while unmuted). Decide after the write-up.
jared added this to the Features 2026-Q4 milestone 2026-09-17 16:51:58 -04:00
jared added the enhancementpriority: lowarea: callsresearch labels 2026-09-17 16:51:58 -04:00
jared self-assigned this 2026-09-17 16:51:58 -04:00
Author
Owner

Inventory (2026-09-20)

Element Call (fork) — what exists

  • Self tile: vm.speaking$ → green speaking border (GridTile/MediaView.speakingBorder), sound-wave glyph in spotlight. Source is LiveKit's boolean participant.isSpeaking (SFU active-speaker updates, ~300 ms cadence, delta-only).
  • LiveKit also exposes participant.audioLevel (0–1) but it is only refreshed by the same active-speaker updates — coarse, not a meter.
  • src/lotus/lotusMutedSpeech.ts (#117) already runs a local AnalyserNode RMS sampler on the mic track — but only while muted, feeding speakingWhileMuted.
  • io.lotus.call_state participants carry speaking: boolean (+ speakingWhileMuted for self). No level field. The stream is throttled to 500 ms trailing (#20) — far too slow for a meter even if a level were added.

Cinny — what exists

  • Call bar MicrophoneButton (features/call-status/CallControl.tsx): plain Mic/MicMute icon, no activity feedback at all.
  • Call bar text line MemberSpeaking: "Name is speaking…" driven by the boolean stream (includes yourself), and MemberGlance promotes speaking avatars.
  • PiP and the room header call chip: nothing per-level.
  • Muted-talk toast (#117) is the only self-mic feedback, and only while muted.

So today: boolean only, 500 ms late, and nothing on the mute button itself.

Smallest useful addition (proposal)

  1. Fork: generalise the #117 sampler into observeLocalMicLevel$ that runs whenever a mic track exists (muted or not), RMS → quantised 0–3 bars with a little hysteresis. Emit a new fromWidget action io.lotus.mic_level { bars } only when the quantised value changes, capped at 10 Hz. Silence and steady speech therefore cost ~0 messages; active talking costs a handful per second. Keep call_state untouched (its 500 ms throttle stays right for what it does).
  2. Cinny: CallEmbed handles io.lotus.mic_levelmicLevelAtom. MicrophoneButton overlays a 3-bar glyph (three 2×{3,5,7}px bars in the icon's bottom-right corner, Primary colour) only while enabled && bars > 0. Nothing else changes; PiP could reuse the same atom later.
  3. Reuse: the same level lets #117 drop its separate muted-only sampler (same signal, bars>0 && muted = talking while muted).

Cost: ~60 lines fork + ~50 lines cinny + a fork release. Alternative "zero-fork" option: pulse the mic icon from the existing boolean speaking for self — free, but it is a yes/no indicator, not a meter, and lags ~0.5 s.

Recommendation: do (1)+(2). Waiting for a go/no-go before building — this is the "decide after the write-up" gate from the issue.

## Inventory (2026-09-20) **Element Call (fork) — what exists** - Self tile: `vm.speaking$` → green speaking border (`GridTile`/`MediaView.speakingBorder`), sound-wave glyph in spotlight. Source is LiveKit's **boolean** `participant.isSpeaking` (SFU active-speaker updates, ~300 ms cadence, delta-only). - LiveKit also exposes `participant.audioLevel` (0–1) but it is only refreshed by the same active-speaker updates — coarse, not a meter. - `src/lotus/lotusMutedSpeech.ts` (#117) already runs a **local AnalyserNode RMS sampler** on the mic track — but only while *muted*, feeding `speakingWhileMuted`. - `io.lotus.call_state` participants carry `speaking: boolean` (+ `speakingWhileMuted` for self). **No level field.** The stream is throttled to 500 ms trailing (#20) — far too slow for a meter even if a level were added. **Cinny — what exists** - Call bar `MicrophoneButton` (`features/call-status/CallControl.tsx`): plain Mic/MicMute icon, no activity feedback at all. - Call bar text line `MemberSpeaking`: "*Name* is speaking…" driven by the boolean stream (includes yourself), and `MemberGlance` promotes speaking avatars. - PiP and the room header call chip: nothing per-level. - Muted-talk toast (#117) is the only self-mic feedback, and only while muted. So today: **boolean only, 500 ms late, and nothing on the mute button itself.** ## Smallest useful addition (proposal) 1. **Fork:** generalise the #117 sampler into `observeLocalMicLevel$` that runs whenever a mic track exists (muted or not), RMS → quantised **0–3 bars** with a little hysteresis. Emit a new fromWidget action `io.lotus.mic_level { bars }` **only when the quantised value changes**, capped at 10 Hz. Silence and steady speech therefore cost ~0 messages; active talking costs a handful per second. Keep `call_state` untouched (its 500 ms throttle stays right for what it does). 2. **Cinny:** `CallEmbed` handles `io.lotus.mic_level` → `micLevelAtom`. `MicrophoneButton` overlays a 3-bar glyph (three 2×{3,5,7}px bars in the icon's bottom-right corner, Primary colour) only while `enabled && bars > 0`. Nothing else changes; PiP could reuse the same atom later. 3. Reuse: the same level lets #117 drop its separate muted-only sampler (same signal, `bars>0 && muted` = talking while muted). Cost: ~60 lines fork + ~50 lines cinny + a fork release. Alternative "zero-fork" option: pulse the mic icon from the existing boolean `speaking` for self — free, but it is a yes/no indicator, not a meter, and lags ~0.5 s. **Recommendation:** do (1)+(2). Waiting for a go/no-go before building — this is the "decide after the write-up" gate from the issue.
jared added the needs-human-review label 2026-09-20 00:31:04 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#146