From 4bf04cf93c08813fa91f8a1fe23bb7f0dccd4c25 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 14 Jul 2026 09:41:06 +0200 Subject: [PATCH] Implement time signature handling and build arrangement events for Ableton export --- src/lib/exporters/ableton/builders.ts | 91 +++++++++++++++++---------- 1 file changed, 59 insertions(+), 32 deletions(-) diff --git a/src/lib/exporters/ableton/builders.ts b/src/lib/exporters/ableton/builders.ts index aa98a40..8f6a399 100644 --- a/src/lib/exporters/ableton/builders.ts +++ b/src/lib/exporters/ableton/builders.ts @@ -280,18 +280,6 @@ function trackAutomationTargets( return targets; } -function groupAutomationTargets(groupTrack: any) { - const targets: AbletonAutomationTargets = {}; - const mixer = groupTrack.DeviceChain.Mixer; - addTarget(targets, FaderParam.LVL, targetFor(mixer.Volume)); - addTarget(targets, FaderParam.PAN, targetFor(mixer.Pan)); - const send = mixer.Sends?.TrackSendHolder?.Send; - if (send) { - addTarget(targets, FaderParam.FX, targetFor(send)); - } - return targets; -} - async function buildMidiClip( koClip: AblClip, clipIdx: number, @@ -1107,6 +1095,44 @@ async function buildReturnTrack( return { ReturnTrack: returnTrack }; } +function getTimeSignatureId(timeSignature: { numerator: number; denominator: number }) { + return ( + TIME_SIGNATURES[`${timeSignature.numerator}/${timeSignature.denominator}`] || + TIME_SIGNATURES['4/4'] + ); +} + +function buildArrangementTimeSignatureEvents( + sections: AblArrangementSection[], + fallbackTimeSignature: { numerator: number; denominator: number }, +) { + const initialTimeSignature = sections[0]?.timeSignature || fallbackTimeSignature; + const events = [ + { + '@Id': 0, + '@Time': -63072000, + '@Value': getTimeSignatureId(initialTimeSignature), + }, + ]; + let previousTimeSignatureId = getTimeSignatureId(initialTimeSignature); + + sections.forEach((section, index) => { + const timeSignatureId = getTimeSignatureId(section.timeSignature); + if (index === 0 || timeSignatureId === previousTimeSignatureId) { + previousTimeSignatureId = timeSignatureId; + return; + } + events.push({ + '@Id': events.length, + '@Time': section.offset, + '@Value': timeSignatureId, + }); + previousTimeSignatureId = timeSignatureId; + }); + + return events; +} + export async function buildProject(projectData: ProjectRawData, exporterParams: ExporterParams) { resetIds(); const abletonVersion = getAbletonVersion(exporterParams); @@ -1132,18 +1158,21 @@ export async function buildProject(projectData: ProjectRawData, exporterParams: project.Ableton.LiveSet.MasterTrack.AutomationEnvelopes.Envelopes.AutomationEnvelope[1].Automation.Events.FloatEvent[ '@Value' ] = projectData.settings.bpm; - // setting up time signature - project.Ableton.LiveSet.MasterTrack.AutomationEnvelopes.Envelopes.AutomationEnvelope[0].Automation.Events.EnumEvent[ - '@Value' - ] = - TIME_SIGNATURES[ - `${projectData.scenesSettings.timeSignature.numerator}/${projectData.scenesSettings.timeSignature.denominator}` - ] || TIME_SIGNATURES['4/4']; + const initialTimeSignature = + transformedData.arrangementSections[0]?.timeSignature || + projectData.scenesSettings.timeSignature; + project.Ableton.LiveSet.MasterTrack.DeviceChain.Mixer.TimeSignature.Manual['@Value'] = + getTimeSignatureId(initialTimeSignature); + project.Ableton.LiveSet.MasterTrack.AutomationEnvelopes.Envelopes.AutomationEnvelope[0].Automation.Events.EnumEvent = + buildArrangementTimeSignatureEvents( + transformedData.arrangementSections, + projectData.scenesSettings.timeSignature, + ) as any; const tracks = await buildTracks( transformedData.tracks, transformedData.groupLanes, - transformedData.scenes.length, + transformedData.scenes, exporterParams, ); project.Ableton.LiveSet.Tracks = { '#': tracks } as any; @@ -1158,18 +1187,16 @@ export async function buildProject(projectData: ProjectRawData, exporterParams: project.Ableton.LiveSet.Tracks['#'].push(returnTrack); } - if (exporterParams.clips) { - const scenes = await buildScenes( - transformedData.scenes, - projectData.settings, - transformedData.scenes.length, - abletonVersion, - ); - if (abletonVersion === '10') { - (project.Ableton.LiveSet as any).SceneNames.Scene = scenes; - } else { - project.Ableton.LiveSet.Scenes.Scene = scenes; - } + const scenes = await buildScenes( + transformedData.scenes, + projectData.settings, + transformedData.scenes.length, + abletonVersion, + ); + if (abletonVersion === '10') { + (project.Ableton.LiveSet as any).SceneNames.Scene = scenes; + } else { + project.Ableton.LiveSet.Scenes.Scene = scenes; } if (exporterParams.sendEffects) {