From 4c6453d115a08aa1f5ae40b3f858595b5d1e561e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Tue, 18 Nov 2025 19:08:18 +0100 Subject: [PATCH] feat: add effect parameter automation and fix mode logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completed Phase 9.3 - Full Automation Playback: - ✅ Effect parameter automation implementation - ✅ Fixed automation mode logic (now applies in all modes) - ✅ Automatic parameter range conversion (normalized to actual values) Effect parameter automation: - Parses effect parameter IDs (format: effect.{effectId}.{paramName}) - Finds corresponding effect nodes in audio graph - Converts normalized 0-1 automation values to actual parameter ranges - Applies parameters using updateEffectParameters during playback - Works with all effect types (filters, dynamics, time-based, etc.) Automation mode fix: - Removed incorrect mode !== 'read' checks - Automation now plays back in all modes (read/write/touch/latch) - Mode will control recording behavior, not playback Technical notes: - Used type assertion (as any) for dynamic parameter updates - Maintains parameter range from automation lane valueRange - Integrated with existing effect update mechanism Phase 9 Status: ✅ 9.1: Automation lanes UI complete ✅ 9.2: Automation points complete ✅ 9.3: Real-time playback (volume, pan, effects) complete ⏳ 9.3: Automation recording (next milestone) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- lib/hooks/useMultiTrackPlayer.ts | 38 +++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/hooks/useMultiTrackPlayer.ts b/lib/hooks/useMultiTrackPlayer.ts index f92cfb0..64d8cf8 100644 --- a/lib/hooks/useMultiTrackPlayer.ts +++ b/lib/hooks/useMultiTrackPlayer.ts @@ -110,7 +110,7 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) { tracks.forEach((track, index) => { // Apply volume automation const volumeLane = track.automation.lanes.find(lane => lane.parameterId === 'volume'); - if (volumeLane && volumeLane.points.length > 0 && volumeLane.mode !== 'read') { + if (volumeLane && volumeLane.points.length > 0) { const automatedValue = evaluateAutomationLinear(volumeLane.points, currentTime); if (automatedValue !== undefined && gainNodesRef.current[index]) { const trackGain = getTrackGain(track, tracks); @@ -124,7 +124,7 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) { // Apply pan automation const panLane = track.automation.lanes.find(lane => lane.parameterId === 'pan'); - if (panLane && panLane.points.length > 0 && panLane.mode !== 'read') { + if (panLane && panLane.points.length > 0) { const automatedValue = evaluateAutomationLinear(panLane.points, currentTime); if (automatedValue !== undefined && panNodesRef.current[index]) { // Pan automation values are 0-1, but StereoPannerNode expects -1 to 1 @@ -136,7 +136,39 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) { } } - // TODO: Apply effect parameter automation + // Apply effect parameter automation + track.automation.lanes.forEach(lane => { + // Check if this is an effect parameter (format: effect.{effectId}.{parameterName}) + if (lane.parameterId.startsWith('effect.') && lane.points.length > 0) { + const parts = lane.parameterId.split('.'); + if (parts.length === 3) { + const effectId = parts[1]; + const paramName = parts[2]; + + // Find the effect in the track's effect chain + const effectIndex = track.effectChain.effects.findIndex(e => e.id === effectId); + if (effectIndex >= 0 && effectNodesRef.current[index] && effectNodesRef.current[index][effectIndex]) { + const automatedValue = evaluateAutomationLinear(lane.points, currentTime); + if (automatedValue !== undefined) { + const effectNodeInfo = effectNodesRef.current[index][effectIndex]; + + // Convert normalized 0-1 value to actual parameter range + const effect = track.effectChain.effects[effectIndex]; + const actualValue = lane.valueRange.min + (automatedValue * (lane.valueRange.max - lane.valueRange.min)); + + // Update the effect parameter + if (effect.parameters) { + const updatedParams = { ...effect.parameters, [paramName]: actualValue } as any; + updateEffectParameters(audioContextRef.current!, effectNodeInfo, { + ...effect, + parameters: updatedParams + }); + } + } + } + } + } + }); }); automationFrameRef.current = requestAnimationFrame(applyAutomation);