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