From db01209f7765c33c09776704f66fc3ec05ee380c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Tue, 18 Nov 2025 15:23:33 +0100 Subject: [PATCH] feat: implement professional logarithmic dB scale for level meters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Converted level meters from linear to logarithmic (dB) scale to match professional audio software behavior and human hearing. The Problem: - Linear scale (0-100%) doesn't match perceived loudness - Doesn't match professional DAW meter behavior - Half-volume audio appears at 50% but sounds much quieter - No industry-standard dB reference The Solution: - Convert linear amplitude to dB: 20 * log10(linear) - Normalize -60dB to 0dB range to 0-100% display - Matches professional audio metering standards dB Scale Mapping: 0 dB (linear 1.0) = 100% (full scale, clipping) -6 dB (linear ~0.5) = 90% (loud) -12 dB (linear ~0.25) = 80% (normal) -20 dB (linear ~0.1) = 67% (moderate) -40 dB (linear ~0.01) = 33% (quiet) -60 dB (linear ~0.001) = 0% (silence threshold) Implementation: - Added linearToDbScale() function to both hooks - useMultiTrackPlayer: playback level monitoring - useRecording: input level monitoring - Formula: (dB - minDb) / (maxDb - minDb) - Range: -60dB (min) to 0dB (max) Benefits: ✅ Professional audio metering standards ✅ Matches human perception of loudness ✅ Consistent with DAWs (Pro Tools, Logic, Ableton) ✅ Better visual feedback for mixing/mastering ✅ More responsive in useful range (-20dB to 0dB) Now properly mastered tracks will show levels in the 90-100% range, matching what you'd see in professional software. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- lib/hooks/useMultiTrackPlayer.ts | 20 +++++++++++++++++++- lib/hooks/useRecording.ts | 24 ++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/hooks/useMultiTrackPlayer.ts b/lib/hooks/useMultiTrackPlayer.ts index 2bdd6bc..6b923ad 100644 --- a/lib/hooks/useMultiTrackPlayer.ts +++ b/lib/hooks/useMultiTrackPlayer.ts @@ -51,6 +51,23 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) { setDuration(maxDuration); }, [tracks]); + // Convert linear amplitude to dB scale normalized to 0-1 range + const linearToDbScale = (linear: number): number => { + if (linear === 0) return 0; + + // Convert to dB (20 * log10(linear)) + const db = 20 * Math.log10(linear); + + // Normalize -60dB to 0dB range to 0-1 + // -60dB or lower = 0%, 0dB = 100% + const minDb = -60; + const maxDb = 0; + const normalized = (db - minDb) / (maxDb - minDb); + + // Clamp to 0-1 range + return Math.max(0, Math.min(1, normalized)); + }; + // Monitor playback levels for all tracks const monitorPlaybackLevels = useCallback(() => { if (!isMonitoringLevelsRef.current || analyserNodesRef.current.length === 0) return; @@ -73,7 +90,8 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) { } } - levels[track.id] = peak; + // Convert linear peak to logarithmic dB scale + levels[track.id] = linearToDbScale(peak); }); setTrackLevels(levels); diff --git a/lib/hooks/useRecording.ts b/lib/hooks/useRecording.ts index a120a2b..a43cc13 100644 --- a/lib/hooks/useRecording.ts +++ b/lib/hooks/useRecording.ts @@ -66,6 +66,23 @@ export function useRecording(): UseRecordingReturn { selectedDeviceIdRef.current = deviceId; }, []); + // Convert linear amplitude to dB scale normalized to 0-1 range + const linearToDbScale = React.useCallback((linear: number): number => { + if (linear === 0) return 0; + + // Convert to dB (20 * log10(linear)) + const db = 20 * Math.log10(linear); + + // Normalize -60dB to 0dB range to 0-1 + // -60dB or lower = 0%, 0dB = 100% + const minDb = -60; + const maxDb = 0; + const normalized = (db - minDb) / (maxDb - minDb); + + // Clamp to 0-1 range + return Math.max(0, Math.min(1, normalized)); + }, []); + // Monitor input level const monitorInputLevel = React.useCallback(() => { if (!analyserRef.current) return; @@ -87,13 +104,16 @@ export function useRecording(): UseRecordingReturn { } } - setState((prev) => ({ ...prev, inputLevel: peak })); + // Convert linear peak to logarithmic dB scale + const dbLevel = linearToDbScale(peak); + + setState((prev) => ({ ...prev, inputLevel: dbLevel })); animationFrameRef.current = requestAnimationFrame(updateLevel); }; updateLevel(); - }, []); + }, [linearToDbScale]); // Start recording const startRecording = React.useCallback(async (): Promise => {