Update pad parser to handle fractional pitch and trim length correctly
This commit is contained in:
@@ -167,10 +167,15 @@ export function collectPads(files: TarFile[], sounds: Sound[]) {
|
|||||||
const soundId = (file.data[2] << 8) + file.data[1];
|
const soundId = (file.data[2] << 8) + file.data[1];
|
||||||
const sound = sounds.find((s) => s.id === soundId);
|
const sound = sounds.find((s) => s.id === soundId);
|
||||||
const pitch = file.data[17] <= 12 ? file.data[17] : -(256 - file.data[17]); // pitch from -12 to +12
|
const pitch = file.data[17] <= 12 ? file.data[17] : -(256 - file.data[17]); // pitch from -12 to +12
|
||||||
const pitchDecimal = file.data[26];
|
const pitchFraction = (file.data[26] ?? 0) / 100;
|
||||||
const pan = (file.data[18] >= 240 ? -(256 - file.data[18]) : file.data[18]) / 16; // normalized pan
|
const pan = (file.data[18] >= 240 ? -(256 - file.data[18]) : file.data[18]) / 16; // normalized pan
|
||||||
const trimLeft = (file.data[6] << 16) + (file.data[5] << 8) + file.data[4];
|
const trimLeft = (file.data[6] << 16) + (file.data[5] << 8) + file.data[4];
|
||||||
const trimRight = trimLeft + (file.data[10] << 16) + (file.data[9] << 8) + file.data[8];
|
const trimLength = new DataView(
|
||||||
|
file.data.buffer,
|
||||||
|
file.data.byteOffset,
|
||||||
|
file.data.byteLength,
|
||||||
|
).getUint32(8, true);
|
||||||
|
const trimRight = trimLeft + trimLength;
|
||||||
|
|
||||||
result[group.id].push({
|
result[group.id].push({
|
||||||
pad: i,
|
pad: i,
|
||||||
@@ -187,12 +192,12 @@ export function collectPads(files: TarFile[], sounds: Sound[]) {
|
|||||||
trimRight,
|
trimRight,
|
||||||
playMode: file.data[23] === 0 ? 'oneshot' : file.data[23] === 1 ? 'key' : 'legato',
|
playMode: file.data[23] === 0 ? 'oneshot' : file.data[23] === 1 ? 'key' : 'legato',
|
||||||
soundLength: sound ? calculateSoundLength(sound) : 0,
|
soundLength: sound ? calculateSoundLength(sound) : 0,
|
||||||
pitch: Math.max(-12, Math.min(12, parseFloat(`${pitch}.${pitchDecimal}`))),
|
pitch: Math.max(-12, Math.min(12, pitch + pitchFraction)),
|
||||||
rootNote: file.data[24],
|
rootNote: file.data[24],
|
||||||
timeStretch: timeStretch(file.data[21]),
|
timeStretch: timeStretch(file.data[21]),
|
||||||
timeStretchBpm: Number(bytesToFloat32(file.data.slice(12, 16)).toFixed(2)),
|
timeStretchBpm: Number(bytesToFloat32(file.data.slice(12, 16)).toFixed(2)),
|
||||||
timeStretchBars: timeStretchBars(file.data[25]),
|
timeStretchBars: timeStretchBars(file.data[25]),
|
||||||
inChokeGroup: file.data[22] === 1,
|
inChokeGroup: file.data[22] !== 0,
|
||||||
midiChannel: file.data[3],
|
midiChannel: file.data[3],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
44
tests/pad-parser.test.ts
Normal file
44
tests/pad-parser.test.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { collectPads } from '../src/lib/parsers';
|
||||||
|
import { TarFile } from '../src/lib/untar';
|
||||||
|
|
||||||
|
function padFile(name: string, length: 26 | 27) {
|
||||||
|
const data = new Uint8Array(length);
|
||||||
|
return { data, file: new TarFile(name, data.length, data) };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('decodes optional fractional pitch arithmetically for 26- and 27-byte pad records', () => {
|
||||||
|
const native = padFile('pads/a/p01', 26);
|
||||||
|
native.data[17] = 0xff;
|
||||||
|
const extended = padFile('pads/a/p02', 27);
|
||||||
|
extended.data[17] = 0xff;
|
||||||
|
extended.data[26] = 91;
|
||||||
|
|
||||||
|
const pads = collectPads([native.file, extended.file], []);
|
||||||
|
|
||||||
|
assert.equal(pads.a[0].pitch, -1);
|
||||||
|
assert.ok(Math.abs(pads.a[1].pitch - -0.09) < 1e-12);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reads trim length as unsigned 32-bit little-endian data', () => {
|
||||||
|
const pad = padFile('pads/a/p01', 26);
|
||||||
|
pad.data.set([0x03, 0x02, 0x01], 4);
|
||||||
|
pad.data.set([0x04, 0x03, 0x02, 0x01], 8);
|
||||||
|
|
||||||
|
const parsed = collectPads([pad.file], []).a[0];
|
||||||
|
|
||||||
|
assert.equal(parsed.trimLeft, 0x010203);
|
||||||
|
assert.equal(parsed.trimRight, 0x010203 + 0x01020304);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats every nonzero mute byte as shared mute-group membership', () => {
|
||||||
|
const member = padFile('pads/a/p01', 26);
|
||||||
|
const nonmember = padFile('pads/a/p02', 26);
|
||||||
|
member.data[22] = 200;
|
||||||
|
|
||||||
|
const pads = collectPads([member.file, nonmember.file], []).a;
|
||||||
|
|
||||||
|
assert.equal(pads[0].inChokeGroup, true);
|
||||||
|
assert.equal(pads[1].inChokeGroup, false);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user