From e2a142b5474ebeebdc8fb8f3cbe5620a7d1fbfa0 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 14 Jul 2026 11:26:26 +0200 Subject: [PATCH] Enhance Ableton exporter by incorporating automated fader parameter handling and default value logic --- src/lib/exporters/ableton/builders.ts | 51 +++++++++++++++++++-------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/lib/exporters/ableton/builders.ts b/src/lib/exporters/ableton/builders.ts index 752b001..5d140cc 100644 --- a/src/lib/exporters/ableton/builders.ts +++ b/src/lib/exporters/ableton/builders.ts @@ -30,7 +30,7 @@ import { filterEffectValues, reverbEffectValues, } from './effects'; -import { convertGroupFaderValue } from './faderPolicies'; +import { convertGroupFaderValue, getFaderDefaultValue } from './faderPolicies'; import { AbletonMidiControllerTargets, buildMidiClipEnvelopes } from './midi'; import { getPadEnvelopeState, getPadSoundState } from './padSound'; import { getSimplerPlaybackState } from './playback'; @@ -545,7 +545,11 @@ async function buildFaderFilterDevice( return { AutoFilter: filter }; } -async function buildDrumRackDevice(koTrack: AblTrack, abletonVersion: AbletonVersion) { +async function buildDrumRackDevice( + koTrack: AblTrack, + abletonVersion: AbletonVersion, + automatedParameters: ReadonlySet, +) { const [drumRackTemplate, drumBranchTemplate] = await Promise.all([ loadTemplate('drumRack', abletonVersion), loadTemplate('drumBranch', abletonVersion), @@ -579,7 +583,11 @@ async function buildDrumRackDevice(koTrack: AblTrack, abletonVersion: AbletonVer drumBranch.BranchInfo.ChokeGroup['@Value'] = subtrack.inChokeGroup ? 1 : 0; drumBranch.MixerDevice.Volume.Manual['@Value'] = subtrack.volume; drumBranch.MixerDevice.SendInfos = {}; - const branchDevices = await buildSimplerDevice(subtrack, abletonVersion); + const branchDevices = await buildSimplerDevice( + subtrack, + abletonVersion, + automatedParameters, + ); assignDeviceListIds([branchDevices]); drumBranch.DeviceChain.MidiToAudioDeviceChain.Devices = branchDevices; @@ -601,6 +609,11 @@ async function buildTrack( const abletonVersion = getAbletonVersion(exporterParams); const midiTrackTemplate = await loadTemplate('midiTrack', abletonVersion); const midiTrack = structuredClone(midiTrackTemplate.MidiTrack); + const automatedParameters = new Set( + [...(groupLane?.sessionPatterns || []), ...(groupLane?.arrangementPatterns || [])].flatMap( + (pattern) => pattern.faderAutomation.map((point) => point.parameter), + ), + ); midiTrack['@Id'] = _localId++; midiTrack.Name.EffectiveName['@Value'] = koTrack.soundId @@ -615,40 +628,44 @@ async function buildTrack( midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = koTrack.volume; if (koTrack.faderParams[FaderParam.PAN] !== -1) { - midiTrack.DeviceChain.Mixer.Pan.Manual['@Value'] = - (koTrack.faderParams[FaderParam.PAN] - 0.5) * 2; + midiTrack.DeviceChain.Mixer.Pan.Manual['@Value'] = convertGroupFaderValue( + FaderParam.PAN, + koTrack.faderParams[FaderParam.PAN], + { track: koTrack }, + ); } if (koTrack.faderParams[FaderParam.LVL] !== -1) { - midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = - koTrack.volume * koTrack.faderParams[FaderParam.LVL]; + midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = convertGroupFaderValue( + FaderParam.LVL, + koTrack.faderParams[FaderParam.LVL], + { track: koTrack }, + ); } if (koTrack.drumRack && exporterParams.includeArchivedSamples) { midiTrack.DeviceChain.DeviceChain.Devices['#'].push( - await buildDrumRackDevice(koTrack, abletonVersion), + await buildDrumRackDevice(koTrack, abletonVersion, automatedParameters), ); } if (!koTrack.drumRack && koTrack.sampleName && exporterParams.includeArchivedSamples) { midiTrack.DeviceChain.DeviceChain.Devices['#'].push( - await buildSimplerDevice(koTrack, abletonVersion), + await buildSimplerDevice(koTrack, abletonVersion, automatedParameters), ); } if (exporterParams.includeArchivedSamples) { for (const parameter of [FaderParam.LPF, FaderParam.HPF] as const) { const staticValue = koTrack.faderParams[parameter]; - const defaultValue = parameter === FaderParam.LPF ? 1 : 0; - const hasAutomation = [ - ...(groupLane?.sessionPatterns || []), - ...(groupLane?.arrangementPatterns || []), - ].some((pattern) => pattern.faderAutomation.some((point) => point.parameter === parameter)); + const defaultValue = getFaderDefaultValue(parameter); + const hasAutomation = automatedParameters.has(parameter); if (hasAutomation || (staticValue !== -1 && staticValue !== defaultValue)) { midiTrack.DeviceChain.DeviceChain.Devices['#'].push( await buildFaderFilterDevice( parameter, staticValue === -1 ? defaultValue : staticValue, + koTrack, abletonVersion, ), ); @@ -671,7 +688,11 @@ async function buildTrack( ); const trackSendHolder = structuredClone(trackSendHolderTemplate.TrackSendHolder); trackSendHolder['@Id'] = 0; - trackSendHolder.Send.Manual['@Value'] = Math.max(0, koTrack.faderParams[FaderParam.FX]); + trackSendHolder.Send.Manual['@Value'] = convertGroupFaderValue( + FaderParam.FX, + Math.max(getFaderDefaultValue(FaderParam.FX), koTrack.faderParams[FaderParam.FX]), + { track: koTrack }, + ); midiTrack.DeviceChain.Mixer.Sends.TrackSendHolder = trackSendHolder; }