Update Ableton exporter to use scenes array instead of maxScenes count

This commit is contained in:
2026-07-14 09:40:28 +02:00
parent b82b23a8ee
commit 18d7095dd2

View File

@@ -671,7 +671,7 @@ async function buildDrumRackDevice(koTrack: AblTrack, abletonVersion: AbletonVer
async function buildTrack(
koTrack: AblTrack,
groupLane: AblGroupLane | undefined,
maxScenes: number,
scenes: AblScene[],
trackGroupId: number,
exporterParams: ExporterParams,
): Promise<ALSMidiTrack> {
@@ -691,14 +691,12 @@ async function buildTrack(
midiTrack.DeviceChain.Mixer.Sends = {} as any;
midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = koTrack.volume;
const useChildGroupMixer = !exporterParams.groupTracks || exporterParams.clips;
if (useChildGroupMixer && koTrack.faderParams[FaderParam.PAN] !== -1) {
if (koTrack.faderParams[FaderParam.PAN] !== -1) {
midiTrack.DeviceChain.Mixer.Pan.Manual['@Value'] =
(koTrack.faderParams[FaderParam.PAN] - 0.5) * 2;
}
if (useChildGroupMixer && koTrack.faderParams[FaderParam.LVL] !== -1) {
if (koTrack.faderParams[FaderParam.LVL] !== -1) {
midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] =
koTrack.volume * koTrack.faderParams[FaderParam.LVL];
}
@@ -719,7 +717,10 @@ async function buildTrack(
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?.patterns.some((pattern) =>
const hasAutomation = [
...(groupLane?.sessionPatterns || []),
...(groupLane?.arrangementPatterns || []),
].some((pattern) =>
pattern.faderAutomation.some((point) => point.parameter === parameter),
);
if (hasAutomation || (staticValue !== -1 && staticValue !== defaultValue)) {
@@ -742,7 +743,7 @@ async function buildTrack(
midiTrack.DeviceChain.AudioOutputRouting.UpperDisplayString['@Value'] = 'Group';
}
if (exporterParams.sendEffects && (!exporterParams.groupTracks || exporterParams.clips)) {
if (exporterParams.sendEffects) {
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>(
'trackSendHolder',
abletonVersion,
@@ -754,11 +755,7 @@ async function buildTrack(
}
fixIds(midiTrack);
const automationTargets = trackAutomationTargets(
midiTrack,
!exporterParams.groupTracks && !exporterParams.clips,
koTrack,
);
const automationTargets = trackAutomationTargets(midiTrack, true, koTrack);
// make sure each tracks has the same empty slots for clips
// must be equeal to GroupTrackSlot if this track is in a group
@@ -766,7 +763,9 @@ async function buildTrack(
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot = [];
midiTrack.DeviceChain.FreezeSequencer.ClipSlotList.ClipSlot = [];
for (let sc = 0; sc < Math.max(MIN_CLIP_LAUNCHER_SCENES, maxScenes); sc++) {
for (let sc = 0; sc < Math.max(MIN_CLIP_LAUNCHER_SCENES, scenes.length); sc++) {
const scene = scenes[sc];
const hasStop = !scene || scene.affectedGroups.includes(koTrack.group);
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[sc] = {
'@Id': sc,
LomId: {
@@ -775,6 +774,9 @@ async function buildTrack(
ClipSlot: {
Value: {},
},
HasStop: {
'@Value': hasStop ? 'true' : 'false',
},
};
midiTrack.DeviceChain.FreezeSequencer.ClipSlotList.ClipSlot[sc] = {
'@Id': sc,
@@ -793,9 +795,7 @@ async function buildTrack(
};
}
if (koTrack.lane) {
if (!exporterParams.clips) {
for (const [clipIdx, koClip] of koTrack.lane.clips.entries()) {
for (const [clipIdx, koClip] of (koTrack.lane?.arrangementClips || []).entries()) {
const midiClip = await buildMidiClip(
koClip,
clipIdx,
@@ -806,28 +806,22 @@ async function buildTrack(
midiTrack.DeviceChain.MainSequencer.MidiControllers,
koTrack,
);
midiTrack.DeviceChain.MainSequencer.ClipTimeable.ArrangerAutomation.Events.MidiClip.push(
midiClip,
);
}
} else {
const sessionClips = [...koTrack.lane.clips];
groupLane?.patterns.forEach((pattern) => {
const sessionClips = [...(koTrack.lane?.sessionClips || [])];
groupLane?.sessionPatterns.forEach((pattern) => {
const hasClip = sessionClips.some((clip) => clip.sceneIndex === pattern.sceneIndex);
const hasSupportedAutomation = pattern.faderAutomation.some(
(point) =>
point.parameter !== FaderParam.TIM &&
point.parameter !== FaderParam.VEL &&
automationTargets[point.parameter]?.some((target) => target.session !== undefined),
);
if (!hasClip && hasSupportedAutomation) {
if (!hasClip && pattern.sceneIndex !== undefined) {
sessionClips.push({
notes: [],
midiEvents: [],
bars: pattern.bars,
offset: pattern.offset,
sceneBars: pattern.sceneBars,
offset: 0,
duration: pattern.duration,
loopDuration: pattern.loopDuration,
sceneIndex: pattern.sceneIndex,
sceneName: pattern.sceneName,
timeSignature: pattern.timeSignature,
@@ -838,6 +832,9 @@ async function buildTrack(
});
for (const koClip of sessionClips) {
if (koClip.sceneIndex === undefined) {
continue;
}
const midiClip = await buildMidiClip(
koClip,
0,
@@ -848,7 +845,6 @@ async function buildTrack(
midiTrack.DeviceChain.MainSequencer.MidiControllers,
koTrack,
);
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex] = {
...midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex],
ClipSlot: {
@@ -858,12 +854,10 @@ async function buildTrack(
},
};
}
}
}
if (!exporterParams.clips && groupLane) {
if (groupLane) {
const envelopes = buildArrangementEnvelopes(
groupLane.patterns,
groupLane.arrangementPatterns,
automationTargets,
koTrack,
false,
@@ -878,7 +872,6 @@ async function buildTrack(
async function buildGroupTrack(
koTrack: AblTrack,
groupLane: AblGroupLane | undefined,
exporterParams: ExporterParams,
maxScenes: number,
) {
@@ -892,19 +885,8 @@ async function buildGroupTrack(
setColorValue(groupTrack, 5 + _localGroupTrackColor++);
groupTrack.Slots.GroupTrackSlot = [];
groupTrack.DeviceChain.Mixer.Sends = {} as any;
if (!exporterParams.clips) {
if (koTrack.faderParams[FaderParam.LVL] !== -1) {
groupTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = koTrack.faderParams[FaderParam.LVL];
}
if (koTrack.faderParams[FaderParam.PAN] !== -1) {
groupTrack.DeviceChain.Mixer.Pan.Manual['@Value'] =
(koTrack.faderParams[FaderParam.PAN] - 0.5) * 2;
}
} else {
groupTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = 1;
groupTrack.DeviceChain.Mixer.Pan.Manual['@Value'] = 0;
}
// adding empty slots for clips (or Ableton will crash)
// must be at least 8 slots to avoid Ableton crashes
@@ -917,36 +899,7 @@ async function buildGroupTrack(
});
}
if (
exporterParams.sendEffects &&
!exporterParams.clips &&
koTrack.faderParams[FaderParam.FX] !== -1
) {
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>(
'trackSendHolder',
abletonVersion,
);
const trackSendHolder = structuredClone(trackSendHolderTemplate.TrackSendHolder);
trackSendHolder['@Id'] = 0;
trackSendHolder.Send.Manual['@Value'] = koTrack.faderParams[FaderParam.FX];
groupTrack.DeviceChain.Mixer.Sends.TrackSendHolder = trackSendHolder;
}
fixIds(groupTrack);
if (!exporterParams.clips && groupLane) {
const envelopes = buildArrangementEnvelopes(
groupLane.patterns,
groupAutomationTargets(groupTrack),
koTrack,
true,
);
groupTrack.AutomationEnvelopes.Envelopes = envelopes.length
? ({ AutomationEnvelope: envelopes } as any)
: {};
}
return { GroupTrack: groupTrack };
}
@@ -966,10 +919,22 @@ async function buildScenes(
sceneContent['@Id'] = _localId++;
if (abletonVersion === '10') {
(sceneContent as any)['@Value'] = scene?.name || ` ${index + 1}`;
const meter = scene?.timeSignature;
(sceneContent as any)['@Value'] = scene
? `${scene.name}${meter ? ` ${meter.numerator}/${meter.denominator}` : ''}`
: ` ${index + 1}`;
} else {
sceneContent.Name['@Value'] = scene?.name || '';
sceneContent.Tempo['@Value'] = settings.bpm;
if (scene?.timeSignature) {
sceneContent.TimeSignatureId['@Value'] =
TIME_SIGNATURES[
`${scene.timeSignature.numerator}/${scene.timeSignature.denominator}`
] || TIME_SIGNATURES['4/4'];
sceneContent.IsTimeSignatureEnabled['@Value'] = 'true';
} else {
sceneContent.IsTimeSignatureEnabled['@Value'] = 'false';
}
}
result.push(sceneContent);
@@ -981,7 +946,7 @@ async function buildScenes(
async function buildTracks(
tracks: AblTrack[],
groupLanes: AblGroupLane[],
maxScenes: number,
scenes: AblScene[],
exporterParams: ExporterParams,
) {
const result = [];
@@ -994,12 +959,12 @@ async function buildTracks(
const groupLane = groupLanes.find((lane) => lane.group === koTrack.group);
if (exporterParams.groupTracks && koTrack.group !== currentGroup[0]) {
currentGroup = koTrack.group;
const groupTrack = await buildGroupTrack(koTrack, groupLane, exporterParams, maxScenes);
const groupTrack = await buildGroupTrack(koTrack, exporterParams, scenes.length);
trackGroupId = groupTrack.GroupTrack['@Id'];
result.push(groupTrack);
}
const midiTrack = await buildTrack(koTrack, groupLane, maxScenes, trackGroupId, exporterParams);
const midiTrack = await buildTrack(koTrack, groupLane, scenes, trackGroupId, exporterParams);
result.push(midiTrack);
}