Update Ableton exporter builders to support versioning and improve color handling
This commit is contained in:
@@ -43,14 +43,30 @@ let _localTrackColor = -1;
|
||||
let _localGroupTrackColor = -1;
|
||||
|
||||
const MIN_CLIP_LAUNCHER_SCENES = 8;
|
||||
type AbletonVersion = '10' | '11';
|
||||
|
||||
function getAbletonVersion(exporterParams: ExporterParams): AbletonVersion {
|
||||
return exporterParams.abletonVersion === '10' ? '10' : '11';
|
||||
}
|
||||
|
||||
function setColorValue(node: any, value: number) {
|
||||
const color = node.Color || node.ColorIndex;
|
||||
color['@Value'] = value;
|
||||
}
|
||||
|
||||
function getColorValue(node: any): number {
|
||||
const color = node.Color || node.ColorIndex;
|
||||
return Number(color['@Value']);
|
||||
}
|
||||
|
||||
async function buildMidiClip(
|
||||
koClip: AblClip,
|
||||
clipIdx: number,
|
||||
color: number,
|
||||
clipForLauncher: boolean = false,
|
||||
abletonVersion: AbletonVersion = '11',
|
||||
): Promise<ALSMidiClipContent> {
|
||||
const midiClipTemplate = await loadTemplate<ALSMidiClip>('midiClip');
|
||||
const midiClipTemplate = await loadTemplate<ALSMidiClip>('midiClip', abletonVersion);
|
||||
const midiClip = structuredClone(midiClipTemplate.MidiClip);
|
||||
|
||||
const beats = getQuarterNotesPerBar(
|
||||
@@ -70,7 +86,7 @@ async function buildMidiClip(
|
||||
midiClip.Loop.LoopEnd['@Value'] = koClip.bars * beats;
|
||||
midiClip.Loop.HiddenLoopStart['@Value'] = 0;
|
||||
midiClip.Loop.HiddenLoopEnd['@Value'] = koClip.bars * beats;
|
||||
midiClip.Color['@Value'] = color;
|
||||
setColorValue(midiClip, color);
|
||||
midiClip.Name['@Value'] = koClip.sceneName;
|
||||
midiClip.TimeSignature.TimeSignatures.RemoteableTimeSignature.Numerator['@Value'] =
|
||||
koClip.timeSignature.numerator;
|
||||
@@ -137,8 +153,8 @@ async function buildMidiClip(
|
||||
return midiClip;
|
||||
}
|
||||
|
||||
async function buildSimplerDevice(koTrack: AblTrack) {
|
||||
const simplerTemplate = await loadTemplate<ALSSimpler>('simpler');
|
||||
async function buildSimplerDevice(koTrack: AblTrack, abletonVersion: AbletonVersion) {
|
||||
const simplerTemplate = await loadTemplate<ALSSimpler>('simpler', abletonVersion);
|
||||
const device = structuredClone(simplerTemplate.OriginalSimpler);
|
||||
|
||||
device.LastPresetRef.Value = {};
|
||||
@@ -288,10 +304,10 @@ async function buildSimplerDevice(koTrack: AblTrack) {
|
||||
};
|
||||
}
|
||||
|
||||
async function buildDrumRackDevice(koTrack: AblTrack) {
|
||||
async function buildDrumRackDevice(koTrack: AblTrack, abletonVersion: AbletonVersion) {
|
||||
const [drumRackTemplate, drumBranchTemplate] = await Promise.all([
|
||||
loadTemplate<ALSDrumRack>('drumRack'),
|
||||
loadTemplate<ALSDrumBranch>('drumBranch'),
|
||||
loadTemplate<ALSDrumRack>('drumRack', abletonVersion),
|
||||
loadTemplate<ALSDrumBranch>('drumBranch', abletonVersion),
|
||||
]);
|
||||
|
||||
const device = structuredClone(drumRackTemplate.DrumGroupDevice);
|
||||
@@ -319,7 +335,10 @@ async function buildDrumRackDevice(koTrack: AblTrack) {
|
||||
drumBranch.BranchInfo.ReceivingNote['@Value'] = note;
|
||||
drumBranch.BranchInfo.SendingNote['@Value'] = 60; // not sure if this matters
|
||||
drumBranch.BranchInfo.ChokeGroup['@Value'] = subtrack.inChokeGroup ? 1 : 0;
|
||||
drumBranch.DeviceChain.MidiToAudioDeviceChain.Devices = await buildSimplerDevice(subtrack);
|
||||
drumBranch.DeviceChain.MidiToAudioDeviceChain.Devices = await buildSimplerDevice(
|
||||
subtrack,
|
||||
abletonVersion,
|
||||
);
|
||||
|
||||
device.Branches.DrumBranch.push(drumBranch);
|
||||
}
|
||||
@@ -335,7 +354,8 @@ async function buildTrack(
|
||||
trackGroupId: number,
|
||||
exporterParams: ExporterParams,
|
||||
): Promise<ALSMidiTrack> {
|
||||
const midiTrackTemplate = await loadTemplate<ALSMidiTrack>('midiTrack');
|
||||
const abletonVersion = getAbletonVersion(exporterParams);
|
||||
const midiTrackTemplate = await loadTemplate<ALSMidiTrack>('midiTrack', abletonVersion);
|
||||
const midiTrack = structuredClone(midiTrackTemplate.MidiTrack);
|
||||
|
||||
midiTrack['@Id'] = _localId++;
|
||||
@@ -343,7 +363,7 @@ async function buildTrack(
|
||||
? `${String(koTrack.soundId).padStart(3, '0')} ${koTrack.name}`
|
||||
: koTrack.name;
|
||||
midiTrack.Name.UserName['@Value'] = midiTrack.Name.EffectiveName['@Value'];
|
||||
midiTrack.Color['@Value'] = 8 + _localTrackColor++; // started with 8th color in the Ableton palette, why not
|
||||
setColorValue(midiTrack, 8 + _localTrackColor++);
|
||||
midiTrack.DeviceChain.MainSequencer.ClipTimeable.ArrangerAutomation.Events.MidiClip = [];
|
||||
midiTrack.TrackGroupId['@Value'] = trackGroupId;
|
||||
midiTrack.DeviceChain.DeviceChain.Devices['#'] = [];
|
||||
@@ -363,11 +383,15 @@ async function buildTrack(
|
||||
}
|
||||
|
||||
if (koTrack.drumRack && exporterParams.includeArchivedSamples) {
|
||||
midiTrack.DeviceChain.DeviceChain.Devices['#'].push(await buildDrumRackDevice(koTrack));
|
||||
midiTrack.DeviceChain.DeviceChain.Devices['#'].push(
|
||||
await buildDrumRackDevice(koTrack, abletonVersion),
|
||||
);
|
||||
}
|
||||
|
||||
if (!koTrack.drumRack && koTrack.sampleName && exporterParams.includeArchivedSamples) {
|
||||
midiTrack.DeviceChain.DeviceChain.Devices['#'].push(await buildSimplerDevice(koTrack));
|
||||
midiTrack.DeviceChain.DeviceChain.Devices['#'].push(
|
||||
await buildSimplerDevice(koTrack, abletonVersion),
|
||||
);
|
||||
}
|
||||
|
||||
// make sure id's are sequential in device chain
|
||||
@@ -380,6 +404,9 @@ async function buildTrack(
|
||||
// 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
|
||||
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot = [];
|
||||
midiTrack.DeviceChain.FreezeSequencer.ClipSlotList.ClipSlot = [];
|
||||
|
||||
for (let sc = 0; sc < Math.max(MIN_CLIP_LAUNCHER_SCENES, maxScenes); sc++) {
|
||||
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[sc] = {
|
||||
'@Id': sc,
|
||||
@@ -410,7 +437,13 @@ async function buildTrack(
|
||||
if (koTrack.lane) {
|
||||
if (!exporterParams.clips) {
|
||||
for (const [clipIdx, koClip] of koTrack.lane.clips.entries()) {
|
||||
const midiClip = await buildMidiClip(koClip, clipIdx, midiTrack.Color['@Value']);
|
||||
const midiClip = await buildMidiClip(
|
||||
koClip,
|
||||
clipIdx,
|
||||
getColorValue(midiTrack),
|
||||
false,
|
||||
abletonVersion,
|
||||
);
|
||||
|
||||
midiTrack.DeviceChain.MainSequencer.ClipTimeable.ArrangerAutomation.Events.MidiClip.push(
|
||||
midiClip,
|
||||
@@ -418,7 +451,13 @@ async function buildTrack(
|
||||
}
|
||||
} else {
|
||||
for (const koClip of koTrack.lane.clips.values()) {
|
||||
const midiClip = await buildMidiClip(koClip, 0, midiTrack.Color['@Value'], true);
|
||||
const midiClip = await buildMidiClip(
|
||||
koClip,
|
||||
0,
|
||||
getColorValue(midiTrack),
|
||||
true,
|
||||
abletonVersion,
|
||||
);
|
||||
|
||||
midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex] = {
|
||||
...midiTrack.DeviceChain.MainSequencer.ClipSlotList.ClipSlot[koClip.sceneIndex],
|
||||
@@ -438,7 +477,10 @@ async function buildTrack(
|
||||
}
|
||||
|
||||
if (exporterParams.sendEffects && !exporterParams.groupTracks) {
|
||||
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>('trackSendHolder');
|
||||
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>(
|
||||
'trackSendHolder',
|
||||
abletonVersion,
|
||||
);
|
||||
const trackSendHolder = structuredClone(trackSendHolderTemplate.TrackSendHolder);
|
||||
|
||||
trackSendHolder.Send.Manual['@Value'] = koTrack.faderParams[FaderParam.FX];
|
||||
@@ -454,13 +496,14 @@ async function buildGroupTrack(
|
||||
exporterParams: ExporterParams,
|
||||
maxScenes: number,
|
||||
) {
|
||||
const groupTrackTemplate = await loadTemplate<ALSGroupTrack>('groupTrack');
|
||||
const abletonVersion = getAbletonVersion(exporterParams);
|
||||
const groupTrackTemplate = await loadTemplate<ALSGroupTrack>('groupTrack', abletonVersion);
|
||||
const groupTrack = structuredClone(groupTrackTemplate.GroupTrack);
|
||||
|
||||
groupTrack['@Id'] = _localGroupId++;
|
||||
groupTrack.Name.EffectiveName['@Value'] = koTrack.group.toLocaleUpperCase();
|
||||
groupTrack.Name.UserName['@Value'] = koTrack.group.toLocaleUpperCase();
|
||||
groupTrack.Color['@Value'] = 5 + _localGroupTrackColor++;
|
||||
setColorValue(groupTrack, 5 + _localGroupTrackColor++);
|
||||
groupTrack.Slots.GroupTrackSlot = [];
|
||||
|
||||
// adding empty slots for clips (or Ableton will crash)
|
||||
@@ -475,7 +518,10 @@ async function buildGroupTrack(
|
||||
}
|
||||
|
||||
if (exporterParams.sendEffects && koTrack.faderParams[FaderParam.FX] !== -1) {
|
||||
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>('trackSendHolder');
|
||||
const trackSendHolderTemplate = await loadTemplate<ALSTrackSendHolder>(
|
||||
'trackSendHolder',
|
||||
abletonVersion,
|
||||
);
|
||||
const trackSendHolder = structuredClone(trackSendHolderTemplate.TrackSendHolder);
|
||||
|
||||
trackSendHolder.Send.Manual['@Value'] = koTrack.faderParams[FaderParam.FX];
|
||||
@@ -486,8 +532,13 @@ async function buildGroupTrack(
|
||||
return { GroupTrack: groupTrack };
|
||||
}
|
||||
|
||||
async function buildScenes(scenes: AblScene[], settings: ProjectSettings, maxScenes: number) {
|
||||
const sceneTemplate = await loadTemplate<ALSScene>('scene');
|
||||
async function buildScenes(
|
||||
scenes: AblScene[],
|
||||
settings: ProjectSettings,
|
||||
maxScenes: number,
|
||||
abletonVersion: AbletonVersion,
|
||||
) {
|
||||
const sceneTemplate = await loadTemplate<ALSScene>('scene', abletonVersion);
|
||||
const result: ALSSceneContent[] = [];
|
||||
const sceneCount = Math.max(MIN_CLIP_LAUNCHER_SCENES, maxScenes);
|
||||
|
||||
@@ -496,8 +547,12 @@ async function buildScenes(scenes: AblScene[], settings: ProjectSettings, maxSce
|
||||
const sceneContent = structuredClone(sceneTemplate.Scene);
|
||||
|
||||
sceneContent['@Id'] = _localId++;
|
||||
sceneContent.Name['@Value'] = scene?.name || '';
|
||||
sceneContent.Tempo['@Value'] = settings.bpm;
|
||||
if (abletonVersion === '10') {
|
||||
sceneContent['@Value'] = scene?.name || ` ${index + 1}`;
|
||||
} else {
|
||||
sceneContent.Name['@Value'] = scene?.name || '';
|
||||
sceneContent.Tempo['@Value'] = settings.bpm;
|
||||
}
|
||||
|
||||
result.push(sceneContent);
|
||||
}
|
||||
@@ -528,14 +583,18 @@ async function buildTracks(tracks: AblTrack[], maxScenes: number, exporterParams
|
||||
return result;
|
||||
}
|
||||
|
||||
async function buildReturnTrack(projectData: ProjectRawData, trackId: number = 0) {
|
||||
const returnTrackTemplate = await loadTemplate<ALSReturnTrack>('returnTrack');
|
||||
async function buildReturnTrack(
|
||||
projectData: ProjectRawData,
|
||||
trackId: number = 0,
|
||||
abletonVersion: AbletonVersion = '11',
|
||||
) {
|
||||
const returnTrackTemplate = await loadTemplate<ALSReturnTrack>('returnTrack', abletonVersion);
|
||||
const returnTrack = structuredClone(returnTrackTemplate.ReturnTrack);
|
||||
|
||||
returnTrack['@Id'] = trackId;
|
||||
returnTrack.Name.EffectiveName['@Value'] = EFFECTS[projectData.effects.effectType] || '';
|
||||
returnTrack.Name.UserName['@Value'] = EFFECTS[projectData.effects.effectType] || '';
|
||||
returnTrack.Color['@Value'] = 28;
|
||||
setColorValue(returnTrack, 28);
|
||||
|
||||
if (projectData.effects.effectType === EffectType.Reverb) {
|
||||
const effectReverbTemplate = await loadTemplate<ALSReverb>('effectReverb');
|
||||
|
||||
Reference in New Issue
Block a user