diff --git a/src/lib/exporters/ableton/automation.ts b/src/lib/exporters/ableton/automation.ts index 1949a06..3a4067b 100644 --- a/src/lib/exporters/ableton/automation.ts +++ b/src/lib/exporters/ableton/automation.ts @@ -8,6 +8,7 @@ export type AbletonAutomationTarget = { maxValue?: number; soundLength?: number; invertValue?: boolean; + staticValue?: number; }; export type AbletonAutomationTargets = Partial>; @@ -119,6 +120,19 @@ function getStaticValue(pattern: AblGroupPatternOccurrence, parameter: FaderPara return value === -1 ? defaultValue(parameter) : value; } +function getConvertedStaticValue( + pattern: AblGroupPatternOccurrence, + parameter: FaderParam, + track: AblTrack, + groupMixer: boolean, + target: AbletonAutomationTarget, +) { + if (pattern.faderParams[parameter] === -1 && target.staticValue !== undefined) { + return target.staticValue; + } + return convertFaderValue(parameter, getStaticValue(pattern, parameter), track, groupMixer, target); +} + function getArrangementPoints( patterns: AblGroupPatternOccurrence[], parameter: FaderParam, @@ -128,13 +142,9 @@ function getArrangementPoints( ) { const points: AutomationPoint[] = []; let hasAutomation = false; - let previousValue = convertFaderValue( - parameter, - patterns[0] ? getStaticValue(patterns[0], parameter) : defaultValue(parameter), - track, - groupMixer, - target, - ); + let previousValue = patterns[0] + ? getConvertedStaticValue(patterns[0], parameter, track, groupMixer, target) + : convertFaderValue(parameter, defaultValue(parameter), track, groupMixer, target); patterns.forEach((pattern) => { const beats = pattern.timeSignature.numerator * (4 / pattern.timeSignature.denominator); @@ -144,13 +154,7 @@ function getArrangementPoints( const sourcePoints = pattern.faderAutomation .filter((point) => point.parameter === parameter) .sort((a, b) => a.position - b.position); - const staticValue = convertFaderValue( - parameter, - getStaticValue(pattern, parameter), - track, - groupMixer, - target, - ); + const staticValue = getConvertedStaticValue(pattern, parameter, track, groupMixer, target); if (sourcePoints.length === 0) { if (hasAutomation) { @@ -199,13 +203,7 @@ function getClipPoints( return []; } - const staticValue = convertFaderValue( - parameter, - getStaticValue(pattern, parameter), - track, - groupMixer, - target, - ); + const staticValue = getConvertedStaticValue(pattern, parameter, track, groupMixer, target); return [ { time: SENTINEL_TIME, value: staticValue }, { time: 0, value: staticValue }, diff --git a/src/lib/exporters/ableton/builders.ts b/src/lib/exporters/ableton/builders.ts index 0687b3c..09c3c0a 100644 --- a/src/lib/exporters/ableton/builders.ts +++ b/src/lib/exporters/ableton/builders.ts @@ -78,6 +78,13 @@ function simplerEnvelopeMilliseconds( return oneshot ? Math.min(2000, milliseconds) : milliseconds; } +function simplerPlaybackDuration(koTrack: AblTrack) { + const trimmedFrames = koTrack.trimRight - koTrack.trimLeft; + return trimmedFrames > 0 && koTrack.sampleRate > 0 + ? trimmedFrames / koTrack.sampleRate + : koTrack.soundLength; +} + function setColorValue(node: any, value: number) { const color = node.Color || node.ColorIndex; color['@Value'] = value; @@ -121,6 +128,10 @@ function targetFor(parameter: any, maxValue?: number, soundLength?: number, inve maxValue, soundLength, invertValue, + staticValue: + parameter?.Manual?.['@Value'] === undefined + ? undefined + : Number(parameter.Manual['@Value']), }; } @@ -196,10 +207,15 @@ function trackAutomationTargets(midiTrack: any, includeArrangementMixer: boolean findSimplers(midiTrack.DeviceChain.DeviceChain.Devices).forEach((simpler) => { const oneshot = Number(simpler.Globals.PlaybackMode['@Value']) === 1; - const sampleRef = simpler.Player.MultiSampleMap.SampleParts.MultiSamplePart.SampleRef; + const samplePart = simpler.Player.MultiSampleMap.SampleParts.MultiSamplePart; + const sampleRef = samplePart.SampleRef; const sampleRate = Number(sampleRef.DefaultSampleRate?.['@Value']); const sampleDuration = Number(sampleRef.DefaultDuration?.['@Value']); - const soundLength = sampleRate > 0 ? sampleDuration / sampleRate : undefined; + const trimmedFrames = Number(samplePart.SampleEnd['@Value']) - Number(samplePart.SampleStart['@Value']); + const soundLength = + sampleRate > 0 + ? (trimmedFrames > 0 ? trimmedFrames : sampleDuration) / sampleRate + : undefined; addTarget(targets, FaderParam.PTC, targetFor(simpler.Pitch.TransposeKey)); addTarget(targets, FaderParam.TUNE, targetFor(simpler.Pitch.TransposeFine)); addTarget( @@ -376,6 +392,7 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers const samplePart = device.Player.MultiSampleMap.SampleParts.MultiSamplePart; const loopModulators = device.Player.LoopModulators; const simplerFilter = device.Filter.Slot.Value.SimplerFilter; + const playbackDuration = simplerPlaybackDuration(koTrack); device.LastPresetRef.Value = {}; device.ShouldShowPresetName['@Value'] = 'false'; @@ -454,12 +471,12 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers : device.VolumeAndPan.Envelope.ReleaseTime; attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( koTrack.attack, - koTrack.soundLength, + playbackDuration, oneshot, ); releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( koTrack.release, - koTrack.soundLength, + playbackDuration, oneshot, oneshot, ); @@ -469,7 +486,7 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers if (koTrack.faderParams[FaderParam.ATK] !== -1) { attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( koTrack.faderParams[FaderParam.ATK] * 255, - koTrack.soundLength, + playbackDuration, oneshot, ); } @@ -478,7 +495,7 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers if (koTrack.faderParams[FaderParam.REL] !== -1) { releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( koTrack.faderParams[FaderParam.REL] * 255, - koTrack.soundLength, + playbackDuration, oneshot, oneshot, );