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,
};
}