import assert from 'node:assert/strict'; import test from 'node:test'; import { buildArrangementEnvelopes, buildClipEnvelopes, convertFaderValue, getFaderValueAt, } from '../src/lib/exporters/ableton/automation'; import { AblGroupPatternOccurrence, AblTrack } from '../src/lib/transformers/ableton'; import { FaderParam } from '../src/types/types'; const faderParams = { [FaderParam.LVL]: 1, [FaderParam.PTC]: 0.5, [FaderParam.TIM]: 0.5, [FaderParam.LPF]: 1, [FaderParam.HPF]: 0, [FaderParam.FX]: 0, [FaderParam.ATK]: 0, [FaderParam.REL]: 0.25, [FaderParam.PAN]: 0.5, [FaderParam.TUNE]: 0.5, [FaderParam.VEL]: -1, [FaderParam.MOD]: 0, }; const occurrence: AblGroupPatternOccurrence = { group: 'a', patternNumber: 1, bars: 1, duration: 8, loopDuration: 4, sceneIndex: 0, sceneName: '01', offset: 0, timeSignature: { numerator: 4, denominator: 4 }, faderParams, faderAutomation: [ { parameter: FaderParam.REL, position: 96, value: 0.75, flags: 0 }, { parameter: FaderParam.VEL, position: 192, value: 0.5, flags: 0 }, ], }; const track = { volume: 0.5, soundLength: 2, } as AblTrack; test('uses original note velocity until a VEL point is reached', () => { assert.equal(getFaderValueAt(occurrence, FaderParam.VEL, 96), undefined); assert.equal(getFaderValueAt(occurrence, FaderParam.VEL, 192), 0.5); }); test('preserves each pad pitch offset while applying group PTC automation', () => { assert.equal(convertFaderValue(FaderParam.PTC, 0.75, track, false, { valueOffset: 7 }), 9.5); }); test('tiles arrangement automation across the scene length', () => { const envelopes = buildArrangementEnvelopes( [occurrence], { [FaderParam.REL]: [{ arrangement: 22000 }] }, track, false, ); const events = envelopes[0].Automation.Events.FloatEvent; assert.equal(envelopes[0].EnvelopeTarget.PointeeId['@Value'], 22000); assert.equal(events[0]['@Time'], -63072000); assert.deepEqual( events .filter((event: any) => event['@Time'] === 1 || event['@Time'] === 5) .map((event: any) => event['@Time']), [1, 5], ); }); test('uses absolute automation targets for Session clip envelopes', () => { const envelopes = buildClipEnvelopes( occurrence, { [FaderParam.REL]: [{ session: 23000 }] }, track, false, ); assert.equal(envelopes[0].EnvelopeTarget.PointeeId['@Value'], 23000); assert.equal(envelopes[0].Automation.Events.FloatEvent[2]['@Time'], 1); assert.deepEqual(envelopes[0].LoopSlot, { Value: {} }); }); test('uses each Simpler target duration and One-Shot release direction', () => { assert.equal( convertFaderValue(FaderParam.REL, 1, track, false, { soundLength: 10, maxValue: 2000, invertValue: true, }), 0, ); assert.equal( convertFaderValue(FaderParam.REL, 0, track, false, { soundLength: 10, maxValue: 2000, invertValue: true, }), 2000, ); const envelopes = buildClipEnvelopes( occurrence, { [FaderParam.REL]: [ { session: 23000, soundLength: 2 }, { session: 23001, soundLength: 4, maxValue: 2000, invertValue: true }, ], }, track, false, ); assert.equal(envelopes[0].Automation.Events.FloatEvent[2]['@Value'], 1500); assert.equal(envelopes[1].Automation.Events.FloatEvent[2]['@Value'], 1000); }); test('preserves the pad envelope before group automation when the static group value is absent', () => { const pattern = { ...occurrence, faderParams: { ...faderParams, [FaderParam.REL]: -1 }, }; const envelopes = buildClipEnvelopes( pattern, { [FaderParam.REL]: [ { session: 23000, soundLength: 4, maxValue: 2000, invertValue: true, staticValue: 321, }, ], }, track, false, ); assert.equal(envelopes[0].Automation.Events.FloatEvent[0]['@Value'], 321); assert.equal(envelopes[0].Automation.Events.FloatEvent[1]['@Value'], 321); assert.equal(envelopes[0].Automation.Events.FloatEvent[2]['@Value'], 1000); });