122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
import type {
|
|
WorkerLike,
|
|
WorkerRequest,
|
|
WorkerResponse,
|
|
} from "../../src/worker/worker-host.types.js";
|
|
|
|
type MessageListener = (ev: MessageEvent) => void;
|
|
type ErrorListener = (ev: ErrorEvent) => void;
|
|
|
|
/**
|
|
* Deterministic `Worker` shim. Tests script the response sequence via the
|
|
* `script` array; each posted request consumes the next handler. Out-of-script
|
|
* requests are echoed back as `ok: false` with a fixed stderr so the host
|
|
* surfaces the matching error code.
|
|
*/
|
|
export class FakeWorker implements WorkerLike {
|
|
public posted: WorkerRequest[] = [];
|
|
public terminated = 0;
|
|
public messageListeners: MessageListener[] = [];
|
|
public errorListeners: ErrorListener[] = [];
|
|
public script: Array<(req: WorkerRequest) => WorkerResponse | "drop"> = [];
|
|
|
|
public postMessage(message: unknown): void {
|
|
const req = message as WorkerRequest;
|
|
this.posted.push(req);
|
|
const handler = this.script.shift();
|
|
if (!handler) {
|
|
queueMicrotask(() => {
|
|
this.emitMessage({
|
|
id: req.id,
|
|
ok: false,
|
|
stderr: "no-handler",
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
const result = handler(req);
|
|
if (result === "drop") return;
|
|
queueMicrotask(() => this.emitMessage(result));
|
|
}
|
|
|
|
public emitMessage(data: unknown): void {
|
|
const ev = { data } as MessageEvent;
|
|
for (const l of this.messageListeners) l(ev);
|
|
}
|
|
|
|
public emitError(message: string, error?: Error): void {
|
|
const ev = { message, error } as unknown as ErrorEvent;
|
|
for (const l of this.errorListeners) l(ev);
|
|
}
|
|
|
|
public addEventListener(
|
|
type: "message" | "error",
|
|
listener: MessageListener | ErrorListener
|
|
): void {
|
|
if (type === "message")
|
|
this.messageListeners.push(listener as MessageListener);
|
|
else this.errorListeners.push(listener as ErrorListener);
|
|
}
|
|
|
|
public removeEventListener(
|
|
type: "message" | "error",
|
|
listener: MessageListener | ErrorListener
|
|
): void {
|
|
if (type === "message")
|
|
this.messageListeners = this.messageListeners.filter(
|
|
(l) => l !== listener
|
|
);
|
|
else
|
|
this.errorListeners = this.errorListeners.filter(
|
|
(l) => l !== listener
|
|
);
|
|
}
|
|
|
|
public terminate(): void {
|
|
this.terminated++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Standard success script: `load` resolves, `writeFile` resolves, `run`
|
|
* resolves with the provided output bytes and stderr, `readFile` resolves
|
|
* with those bytes, both `deleteFile` calls resolve. Drop into the worker's
|
|
* `script` array.
|
|
*/
|
|
export function successScript(
|
|
outputBytes: Uint8Array,
|
|
stderr = ""
|
|
): Array<(req: WorkerRequest) => WorkerResponse> {
|
|
return [
|
|
(req) => ({ id: req.id, ok: true }), // load
|
|
(req) => ({ id: req.id, ok: true }), // writeFile
|
|
(req) => ({ id: req.id, ok: true, value: { stderr } }), // run
|
|
(req) => ({ id: req.id, ok: true, value: outputBytes }), // readFile
|
|
(req) => ({ id: req.id, ok: true }), // deleteFile input
|
|
(req) => ({ id: req.id, ok: true }), // deleteFile output
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Build a deterministic `Float32Array` interleaved into `Uint8Array` form
|
|
* for use as the `readFile` payload.
|
|
*/
|
|
export function buildPcmBytes(
|
|
frames: number,
|
|
channels: number,
|
|
pattern: (frame: number, channel: number) => number = (f, c) =>
|
|
Math.sin((f * (c + 1)) / 100) * 0.5
|
|
): Uint8Array {
|
|
const interleaved = new Float32Array(frames * channels);
|
|
for (let f = 0; f < frames; f++) {
|
|
for (let c = 0; c < channels; c++) {
|
|
interleaved[f * channels + c] = pattern(f, c);
|
|
}
|
|
}
|
|
return new Uint8Array(
|
|
interleaved.buffer,
|
|
interleaved.byteOffset,
|
|
interleaved.byteLength
|
|
);
|
|
}
|