Implement parsing for output compressor and sidechain effects state
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
Scene,
|
Scene,
|
||||||
SceneGroupPatternData,
|
SceneGroupPatternData,
|
||||||
ScenesSettings,
|
ScenesSettings,
|
||||||
|
SidechainGroupRouting,
|
||||||
SongPosition,
|
SongPosition,
|
||||||
Sound,
|
Sound,
|
||||||
TimeSignature,
|
TimeSignature,
|
||||||
@@ -608,12 +609,43 @@ export function collectEffects(files: TarFile[]) {
|
|||||||
const param2 = bytesToFloat32(
|
const param2 = bytesToFloat32(
|
||||||
fxFile.data.slice(76 + (effectType - 1) * 4, 80 + (effectType - 1) * 4),
|
fxFile.data.slice(76 + (effectType - 1) * 4, 80 + (effectType - 1) * 4),
|
||||||
);
|
);
|
||||||
|
const outputCompressor =
|
||||||
|
fxFile.data.length >= 144
|
||||||
|
? {
|
||||||
|
drive: bytesToFloat32(fxFile.data.slice(136, 140)),
|
||||||
|
speed: bytesToFloat32(fxFile.data.slice(140, 144)),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
const sidechain =
|
||||||
|
fxFile.data.length >= 152
|
||||||
|
? {
|
||||||
|
length: bytesToFloat32(fxFile.data.slice(144, 148)),
|
||||||
|
shape: bytesToFloat32(fxFile.data.slice(148, 152)),
|
||||||
|
routing:
|
||||||
|
fxFile.data.length >= 160
|
||||||
|
? (Object.fromEntries(
|
||||||
|
projectGroups.map((group, index) => {
|
||||||
|
const offset = 152 + index * 2;
|
||||||
|
const rawValue = fxFile.data[offset] | (fxFile.data[offset + 1] << 8);
|
||||||
|
const routing: SidechainGroupRouting = {
|
||||||
|
rawValue,
|
||||||
|
sourcePadMask: rawValue & 0x0fff,
|
||||||
|
duckedDestination: (rawValue & 0x8000) !== 0,
|
||||||
|
};
|
||||||
|
return [group, routing];
|
||||||
|
}),
|
||||||
|
) as Record<Group, SidechainGroupRouting>)
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rawData: fxFile.data,
|
rawData: fxFile.data,
|
||||||
effectType,
|
effectType,
|
||||||
param1,
|
param1,
|
||||||
param2,
|
param2,
|
||||||
|
...(outputCompressor ? { outputCompressor } : {}),
|
||||||
|
...(sidechain ? { sidechain } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,11 +60,30 @@ export type ScenesSettings = {
|
|||||||
timeSignature: TimeSignature;
|
timeSignature: TimeSignature;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OutputCompressorSettings = {
|
||||||
|
drive: number;
|
||||||
|
speed: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SidechainGroupRouting = {
|
||||||
|
rawValue: number;
|
||||||
|
sourcePadMask: number;
|
||||||
|
duckedDestination: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SidechainSettings = {
|
||||||
|
length: number;
|
||||||
|
shape: number;
|
||||||
|
routing?: Record<Group, SidechainGroupRouting>;
|
||||||
|
};
|
||||||
|
|
||||||
export type Effects = {
|
export type Effects = {
|
||||||
rawData: Uint8Array;
|
rawData: Uint8Array;
|
||||||
effectType: EffectType;
|
effectType: EffectType;
|
||||||
param1: number;
|
param1: number;
|
||||||
param2: number;
|
param2: number;
|
||||||
|
outputCompressor?: OutputCompressorSettings;
|
||||||
|
sidechain?: SidechainSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Pattern = {
|
export type Pattern = {
|
||||||
|
|||||||
68
tests/project-effects.test.ts
Normal file
68
tests/project-effects.test.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { collectEffects } from '../src/lib/parsers';
|
||||||
|
import { TarFile } from '../src/lib/untar';
|
||||||
|
|
||||||
|
function effectsFile(length: number) {
|
||||||
|
const data = new Uint8Array(length);
|
||||||
|
data[4] = 6;
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
file: new TarFile('fx_settings', data.length, data),
|
||||||
|
view: new DataView(data.buffer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('preserves selected FX parsing for short files without extended state', () => {
|
||||||
|
const fixture = effectsFile(100);
|
||||||
|
fixture.view.setFloat32(32, 0.25, true);
|
||||||
|
fixture.view.setFloat32(96, 0.75, true);
|
||||||
|
|
||||||
|
const effects = collectEffects([fixture.file]);
|
||||||
|
|
||||||
|
assert.equal(effects.effectType, 6);
|
||||||
|
assert.equal(effects.param1, 0.25);
|
||||||
|
assert.equal(effects.param2, 0.75);
|
||||||
|
assert.equal(effects.rawData, fixture.data);
|
||||||
|
assert.equal(effects.outputCompressor, undefined);
|
||||||
|
assert.equal(effects.sidechain, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parses output compressor state from a 144-byte file without sidechain state', () => {
|
||||||
|
const fixture = effectsFile(144);
|
||||||
|
fixture.view.setFloat32(136, 0.25, true);
|
||||||
|
fixture.view.setFloat32(140, 0.75, true);
|
||||||
|
|
||||||
|
const effects = collectEffects([fixture.file]);
|
||||||
|
|
||||||
|
assert.deepEqual(effects.outputCompressor, { drive: 0.25, speed: 0.75 });
|
||||||
|
assert.equal(effects.sidechain, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parses P07-like output dynamics and sidechain state from a 160-byte file', () => {
|
||||||
|
const fixture = effectsFile(160);
|
||||||
|
fixture.view.setFloat32(32, 0.5, true);
|
||||||
|
fixture.view.setFloat32(96, 0.5, true);
|
||||||
|
fixture.view.setFloat32(136, 0.5, true);
|
||||||
|
fixture.view.setFloat32(140, 0.5, true);
|
||||||
|
fixture.view.setFloat32(144, 0, true);
|
||||||
|
fixture.view.setFloat32(148, 0.5, true);
|
||||||
|
fixture.view.setUint16(152, 0x0600, true);
|
||||||
|
fixture.view.setUint16(154, 0x8000, true);
|
||||||
|
fixture.view.setUint16(156, 0x8000, true);
|
||||||
|
fixture.view.setUint16(158, 0x8000, true);
|
||||||
|
|
||||||
|
const effects = collectEffects([fixture.file]);
|
||||||
|
|
||||||
|
assert.deepEqual(effects.outputCompressor, { drive: 0.5, speed: 0.5 });
|
||||||
|
assert.deepEqual(effects.sidechain, {
|
||||||
|
length: 0,
|
||||||
|
shape: 0.5,
|
||||||
|
routing: {
|
||||||
|
a: { rawValue: 0x0600, sourcePadMask: 0x0600, duckedDestination: false },
|
||||||
|
b: { rawValue: 0x8000, sourcePadMask: 0, duckedDestination: true },
|
||||||
|
c: { rawValue: 0x8000, sourcePadMask: 0, duckedDestination: true },
|
||||||
|
d: { rawValue: 0x8000, sourcePadMask: 0, duckedDestination: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user