From eb5a25fe0a3d1e862a22c006486d0c0197565e7f Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Mon, 13 Jul 2026 05:29:26 +0200 Subject: [PATCH] Improve Ableton automation export for filters and add dedicated filter building function --- src/lib/exporters/ableton/automation.ts | 4 +- src/lib/exporters/ableton/builders.ts | 64 +++++++++++++++++-------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/lib/exporters/ableton/automation.ts b/src/lib/exporters/ableton/automation.ts index 25936f2..dc659c3 100644 --- a/src/lib/exporters/ableton/automation.ts +++ b/src/lib/exporters/ableton/automation.ts @@ -47,9 +47,9 @@ export function convertFaderValue( case FaderParam.PTC: return (value - 0.5) * 10; case FaderParam.LPF: - return filterFreqFromNormalized(value * 0.9); + return 20 + value * 0.9 * 115; case FaderParam.HPF: - return filterFreqFromNormalized(value * 0.7); + return 20 + value * 0.7 * 115; case FaderParam.FX: return value; case FaderParam.ATK: diff --git a/src/lib/exporters/ableton/builders.ts b/src/lib/exporters/ableton/builders.ts index 7a89d05..63e1a42 100644 --- a/src/lib/exporters/ableton/builders.ts +++ b/src/lib/exporters/ableton/builders.ts @@ -122,6 +122,25 @@ function findSimplers(node: any, result: any[] = []): any[] { return result; } +function findAutoFilters(node: any, result: any[] = []): any[] { + if (Array.isArray(node)) { + node.forEach((item) => { + findAutoFilters(item, result); + }); + return result; + } + if (!node || typeof node !== 'object') { + return result; + } + if (node.AutoFilter) { + result.push(node.AutoFilter); + } + Object.values(node).forEach((value) => { + findAutoFilters(value, result); + }); + return result; +} + function addTarget( targets: AbletonAutomationTargets, parameter: FaderParam, @@ -157,13 +176,17 @@ function trackAutomationTargets(midiTrack: any, includeArrangementMixer: boolean findSimplers(midiTrack.DeviceChain.DeviceChain.Devices).forEach((simpler) => { addTarget(targets, FaderParam.PTC, targetFor(simpler.Pitch.TransposeKey)); addTarget(targets, FaderParam.TUNE, targetFor(simpler.Pitch.TransposeFine)); - addTarget(targets, FaderParam.LPF, targetFor(simpler.Filter.Slot.Value.SimplerFilter.Freq)); - addTarget(targets, FaderParam.HPF, targetFor(simpler.Filter.Slot.Value.SimplerFilter.Freq)); addTarget(targets, FaderParam.ATK, targetFor(simpler.VolumeAndPan.Envelope.AttackTime)); addTarget(targets, FaderParam.REL, targetFor(simpler.VolumeAndPan.Envelope.ReleaseTime)); addTarget(targets, FaderParam.MOD, targetFor(simpler.Pitch.PitchLfoAmount)); }); + findAutoFilters(midiTrack.DeviceChain.DeviceChain.Devices).forEach((filter) => { + const parameter = + Number(filter.FilterType.Manual['@Value']) === 1 ? FaderParam.HPF : FaderParam.LPF; + addTarget(targets, parameter, targetFor(filter.Cutoff)); + }); + return targets; } @@ -395,24 +418,6 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers device.Lfo.Slot.Value.SimplerLfo.RateType.Manual['@Value'] = 1; // tempo synced 1/16 } - if (koTrack.faderParams[FaderParam.LPF] !== -1 && koTrack.faderParams[FaderParam.LPF] !== 1) { - device.Filter.IsOn.Manual['@Value'] = 'true'; - device.Filter.Slot.Value.SimplerFilter.Type.Manual['@Value'] = 0; // Low Pass - device.Filter.Slot.Value.SimplerFilter.Slope.Manual['@Value'] = 'true'; // 24db/oct - device.Filter.Slot.Value.SimplerFilter.Freq.Manual['@Value'] = filterFreqFromNormalized( - koTrack.faderParams[FaderParam.LPF] * 0.9, // lowering max freq to match KO behavior - ); - } - - if (koTrack.faderParams[FaderParam.HPF] !== -1 && koTrack.faderParams[FaderParam.HPF] !== 0) { - device.Filter.IsOn.Manual['@Value'] = 'true'; - device.Filter.Slot.Value.SimplerFilter.Type.Manual['@Value'] = 1; // High Pass - device.Filter.Slot.Value.SimplerFilter.Slope.Manual['@Value'] = 'false'; // 12db/oct - device.Filter.Slot.Value.SimplerFilter.Freq.Manual['@Value'] = filterFreqFromNormalized( - koTrack.faderParams[FaderParam.HPF] * 0.7, // lowering max freq to match KO behavior - ); - } - // TODO: add bandpass filter if (koTrack.timeStretch === 'bars') { @@ -471,6 +476,25 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers }; } +async function buildFaderFilterDevice( + parameter: FaderParam.LPF | FaderParam.HPF, + value: number, + abletonVersion: AbletonVersion, +) { + const filterTemplate = await loadTemplate('effectFilter', abletonVersion); + const filter = structuredClone(filterTemplate.AutoFilter); + const highPass = parameter === FaderParam.HPF; + filter.LastPresetRef.Value = {}; + filter.UserName['@Value'] = highPass ? 'EP HPF' : 'EP LPF'; + filter.FilterType.Manual['@Value'] = highPass ? 1 : 0; + filter.LegacyFilterType.Manual['@Value'] = highPass ? 1 : 0; + filter.Slope.Manual['@Value'] = highPass ? 'false' : 'true'; + filter.Cutoff.Manual['@Value'] = highPass + ? 20 + value * 0.7 * 115 + : 20 + value * 0.9 * 115; + return { AutoFilter: filter }; +} + async function buildDrumRackDevice(koTrack: AblTrack, abletonVersion: AbletonVersion) { const [drumRackTemplate, drumBranchTemplate] = await Promise.all([ loadTemplate('drumRack', abletonVersion),