feat(tests): Add comprehensive hybrid test suite for Ableton project data
This commit is contained in:
@@ -49,6 +49,10 @@ test('uses original note velocity until a VEL point is reached', () => {
|
|||||||
assert.equal(getFaderValueAt(occurrence, FaderParam.VEL, 192), 0.5);
|
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', () => {
|
test('tiles arrangement automation across the scene length', () => {
|
||||||
const envelopes = buildArrangementEnvelopes(
|
const envelopes = buildArrangementEnvelopes(
|
||||||
[occurrence],
|
[occurrence],
|
||||||
|
|||||||
232
tests/ableton-hybrid.test.ts
Normal file
232
tests/ableton-hybrid.test.ts
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import abletonTransformer, {
|
||||||
|
AblTrack,
|
||||||
|
getDrumRackMidiNote,
|
||||||
|
} from '../src/lib/transformers/ableton';
|
||||||
|
import {
|
||||||
|
EffectType,
|
||||||
|
FaderParam,
|
||||||
|
Group,
|
||||||
|
GroupFaderParam,
|
||||||
|
Note,
|
||||||
|
Pad,
|
||||||
|
PadCode,
|
||||||
|
ProjectRawData,
|
||||||
|
} from '../src/types/types';
|
||||||
|
|
||||||
|
function faderParams(): GroupFaderParam {
|
||||||
|
return Object.fromEntries(
|
||||||
|
(['a', 'b', 'c', 'd'] as Group[]).map((group) => [
|
||||||
|
group,
|
||||||
|
Object.fromEntries(Array.from({ length: 12 }, (_, parameter) => [parameter, -1])),
|
||||||
|
]),
|
||||||
|
) as GroupFaderParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(index: number, overrides: Partial<Pad> = {}): Pad {
|
||||||
|
return {
|
||||||
|
pad: index + 1,
|
||||||
|
name: `a${index}`,
|
||||||
|
group: 'a',
|
||||||
|
file: null,
|
||||||
|
rawData: null,
|
||||||
|
soundId: index + 1,
|
||||||
|
volume: 100,
|
||||||
|
attack: 0,
|
||||||
|
release: 0,
|
||||||
|
trimLeft: 0,
|
||||||
|
trimRight: 46875,
|
||||||
|
soundLength: 1,
|
||||||
|
playMode: 'oneshot',
|
||||||
|
pan: 0,
|
||||||
|
pitch: 0,
|
||||||
|
rootNote: 60,
|
||||||
|
timeStretch: 'off',
|
||||||
|
timeStretchBpm: 0,
|
||||||
|
timeStretchBars: 0,
|
||||||
|
inChokeGroup: false,
|
||||||
|
midiChannel: 0,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function note(noteValue: number, position: number, velocity = 100): Note {
|
||||||
|
return {
|
||||||
|
note: noteValue,
|
||||||
|
position,
|
||||||
|
duration: 48,
|
||||||
|
velocity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function project(): ProjectRawData {
|
||||||
|
const params = faderParams();
|
||||||
|
return {
|
||||||
|
pads: {
|
||||||
|
a: [
|
||||||
|
pad(0, { rootNote: 48, volume: 150, inChokeGroup: true }),
|
||||||
|
pad(1, { playMode: 'key', inChokeGroup: true }),
|
||||||
|
pad(2, { rootNote: 55, volume: 80, inChokeGroup: true }),
|
||||||
|
],
|
||||||
|
b: [],
|
||||||
|
c: [],
|
||||||
|
d: [],
|
||||||
|
},
|
||||||
|
scenes: [
|
||||||
|
{
|
||||||
|
name: '01',
|
||||||
|
patterns: [
|
||||||
|
{ pad: 'a0', group: 'a', notes: [note(72, 0, 110)], bars: 1 },
|
||||||
|
{ pad: 'a1', group: 'a', notes: [note(60, 0), note(64, 96)], bars: 1 },
|
||||||
|
],
|
||||||
|
patternEvents: {
|
||||||
|
a: {
|
||||||
|
group: 'a',
|
||||||
|
patternNumber: 1,
|
||||||
|
bars: 1,
|
||||||
|
recordCount: 0,
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '02',
|
||||||
|
patterns: [
|
||||||
|
{ pad: 'a0', group: 'a', notes: [note(72, 24, 90)], bars: 1 },
|
||||||
|
{ pad: 'a1', group: 'a', notes: [note(67, 48)], bars: 1 },
|
||||||
|
],
|
||||||
|
patternEvents: {
|
||||||
|
a: {
|
||||||
|
group: 'a',
|
||||||
|
patternNumber: 2,
|
||||||
|
bars: 1,
|
||||||
|
recordCount: 0,
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
bpm: 120,
|
||||||
|
scale: 0,
|
||||||
|
rootNote: 0,
|
||||||
|
groupFaderParams: params,
|
||||||
|
faderAssignment: {
|
||||||
|
a: FaderParam.LVL,
|
||||||
|
b: FaderParam.LVL,
|
||||||
|
c: FaderParam.LVL,
|
||||||
|
d: FaderParam.LVL,
|
||||||
|
},
|
||||||
|
rawData: new Uint8Array(),
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
rawData: new Uint8Array(),
|
||||||
|
effectType: EffectType.Off,
|
||||||
|
param1: 0,
|
||||||
|
param2: 0,
|
||||||
|
},
|
||||||
|
sounds: [],
|
||||||
|
projectFile: null as unknown as File,
|
||||||
|
scenesSettings: { timeSignature: { numerator: 4, denominator: 4 } },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackShape(tracks: AblTrack[]) {
|
||||||
|
return tracks.map((track) => ({
|
||||||
|
name: track.name,
|
||||||
|
drumRack: track.drumRack,
|
||||||
|
padCode: track.padCode,
|
||||||
|
children: track.tracks.map((child) => child.padCode),
|
||||||
|
clips: track.lane?.clips.map((clip) => ({
|
||||||
|
sceneIndex: clip.sceneIndex,
|
||||||
|
notes: clip.notes.map((item) => item.note),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test('automatically builds a per-group rack without flattening chromatic pads', () => {
|
||||||
|
const result = abletonTransformer(project(), {
|
||||||
|
includeArchivedSamples: true,
|
||||||
|
exportAllPadsWithSamples: true,
|
||||||
|
customSceneNames: { '01': 'Same', '02': 'Same' },
|
||||||
|
});
|
||||||
|
const rack = result.tracks.find((track) => track.drumRack);
|
||||||
|
const melodic = result.tracks.find((track) => track.padCode === 'a1' && !track.drumRack);
|
||||||
|
|
||||||
|
assert.ok(rack);
|
||||||
|
assert.ok(melodic);
|
||||||
|
assert.equal(rack.name, 'A Pads');
|
||||||
|
assert.deepEqual(
|
||||||
|
rack.tracks.map((track) => track.padCode),
|
||||||
|
['a0', 'a2'],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
rack.lane?.clips.map((clip) => [clip.sceneIndex, clip.sceneName, clip.notes[0].note]),
|
||||||
|
[
|
||||||
|
[0, 'Same', getDrumRackMidiNote('a0')],
|
||||||
|
[1, 'Same', getDrumRackMidiNote('a0')],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
melodic.lane?.clips.map((clip) => clip.notes.map((item) => item.note)),
|
||||||
|
[
|
||||||
|
[60, 64],
|
||||||
|
[67],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preserves rack playback pitch, unused-pad root note, AMP and choke membership', () => {
|
||||||
|
const result = abletonTransformer(project(), {
|
||||||
|
includeArchivedSamples: true,
|
||||||
|
exportAllPadsWithSamples: true,
|
||||||
|
});
|
||||||
|
const rack = result.tracks.find((track) => track.drumRack);
|
||||||
|
const a0 = rack?.tracks.find((track) => track.padCode === 'a0');
|
||||||
|
const a2 = rack?.tracks.find((track) => track.padCode === 'a2');
|
||||||
|
const melodic = result.tracks.find((track) => track.padCode === 'a1' && !track.drumRack);
|
||||||
|
|
||||||
|
assert.ok(a0);
|
||||||
|
assert.ok(a2);
|
||||||
|
assert.ok(melodic);
|
||||||
|
assert.equal(a0.drumRackSendingNote, 72);
|
||||||
|
assert.equal(a0.volume, 0.75);
|
||||||
|
assert.equal(a0.inChokeGroup, true);
|
||||||
|
assert.equal(a2.drumRackSendingNote, 55);
|
||||||
|
assert.equal(a2.volume, 0.4);
|
||||||
|
assert.equal(a2.inChokeGroup, true);
|
||||||
|
assert.equal(melodic.inChokeGroup, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses one hybrid classification for Arrangement and Session exports', () => {
|
||||||
|
const arrangement = abletonTransformer(project(), {
|
||||||
|
includeArchivedSamples: true,
|
||||||
|
exportAllPadsWithSamples: true,
|
||||||
|
clips: false,
|
||||||
|
});
|
||||||
|
const session = abletonTransformer(project(), {
|
||||||
|
includeArchivedSamples: true,
|
||||||
|
exportAllPadsWithSamples: true,
|
||||||
|
clips: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(trackShape(arrangement.tracks), trackShape(session.tracks));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not create selector-note racks when samples are excluded', () => {
|
||||||
|
const result = abletonTransformer(project(), {
|
||||||
|
includeArchivedSamples: false,
|
||||||
|
exportAllPadsWithSamples: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.tracks.some((track) => track.drumRack), false);
|
||||||
|
assert.deepEqual(
|
||||||
|
result.tracks.map((track) => track.padCode),
|
||||||
|
['a0', 'a1', 'a2'] as PadCode[],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
result.tracks[0].lane?.clips.map((clip) => clip.notes.map((item) => item.note)),
|
||||||
|
[[72], [72]],
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user