/** * waveform.js — Microphone waveform visualizer using Web Audio API. * Exports startWaveform() and stopWaveform(). */ /** * Start visualizing the microphone input on a canvas element. * @param {HTMLCanvasElement} canvas - The canvas to draw on. * @param {MediaStream} stream - The audio stream from getUserMedia. * @returns {Function} cleanup - Call this to stop the visualizer and release resources. */ export function startWaveform(canvas, stream) { const ctx = canvas.getContext('2d'); const dpr = window.devicePixelRatio || 1; // Set canvas resolution to match display size const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); const width = rect.width; const height = rect.height; // Web Audio setup const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const source = audioCtx.createMediaStreamSource(stream); const analyser = audioCtx.createAnalyser(); analyser.fftSize = 256; source.connect(analyser); const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); let animFrameId = null; function draw() { animFrameId = requestAnimationFrame(draw); analyser.getByteTimeDomainData(dataArray); // Clear ctx.fillStyle = '#F0F2F5'; ctx.fillRect(0, 0, width, height); // Draw waveform ctx.lineWidth = 2; ctx.strokeStyle = '#25D366'; ctx.beginPath(); const sliceWidth = width / bufferLength; let x = 0; for (let i = 0; i < bufferLength; i++) { const v = dataArray[i] / 128.0; const y = (v * height) / 2; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } x += sliceWidth; } ctx.lineTo(width, height / 2); ctx.stroke(); } draw(); // Return cleanup function return function cleanup() { if (animFrameId) cancelAnimationFrame(animFrameId); try { source.disconnect(); } catch (e) { /* ignore */ } try { audioCtx.close(); } catch (e) { /* ignore */ } }; } /** * Stop the waveform and draw a flat idle line. * @param {HTMLCanvasElement} canvas */ export function stopWaveform(canvas) { const ctx = canvas.getContext('2d'); const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); const width = rect.width; const height = rect.height; // Fill background ctx.fillStyle = '#F0F2F5'; ctx.fillRect(0, 0, width, height); // Draw flat center line ctx.strokeStyle = '#D0D5DA'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, height / 2); ctx.lineTo(width, height / 2); ctx.stroke(); }