Files
ep-133-project-export/tests/pattern-events.test.ts

117 lines
3.3 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { collectScenesAndPatterns, parsePatternEvents } from '../src/lib/parsers';
import { TarFile } from '../src/lib/untar';
function buildPattern(records: number[][], bars: number = 4, declaredCount = records.length) {
const data = new Uint8Array(4 + records.length * 8);
data[1] = bars;
data[2] = declaredCount & 0xff;
data[3] = declaredCount >> 8;
records.forEach((record, index) => {
data.set(record, 4 + index * 8);
});
return data;
}
test('parses note records without changing the derived note shape', () => {
const result = parsePatternEvents(buildPattern([[0x34, 0x12, 3 << 3, 64, 99, 0x78, 0x56, 7]]));
assert.equal(result.bars, 4);
assert.equal(result.recordCount, 1);
assert.deepEqual(result.notes, {
3: [{ note: 64, position: 0x1234, duration: 0x5678, velocity: 99 }],
});
assert.deepEqual(result.events[0], {
type: 'note',
kind: 0,
position: 0x1234,
source: 3,
flags: 7,
rawData: new Uint8Array([0x34, 0x12, 3 << 3, 64, 99, 0x78, 0x56, 7]),
pad: 3,
note: 64,
velocity: 99,
duration: 0x5678,
});
});
test('parses and normalizes fader records', () => {
const result = parsePatternEvents(buildPattern([[0x60, 0, 1, 7, 9, 0xff, 0x7f, 128]]));
assert.deepEqual(result.notes, {});
assert.deepEqual(result.events[0], {
type: 'fader',
kind: 1,
position: 96,
source: 0,
flags: 128,
rawData: new Uint8Array([0x60, 0, 1, 7, 9, 0xff, 0x7f, 128]),
parameter: 7,
reserved: 9,
rawValue: 32767,
value: 1,
});
});
test('preserves MIDI and unknown records', () => {
const midi = [0x27, 0, (2 << 3) | 2, 0xd0, 0x62, 0xff, 0, 0x3d];
const unknown = [2, 0, (5 << 3) | 6, 10, 11, 12, 13, 14];
const result = parsePatternEvents(buildPattern([midi, unknown]));
assert.deepEqual(result.events[0], {
type: 'midi',
kind: 2,
position: 0x27,
source: 2,
flags: 0x3d,
rawData: new Uint8Array(midi),
data: new Uint8Array([0xd0, 0x62, 0xff, 0]),
});
assert.deepEqual(result.events[1], {
type: 'unknown',
kind: 6,
position: 2,
source: 5,
flags: 14,
rawData: new Uint8Array(unknown),
data: new Uint8Array([10, 11, 12, 13]),
});
});
test('retains MIDI-only sources as empty-note scene patterns', () => {
const patternData = buildPattern([[0x27, 0, (2 << 3) | 2, 0xd0, 0x62, 0xff, 0, 0x3d]]);
const scenes = collectScenesAndPatterns(
[new TarFile('patterns/a01', patternData.length, patternData)],
'TE032AS001',
);
assert.equal(scenes.length, 1);
assert.equal(scenes[0].patterns.length, 1);
assert.equal(scenes[0].patterns[0].pad, 'a2');
assert.deepEqual(scenes[0].patterns[0].notes, []);
assert.equal(scenes[0].patternEvents.a?.events.length, 1);
});
test('uses the uint16 header count and ignores undeclared or partial records', () => {
const records = Array.from({ length: 300 }, (_, index) => [
index & 0xff,
index >> 8,
0,
60,
100,
1,
0,
0,
]);
const data = buildPattern([...records, [0, 0, 1, 0, 0, 0, 0, 0]], 8, 300);
const truncated = data.slice(0, data.length - 3);
const result = parsePatternEvents(truncated);
assert.equal(result.recordCount, 300);
assert.equal(result.events.length, 300);
assert.equal(result.events[299].position, 299);
});