278 lines
6.5 KiB
TypeScript
278 lines
6.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import abletonTransformer from '../src/lib/transformers/ableton';
|
|
import {
|
|
EffectType,
|
|
FaderParam,
|
|
Group,
|
|
GroupFaderParam,
|
|
GroupPattern,
|
|
Note,
|
|
Pad,
|
|
PatternEventKind,
|
|
ProjectRawData,
|
|
Scene,
|
|
} 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(group: Group, index: number): Pad {
|
|
return {
|
|
pad: index + 1,
|
|
name: `${group}${index}`,
|
|
group,
|
|
file: null,
|
|
rawData: null,
|
|
soundId: 0,
|
|
volume: 100,
|
|
attack: 0,
|
|
release: 0,
|
|
trimLeft: 0,
|
|
trimRight: 0,
|
|
soundLength: 0,
|
|
playMode: 'oneshot',
|
|
pan: 0,
|
|
pitch: 0,
|
|
rootNote: 60,
|
|
timeStretch: 'off',
|
|
timeStretchBpm: 0,
|
|
timeStretchBars: 0,
|
|
inChokeGroup: false,
|
|
midiChannel: 0,
|
|
};
|
|
}
|
|
|
|
function pattern(
|
|
group: Group,
|
|
patternNumber: number,
|
|
bars: number,
|
|
source: number,
|
|
noteValue: number,
|
|
): GroupPattern {
|
|
const note: Note = { note: noteValue, position: 0, duration: 48, velocity: 100 };
|
|
return {
|
|
group,
|
|
patternNumber,
|
|
bars,
|
|
recordCount: 1,
|
|
events: [
|
|
{
|
|
type: 'note',
|
|
kind: PatternEventKind.Note,
|
|
position: 0,
|
|
source,
|
|
flags: 0,
|
|
rawData: new Uint8Array(8),
|
|
pad: source,
|
|
note: noteValue,
|
|
velocity: 100,
|
|
duration: 48,
|
|
},
|
|
],
|
|
notes: { [source]: [note] },
|
|
};
|
|
}
|
|
|
|
function faderPattern(group: Group, patternNumber: number): GroupPattern {
|
|
return {
|
|
group,
|
|
patternNumber,
|
|
bars: 1,
|
|
recordCount: 1,
|
|
events: [
|
|
{
|
|
type: 'fader',
|
|
kind: PatternEventKind.Fader,
|
|
position: 0,
|
|
source: 0,
|
|
flags: 0,
|
|
rawData: new Uint8Array(8),
|
|
parameter: FaderParam.LPF,
|
|
reserved: 0,
|
|
rawValue: 0,
|
|
value: 0,
|
|
},
|
|
],
|
|
notes: {},
|
|
};
|
|
}
|
|
|
|
function scene(
|
|
number: number,
|
|
patternNumbers: Record<Group, number>,
|
|
numerator: number,
|
|
denominator: number,
|
|
): Scene {
|
|
return {
|
|
number,
|
|
name: String(number).padStart(2, '0'),
|
|
patternNumbers,
|
|
timeSignature: { numerator, denominator },
|
|
patterns: [],
|
|
patternEvents: {},
|
|
};
|
|
}
|
|
|
|
function project(): ProjectRawData {
|
|
const params = faderParams();
|
|
return {
|
|
pads: {
|
|
a: [pad('a', 0), pad('a', 1)],
|
|
b: [pad('b', 0)],
|
|
c: [pad('c', 0)],
|
|
d: [],
|
|
},
|
|
groupPatterns: [
|
|
pattern('a', 1, 2, 0, 60),
|
|
pattern('b', 2, 1, 0, 36),
|
|
pattern('a', 3, 1, 1, 64),
|
|
pattern('c', 3, 2, 0, 67),
|
|
pattern('b', 1, 1, 0, 38),
|
|
faderPattern('d', 2),
|
|
],
|
|
scenes: [
|
|
scene(1, { a: 1, b: 2, c: 0, d: 0 }, 3, 4),
|
|
scene(2, { a: 1, b: 0, c: 0, d: 0 }, 2, 4),
|
|
],
|
|
songPositions: [
|
|
{ position: 0, sceneNumber: 2 },
|
|
{ position: 1, sceneNumber: 1 },
|
|
{ position: 2, sceneNumber: 2 },
|
|
],
|
|
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 } },
|
|
};
|
|
}
|
|
|
|
test('combines genuine Session scenes, orphan patterns and the repeated Song Arrangement', () => {
|
|
const result = abletonTransformer(project(), {
|
|
allScenes: true,
|
|
includeArchivedSamples: false,
|
|
keepUnusedPatterns: true,
|
|
});
|
|
|
|
assert.deepEqual(
|
|
result.scenes.map((item) => [item.name, item.kind, item.affectedGroups]),
|
|
[
|
|
['01', 'scene', ['a', 'b', 'c', 'd']],
|
|
['02', 'scene', ['a', 'b', 'c', 'd']],
|
|
['PATTERN 01 (B)', 'pattern', ['b']],
|
|
['PATTERN 03 (A/C)', 'pattern', ['a', 'c']],
|
|
],
|
|
);
|
|
assert.deepEqual(
|
|
result.arrangementSections.map((section) => [
|
|
section.sceneNumber,
|
|
section.offset,
|
|
section.duration,
|
|
`${section.timeSignature.numerator}/${section.timeSignature.denominator}`,
|
|
]),
|
|
[
|
|
[2, 0, 4, '2/4'],
|
|
[1, 4, 6, '3/4'],
|
|
[2, 10, 4, '2/4'],
|
|
],
|
|
);
|
|
|
|
const a0 = result.tracks.find((track) => track.padCode === 'a0');
|
|
const a1 = result.tracks.find((track) => track.padCode === 'a1');
|
|
const b0 = result.tracks.find((track) => track.padCode === 'b0');
|
|
const c0 = result.tracks.find((track) => track.padCode === 'c0');
|
|
|
|
assert.deepEqual(
|
|
a0?.lane?.sessionClips.map((clip) => clip.sceneIndex),
|
|
[0, 1],
|
|
);
|
|
assert.deepEqual(
|
|
a0?.lane?.arrangementClips.map((clip) => [clip.occurrenceIndex, clip.offset]),
|
|
[
|
|
[0, 0],
|
|
[1, 4],
|
|
[2, 10],
|
|
],
|
|
);
|
|
assert.deepEqual(
|
|
a1?.lane?.sessionClips.map((clip) => clip.sceneIndex),
|
|
[3],
|
|
);
|
|
assert.deepEqual(a1?.lane?.arrangementClips, []);
|
|
assert.equal(a1?.lane?.sessionClips[0].loopDuration, 4);
|
|
assert.deepEqual(
|
|
b0?.lane?.sessionClips.map((clip) => clip.sceneIndex),
|
|
[0, 2],
|
|
);
|
|
assert.deepEqual(
|
|
b0?.lane?.arrangementClips.map((clip) => [clip.occurrenceIndex, clip.offset]),
|
|
[[1, 4]],
|
|
);
|
|
assert.deepEqual(
|
|
c0?.lane?.sessionClips.map((clip) => clip.sceneIndex),
|
|
[3],
|
|
);
|
|
assert.deepEqual(c0?.lane?.arrangementClips, []);
|
|
assert.equal(c0?.lane?.sessionClips[0].loopDuration, 8);
|
|
});
|
|
|
|
test('keeps Session material and leaves Arrangement empty when no Song is present', () => {
|
|
const data = project();
|
|
data.songPositions = [];
|
|
const result = abletonTransformer(data, {
|
|
allScenes: false,
|
|
selectedScenes: ['02'],
|
|
includeArchivedSamples: false,
|
|
keepUnusedPatterns: false,
|
|
});
|
|
|
|
assert.deepEqual(
|
|
result.scenes.map((item) => item.name),
|
|
['02'],
|
|
);
|
|
assert.deepEqual(result.arrangementSections, []);
|
|
assert.ok(result.tracks.every((track) => (track.lane?.arrangementClips.length || 0) === 0));
|
|
});
|
|
|
|
test('keeps the complete Song Arrangement when Session scene selection is limited', () => {
|
|
const result = abletonTransformer(project(), {
|
|
allScenes: false,
|
|
selectedScenes: ['02'],
|
|
includeArchivedSamples: false,
|
|
keepUnusedPatterns: false,
|
|
});
|
|
|
|
assert.deepEqual(
|
|
result.scenes.map((item) => item.name),
|
|
['02'],
|
|
);
|
|
assert.deepEqual(
|
|
result.arrangementSections.map((section) => section.sceneNumber),
|
|
[2, 1, 2],
|
|
);
|
|
});
|