diff --git a/components/controls/MasterMeter.tsx b/components/controls/MasterMeter.tsx
new file mode 100644
index 0000000..770268c
--- /dev/null
+++ b/components/controls/MasterMeter.tsx
@@ -0,0 +1,120 @@
+'use client';
+
+import * as React from 'react';
+
+export interface MasterMeterProps {
+ /** Peak level (0-1) */
+ peakLevel: number;
+ /** RMS level (0-1) */
+ rmsLevel: number;
+ /** Whether clipping has occurred */
+ isClipping: boolean;
+ /** Callback to reset clip indicator */
+ onResetClip?: () => void;
+}
+
+export function MasterMeter({
+ peakLevel,
+ rmsLevel,
+ isClipping,
+ onResetClip,
+}: MasterMeterProps) {
+ // Convert linear 0-1 to dB scale for display
+ const linearToDb = (linear: number): number => {
+ if (linear === 0) return -60;
+ const db = 20 * Math.log10(linear);
+ return Math.max(-60, Math.min(0, db));
+ };
+
+ const peakDb = linearToDb(peakLevel);
+ const rmsDb = linearToDb(rmsLevel);
+
+ // Calculate bar heights (0-100%)
+ const peakHeight = ((peakDb + 60) / 60) * 100;
+ const rmsHeight = ((rmsDb + 60) / 60) * 100;
+
+ return (
+
+ {/* Clip Indicator */}
+
+
+ {/* Meters */}
+
+ {/* Peak Meter (Left) */}
+
+
+
-3 ? 'bg-red-500' :
+ peakDb > -6 ? 'bg-yellow-500' :
+ 'bg-green-500'
+ }`} />
+
+ {/* dB markers */}
+
+
+
+ {/* RMS Meter (Right) */}
+
+
+
-3 ? 'bg-red-400' :
+ rmsDb > -6 ? 'bg-yellow-400' :
+ 'bg-green-400'
+ }`} />
+
+ {/* dB markers */}
+
+
+
+
+ {/* Labels and Values */}
+
+
+ PK:
+ -3 ? 'text-red-500' :
+ peakDb > -6 ? 'text-yellow-500' :
+ 'text-green-500'
+ }`}>
+ {peakDb > -60 ? `${peakDb.toFixed(1)}` : '-∞'}
+
+
+
+ RM:
+ -3 ? 'text-red-400' :
+ rmsDb > -6 ? 'text-yellow-400' :
+ 'text-green-400'
+ }`}>
+ {rmsDb > -60 ? `${rmsDb.toFixed(1)}` : '-∞'}
+
+
+
dB
+
+
+ );
+}
diff --git a/components/editor/AudioEditor.tsx b/components/editor/AudioEditor.tsx
index e66d7ee..70c6417 100644
--- a/components/editor/AudioEditor.tsx
+++ b/components/editor/AudioEditor.tsx
@@ -219,6 +219,10 @@ export function AudioEditor() {
currentTime,
duration,
trackLevels,
+ masterPeakLevel,
+ masterRmsLevel,
+ masterIsClipping,
+ resetClipIndicator,
play,
pause,
stop,
@@ -1056,6 +1060,10 @@ export function AudioEditor() {
onPunchOutTimeChange={setPunchOutTime}
overdubEnabled={overdubEnabled}
onOverdubEnabledChange={setOverdubEnabled}
+ masterPeakLevel={masterPeakLevel}
+ masterRmsLevel={masterRmsLevel}
+ masterIsClipping={masterIsClipping}
+ onResetClip={resetClipIndicator}
/>
diff --git a/components/editor/PlaybackControls.tsx b/components/editor/PlaybackControls.tsx
index 266922f..5580fa0 100644
--- a/components/editor/PlaybackControls.tsx
+++ b/components/editor/PlaybackControls.tsx
@@ -4,6 +4,7 @@ import * as React from 'react';
import { Play, Pause, Square, SkipBack, Volume2, VolumeX, Circle, AlignVerticalJustifyStart, AlignVerticalJustifyEnd, Layers } from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { Slider } from '@/components/ui/Slider';
+import { MasterMeter } from '@/components/controls/MasterMeter';
import { cn } from '@/lib/utils/cn';
export interface PlaybackControlsProps {
@@ -32,6 +33,10 @@ export interface PlaybackControlsProps {
onPunchOutTimeChange?: (time: number) => void;
overdubEnabled?: boolean;
onOverdubEnabledChange?: (enabled: boolean) => void;
+ masterPeakLevel?: number;
+ masterRmsLevel?: number;
+ masterIsClipping?: boolean;
+ onResetClip?: () => void;
}
export function PlaybackControls({
@@ -60,6 +65,10 @@ export function PlaybackControls({
onPunchOutTimeChange,
overdubEnabled = false,
onOverdubEnabledChange,
+ masterPeakLevel = 0,
+ masterRmsLevel = 0,
+ masterIsClipping = false,
+ onResetClip,
}: PlaybackControlsProps) {
const [isMuted, setIsMuted] = React.useState(false);
const [previousVolume, setPreviousVolume] = React.useState(volume);
@@ -275,29 +284,40 @@ export function PlaybackControls({
)}
- {/* Volume Control */}
-