Enhance Ableton exporter to support static automation values and update simpler playback duration calculation

This commit is contained in:
2026-07-13 08:54:03 +02:00
parent 3fcec15150
commit 93afb8953c
2 changed files with 42 additions and 27 deletions

View File

@@ -8,6 +8,7 @@ export type AbletonAutomationTarget = {
maxValue?: number; maxValue?: number;
soundLength?: number; soundLength?: number;
invertValue?: boolean; invertValue?: boolean;
staticValue?: number;
}; };
export type AbletonAutomationTargets = Partial<Record<FaderParam, AbletonAutomationTarget[]>>; export type AbletonAutomationTargets = Partial<Record<FaderParam, AbletonAutomationTarget[]>>;
@@ -119,6 +120,19 @@ function getStaticValue(pattern: AblGroupPatternOccurrence, parameter: FaderPara
return value === -1 ? defaultValue(parameter) : value; 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( function getArrangementPoints(
patterns: AblGroupPatternOccurrence[], patterns: AblGroupPatternOccurrence[],
parameter: FaderParam, parameter: FaderParam,
@@ -128,13 +142,9 @@ function getArrangementPoints(
) { ) {
const points: AutomationPoint[] = []; const points: AutomationPoint[] = [];
let hasAutomation = false; let hasAutomation = false;
let previousValue = convertFaderValue( let previousValue = patterns[0]
parameter, ? getConvertedStaticValue(patterns[0], parameter, track, groupMixer, target)
patterns[0] ? getStaticValue(patterns[0], parameter) : defaultValue(parameter), : convertFaderValue(parameter, defaultValue(parameter), track, groupMixer, target);
track,
groupMixer,
target,
);
patterns.forEach((pattern) => { patterns.forEach((pattern) => {
const beats = pattern.timeSignature.numerator * (4 / pattern.timeSignature.denominator); const beats = pattern.timeSignature.numerator * (4 / pattern.timeSignature.denominator);
@@ -144,13 +154,7 @@ function getArrangementPoints(
const sourcePoints = pattern.faderAutomation const sourcePoints = pattern.faderAutomation
.filter((point) => point.parameter === parameter) .filter((point) => point.parameter === parameter)
.sort((a, b) => a.position - b.position); .sort((a, b) => a.position - b.position);
const staticValue = convertFaderValue( const staticValue = getConvertedStaticValue(pattern, parameter, track, groupMixer, target);
parameter,
getStaticValue(pattern, parameter),
track,
groupMixer,
target,
);
if (sourcePoints.length === 0) { if (sourcePoints.length === 0) {
if (hasAutomation) { if (hasAutomation) {
@@ -199,13 +203,7 @@ function getClipPoints(
return []; return [];
} }
const staticValue = convertFaderValue( const staticValue = getConvertedStaticValue(pattern, parameter, track, groupMixer, target);
parameter,
getStaticValue(pattern, parameter),
track,
groupMixer,
target,
);
return [ return [
{ time: SENTINEL_TIME, value: staticValue }, { time: SENTINEL_TIME, value: staticValue },
{ time: 0, value: staticValue }, { time: 0, value: staticValue },

View File

@@ -78,6 +78,13 @@ function simplerEnvelopeMilliseconds(
return oneshot ? Math.min(2000, milliseconds) : milliseconds; 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) { function setColorValue(node: any, value: number) {
const color = node.Color || node.ColorIndex; const color = node.Color || node.ColorIndex;
color['@Value'] = value; color['@Value'] = value;
@@ -121,6 +128,10 @@ function targetFor(parameter: any, maxValue?: number, soundLength?: number, inve
maxValue, maxValue,
soundLength, soundLength,
invertValue, 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) => { findSimplers(midiTrack.DeviceChain.DeviceChain.Devices).forEach((simpler) => {
const oneshot = Number(simpler.Globals.PlaybackMode['@Value']) === 1; 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 sampleRate = Number(sampleRef.DefaultSampleRate?.['@Value']);
const sampleDuration = Number(sampleRef.DefaultDuration?.['@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.PTC, targetFor(simpler.Pitch.TransposeKey));
addTarget(targets, FaderParam.TUNE, targetFor(simpler.Pitch.TransposeFine)); addTarget(targets, FaderParam.TUNE, targetFor(simpler.Pitch.TransposeFine));
addTarget( addTarget(
@@ -376,6 +392,7 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers
const samplePart = device.Player.MultiSampleMap.SampleParts.MultiSamplePart; const samplePart = device.Player.MultiSampleMap.SampleParts.MultiSamplePart;
const loopModulators = device.Player.LoopModulators; const loopModulators = device.Player.LoopModulators;
const simplerFilter = device.Filter.Slot.Value.SimplerFilter; const simplerFilter = device.Filter.Slot.Value.SimplerFilter;
const playbackDuration = simplerPlaybackDuration(koTrack);
device.LastPresetRef.Value = {}; device.LastPresetRef.Value = {};
device.ShouldShowPresetName['@Value'] = 'false'; device.ShouldShowPresetName['@Value'] = 'false';
@@ -454,12 +471,12 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers
: device.VolumeAndPan.Envelope.ReleaseTime; : device.VolumeAndPan.Envelope.ReleaseTime;
attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds(
koTrack.attack, koTrack.attack,
koTrack.soundLength, playbackDuration,
oneshot, oneshot,
); );
releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds(
koTrack.release, koTrack.release,
koTrack.soundLength, playbackDuration,
oneshot, oneshot,
oneshot, oneshot,
); );
@@ -469,7 +486,7 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers
if (koTrack.faderParams[FaderParam.ATK] !== -1) { if (koTrack.faderParams[FaderParam.ATK] !== -1) {
attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( attackTarget.Manual['@Value'] = simplerEnvelopeMilliseconds(
koTrack.faderParams[FaderParam.ATK] * 255, koTrack.faderParams[FaderParam.ATK] * 255,
koTrack.soundLength, playbackDuration,
oneshot, oneshot,
); );
} }
@@ -478,7 +495,7 @@ async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVers
if (koTrack.faderParams[FaderParam.REL] !== -1) { if (koTrack.faderParams[FaderParam.REL] !== -1) {
releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds( releaseTarget.Manual['@Value'] = simplerEnvelopeMilliseconds(
koTrack.faderParams[FaderParam.REL] * 255, koTrack.faderParams[FaderParam.REL] * 255,
koTrack.soundLength, playbackDuration,
oneshot, oneshot,
oneshot, oneshot,
); );