UNPKG

murmuraba

Version:

Real-time audio noise reduction with advanced chunked processing for web applications

135 lines (134 loc) 5.71 kB
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; import { useRef, useEffect, useState } from 'react'; export const SimpleWaveformAnalyzer = ({ stream, isActive = true, isPaused = false, width = 800, height = 200 }) => { const canvasRef = useRef(null); const audioContextRef = useRef(null); const analyserRef = useRef(null); const sourceRef = useRef(null); const animationRef = useRef(0); const [error, setError] = useState(null); useEffect(() => { if (!stream || !isActive || isPaused) { // Stop animation if paused or inactive if (animationRef.current) { cancelAnimationFrame(animationRef.current); } return; } const initializeWaveform = async () => { try { // Clean up previous context if (audioContextRef.current && audioContextRef.current.state !== 'closed') { await audioContextRef.current.close(); } // Create new AudioContext const audioContext = new AudioContext(); if (audioContext.state === 'suspended') { await audioContext.resume(); } // Create analyser const analyser = audioContext.createAnalyser(); analyser.fftSize = 2048; analyser.smoothingTimeConstant = 0.8; // Create source and connect const source = audioContext.createMediaStreamSource(stream); source.connect(analyser); // Store references audioContextRef.current = audioContext; analyserRef.current = analyser; sourceRef.current = source; console.log('SimpleWaveformAnalyzer: Initialized successfully'); setError(null); startDrawing(); } catch (err) { console.error('SimpleWaveformAnalyzer: Initialization failed:', err); setError('Failed to initialize waveform'); } }; const startDrawing = () => { const canvas = canvasRef.current; const analyser = analyserRef.current; if (!canvas || !analyser) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); const draw = () => { if (!isActive || isPaused) return; animationRef.current = requestAnimationFrame(draw); // Get audio data analyser.getByteTimeDomainData(dataArray); // Clear canvas ctx.fillStyle = '#0A0B0E'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw waveform ctx.lineWidth = 2; ctx.strokeStyle = '#52A32F'; ctx.beginPath(); const sliceWidth = canvas.width / bufferLength; let x = 0; for (let i = 0; i < bufferLength; i++) { const v = dataArray[i] / 128.0; const y = v * canvas.height / 2; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } x += sliceWidth; } ctx.lineTo(canvas.width, canvas.height / 2); ctx.stroke(); // Draw amplitude meter const average = dataArray.reduce((sum, value) => sum + Math.abs(value - 128), 0) / bufferLength; const normalizedAmplitude = average / 128; // Amplitude bar with gradient const ampGradient = ctx.createLinearGradient(10, 0, 110, 0); ampGradient.addColorStop(0, '#52A32F'); ampGradient.addColorStop(1, '#52A32F88'); ctx.fillStyle = ampGradient; ctx.fillRect(10, 10, normalizedAmplitude * 100, 10); // Meter border ctx.strokeStyle = '#2E3039'; ctx.strokeRect(10, 10, 100, 10); // Level text ctx.fillStyle = '#CACBDA'; ctx.font = '12px monospace'; ctx.fillText(`Level: ${(normalizedAmplitude * 100).toFixed(1)}%`, 10, 35); }; draw(); }; initializeWaveform(); return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [stream?.id, isActive, isPaused]); // Cleanup on unmount useEffect(() => { return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } if (audioContextRef.current && audioContextRef.current.state !== 'closed') { audioContextRef.current.close(); } }; }, []); if (error) { return (_jsx("div", { style: { padding: '20px', textAlign: 'center', color: '#ff6b6b' }, children: _jsxs("p", { children: ["\u26A0\uFE0F ", error] }) })); } return (_jsx("canvas", { ref: canvasRef, width: width, height: height, style: { width: '100%', height: '200px', borderRadius: '10px', backgroundColor: '#0A0B0E', boxShadow: stream ? '0 4px 20px rgba(102, 126, 234, 0.3)' : 'none' } })); };