import { Component, createSignal, createEffect, onCleanup } from 'solid-js'; import { AudioEngine } from '../lib/audio/types'; import { appStore } from '../stores/appStore'; interface EnergyMeterProps { audioEngine?: AudioEngine; } export const EnergyMeter: Component = (props) => { const [energy, setEnergy] = createSignal(0); const [metrics, setMetrics] = createSignal({ noiseFloor: 0, snr: 0, threshold: 0.02, snrThreshold: 3.0 }); const [isSpeaking, setIsSpeaking] = createSignal(false); const updateFromEngine = (engine: AudioEngine) => { const currentE = engine.getCurrentEnergy(); const currentM = engine.getSignalMetrics(); setEnergy(currentE); setMetrics(currentM); // Check if speaking based on SNR threshold (matching VAD logic) setIsSpeaking(currentM.snr > currentM.snrThreshold || currentE > currentM.threshold); }; createEffect(() => { const engine = props.audioEngine; if (!engine) return; updateFromEngine(engine); const unsubscribe = engine.onVisualizationUpdate(() => { updateFromEngine(engine); }); onCleanup(() => unsubscribe()); }); // Logarithmic scaling for better visualization const toPercent = (val: number) => { // e.g. mapping 0.0001 -> 1.0 to 0% -> 100% log scale // log10(0.0001) = -4, log10(1) = 0 const minLog = -4; const maxLog = 0; const v = Math.max(0.0001, val); const log = Math.log10(v); return Math.max(0, Math.min(100, ((log - minLog) / (maxLog - minLog)) * 100)); }; return (

Signal_Analysis

{/* Speaking indicator - Neumorphic LED style */}
{isSpeaking() ? 'SPEECH' : 'SILENCE'}
{/* Energy Bar */}
{/* Energy Fill - color based on speech state */}
{/* Noise Floor Marker */}
{/* Energy Threshold Marker */}
Noise {metrics().noiseFloor.toFixed(5)}
Energy {energy().toFixed(4)}
SNR_Ratio metrics().snrThreshold ? 'text-emerald-500' : 'text-amber-500'}`}> {metrics().snr.toFixed(1)} dB
); };