Improve fader value conversion and support target max value in Ableton exports

This commit is contained in:
2026-07-13 08:47:34 +02:00
parent dc7a09188f
commit a5b61b9443
2 changed files with 69 additions and 25 deletions

View File

@@ -5,6 +5,7 @@ import { koEnvRangeToSeconds } from './utils';
export type AbletonAutomationTarget = { export type AbletonAutomationTarget = {
arrangement?: number; arrangement?: number;
session?: number; session?: number;
maxValue?: number;
}; };
export type AbletonAutomationTargets = Partial<Record<FaderParam, AbletonAutomationTarget[]>>; export type AbletonAutomationTargets = Partial<Record<FaderParam, AbletonAutomationTarget[]>>;
@@ -37,34 +38,52 @@ export function convertFaderValue(
value: number, value: number,
track: AblTrack, track: AblTrack,
groupMixer: boolean, groupMixer: boolean,
target?: AbletonAutomationTarget,
) { ) {
let convertedValue: number;
switch (parameter) { switch (parameter) {
case FaderParam.LVL: case FaderParam.LVL:
if (groupMixer) { if (groupMixer) {
return value; convertedValue = value;
} else {
convertedValue = track.volume * value;
} }
return track.volume * value; break;
case FaderParam.PTC: case FaderParam.PTC:
return (value - 0.5) * 10; convertedValue = (value - 0.5) * 10;
break;
case FaderParam.LPF: case FaderParam.LPF:
return 20 + value * 0.9 * 115; convertedValue = 20 + value * 0.9 * 115;
break;
case FaderParam.HPF: case FaderParam.HPF:
return 20 + value * 0.7 * 115; convertedValue = 20 + value * 0.7 * 115;
break;
case FaderParam.FX: case FaderParam.FX:
return value; convertedValue = value;
break;
case FaderParam.ATK: case FaderParam.ATK:
return koEnvRangeToSeconds(value * 255, track.soundLength) * 1000; convertedValue = koEnvRangeToSeconds(value * 255, track.soundLength) * 1000;
break;
case FaderParam.REL: case FaderParam.REL:
return koEnvRangeToSeconds(value * 255, track.soundLength) * 1000; convertedValue = koEnvRangeToSeconds(value * 255, track.soundLength) * 1000;
break;
case FaderParam.PAN: case FaderParam.PAN:
return (value - 0.5) * 2; convertedValue = (value - 0.5) * 2;
break;
case FaderParam.TUNE: case FaderParam.TUNE:
return Math.max(-0.5, Math.min(0.5, value - 0.5)) * 100; convertedValue = Math.max(-0.5, Math.min(0.5, value - 0.5)) * 100;
break;
case FaderParam.MOD: case FaderParam.MOD:
return value / 20; convertedValue = value / 20;
break;
default: default:
return value; convertedValue = value;
break;
} }
return target?.maxValue === undefined
? convertedValue
: Math.min(target.maxValue, convertedValue);
} }
export function getFaderValueAt( export function getFaderValueAt(
@@ -95,6 +114,7 @@ function getArrangementPoints(
parameter: FaderParam, parameter: FaderParam,
track: AblTrack, track: AblTrack,
groupMixer: boolean, groupMixer: boolean,
target: AbletonAutomationTarget,
) { ) {
const points: AutomationPoint[] = []; const points: AutomationPoint[] = [];
let hasAutomation = false; let hasAutomation = false;
@@ -103,6 +123,7 @@ function getArrangementPoints(
patterns[0] ? getStaticValue(patterns[0], parameter) : defaultValue(parameter), patterns[0] ? getStaticValue(patterns[0], parameter) : defaultValue(parameter),
track, track,
groupMixer, groupMixer,
target,
); );
patterns.forEach((pattern) => { patterns.forEach((pattern) => {
@@ -118,6 +139,7 @@ function getArrangementPoints(
getStaticValue(pattern, parameter), getStaticValue(pattern, parameter),
track, track,
groupMixer, groupMixer,
target,
); );
if (sourcePoints.length === 0) { if (sourcePoints.length === 0) {
@@ -139,7 +161,7 @@ function getArrangementPoints(
if (loopOffset + point.position >= sceneTicks) { if (loopOffset + point.position >= sceneTicks) {
return; return;
} }
const value = convertFaderValue(parameter, point.value, track, groupMixer); const value = convertFaderValue(parameter, point.value, track, groupMixer, target);
points.push({ time: startTime + (loopOffset + point.position) / 96, value }); points.push({ time: startTime + (loopOffset + point.position) / 96, value });
previousValue = value; previousValue = value;
}); });
@@ -158,6 +180,7 @@ function getClipPoints(
parameter: FaderParam, parameter: FaderParam,
track: AblTrack, track: AblTrack,
groupMixer: boolean, groupMixer: boolean,
target: AbletonAutomationTarget,
) { ) {
const sourcePoints = pattern.faderAutomation const sourcePoints = pattern.faderAutomation
.filter((point) => point.parameter === parameter) .filter((point) => point.parameter === parameter)
@@ -171,13 +194,14 @@ function getClipPoints(
getStaticValue(pattern, parameter), getStaticValue(pattern, parameter),
track, track,
groupMixer, groupMixer,
target,
); );
return [ return [
{ time: SENTINEL_TIME, value: staticValue }, { time: SENTINEL_TIME, value: staticValue },
{ time: 0, value: staticValue }, { time: 0, value: staticValue },
...sourcePoints.map((point) => ({ ...sourcePoints.map((point) => ({
time: point.position / 96, time: point.position / 96,
value: convertFaderValue(parameter, point.value, track, groupMixer), value: convertFaderValue(parameter, point.value, track, groupMixer, target),
})), })),
]; ];
} }
@@ -212,14 +236,14 @@ export function buildArrangementEnvelopes(
if (parameter === FaderParam.TIM || parameter === FaderParam.VEL) { if (parameter === FaderParam.TIM || parameter === FaderParam.VEL) {
return; return;
} }
const points = getArrangementPoints(patterns, parameter, track, groupMixer);
if (points.length === 0) {
return;
}
targetList?.forEach((target) => { targetList?.forEach((target) => {
if (target.arrangement === undefined) { if (target.arrangement === undefined) {
return; return;
} }
const points = getArrangementPoints(patterns, parameter, track, groupMixer, target);
if (points.length === 0) {
return;
}
envelopes.push({ envelopes.push({
'@Id': envelopes.length, '@Id': envelopes.length,
EnvelopeTarget: { PointeeId: { '@Value': target.arrangement } }, EnvelopeTarget: { PointeeId: { '@Value': target.arrangement } },
@@ -246,14 +270,14 @@ export function buildClipEnvelopes(
if (parameter === FaderParam.TIM || parameter === FaderParam.VEL) { if (parameter === FaderParam.TIM || parameter === FaderParam.VEL) {
return; return;
} }
const points = getClipPoints(pattern, parameter, track, groupMixer);
if (points.length === 0) {
return;
}
targetList?.forEach((target) => { targetList?.forEach((target) => {
if (target.session === undefined) { if (target.session === undefined) {
return; return;
} }
const points = getClipPoints(pattern, parameter, track, groupMixer, target);
if (points.length === 0) {
return;
}
envelopes.push({ envelopes.push({
'@Id': envelopes.length, '@Id': envelopes.length,
EnvelopeTarget: { PointeeId: { '@Value': target.session } }, EnvelopeTarget: { PointeeId: { '@Value': target.session } },

View File

@@ -104,7 +104,7 @@ function setLive10SampleReference(fileRef: any, sampleName: string) {
fileRef.LivePackId = { '@Value': '' }; fileRef.LivePackId = { '@Value': '' };
} }
function targetFor(parameter: any): { arrangement?: number; session?: number } { function targetFor(parameter: any, maxValue?: number) {
const automationTarget = const automationTarget =
parameter?.AutomationTarget?.['@Id'] === undefined parameter?.AutomationTarget?.['@Id'] === undefined
? undefined ? undefined
@@ -112,6 +112,7 @@ function targetFor(parameter: any): { arrangement?: number; session?: number } {
return { return {
arrangement: automationTarget, arrangement: automationTarget,
session: automationTarget, session: automationTarget,
maxValue,
}; };
} }
@@ -186,10 +187,29 @@ 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;
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(targets, FaderParam.ATK, targetFor(simpler.VolumeAndPan.Envelope.AttackTime)); addTarget(
addTarget(targets, FaderParam.REL, targetFor(simpler.VolumeAndPan.Envelope.ReleaseTime)); targets,
FaderParam.ATK,
targetFor(
oneshot
? simpler.VolumeAndPan.OneShotEnvelope.FadeInTime
: simpler.VolumeAndPan.Envelope.AttackTime,
oneshot ? 2000 : undefined,
),
);
addTarget(
targets,
FaderParam.REL,
targetFor(
oneshot
? simpler.VolumeAndPan.OneShotEnvelope.FadeOutTime
: simpler.VolumeAndPan.Envelope.ReleaseTime,
oneshot ? 2000 : undefined,
),
);
addTarget(targets, FaderParam.MOD, targetFor(simpler.Pitch.PitchLfoAmount)); addTarget(targets, FaderParam.MOD, targetFor(simpler.Pitch.PitchLfoAmount));
}); });