165 lines
4.9 KiB
TypeScript
165 lines
4.9 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
buildArrangementEnvelopes,
|
|
buildClipEnvelopes,
|
|
convertFaderValue,
|
|
getFaderValueAt,
|
|
getScaledNoteVelocity,
|
|
} 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.PAN, 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);
|
|
assert.equal(getScaledNoteVelocity(101, occurrence, 96), 101);
|
|
assert.equal(getScaledNoteVelocity(101, occurrence, 192), 51);
|
|
});
|
|
|
|
test('scales P07-like note velocities with static and automated VEL values', () => {
|
|
const staticVelocity = {
|
|
...occurrence,
|
|
faderParams: {
|
|
...occurrence.faderParams,
|
|
[FaderParam.VEL]: 0.5325199961662292,
|
|
},
|
|
faderAutomation: [{ parameter: FaderParam.VEL, position: 192, value: 0.25, flags: 0 }],
|
|
};
|
|
|
|
assert.deepEqual(
|
|
[127, 127, 48].map((velocity) => getScaledNoteVelocity(velocity, staticVelocity, 96)),
|
|
[68, 68, 26],
|
|
);
|
|
assert.equal(getScaledNoteVelocity(127, staticVelocity, 192), 32);
|
|
assert.equal(getScaledNoteVelocity(0, staticVelocity, 192), 1);
|
|
});
|
|
|
|
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.PAN]: [{ 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.PAN]: [{ 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('does not create Session envelopes without recorded parameter events', () => {
|
|
const withoutPan = {
|
|
...occurrence,
|
|
faderAutomation: occurrence.faderAutomation.filter(
|
|
(point) => point.parameter !== FaderParam.PAN,
|
|
),
|
|
};
|
|
const envelopes = buildClipEnvelopes(
|
|
withoutPan,
|
|
{ [FaderParam.PAN]: [{ session: 23000 }] },
|
|
track,
|
|
false,
|
|
);
|
|
|
|
assert.deepEqual(envelopes, []);
|
|
});
|
|
|
|
test('canonicalizes an effectively-open filter base before automation starts', () => {
|
|
const filterOccurrence = {
|
|
...occurrence,
|
|
faderParams: {
|
|
...occurrence.faderParams,
|
|
[FaderParam.LPF]: 0.9996299743652344,
|
|
},
|
|
faderAutomation: [{ parameter: FaderParam.LPF, position: 96, value: 0.5, flags: 0 }],
|
|
};
|
|
const envelopes = buildClipEnvelopes(
|
|
filterOccurrence,
|
|
{ [FaderParam.LPF]: [{ session: 23000 }] },
|
|
track,
|
|
false,
|
|
);
|
|
|
|
assert.equal(envelopes[0].Automation.Events.FloatEvent[0]['@Value'], 123.5);
|
|
assert.equal(envelopes[0].Automation.Events.FloatEvent[1]['@Value'], 123.5);
|
|
});
|
|
|
|
test('keeps research-only group parameters out of Live target envelopes', () => {
|
|
const researchOccurrence = {
|
|
...occurrence,
|
|
faderAutomation: [
|
|
{ parameter: FaderParam.TIM, position: 0, value: 0.25, flags: 0 },
|
|
{ parameter: FaderParam.ATK, position: 0, value: 0.5, flags: 0 },
|
|
{ parameter: FaderParam.REL, position: 0, value: 0.75, flags: 0 },
|
|
],
|
|
};
|
|
const targets = {
|
|
[FaderParam.TIM]: [{ session: 23000 }],
|
|
[FaderParam.ATK]: [{ session: 23001 }],
|
|
[FaderParam.REL]: [{ session: 23002 }],
|
|
};
|
|
|
|
assert.deepEqual(buildClipEnvelopes(researchOccurrence, targets, track, false), []);
|
|
});
|