fix: Resolve listening post audio stuttering introduced in v2.15.0

Throttle audio waterfall rendering (50ms→200ms), eliminate per-frame
Array.from() allocation, drain stale pipe buffer before streaming,
increase chunk size to 8192, and remove debug logging from animation
hot paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-10 20:24:51 +00:00
parent a1cb6b2692
commit a354fee792
2 changed files with 22 additions and 29 deletions
+19 -2
View File
@@ -1456,13 +1456,30 @@ def stream_audio() -> Response:
if not proc or not proc.stdout: if not proc or not proc.stdout:
return return
try: try:
# First byte timeout to avoid hanging clients forever # Drain stale audio that accumulated in the pipe buffer
# between pipeline start and stream connection. Keep the
# first chunk (contains WAV header) and discard the rest
# so the browser starts close to real-time.
header_chunk = None
while True:
ready, _, _ = select.select([proc.stdout], [], [], 0)
if not ready:
break
chunk = proc.stdout.read(8192)
if not chunk:
break
if header_chunk is None:
header_chunk = chunk
if header_chunk:
yield header_chunk
# Stream real-time audio
first_chunk_deadline = time.time() + 3.0 first_chunk_deadline = time.time() + 3.0
while audio_running and proc.poll() is None: while audio_running and proc.poll() is None:
# Use select to avoid blocking forever # Use select to avoid blocking forever
ready, _, _ = select.select([proc.stdout], [], [], 2.0) ready, _, _ = select.select([proc.stdout], [], [], 2.0)
if ready: if ready:
chunk = proc.stdout.read(4096) chunk = proc.stdout.read(8192)
if chunk: if chunk:
yield chunk yield chunk
else: else:
+3 -27
View File
@@ -1742,9 +1742,6 @@ function initSynthesizer() {
drawSynthesizer(); drawSynthesizer();
} }
// Debug: log signal level periodically
let lastSynthDebugLog = 0;
function drawSynthesizer() { function drawSynthesizer() {
if (!synthCtx || !synthCanvas) return; if (!synthCtx || !synthCanvas) return;
@@ -1760,19 +1757,6 @@ function drawSynthesizer() {
let activityLevel = 0; let activityLevel = 0;
let signalIntensity = 0; let signalIntensity = 0;
// Debug logging every 2 seconds
const now = Date.now();
if (now - lastSynthDebugLog > 2000) {
console.log('[SYNTH] State:', {
isScannerRunning,
isDirectListening,
scannerSignalActive,
currentSignalLevel,
visualizerAnalyser: !!visualizerAnalyser
});
lastSynthDebugLog = now;
}
if (isScannerRunning && !isScannerPaused) { if (isScannerRunning && !isScannerPaused) {
// Use actual signal level data (0-5000 range, normalize to 0-1) // Use actual signal level data (0-5000 range, normalize to 0-1)
signalIntensity = Math.min(1, currentSignalLevel / 3000); signalIntensity = Math.min(1, currentSignalLevel / 3000);
@@ -1864,13 +1848,6 @@ function drawSynthesizer() {
synthCtx.lineTo(width, height / 2); synthCtx.lineTo(width, height / 2);
synthCtx.stroke(); synthCtx.stroke();
// Debug: show signal level value
if (isScannerRunning || isDirectListening) {
synthCtx.fillStyle = 'rgba(255, 255, 255, 0.5)';
synthCtx.font = '9px monospace';
synthCtx.fillText(`lvl:${Math.round(currentSignalLevel)}`, 4, 10);
}
synthAnimationId = requestAnimationFrame(drawSynthesizer); synthAnimationId = requestAnimationFrame(drawSynthesizer);
} }
@@ -3109,7 +3086,7 @@ let waterfallEndFreq = 108;
let waterfallRowImage = null; let waterfallRowImage = null;
let waterfallPalette = null; let waterfallPalette = null;
let lastWaterfallDraw = 0; let lastWaterfallDraw = 0;
const WATERFALL_MIN_INTERVAL_MS = 50; const WATERFALL_MIN_INTERVAL_MS = 200;
let waterfallInteractionBound = false; let waterfallInteractionBound = false;
let waterfallResizeObserver = null; let waterfallResizeObserver = null;
let waterfallMode = 'rf'; let waterfallMode = 'rf';
@@ -3436,9 +3413,8 @@ function startAudioWaterfall() {
if (ts - lastAudioWaterfallDraw >= WATERFALL_MIN_INTERVAL_MS) { if (ts - lastAudioWaterfallDraw >= WATERFALL_MIN_INTERVAL_MS) {
lastAudioWaterfallDraw = ts; lastAudioWaterfallDraw = ts;
visualizerAnalyser.getByteFrequencyData(dataArray); visualizerAnalyser.getByteFrequencyData(dataArray);
const bins = Array.from(dataArray, v => v); drawWaterfallRow(dataArray);
drawWaterfallRow(bins); drawSpectrumLine(dataArray, 0, maxFreqKhz, 'kHz');
drawSpectrumLine(bins, 0, maxFreqKhz, 'kHz');
} }
audioWaterfallAnimId = requestAnimationFrame(drawFrame); audioWaterfallAnimId = requestAnimationFrame(drawFrame);
}; };