Initial Commit
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import type { BlockContext, ProcessorConfig } from "@suaveplan/audio-core";
|
||||
import type {
|
||||
ChainContext,
|
||||
ChainHandle,
|
||||
} from "@suaveplan/audio-fx-plugin-base/base";
|
||||
import { defaultAmbientState } from "@suaveplan/audio-fx-plugin-base/chain";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { SpessaSynthPlugin } from "../src/plugin/spessasynth-plugin.js";
|
||||
import { buildFakeSf2Bytes, makeStubModule } from "./stub-module.js";
|
||||
|
||||
const SR = 48_000;
|
||||
const BLOCK = 64;
|
||||
|
||||
function newCtx(): ChainContext {
|
||||
const chain: ChainHandle = {
|
||||
id: "auto",
|
||||
name: "auto",
|
||||
channels: 2,
|
||||
sampleRate: SR,
|
||||
blockSize: BLOCK,
|
||||
slotCount: 1,
|
||||
};
|
||||
return {
|
||||
chain,
|
||||
position: { index: 0, total: 1 },
|
||||
predecessors: [],
|
||||
successors: [],
|
||||
ambient: defaultAmbientState(),
|
||||
buses: { get: () => undefined, list: () => [] },
|
||||
};
|
||||
}
|
||||
|
||||
describe("automation: master gain ramp affects rendered output", () => {
|
||||
it("master gain set to 0 produces silence; set to 2 produces louder output", {
|
||||
timeout: 5_000,
|
||||
}, async () => {
|
||||
const mod = makeStubModule();
|
||||
const plugin = new SpessaSynthPlugin({
|
||||
moduleLoader: async () => mod,
|
||||
});
|
||||
await plugin.init({
|
||||
soundFontBytes: buildFakeSf2Bytes(64),
|
||||
sampleRate: SR,
|
||||
});
|
||||
const config: ProcessorConfig = {
|
||||
sampleRate: SR,
|
||||
channels: 2,
|
||||
blockSize: BLOCK,
|
||||
};
|
||||
await plugin.setup(config, newCtx());
|
||||
|
||||
plugin.sendMidi({
|
||||
type: "noteOn",
|
||||
channel: 0,
|
||||
note: 60,
|
||||
velocity: 100,
|
||||
});
|
||||
|
||||
const ctx: BlockContext = {
|
||||
blockOffsetSamples: 0,
|
||||
sampleRate: SR,
|
||||
channels: 2,
|
||||
opts: {},
|
||||
};
|
||||
|
||||
// Render with master gain 1 (default).
|
||||
const outBaseline: Float32Array[] = [
|
||||
new Float32Array(BLOCK),
|
||||
new Float32Array(BLOCK),
|
||||
];
|
||||
plugin.process([], outBaseline, ctx);
|
||||
let baselineRms = 0;
|
||||
for (let i = 0; i < BLOCK; i++) baselineRms += outBaseline[0]![i]! ** 2;
|
||||
baselineRms = Math.sqrt(baselineRms / BLOCK);
|
||||
expect(baselineRms).toBeGreaterThan(0);
|
||||
|
||||
// Silence: master 0.
|
||||
plugin.setParameter("master/gain", 0);
|
||||
const outSilent: Float32Array[] = [
|
||||
new Float32Array(BLOCK),
|
||||
new Float32Array(BLOCK),
|
||||
];
|
||||
plugin.process([], outSilent, ctx);
|
||||
let silentRms = 0;
|
||||
for (let i = 0; i < BLOCK; i++) {
|
||||
silentRms += outSilent[0]![i]! ** 2;
|
||||
}
|
||||
expect(silentRms).toBe(0);
|
||||
|
||||
// Loud: master 2.
|
||||
plugin.setParameter("master/gain", 2);
|
||||
const outLoud: Float32Array[] = [
|
||||
new Float32Array(BLOCK),
|
||||
new Float32Array(BLOCK),
|
||||
];
|
||||
plugin.process([], outLoud, ctx);
|
||||
let loudRms = 0;
|
||||
for (let i = 0; i < BLOCK; i++) loudRms += outLoud[0]![i]! ** 2;
|
||||
loudRms = Math.sqrt(loudRms / BLOCK);
|
||||
// Should be roughly 2× the baseline.
|
||||
expect(loudRms).toBeGreaterThan(baselineRms);
|
||||
});
|
||||
|
||||
it("channel volume parameter flushes to CC#7 each block", {
|
||||
timeout: 5_000,
|
||||
}, async () => {
|
||||
const mod = makeStubModule();
|
||||
const plugin = new SpessaSynthPlugin({
|
||||
moduleLoader: async () => mod,
|
||||
});
|
||||
await plugin.init({
|
||||
soundFontBytes: buildFakeSf2Bytes(64),
|
||||
sampleRate: SR,
|
||||
});
|
||||
await plugin.setup(
|
||||
{ sampleRate: SR, channels: 2, blockSize: BLOCK },
|
||||
newCtx()
|
||||
);
|
||||
plugin.setParameter("channel/0/volume", 1);
|
||||
const out: Float32Array[] = [
|
||||
new Float32Array(BLOCK),
|
||||
new Float32Array(BLOCK),
|
||||
];
|
||||
plugin.process([], out, {
|
||||
blockOffsetSamples: 0,
|
||||
sampleRate: SR,
|
||||
channels: 2,
|
||||
opts: {},
|
||||
});
|
||||
plugin.setParameter("channel/0/volume", 0);
|
||||
plugin.process([], out, {
|
||||
blockOffsetSamples: 0,
|
||||
sampleRate: SR,
|
||||
channels: 2,
|
||||
opts: {},
|
||||
});
|
||||
const proc = (
|
||||
mod as {
|
||||
lastProcessor?: {
|
||||
observer: {
|
||||
cc: Array<{
|
||||
channel: number;
|
||||
controller: number;
|
||||
value: number;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}
|
||||
).lastProcessor!;
|
||||
const ch0Vol = proc.observer.cc.filter(
|
||||
(e) => e.channel === 0 && e.controller === 7
|
||||
);
|
||||
// First flush wrote 127 (volume=1), second wrote 0.
|
||||
expect(ch0Vol[0]?.value).toBe(127);
|
||||
expect(ch0Vol[ch0Vol.length - 1]?.value).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user