/** * Real `MidiAccessLike` + `MidiAccessAdapter` fixtures — mirrors the * `tests/fake-midi.ts` shipped by `@suaveplan/audio-controllers`. Lives * outside `src/` so it does not appear in coverage reports yet remains * shared by every Phase-3 unit / story / integration test. * * Re-implements the audio-controllers fixture locally because that * fixture is not published through the package exports. The shape is * intentionally identical so the controller plumbing is unchanged. */ import type { MidiAccessAdapter, MidiAccessLike, MidiInputLike, MidiOutputLike, } from "@suaveplan/audio-controllers"; export interface FakeMidiInput extends MidiInputLike { emit(data: ReadonlyArray, timeStamp?: number): void; } export interface FakeMidiOutput extends MidiOutputLike { readonly sent: number[][]; disconnect(): void; } export interface FakeMidiAccess extends MidiAccessLike { readonly mutableInputs: Map; readonly mutableOutputs: Map; disconnectPort(port: FakeMidiInput | FakeMidiOutput): void; reconnectPort(port: FakeMidiInput | FakeMidiOutput): void; } function createInput(id: string, name: string): FakeMidiInput { const port: FakeMidiInput = { id, name, state: "connected", type: "input", onmidimessage: null, emit(data: ReadonlyArray, timeStamp = 0) { port.onmidimessage?.({ data: new Uint8Array(data), timeStamp, }); }, } as FakeMidiInput; return port; } function createOutput(id: string, name: string): FakeMidiOutput { const sent: number[][] = []; let connectedState: "connected" | "disconnected" = "connected"; const port = { id, name, get state(): "connected" | "disconnected" { return connectedState; }, type: "output" as const, sent, send(data: ReadonlyArray | Uint8Array) { sent.push(Array.isArray(data) ? data.slice() : Array.from(data)); }, disconnect() { connectedState = "disconnected"; }, } satisfies FakeMidiOutput; return port; } export function createFakeAccess( options: { readonly inputName?: string; readonly outputName?: string } = {} ): FakeMidiAccess { const inputName = options.inputName ?? "Demo Input"; const outputName = options.outputName ?? "Demo Output"; const input = createInput("in-1", inputName); const output = createOutput("out-1", outputName); const mutableInputs = new Map([[input.id, input]]); const mutableOutputs = new Map([ [output.id, output], ]); const access: FakeMidiAccess = { get inputs(): ReadonlyMap { return mutableInputs; }, get outputs(): ReadonlyMap { return mutableOutputs; }, mutableInputs, mutableOutputs, onstatechange: null, disconnectPort(port) { if (port.type === "input") mutableInputs.delete(port.id); else { port.disconnect(); mutableOutputs.delete(port.id); } access.onstatechange?.({ port: { ...port, state: "disconnected" }, }); }, reconnectPort(port) { if (port.type === "input") mutableInputs.set(port.id, port); else mutableOutputs.set(port.id, port); access.onstatechange?.({ port: { ...port, state: "connected" }, }); }, }; return access; } export function createFakeAdapter( access: FakeMidiAccess, options: { readonly reject?: Error } = {} ): MidiAccessAdapter { return { async requestAccess() { if (options.reject) throw options.reject; return access; }, }; } export function getFirstInput(access: FakeMidiAccess): FakeMidiInput { const first = access.mutableInputs.values().next().value; if (!first) throw new Error("no input"); return first; }