my-sea voice: volume-reactive equalizer glow + muted (--priRd/.fa-ban) state — TDD
Phase 5b of the my-sea voice batch. Resolves the Bug-B-vs-item-7 conflict per user call 2026-05-29: a click while connected MUTES (leaving stays on BYE/NVM), and the item-7 "--priRd/.fa-ban disconnected" visual maps onto the MUTED state. Replaces Phase 3's 2x-pulse stand-in with a real Web-Audio equalizer. - voice-mesh.js: an AnalyserNode taps each incoming peer stream (lazy AudioContext); `inputLevel()` returns the loudest current RMS (~0..1) across peers. Analysers torn down per-peer + on call end. - voice-glow.js: the live-mic glow now resolves to — alone → `.voice-pulse` (steady 2s cadence); others connected → `.voice-eq`, whose `--voice-level` CSS var is fed each frame from inputLevel() via a self-stopping rAF loop; muted → `.voice-muted` modifier on either (recolor only). rAF cancelled on destroy. - _burger.scss: `.voice-eq` box-shadow spread+alpha scale with `--voice-level` (no keyframe — audio drives it); `.voice-muted` recolors to --priRd (halo stays --ninUser) + flips the voice sub-btn icon to .fa-ban. Drops the unused `.voice-pulse--fast`. - VoiceGlowSpec: +4 specs (equalizer swap, muted-alone, muted-with-others, unmute clears); VoiceMeshSpec: +1 (inputLevel 0 with no analysers). 438 Jasmine specs green. Live-verify on staging (audio can't be auto-tested): the equalizer reacting to real speech + the muted/unmuted colour swap on a live call. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,8 @@ describe("VoiceGlow", () => {
|
||||
|
||||
const hasGlow = (el) => el.classList.contains("voice-glow");
|
||||
const hasPulse = (el) => el.classList.contains("voice-pulse");
|
||||
const hasFast = (el) => el.classList.contains("voice-pulse--fast");
|
||||
const hasEq = (el) => el.classList.contains("voice-eq");
|
||||
const hasMuted = (el) => el.classList.contains("voice-muted");
|
||||
|
||||
describe("bindVoiceGlow()", () => {
|
||||
it("returns null when the burger btn is absent", () => {
|
||||
@@ -102,7 +103,7 @@ describe("VoiceGlow", () => {
|
||||
vg.setVoiceState({ inCall: true, peerCount: 0 });
|
||||
expect(hasGlow(burger)).toBe(true);
|
||||
expect(hasPulse(burger)).toBe(true);
|
||||
expect(hasFast(burger)).toBe(false);
|
||||
expect(hasEq(burger)).toBe(false);
|
||||
});
|
||||
|
||||
it("pulses the voice sub-btn while alone + fan open", () => {
|
||||
@@ -113,13 +114,35 @@ describe("VoiceGlow", () => {
|
||||
expect(hasPulse(burger)).toBe(false);
|
||||
});
|
||||
|
||||
it("doubles the cadence once a 2nd party connects", () => {
|
||||
it("switches to the equalizer once a 2nd party connects", () => {
|
||||
vg = bindVoiceGlow();
|
||||
vg.setVoiceState({ inCall: true, peerCount: 1 });
|
||||
expect(hasFast(burger)).toBe(true);
|
||||
expect(hasEq(burger)).toBe(true);
|
||||
expect(hasPulse(burger)).toBe(false);
|
||||
});
|
||||
|
||||
it("recolors to muted (--priRd) when the mic is muted, both alone…", () => {
|
||||
vg = bindVoiceGlow();
|
||||
vg.setVoiceState({ inCall: true, peerCount: 0, muted: true });
|
||||
expect(hasPulse(burger)).toBe(true); // still pulsing (alone)
|
||||
expect(hasMuted(burger)).toBe(true); // …but in the muted colour
|
||||
});
|
||||
|
||||
it("…and while the equalizer runs with others connected", () => {
|
||||
vg = bindVoiceGlow();
|
||||
vg.setVoiceState({ inCall: true, peerCount: 2, muted: true });
|
||||
expect(hasEq(burger)).toBe(true);
|
||||
expect(hasMuted(burger)).toBe(true);
|
||||
});
|
||||
|
||||
it("drops the muted colour on unmute", () => {
|
||||
vg = bindVoiceGlow();
|
||||
vg.setVoiceState({ inCall: true, peerCount: 0, muted: true });
|
||||
expect(hasMuted(burger)).toBe(true);
|
||||
vg.setVoiceState({ inCall: true, peerCount: 0, muted: false });
|
||||
expect(hasMuted(burger)).toBe(false);
|
||||
});
|
||||
|
||||
it("hands the pulse to whichever surface is showing (fan toggles)", () => {
|
||||
vg = bindVoiceGlow();
|
||||
vg.setVoiceState({ inCall: true, peerCount: 0 });
|
||||
@@ -145,8 +168,8 @@ describe("VoiceGlow", () => {
|
||||
vg = bindVoiceGlow();
|
||||
vg.setVoiceState({ inCall: true, peerCount: 1 });
|
||||
vg.render(); vg.render();
|
||||
const fastCount = (burger.className.match(/voice-pulse--fast/g) || []).length;
|
||||
expect(fastCount).toBe(1);
|
||||
const eqCount = (burger.className.match(/voice-eq/g) || []).length;
|
||||
expect(eqCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user