import { useEffect, useRef, useState } from "react" import { cn } from "@/lib/utils" interface LevelMeterProps { audioContext?: AudioContext source?: AudioNode channels?: 1 | 2 // Mono or stereo orientation?: "vertical" | "horizontal" className?: string } export function LevelMeter({ audioContext, source, channels = 2, orientation = "vertical", className, }: LevelMeterProps) { const [levels, setLevels] = useState(Array(channels).fill(-60)) const [peaks, setPeaks] = useState(Array(channels).fill(-60)) const analyserRef = useRef(null) const animationRef = useRef(0) useEffect(() => { if (!audioContext || !source) return // Create analyser node const analyser = audioContext.createAnalyser() analyser.fftSize = 256 analyser.smoothingTimeConstant = 0.8 // Connect source to analyser source.connect(analyser) analyserRef.current = analyser // Create data array const dataArray = new Float32Array(analyser.frequencyBinCount) // Animation loop const updateLevels = () => { if (!analyserRef.current) return analyserRef.current.getFloatTimeDomainData(dataArray) // Calculate RMS for each channel const newLevels: number[] = [] const channelSize = dataArray.length / channels for (let ch = 0; ch < channels; ch++) { let sum = 0 const start = ch * channelSize const end = start + channelSize for (let i = start; i < end; i++) { sum += dataArray[i] * dataArray[i] } const rms = Math.sqrt(sum / channelSize) const db = 20 * Math.log10(Math.max(0.00001, rms)) newLevels.push(db) } setLevels(newLevels) // Update peaks setPeaks((prev) => prev.map((peak, i) => { const newLevel = newLevels[i] if (newLevel > peak) return newLevel return peak - 0.5 // Peak hold decay }), ) animationRef.current = requestAnimationFrame(updateLevels) } updateLevels() return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current) } if (analyserRef.current && source) { source.disconnect(analyserRef.current) } } }, [audioContext, source, channels]) const renderMeter = (level: number, peak: number, index: number) => { // Convert dB to percentage (0 dB = 100%, -60 dB = 0%) const levelPercent = Math.max(0, Math.min(100, ((level + 60) / 60) * 100)) const peakPercent = Math.max(0, Math.min(100, ((peak + 60) / 60) * 100)) // Color based on level const getColor = (percent: number) => { if (percent > 90) return "bg-red-500" if (percent > 70) return "bg-yellow-500" return "bg-green-500" } if (orientation === "vertical") { return (
{/* Level bar */}
{/* Peak indicator */}
{/* Scale marks */}
{[0, -6, -12, -24, -48].map((db, i) => (
))}
) } // Horizontal orientation return (
{/* Level bar */}
{/* Peak indicator */}
) } return (
{levels.map((level, i) => renderMeter(level, peaks[i], i))}
) }