import { readFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; 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"; const FIXTURE_URL = new URL("./fixtures/tiny-piano.sf2", import.meta.url); const FIXTURE_PATH = fileURLToPath(FIXTURE_URL); const SR = 48_000; const BLOCK = 512; function newCtx(): ChainContext { const chain: ChainHandle = { id: "integration", name: "integration", channels: 2, sampleRate: SR, blockSize: BLOCK, slotCount: 1, }; return { chain, position: { index: 0, total: 1 }, predecessors: [], successors: [], ambient: defaultAmbientState(), buses: { get: () => undefined, list: () => [] }, }; } function newConfig(): ProcessorConfig { return { sampleRate: SR, channels: 2, blockSize: BLOCK }; } function blockCtx(): BlockContext { return { blockOffsetSamples: 0, sampleRate: SR, channels: 2, opts: {}, }; } function rms(buf: Float32Array): number { let sum = 0; for (let i = 0; i < buf.length; i++) sum += buf[i]! ** 2; return Math.sqrt(sum / buf.length); } describe("integration: render via the vendored tiny-piano SF2 fixture", () => { it("renders a C-major chord with non-zero RMS through process()", { timeout: 30_000, }, async () => { const bytes = new Uint8Array(await readFile(FIXTURE_PATH)); const plugin = new SpessaSynthPlugin(); await plugin.init({ soundFontBytes: bytes, sampleRate: SR, }); await plugin.setup(newConfig(), newCtx()); // C major: C4, E4, G4. for (const note of [60, 64, 67]) { plugin.sendMidi({ type: "noteOn", channel: 0, note, velocity: 100, }); } // Render half a second. const blocks = Math.ceil(SR / 2 / BLOCK); const merged = new Float32Array(blocks * BLOCK); for (let b = 0; b < blocks; b++) { const out: Float32Array[] = [ new Float32Array(BLOCK), new Float32Array(BLOCK), ]; plugin.process([], out, blockCtx()); merged.set(out[0]!, b * BLOCK); } const r = rms(merged); expect(r).toBeGreaterThan(0); plugin.dispose(); }); });