Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -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);
});
});