Update Ableton exporter to use scenes array instead of maxScenes count
This commit is contained in:
@@ -671,7 +671,7 @@ async function buildDrumRackDevice(koTrack: AblTrack, abletonVersion: AbletonVer
|
|||||||
async function buildTrack(
|
async function buildTrack(
|
||||||
koTrack: AblTrack,
|
koTrack: AblTrack,
|
||||||
groupLane: AblGroupLane | undefined,
|
groupLane: AblGroupLane | undefined,
|
||||||
maxScenes: number,
|
scenes: AblScene[],
|
||||||
trackGroupId: number,
|
trackGroupId: number,
|
||||||
exporterParams: ExporterParams,
|
exporterParams: ExporterParams,
|
||||||
): Promise<ALSMidiTrack> {
|
): Promise<ALSMidiTrack> {
|
||||||
@@ -691,14 +691,12 @@ async function buildTrack(
|
|||||||
midiTrack.DeviceChain.Mixer.Sends = {} as any;
|
midiTrack.DeviceChain.Mixer.Sends = {} as any;
|
||||||
midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = koTrack.volume;
|
midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = koTrack.volume;
|
||||||
|
|
||||||
const useChildGroupMixer = !exporterParams.groupTracks || exporterParams.clips;
|
if (koTrack.faderParams[FaderParam.PAN] !== -1) {
|
||||||
|
|
||||||
if (useChildGroupMixer && koTrack.faderParams[FaderParam.PAN] !== -1) {
|
|
||||||
midiTrack.DeviceChain.Mixer.Pan.Manual['@Value'] =
|
midiTrack.DeviceChain.Mixer.Pan.Manual['@Value'] =
|
||||||
(koTrack.faderParams[FaderParam.PAN] - 0.5) * 2;
|
(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'] =
|
midiTrack.DeviceChain.Mixer.Volume.Manual['@Value'] =
|
||||||
koTrack.volume * koTrack.faderParams[FaderParam.LVL];
|
koTrack.volume * koTrack.faderParams[FaderParam.LVL];
|
||||||
}
|
}
|
||||||
@@ -719,7 +717,10 @@ async function buildTrack(
|
|||||||
for (const parameter of [FaderParam.LPF, FaderParam.HPF] as const) {
|
for (const parameter of [FaderParam.LPF, FaderParam.HPF] as const) {
|
||||||
const staticValue = koTrack.faderParams[parameter];
|
const staticValue = koTrack.faderParams[parameter];
|
||||||
const defaultValue = parameter === FaderParam.LPF ? 1 : 0;
|
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),
|
pattern.faderAutomation.some((point) => point.parameter === parameter),
|
||||||
);
|
);
|
||||||
if (hasAutomation || (staticValue !== -1 && staticValue !== defaultValue)) {
|
if (hasAutomation || (staticValue !== -1 && staticValue !== defaultValue)) {
|
||||||
@@ -742,7 +743,7 @@ async function buildTrack(
|
|||||||
midiTrack.DeviceChain.AudioOutputRouting.UpperDisplayString['@Value'] = 'Group';
|
midiTrack.DeviceChain.AudioOutputRouting.UpperDisplayString['@Value'] = 'Group';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exporterParams.sendEffects && (!exporterParams.groupTracks || exporterParams.clips)) {
|
if (exporterParams.sendEffects) {
|
||||||
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>(
|
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>(
|
||||||
'trackSendHolder',
|
'trackSendHolder',
|
||||||
abletonVersion,
|
abletonVersion,
|
||||||
@@ -754,11 +755,7 @@ async function buildTrack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fixIds(midiTrack);
|
fixIds(midiTrack);
|
||||||
const automationTargets = trackAutomationTargets(
|
const automationTargets = trackAutomationTargets(midiTrack, true, koTrack);
|
||||||
midiTrack,
|
|
||||||
!exporterParams.groupTracks && !exporterParams.clips,
|
|
||||||
koTrack,
|
|
||||||
);
|
|
||||||
|
|
||||||
// make sure each tracks has the same empty slots for 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 equeal to GroupTrackSlot if this track is in a group
|
||||||
@@ -766,7 +763,9 @@ async function buildTrack(
|
|||||||
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot = [];
|
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot = [];
|
||||||
midiTrack.DeviceChain.FreezeSequencer.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] = {
|
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[sc] = {
|
||||||
'@Id': sc,
|
'@Id': sc,
|
||||||
LomId: {
|
LomId: {
|
||||||
@@ -775,6 +774,9 @@ async function buildTrack(
|
|||||||
ClipSlot: {
|
ClipSlot: {
|
||||||
Value: {},
|
Value: {},
|
||||||
},
|
},
|
||||||
|
HasStop: {
|
||||||
|
'@Value': hasStop ? 'true' : 'false',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
midiTrack.DeviceChain.FreezeSequencer.ClipSlotList.ClipSlot[sc] = {
|
midiTrack.DeviceChain.FreezeSequencer.ClipSlotList.ClipSlot[sc] = {
|
||||||
'@Id': sc,
|
'@Id': sc,
|
||||||
@@ -793,77 +795,69 @@ async function buildTrack(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (koTrack.lane) {
|
for (const [clipIdx, koClip] of (koTrack.lane?.arrangementClips || []).entries()) {
|
||||||
if (!exporterParams.clips) {
|
const midiClip = await buildMidiClip(
|
||||||
for (const [clipIdx, koClip] of koTrack.lane.clips.entries()) {
|
koClip,
|
||||||
const midiClip = await buildMidiClip(
|
clipIdx,
|
||||||
koClip,
|
getColorValue(midiTrack),
|
||||||
clipIdx,
|
false,
|
||||||
getColorValue(midiTrack),
|
abletonVersion,
|
||||||
false,
|
automationTargets,
|
||||||
abletonVersion,
|
midiTrack.DeviceChain.MainSequencer.MidiControllers,
|
||||||
automationTargets,
|
koTrack,
|
||||||
midiTrack.DeviceChain.MainSequencer.MidiControllers,
|
);
|
||||||
koTrack,
|
midiTrack.DeviceChain.MainSequencer.ClipTimeable.ArrangerAutomation.Events.MidiClip.push(
|
||||||
);
|
midiClip,
|
||||||
|
);
|
||||||
midiTrack.DeviceChain.MainSequencer.ClipTimeable.ArrangerAutomation.Events.MidiClip.push(
|
|
||||||
midiClip,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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: [],
|
|
||||||
midiEvents: [],
|
|
||||||
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,
|
|
||||||
midiTrack.DeviceChain.MainSequencer.MidiControllers,
|
|
||||||
koTrack,
|
|
||||||
);
|
|
||||||
|
|
||||||
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex] = {
|
|
||||||
...midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex],
|
|
||||||
ClipSlot: {
|
|
||||||
Value: {
|
|
||||||
MidiClip: midiClip,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exporterParams.clips && groupLane) {
|
const sessionClips = [...(koTrack.lane?.sessionClips || [])];
|
||||||
|
groupLane?.sessionPatterns.forEach((pattern) => {
|
||||||
|
const hasClip = sessionClips.some((clip) => clip.sceneIndex === pattern.sceneIndex);
|
||||||
|
if (!hasClip && pattern.sceneIndex !== undefined) {
|
||||||
|
sessionClips.push({
|
||||||
|
notes: [],
|
||||||
|
midiEvents: [],
|
||||||
|
bars: pattern.bars,
|
||||||
|
offset: 0,
|
||||||
|
duration: pattern.duration,
|
||||||
|
loopDuration: pattern.loopDuration,
|
||||||
|
sceneIndex: pattern.sceneIndex,
|
||||||
|
sceneName: pattern.sceneName,
|
||||||
|
timeSignature: pattern.timeSignature,
|
||||||
|
faderParams: pattern.faderParams,
|
||||||
|
groupPattern: pattern,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const koClip of sessionClips) {
|
||||||
|
if (koClip.sceneIndex === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const midiClip = await buildMidiClip(
|
||||||
|
koClip,
|
||||||
|
0,
|
||||||
|
getColorValue(midiTrack),
|
||||||
|
true,
|
||||||
|
abletonVersion,
|
||||||
|
automationTargets,
|
||||||
|
midiTrack.DeviceChain.MainSequencer.MidiControllers,
|
||||||
|
koTrack,
|
||||||
|
);
|
||||||
|
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex] = {
|
||||||
|
...midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex],
|
||||||
|
ClipSlot: {
|
||||||
|
Value: {
|
||||||
|
MidiClip: midiClip,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupLane) {
|
||||||
const envelopes = buildArrangementEnvelopes(
|
const envelopes = buildArrangementEnvelopes(
|
||||||
groupLane.patterns,
|
groupLane.arrangementPatterns,
|
||||||
automationTargets,
|
automationTargets,
|
||||||
koTrack,
|
koTrack,
|
||||||
false,
|
false,
|
||||||
@@ -878,7 +872,6 @@ async function buildTrack(
|
|||||||
|
|
||||||
async function buildGroupTrack(
|
async function buildGroupTrack(
|
||||||
koTrack: AblTrack,
|
koTrack: AblTrack,
|
||||||
groupLane: AblGroupLane | undefined,
|
|
||||||
exporterParams: ExporterParams,
|
exporterParams: ExporterParams,
|
||||||
maxScenes: number,
|
maxScenes: number,
|
||||||
) {
|
) {
|
||||||
@@ -892,19 +885,8 @@ async function buildGroupTrack(
|
|||||||
setColorValue(groupTrack, 5 + _localGroupTrackColor++);
|
setColorValue(groupTrack, 5 + _localGroupTrackColor++);
|
||||||
groupTrack.Slots.GroupTrackSlot = [];
|
groupTrack.Slots.GroupTrackSlot = [];
|
||||||
groupTrack.DeviceChain.Mixer.Sends = {} as any;
|
groupTrack.DeviceChain.Mixer.Sends = {} as any;
|
||||||
|
groupTrack.DeviceChain.Mixer.Volume.Manual['@Value'] = 1;
|
||||||
if (!exporterParams.clips) {
|
groupTrack.DeviceChain.Mixer.Pan.Manual['@Value'] = 0;
|
||||||
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)
|
// adding empty slots for clips (or Ableton will crash)
|
||||||
// must be at least 8 slots to avoid Ableton crashes
|
// 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);
|
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 };
|
return { GroupTrack: groupTrack };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -966,10 +919,22 @@ async function buildScenes(
|
|||||||
|
|
||||||
sceneContent['@Id'] = _localId++;
|
sceneContent['@Id'] = _localId++;
|
||||||
if (abletonVersion === '10') {
|
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 {
|
} else {
|
||||||
sceneContent.Name['@Value'] = scene?.name || '';
|
sceneContent.Name['@Value'] = scene?.name || '';
|
||||||
sceneContent.Tempo['@Value'] = settings.bpm;
|
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);
|
result.push(sceneContent);
|
||||||
@@ -981,7 +946,7 @@ async function buildScenes(
|
|||||||
async function buildTracks(
|
async function buildTracks(
|
||||||
tracks: AblTrack[],
|
tracks: AblTrack[],
|
||||||
groupLanes: AblGroupLane[],
|
groupLanes: AblGroupLane[],
|
||||||
maxScenes: number,
|
scenes: AblScene[],
|
||||||
exporterParams: ExporterParams,
|
exporterParams: ExporterParams,
|
||||||
) {
|
) {
|
||||||
const result = [];
|
const result = [];
|
||||||
@@ -994,12 +959,12 @@ async function buildTracks(
|
|||||||
const groupLane = groupLanes.find((lane) => lane.group === koTrack.group);
|
const groupLane = groupLanes.find((lane) => lane.group === koTrack.group);
|
||||||
if (exporterParams.groupTracks && koTrack.group !== currentGroup[0]) {
|
if (exporterParams.groupTracks && koTrack.group !== currentGroup[0]) {
|
||||||
currentGroup = koTrack.group;
|
currentGroup = koTrack.group;
|
||||||
const groupTrack = await buildGroupTrack(koTrack, groupLane, exporterParams, maxScenes);
|
const groupTrack = await buildGroupTrack(koTrack, exporterParams, scenes.length);
|
||||||
trackGroupId = groupTrack.GroupTrack['@Id'];
|
trackGroupId = groupTrack.GroupTrack['@Id'];
|
||||||
result.push(groupTrack);
|
result.push(groupTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
const midiTrack = await buildTrack(koTrack, groupLane, maxScenes, trackGroupId, exporterParams);
|
const midiTrack = await buildTrack(koTrack, groupLane, scenes, trackGroupId, exporterParams);
|
||||||
|
|
||||||
result.push(midiTrack);
|
result.push(midiTrack);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user