From a0ce83a6548a9b0a2946b458a32a9ce348f8e1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Tue, 18 Nov 2025 15:21:31 +0100 Subject: [PATCH] fix: use Float32Array for accurate full-range level measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switched from Uint8Array to Float32Array for level monitoring to get accurate, full-precision audio measurements. The Problem: - getByteTimeDomainData() uses Uint8Array (0-255) - Byte conversion: (value - 128) / 128 has asymmetric range - Positive peaks: (255-128)/128 = 0.992 (not full 1.0) - Precision loss from byte quantization - Mastered tracks with peaks at 0dBFS only showed ~50% The Solution: - Switched to getFloatTimeDomainData() with Float32Array - Returns actual sample values directly in -1.0 to +1.0 range - No conversion needed, no precision loss - Accurate representation of audio peaks Changes Applied: - useMultiTrackPlayer: Float32Array with analyser.fftSize samples - useRecording: Float32Array with analyser.fftSize samples - Peak detection: Math.abs() on float values directly Benefits: ✅ Full 0-100% range for properly mastered audio ✅ Higher precision (32-bit float vs 8-bit byte) ✅ Symmetric range (-1.0 to +1.0, not -1.0 to ~0.992) ✅ Accurate metering for professional audio files Now mastered tracks with peaks at 0dBFS will correctly show ~100% on the meters instead of being capped at 50%. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- lib/hooks/useMultiTrackPlayer.ts | 12 ++++++------ lib/hooks/useRecording.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/hooks/useMultiTrackPlayer.ts b/lib/hooks/useMultiTrackPlayer.ts index ba41797..2bdd6bc 100644 --- a/lib/hooks/useMultiTrackPlayer.ts +++ b/lib/hooks/useMultiTrackPlayer.ts @@ -61,15 +61,15 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) { const track = tracksRef.current[index]; if (!track) return; - const dataArray = new Uint8Array(analyser.frequencyBinCount); - analyser.getByteTimeDomainData(dataArray); + const dataArray = new Float32Array(analyser.fftSize); + analyser.getFloatTimeDomainData(dataArray); - // Calculate peak level (more responsive than RMS for visual meters) + // Calculate peak level using float data (-1 to +1 range) let peak = 0; for (let i = 0; i < dataArray.length; i++) { - const normalized = Math.abs((dataArray[i] - 128) / 128); - if (normalized > peak) { - peak = normalized; + const abs = Math.abs(dataArray[i]); + if (abs > peak) { + peak = abs; } } diff --git a/lib/hooks/useRecording.ts b/lib/hooks/useRecording.ts index 606a98d..a120a2b 100644 --- a/lib/hooks/useRecording.ts +++ b/lib/hooks/useRecording.ts @@ -71,19 +71,19 @@ export function useRecording(): UseRecordingReturn { if (!analyserRef.current) return; const analyser = analyserRef.current; - const dataArray = new Uint8Array(analyser.frequencyBinCount); + const dataArray = new Float32Array(analyser.fftSize); const updateLevel = () => { if (!isMonitoringRef.current) return; - analyser.getByteTimeDomainData(dataArray); + analyser.getFloatTimeDomainData(dataArray); - // Calculate peak level (more responsive than RMS for visual meters) + // Calculate peak level using float data (-1 to +1 range) let peak = 0; for (let i = 0; i < dataArray.length; i++) { - const normalized = Math.abs((dataArray[i] - 128) / 128); - if (normalized > peak) { - peak = normalized; + const abs = Math.abs(dataArray[i]); + if (abs > peak) { + peak = abs; } }