From af18829b956ddcb8cfc7dc2c3719386cdcaa18ab Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Mon, 13 Jul 2026 08:49:30 +0200 Subject: [PATCH] Update Ableton effect parameter mapping and refactor value calculation logic across multiple effects (Reverb, Delay, Distortion, Chorus, Filter, Compressor). --- src/lib/exporters/ableton/builders.ts | 58 ++++++++++++++++++++------- src/lib/exporters/ableton/effects.ts | 4 +- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/lib/exporters/ableton/builders.ts b/src/lib/exporters/ableton/builders.ts index 6202bfd..11739b2 100644 --- a/src/lib/exporters/ableton/builders.ts +++ b/src/lib/exporters/ableton/builders.ts @@ -978,14 +978,22 @@ async function buildReturnTrack( if (projectData.effects.effectType === EffectType.Reverb) { const effectReverbTemplate = await loadTemplate('effectReverb', abletonVersion); const effectReverb = structuredClone(effectReverbTemplate.Reverb); + const values = reverbEffectValues(projectData.effects.param1, projectData.effects.param2); // these params are very rough approximation of the KO's reverb // it's not exact, but close enough effectReverb.PreDelay.Manual['@Value'] = 0.5; effectReverb.RoomSize.Manual['@Value'] = 300; effectReverb.ShelfHiFreq.Manual['@Value'] = 1000; - effectReverb.DecayTime.Manual['@Value'] = projectData.effects.param1 * 8000; // max 8 seconds which is kinda close to how it sounds in KO - effectReverb.ShelfHiGain.Manual['@Value'] = projectData.effects.param2; // param2 is "color" which roughly matches the high shelf gain cut amount + effectReverb.ShelfLoFreq.Manual['@Value'] = 1000; + effectReverb.DecayTime.Manual['@Value'] = values.decayTime; + effectReverb.ShelfHighOn.Manual['@Value'] = values.highShelfOn ? 'true' : 'false'; + effectReverb.ShelfHiGain.Manual['@Value'] = values.highShelfGain; + effectReverb.ShelfLowOn.Manual['@Value'] = values.lowShelfOn ? 'true' : 'false'; + effectReverb.ShelfLoGain.Manual['@Value'] = values.lowShelfGain; + effectReverb.MixReflect.Manual['@Value'] = 1; + effectReverb.MixDiffuse.Manual['@Value'] = 1; + effectReverb.MixDirect.Manual['@Value'] = 0; returnTrack.DeviceChain.DeviceChain.Devices = { Reverb: effectReverb }; } @@ -993,15 +1001,16 @@ async function buildReturnTrack( if (projectData.effects.effectType === EffectType.Delay) { const effectDelayTemplate = await loadTemplate('effectDelay', abletonVersion); const effectDelay = structuredClone(effectDelayTemplate.Delay); + const values = delayEffectValues(projectData.effects.param1, projectData.effects.param2); effectDelay.DelayLine_SyncL.Manual['@Value'] = 'false'; effectDelay.DelayLine_SyncR.Manual['@Value'] = 'false'; // don't even ask how I came up with this formula - effectDelay.DelayLine_TimeL.Manual['@Value'] = - 0.017397 * (287.725 ** projectData.effects.param1 - 1); - effectDelay.DelayLine_TimeR.Manual['@Value'] = - 0.017397 * (287.725 ** projectData.effects.param1 - 1); - effectDelay.Feedback.Manual['@Value'] = projectData.effects.param2; + effectDelay.DelayLine_TimeL.Manual['@Value'] = values.time; + effectDelay.DelayLine_TimeR.Manual['@Value'] = values.time; + effectDelay.Feedback.Manual['@Value'] = values.feedback; + effectDelay.Filter_On.Manual['@Value'] = 'false'; + effectDelay.DryWet.Manual['@Value'] = 1; returnTrack.DeviceChain.DeviceChain.Devices = { Delay: effectDelay }; } @@ -1012,9 +1021,11 @@ async function buildReturnTrack( abletonVersion, ); const effectDistortion = structuredClone(effectDistortionTemplate.Overdrive); + const values = distortionEffectValues(projectData.effects.param1, projectData.effects.param2); - effectDistortion.Drive.Manual['@Value'] = projectData.effects.param1 * 50; - effectDistortion.Tone.Manual['@Value'] = projectData.effects.param2 * 100; + effectDistortion.Drive.Manual['@Value'] = values.drive; + effectDistortion.Tone.Manual['@Value'] = values.tone; + effectDistortion.DryWet.Manual['@Value'] = 100; returnTrack.DeviceChain.DeviceChain.Devices = { Overdrive: effectDistortion }; } @@ -1024,13 +1035,16 @@ async function buildReturnTrack( const effectChorus = structuredClone( abletonVersion === '10' ? (effectChorusTemplate as any).Chorus : effectChorusTemplate.Chorus2, ); + const values = chorusEffectValues(projectData.effects.param1, projectData.effects.param2); if (abletonVersion === '10') { - effectChorus.ModAmount.Manual['@Value'] = projectData.effects.param1 * 6.5; + effectChorus.ModFreq.Manual['@Value'] = values.rate; + effectChorus.LfoMultiplier.Manual['@Value'] = 'false'; } else { - effectChorus.Amount.Manual['@Value'] = projectData.effects.param1; + effectChorus.Rate.Manual['@Value'] = values.rate; } - effectChorus.Feedback.Manual['@Value'] = projectData.effects.param2 * 0.8; + effectChorus.Feedback.Manual['@Value'] = values.feedback; + effectChorus.DryWet.Manual['@Value'] = 1; returnTrack.DeviceChain.DeviceChain.Devices = ( abletonVersion === '10' ? { Chorus: effectChorus } : { Chorus2: effectChorus } @@ -1041,9 +1055,12 @@ async function buildReturnTrack( if (projectData.effects.effectType === EffectType.Filter) { const effectFilterTemplate = await loadTemplate('effectFilter', abletonVersion); const effectFilter = structuredClone(effectFilterTemplate.AutoFilter); + const values = filterEffectValues(projectData.effects.param1, projectData.effects.param2); - effectFilter.Cutoff.Manual['@Value'] = 20 + projectData.effects.param1 * (135 - 20); - effectFilter.Resonance.Manual['@Value'] = projectData.effects.param2 * 1.25; + effectFilter.FilterType.Manual['@Value'] = values.filterType; + effectFilter.LegacyFilterType.Manual['@Value'] = values.filterType; + effectFilter.Cutoff.Manual['@Value'] = values.cutoff; + effectFilter.Resonance.Manual['@Value'] = values.resonance; returnTrack.DeviceChain.DeviceChain.Devices = { AutoFilter: effectFilter }; } @@ -1054,8 +1071,19 @@ async function buildReturnTrack( abletonVersion, ); const effectCompressor = structuredClone(effectCompressorTemplate.Compressor2); + const values = compressorEffectValues(projectData.effects.param1, projectData.effects.param2); - // params are ignored for now, just putting a basic compressor + effectCompressor.Threshold.Manual['@Value'] = values.threshold; + effectCompressor.Ratio.Manual['@Value'] = values.ratio; + effectCompressor.Attack.Manual['@Value'] = values.attack; + effectCompressor.Release.Manual['@Value'] = values.release; + effectCompressor.AutoReleaseControlOnOff.Manual['@Value'] = 'false'; + effectCompressor.Gain.Manual['@Value'] = 0; + effectCompressor.GainCompensation.Manual['@Value'] = 'true'; + effectCompressor.DryWet.Manual['@Value'] = 1; + effectCompressor.Model.Manual['@Value'] = 0; + effectCompressor.Knee.Manual['@Value'] = 6; + effectCompressor.LookAhead.Manual['@Value'] = 0; returnTrack.DeviceChain.DeviceChain.Devices = { Compressor2: effectCompressor }; } diff --git a/src/lib/exporters/ableton/effects.ts b/src/lib/exporters/ableton/effects.ts index 2279ccf..f041775 100644 --- a/src/lib/exporters/ableton/effects.ts +++ b/src/lib/exporters/ableton/effects.ts @@ -1,5 +1,3 @@ -type AbletonVersion = '10' | '11'; - function normalized(value: number) { return Math.max(0, Math.min(1, value)); } @@ -32,7 +30,7 @@ export function distortionEffectValues(x: number, y: number) { }; } -export function chorusEffectValues(x: number, y: number, _abletonVersion: AbletonVersion) { +export function chorusEffectValues(x: number, y: number) { return { rate: 0.1 * 100 ** normalized(x), feedback: normalized(y) * 0.95,