diff --git a/components/editor/AudioEditor.tsx b/components/editor/AudioEditor.tsx
index c53e5b8..8a5c4c2 100644
--- a/components/editor/AudioEditor.tsx
+++ b/components/editor/AudioEditor.tsx
@@ -294,6 +294,12 @@ export function AudioEditor() {
stop,
seek,
togglePlayPause,
+ loopEnabled,
+ loopStart,
+ loopEnd,
+ toggleLoop,
+ setLoopPoints,
+ setLoopFromSelection,
} = useMultiTrackPlayer(tracks, masterVolume, handleAutomationRecording);
// Reset latch triggered state when playback stops
@@ -1972,6 +1978,11 @@ export function AudioEditor() {
onPunchOutTimeChange={setPunchOutTime}
overdubEnabled={overdubEnabled}
onOverdubEnabledChange={setOverdubEnabled}
+ loopEnabled={loopEnabled}
+ loopStart={loopStart}
+ loopEnd={loopEnd}
+ onToggleLoop={toggleLoop}
+ onSetLoopPoints={setLoopPoints}
/>
diff --git a/components/editor/PlaybackControls.tsx b/components/editor/PlaybackControls.tsx
index 8e2743f..ad8c5ca 100644
--- a/components/editor/PlaybackControls.tsx
+++ b/components/editor/PlaybackControls.tsx
@@ -1,7 +1,7 @@
'use client';
import * as React from 'react';
-import { Play, Pause, Square, SkipBack, Circle, AlignVerticalJustifyStart, AlignVerticalJustifyEnd, Layers } from 'lucide-react';
+import { Play, Pause, Square, SkipBack, Circle, AlignVerticalJustifyStart, AlignVerticalJustifyEnd, Layers, Repeat } from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { cn } from '@/lib/utils/cn';
@@ -31,6 +31,11 @@ export interface PlaybackControlsProps {
onPunchOutTimeChange?: (time: number) => void;
overdubEnabled?: boolean;
onOverdubEnabledChange?: (enabled: boolean) => void;
+ loopEnabled?: boolean;
+ loopStart?: number;
+ loopEnd?: number;
+ onToggleLoop?: () => void;
+ onSetLoopPoints?: (start: number, end: number) => void;
}
export function PlaybackControls({
@@ -59,6 +64,11 @@ export function PlaybackControls({
onPunchOutTimeChange,
overdubEnabled = false,
onOverdubEnabledChange,
+ loopEnabled = false,
+ loopStart = 0,
+ loopEnd = 0,
+ onToggleLoop,
+ onSetLoopPoints,
}: PlaybackControlsProps) {
const handlePlayPause = () => {
if (isPlaying) {
@@ -249,8 +259,80 @@ export function PlaybackControls({
>
)}
+
+ {/* Loop Toggle */}
+ {onToggleLoop && (
+
+
+
+ )}
+
+ {/* Loop Points - Show when enabled */}
+ {loopEnabled && onSetLoopPoints && (
+
+
+
+
onSetLoopPoints(parseFloat(e.target.value), loopEnd)}
+ className="flex-1 px-2 py-1 bg-background border border-border rounded text-xs font-mono"
+ />
+
+
+
+
+
+
onSetLoopPoints(loopStart, parseFloat(e.target.value))}
+ className="flex-1 px-2 py-1 bg-background border border-border rounded text-xs font-mono"
+ />
+
+
+
+ )}
);
}
diff --git a/lib/hooks/useMultiTrackPlayer.ts b/lib/hooks/useMultiTrackPlayer.ts
index e9f0c70..edc2e64 100644
--- a/lib/hooks/useMultiTrackPlayer.ts
+++ b/lib/hooks/useMultiTrackPlayer.ts
@@ -9,6 +9,9 @@ export interface MultiTrackPlayerState {
isPlaying: boolean;
currentTime: number;
duration: number;
+ loopEnabled: boolean;
+ loopStart: number;
+ loopEnd: number;
}
export interface TrackLevel {
@@ -32,6 +35,9 @@ export function useMultiTrackPlayer(
const [masterPeakLevel, setMasterPeakLevel] = useState(0);
const [masterRmsLevel, setMasterRmsLevel] = useState(0);
const [masterIsClipping, setMasterIsClipping] = useState(false);
+ const [loopEnabled, setLoopEnabled] = useState(false);
+ const [loopStart, setLoopStart] = useState(0);
+ const [loopEnd, setLoopEnd] = useState(0);
const audioContextRef = useRef(null);
const sourceNodesRef = useRef([]);
@@ -51,12 +57,22 @@ export function useMultiTrackPlayer(
const tracksRef = useRef