Improve group track and clip handling for Ableton export

This commit is contained in:
2026-07-13 05:00:43 +02:00
parent d8212f291f
commit a370f00150

View File

@@ -515,6 +515,7 @@ async function buildDrumRackDevice(koTrack: AblTrack, abletonVersion: AbletonVer
async function buildTrack(
koTrack: AblTrack,
groupLane: AblGroupLane | undefined,
maxScenes: number,
trackGroupId: number,
exporterParams: ExporterParams,
@@ -532,19 +533,17 @@ async function buildTrack(
midiTrack.DeviceChain.MainSequencer.ClipTimeable.ArrangerAutomation.Events.MidiClip = [];
midiTrack.TrackGroupId['@Value'] = trackGroupId;
midiTrack.DeviceChain.DeviceChain.Devices['#'] = [];
midiTrack.DeviceChain.Mixer.Sends = {};
midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = koTrack.volume;
if (koTrack.faderParams[FaderParam.PAN] !== -1) {
if (!exporterParams.groupTracks && koTrack.faderParams[FaderParam.PAN] !== -1) {
midiTrack.DeviceChain.Mixer.Pan.Manual['@Value'] =
(koTrack.faderParams[FaderParam.PAN] - 0.5) * 2;
}
if (koTrack.faderParams[FaderParam.LVL] !== -1) {
// setting volume to the minimum of track volume and fader param
midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = Math.min(
koTrack.volume,
koTrack.faderParams[FaderParam.LVL],
);
if (!exporterParams.groupTracks && koTrack.faderParams[FaderParam.LVL] !== -1) {
midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] =
koTrack.volume * koTrack.faderParams[FaderParam.LVL];
}
if (koTrack.drumRack && exporterParams.includeArchivedSamples) {
@@ -566,6 +565,28 @@ async function buildTrack(
});
});
if (exporterParams.groupTracks) {
midiTrack.DeviceChain.AudioOutputRouting.Target['@Value'] = 'AudioOut/GroupTrack';
midiTrack.DeviceChain.AudioOutputRouting.UpperDisplayString['@Value'] = 'Group';
}
if (exporterParams.sendEffects && (!exporterParams.groupTracks || exporterParams.clips)) {
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>(
'trackSendHolder',
abletonVersion,
);
const trackSendHolder = structuredClone(trackSendHolderTemplate.TrackSendHolder);
trackSendHolder['@Id'] = 0;
trackSendHolder.Send.Manual['@Value'] = koTrack.faderParams[FaderParam.FX];
midiTrack.DeviceChain.Mixer.Sends.TrackSendHolder = trackSendHolder;
}
fixIds(midiTrack);
const automationTargets = trackAutomationTargets(
midiTrack,
!exporterParams.groupTracks && !exporterParams.clips,
);
// make sure each tracks has the same empty slots for clips
// must be equeal to GroupTrackSlot if this track is in a group
// must be at least 8 slots to avoid Ableton crashes
@@ -608,6 +629,8 @@ async function buildTrack(
getColorValue(midiTrack),
false,
abletonVersion,
automationTargets,
koTrack,
);
midiTrack.DeviceChain.MainSequencer.ClipTimeable.ArrangerAutomation.Events.MidiClip.push(
@@ -615,13 +638,39 @@ async function buildTrack(
);
}
} else {
for (const koClip of koTrack.lane.clips.values()) {
const sessionClips = [...koTrack.lane.clips];
groupLane?.patterns.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) {
sessionClips.push({
notes: [],
bars: pattern.bars,
offset: pattern.offset,
sceneBars: pattern.sceneBars,
sceneIndex: pattern.sceneIndex,
sceneName: pattern.sceneName,
timeSignature: pattern.timeSignature,
faderParams: pattern.faderParams,
groupPattern: pattern,
});
}
});
for (const koClip of sessionClips) {
const midiClip = await buildMidiClip(
koClip,
0,
getColorValue(midiTrack),
true,
abletonVersion,
automationTargets,
koTrack,
);
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex] = {
@@ -636,21 +685,16 @@ async function buildTrack(
}
}
if (exporterParams.groupTracks) {
midiTrack.DeviceChain.AudioOutputRouting.Target['@Value'] = 'AudioOut/GroupTrack';
midiTrack.DeviceChain.AudioOutputRouting.UpperDisplayString['@Value'] = 'Group';
}
if (exporterParams.sendEffects && !exporterParams.groupTracks) {
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>(
'trackSendHolder',
abletonVersion,
if (!exporterParams.clips && groupLane) {
const envelopes = buildArrangementEnvelopes(
groupLane.patterns,
automationTargets,
koTrack,
false,
);
const trackSendHolder = structuredClone(trackSendHolderTemplate.TrackSendHolder);
trackSendHolder.Send.Manual['@Value'] = koTrack.faderParams[FaderParam.FX];
midiTrack.DeviceChain.Mixer.Sends.TrackSendHolder = trackSendHolder;
midiTrack.AutomationEnvelopes.Envelopes = envelopes.length
? ({ AutomationEnvelope: envelopes } as any)
: {};
}
return { MidiTrack: midiTrack };