diff --git a/src/lib/exporters/ableton/midi.ts b/src/lib/exporters/ableton/midi.ts index 414aff4..e291a6a 100644 --- a/src/lib/exporters/ableton/midi.ts +++ b/src/lib/exporters/ableton/midi.ts @@ -52,11 +52,12 @@ export function buildMidiClipEnvelopes( } eventCount++; + const time = event.position / 96; if (value === previousValue) { + points.push({ time, value }); return; } - const time = event.position / 96; points.push({ time, value: previousValue }); points.push({ time, value }); previousValue = value; diff --git a/tests/ableton-midi.test.ts b/tests/ableton-midi.test.ts index 88b206e..b33fd21 100644 --- a/tests/ableton-midi.test.ts +++ b/tests/ableton-midi.test.ts @@ -99,11 +99,29 @@ test('builds native Live 10 Channel Pressure steps with raw values', () => { [1, 75], [1, 75], [1, 20], + [2, 20], ], ); assert.deepEqual(envelope.LoopSlot, { Value: {} }); }); +test('retains an unchanged zero-pressure event at its original time', () => { + const envelopes = buildMidiClipEnvelopes([midiEvent(48, 2, 0)], { + 'ControllerTargets.129': { '@Id': 45123 }, + }); + + assert.deepEqual( + envelopes[0].Automation.Events.FloatEvent.map((event: any) => [ + event['@Time'], + event['@Value'], + ]), + [ + [-63072000, 0], + [0.5, 0], + ], + ); +}); + test('routes each source curve only to its matching pad clip, including MIDI-only pads', () => { const source0 = midiEvent(24, 0, 30); const source1 = midiEvent(48, 1, 60);