diff --git a/src/lib/exporters/ableton/automation.ts b/src/lib/exporters/ableton/automation.ts index 9b63830..6ba00e7 100644 --- a/src/lib/exporters/ableton/automation.ts +++ b/src/lib/exporters/ableton/automation.ts @@ -147,39 +147,49 @@ function getArrangementPoints( groupMixer: boolean, target: AbletonAutomationTarget, ) { + const orderedPatterns = patterns.toSorted((a, b) => { + const offsetDifference = a.offset - b.offset; + return offsetDifference || (a.occurrenceIndex ?? 0) - (b.occurrenceIndex ?? 0); + }); + const hasAutomation = orderedPatterns.some((pattern) => + pattern.faderAutomation.some((point) => point.parameter === parameter), + ); + if (!hasAutomation) { + return []; + } + const points: AutomationPoint[] = []; - let hasAutomation = false; - let previousValue = patterns[0] - ? getConvertedStaticValue(patterns[0], parameter, track, groupMixer, target) + let previousValue = orderedPatterns[0] + ? getConvertedStaticValue(orderedPatterns[0], parameter, track, groupMixer, target) : convertFaderValue(parameter, defaultValue(parameter), track, groupMixer, target); - patterns.forEach((pattern) => { - const beats = pattern.timeSignature.numerator * (4 / pattern.timeSignature.denominator); - const patternTicks = pattern.bars * beats * 96; - const sceneTicks = pattern.sceneBars * beats * 96; - const startTime = pattern.offset * beats; + orderedPatterns.forEach((pattern) => { + const patternTicks = Math.max(1, pattern.loopDuration * 96); + const occurrenceTicks = Math.max(1, pattern.duration * 96); + const startTime = pattern.offset; const sourcePoints = pattern.faderAutomation .filter((point) => point.parameter === parameter) .sort((a, b) => a.position - b.position); const staticValue = getConvertedStaticValue(pattern, parameter, track, groupMixer, target); + points.push({ time: Math.max(0, startTime - BOUNDARY_EPSILON), value: previousValue }); + points.push({ time: startTime, value: staticValue }); + previousValue = staticValue; + if (sourcePoints.length === 0) { - if (hasAutomation) { - points.push({ time: Math.max(0, startTime - BOUNDARY_EPSILON), value: previousValue }); - points.push({ time: startTime, value: staticValue }); - } - previousValue = staticValue; return; } - hasAutomation = true; - for (let loopOffset = 0; loopOffset < sceneTicks; loopOffset += patternTicks) { + for (let loopOffset = 0; loopOffset < occurrenceTicks; loopOffset += patternTicks) { const loopStart = startTime + loopOffset / 96; - points.push({ time: Math.max(0, loopStart - BOUNDARY_EPSILON), value: previousValue }); - points.push({ time: loopStart, value: staticValue }); + if (loopOffset > 0) { + points.push({ time: Math.max(0, loopStart - BOUNDARY_EPSILON), value: previousValue }); + points.push({ time: loopStart, value: staticValue }); + previousValue = staticValue; + } sourcePoints.forEach((point) => { - if (loopOffset + point.position >= sceneTicks) { + if (loopOffset + point.position >= occurrenceTicks) { return; } const value = convertFaderValue(parameter, point.value, track, groupMixer, target); @@ -189,10 +199,6 @@ function getArrangementPoints( } }); - if (!hasAutomation) { - return []; - } - return [{ time: SENTINEL_TIME, value: points[0]?.value ?? previousValue }, ...points]; } @@ -206,9 +212,6 @@ function getClipPoints( const sourcePoints = pattern.faderAutomation .filter((point) => point.parameter === parameter) .sort((a, b) => a.position - b.position); - if (sourcePoints.length === 0) { - return []; - } const staticValue = getConvertedStaticValue(pattern, parameter, track, groupMixer, target); return [