diff --git a/components/editor/AudioEditor.tsx b/components/editor/AudioEditor.tsx index 8a5c4c2..995081a 100644 --- a/components/editor/AudioEditor.tsx +++ b/components/editor/AudioEditor.tsx @@ -300,6 +300,8 @@ export function AudioEditor() { toggleLoop, setLoopPoints, setLoopFromSelection, + playbackRate, + changePlaybackRate, } = useMultiTrackPlayer(tracks, masterVolume, handleAutomationRecording); // Reset latch triggered state when playback stops @@ -1983,6 +1985,8 @@ export function AudioEditor() { loopEnd={loopEnd} onToggleLoop={toggleLoop} onSetLoopPoints={setLoopPoints} + playbackRate={playbackRate} + onPlaybackRateChange={changePlaybackRate} /> diff --git a/components/editor/PlaybackControls.tsx b/components/editor/PlaybackControls.tsx index ad8c5ca..8fd3c5e 100644 --- a/components/editor/PlaybackControls.tsx +++ b/components/editor/PlaybackControls.tsx @@ -36,6 +36,8 @@ export interface PlaybackControlsProps { loopEnd?: number; onToggleLoop?: () => void; onSetLoopPoints?: (start: number, end: number) => void; + playbackRate?: number; + onPlaybackRateChange?: (rate: number) => void; } export function PlaybackControls({ @@ -69,6 +71,8 @@ export function PlaybackControls({ loopEnd = 0, onToggleLoop, onSetLoopPoints, + playbackRate = 1.0, + onPlaybackRateChange, }: PlaybackControlsProps) { const handlePlayPause = () => { if (isPlaying) { @@ -276,6 +280,26 @@ export function PlaybackControls({ )} + + {/* Playback Speed Control */} + {onPlaybackRateChange && ( +
+ +
+ )} diff --git a/lib/hooks/useMultiTrackPlayer.ts b/lib/hooks/useMultiTrackPlayer.ts index edc2e64..9bb0d4d 100644 --- a/lib/hooks/useMultiTrackPlayer.ts +++ b/lib/hooks/useMultiTrackPlayer.ts @@ -12,6 +12,7 @@ export interface MultiTrackPlayerState { loopEnabled: boolean; loopStart: number; loopEnd: number; + playbackRate: number; } export interface TrackLevel { @@ -38,6 +39,7 @@ export function useMultiTrackPlayer( const [loopEnabled, setLoopEnabled] = useState(false); const [loopStart, setLoopStart] = useState(0); const [loopEnd, setLoopEnd] = useState(0); + const [playbackRate, setPlaybackRate] = useState(1.0); const audioContextRef = useRef(null); const sourceNodesRef = useRef([]); @@ -60,6 +62,7 @@ export function useMultiTrackPlayer( const loopEnabledRef = useRef(false); const loopStartRef = useRef(0); const loopEndRef = useRef(0); + const playbackRateRef = useRef(1.0); // Keep tracksRef in sync with tracks prop useEffect(() => { @@ -73,6 +76,11 @@ export function useMultiTrackPlayer( loopEndRef.current = loopEnd; }, [loopEnabled, loopStart, loopEnd]); + // Keep playbackRate ref in sync with state + useEffect(() => { + playbackRateRef.current = playbackRate; + }, [playbackRate]); + // Keep onRecordAutomationRef in sync useEffect(() => { onRecordAutomationRef.current = onRecordAutomation; @@ -313,7 +321,7 @@ export function useMultiTrackPlayer( const updatePlaybackPosition = useCallback(() => { if (!audioContextRef.current) return; - const elapsed = audioContextRef.current.currentTime - startTimeRef.current; + const elapsed = (audioContextRef.current.currentTime - startTimeRef.current) * playbackRateRef.current; const newTime = pausedAtRef.current + elapsed; // Check if loop is enabled and we've reached the loop end @@ -346,6 +354,7 @@ export function useMultiTrackPlayer( const source = audioContext.createBufferSource(); source.buffer = track.audioBuffer; + source.playbackRate.value = playbackRateRef.current; // Connect to existing nodes (gain, pan, effects are still connected) const trackIndex = tracks.indexOf(track); @@ -465,6 +474,9 @@ export function useMultiTrackPlayer( outputNode.connect(masterGain); console.log('[MultiTrackPlayer] Effect output connected with', effectNodes.length, 'effect nodes'); + // Set playback rate + source.playbackRate.value = playbackRateRef.current; + // Start playback from current position source.start(0, pausedAtRef.current); @@ -902,6 +914,17 @@ export function useMultiTrackPlayer( } }, [setLoopPoints]); + const changePlaybackRate = useCallback((rate: number) => { + // Clamp rate between 0.25x and 2x + const clampedRate = Math.max(0.25, Math.min(2.0, rate)); + setPlaybackRate(clampedRate); + + // Update playback rate on all active source nodes + sourceNodesRef.current.forEach(source => { + source.playbackRate.value = clampedRate; + }); + }, []); + return { isPlaying, currentTime, @@ -923,5 +946,7 @@ export function useMultiTrackPlayer( toggleLoop, setLoopPoints, setLoopFromSelection, + playbackRate, + changePlaybackRate, }; }