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,221 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
AUDIO_FX_CODES,
type AudioFxError,
} from "../error-codes/error-codes.js";
import { DefaultBusRegistry } from "./bus-registry.js";
function frame(samples: number[], channels = 1): Float32Array[] {
const out: Float32Array[] = [];
for (let ch = 0; ch < channels; ch++) {
out.push(Float32Array.from(samples));
}
return out;
}
describe("DefaultBusRegistry: publish + read round-trip", () => {
it("returns undefined for an unknown bus", { timeout: 5_000 }, () => {
const r = new DefaultBusRegistry();
expect(r.get("missing")).toBeUndefined();
expect(r.list()).toEqual([]);
});
it("publishes a named bus reachable via get + list", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const pub = r.publish("kick", 2);
expect(pub.bus.name).toBe("kick");
expect(pub.bus.channels).toBe(2);
expect(r.get("kick")).toBe(pub.bus);
expect(r.list()).toEqual(["kick"]);
});
it("round-trips a buffer from write to read", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const pub = r.publish("kick", 1);
pub.write(frame([0.1, 0.2, 0.3]));
const scratch = [new Float32Array(3)];
r.get("kick")!.read(scratch);
expect(Array.from(scratch[0]!)).toEqual([
expect.closeTo(0.1, 6),
expect.closeTo(0.2, 6),
expect.closeTo(0.3, 6),
]);
});
it("reads silence before any write", { timeout: 5_000 }, () => {
const r = new DefaultBusRegistry();
r.publish("kick", 1);
const scratch = [new Float32Array(4)];
r.get("kick")!.read(scratch);
expect(Array.from(scratch[0]!)).toEqual([0, 0, 0, 0]);
});
it("pads with silence when scratch is larger than the written frame", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const pub = r.publish("kick", 1);
pub.write(frame([1, 1]));
const scratch = [new Float32Array(4)];
r.get("kick")!.read(scratch);
expect(Array.from(scratch[0]!)).toEqual([1, 1, 0, 0]);
});
it("reuses the accumulator when consecutive writes are the same size", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const pub = r.publish("kick", 1);
pub.write(frame([1, 2, 3]));
pub.write(frame([4, 5, 6])); // same size — accumulator reused
const scratch = [new Float32Array(3)];
r.get("kick")!.read(scratch);
expect(Array.from(scratch[0]!)).toEqual([4, 5, 6]);
});
});
describe("DefaultBusRegistry: channel-count enforcement", () => {
it("throws BUS_CHANNEL_MISMATCH when read scratch length differs", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
r.publish("kick", 2);
try {
r.get("kick")!.read([new Float32Array(4)]);
} catch (error) {
expect((error as AudioFxError).code).toBe(
AUDIO_FX_CODES.BUS_CHANNEL_MISMATCH
);
expect((error as AudioFxError).context).toEqual({
bus: "kick",
expected: 2,
actual: 1,
});
return;
}
throw new Error("expected BUS_CHANNEL_MISMATCH");
});
it("throws BUS_CHANNEL_MISMATCH when write frame length differs", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const pub = r.publish("kick", 1);
try {
pub.write(frame([1, 1], 2));
} catch (error) {
expect((error as AudioFxError).code).toBe(
AUDIO_FX_CODES.BUS_CHANNEL_MISMATCH
);
return;
}
throw new Error("expected BUS_CHANNEL_MISMATCH");
});
});
describe("DefaultBusRegistry: close semantics", () => {
it("close removes the bus from get/list and reads as silence", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const pub = r.publish("kick", 1);
const bus = pub.bus;
pub.write(frame([1, 1]));
pub.close();
expect(r.get("kick")).toBeUndefined();
expect(r.list()).toEqual([]);
// The local bus handle is still callable but reads silence.
const scratch = [new Float32Array(2)];
bus.read(scratch);
expect(Array.from(scratch[0]!)).toEqual([0, 0]);
});
it("close is idempotent", { timeout: 5_000 }, () => {
const r = new DefaultBusRegistry();
const pub = r.publish("kick", 1);
pub.close();
// second close must be a no-op
expect(() => pub.close()).not.toThrow();
});
it("write on a closed publisher is a no-op", { timeout: 5_000 }, () => {
const r = new DefaultBusRegistry();
const pub = r.publish("kick", 1);
pub.close();
expect(() => pub.write(frame([1]))).not.toThrow();
});
it("republishing the same name closes the previous publisher", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const first = r.publish("kick", 1);
const events: Array<unknown> = [];
r.subscribe("kick", (b) => events.push(b));
const second = r.publish("kick", 2);
expect(r.get("kick")).toBe(second.bus);
expect(first.bus.channels).toBe(1);
// subscribers see: close of first (undefined), then second (bus)
expect(events.length).toBe(2);
expect(events[0]).toBeUndefined();
expect(events[1]).toBe(second.bus);
});
});
describe("DefaultBusRegistry: subscribe", () => {
it("fires the listener on publish and on close", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const events: Array<unknown> = [];
const unsub = r.subscribe("kick", (b) => events.push(b));
const pub = r.publish("kick", 1);
pub.close();
unsub();
expect(events.length).toBe(2);
expect(events[0]).toBe(pub.bus);
expect(events[1]).toBeUndefined();
});
it("multiple listeners on the same name all fire", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
let countA = 0;
let countB = 0;
r.subscribe("kick", () => countA++);
r.subscribe("kick", () => countB++);
r.publish("kick", 1);
expect(countA).toBe(1);
expect(countB).toBe(1);
});
it("unsubscribe removes only that listener; cleans up empty buckets", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
let countA = 0;
let countB = 0;
const unsubA = r.subscribe("kick", () => countA++);
r.subscribe("kick", () => countB++);
unsubA();
r.publish("kick", 1);
expect(countA).toBe(0);
expect(countB).toBe(1);
});
it("subscribe before any publish is harmless", {
timeout: 5_000,
}, () => {
const r = new DefaultBusRegistry();
const events: Array<unknown> = [];
r.subscribe("kick", (b) => events.push(b));
expect(events.length).toBe(0);
r.publish("kick", 1);
expect(events.length).toBe(1);
});
});