From d5f86370d7ef8abac8305001b2fe708fcc5bec6d Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 14 Jul 2026 06:51:10 +0200 Subject: [PATCH] Update Ableton MIDI exporter to support locked envelopes and improve robustness --- src/lib/exporters/ableton/midi.ts | 14 ++++++++++---- tests/ableton-midi.test.ts | 6 +++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/lib/exporters/ableton/midi.ts b/src/lib/exporters/ableton/midi.ts index e291a6a..227fec2 100644 --- a/src/lib/exporters/ableton/midi.ts +++ b/src/lib/exporters/ableton/midi.ts @@ -1,6 +1,9 @@ import type { AblMidiEvent } from '../../transformers/ableton'; -export type AbletonMidiControllerTargets = Record; +export type AbletonMidiControllerTargets = Record< + string, + { '@Id': number; LockEnvelope?: { '@Value': number | string } } +>; type AutomationPoint = { time: number; @@ -36,10 +39,13 @@ export function buildMidiClipEnvelopes( events: AblMidiEvent[], controllerTargets: AbletonMidiControllerTargets, ) { - const target = controllerTargets[`ControllerTargets.${CHANNEL_PRESSURE_TARGET}`]?.['@Id']; - if (target === undefined) { + const target = controllerTargets[`ControllerTargets.${CHANNEL_PRESSURE_TARGET}`]; + if (!target) { return []; } + if (target.LockEnvelope) { + target.LockEnvelope['@Value'] = 1; + } const points: AutomationPoint[] = [{ time: SENTINEL_TIME, value: 0 }]; let previousValue = 0; @@ -70,7 +76,7 @@ export function buildMidiClipEnvelopes( return [ { '@Id': 0, - EnvelopeTarget: { PointeeId: { '@Value': target } }, + EnvelopeTarget: { PointeeId: { '@Value': target['@Id'] } }, Automation: { Events: { FloatEvent: floatEvents(points) }, AutomationTransformViewState: { diff --git a/tests/ableton-midi.test.ts b/tests/ableton-midi.test.ts index b33fd21..d15dd8a 100644 --- a/tests/ableton-midi.test.ts +++ b/tests/ableton-midi.test.ts @@ -83,14 +83,18 @@ test('decodes only the captured EP Channel Pressure record shape', () => { }); test('builds native Live 10 Channel Pressure steps with raw values', () => { + const controllerTargets = { + 'ControllerTargets.129': { '@Id': 45123, LockEnvelope: { '@Value': 0 } }, + }; const envelopes = buildMidiClipEnvelopes( [midiEvent(192, 2, 20), midiEvent(96, 2, 75), midiEvent(96, 2, 20)], - { 'ControllerTargets.129': { '@Id': 45123 } }, + controllerTargets, ); const envelope = envelopes[0]; const events = envelope.Automation.Events.FloatEvent; assert.equal(envelope.EnvelopeTarget.PointeeId['@Value'], 45123); + assert.equal(controllerTargets['ControllerTargets.129'].LockEnvelope['@Value'], 1); assert.deepEqual( events.map((event: any) => [event['@Time'], event['@Value']]), [