Agent completed work jubii

This commit is contained in:
Lucas Winther
2026-07-09 08:07:32 +02:00
parent 6466d3a45c
commit eb72a2e646
29 changed files with 2051 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
import { describe, test, expect } from "bun:test";
import {
TARGET_SAMPLE_RATE,
downmixToMono,
resampleLinear,
} from "../src/lib/audio";
// Pure helpers only — no browser APIs, no network, no model weights.
describe("downmixToMono", () => {
test("throws on zero channels", () => {
expect(() => downmixToMono([])).toThrow();
});
test("single channel is copied, not aliased", () => {
const channel = Float32Array.from([0.1, -0.5, 1]);
const mono = downmixToMono([channel]);
expect(Array.from(mono)).toEqual(Array.from(channel));
mono[0] = 42;
expect(channel[0]).toBeCloseTo(0.1);
});
test("averages stereo channels", () => {
const left = Float32Array.from([1, 0, -1, 0.5]);
const right = Float32Array.from([0, 0, 1, 0.5]);
const mono = downmixToMono([left, right]);
expect(Array.from(mono)).toEqual([0.5, 0, 0, 0.5]);
});
test("truncates to the shortest channel", () => {
const a = Float32Array.from([1, 1, 1]);
const b = Float32Array.from([1, 1]);
expect(downmixToMono([a, b]).length).toBe(2);
});
});
describe("resampleLinear", () => {
test("rejects non-positive rates", () => {
const input = new Float32Array(4);
expect(() => resampleLinear(input, 0, 16_000)).toThrow();
expect(() => resampleLinear(input, 44_100, -1)).toThrow();
});
test("identity when rates match (copy, not alias)", () => {
const input = Float32Array.from([0.25, -0.25]);
const out = resampleLinear(input, 16_000, 16_000);
expect(Array.from(out)).toEqual([0.25, -0.25]);
out[0] = 9;
expect(input[0]).toBeCloseTo(0.25);
});
test("empty input stays empty", () => {
expect(resampleLinear(new Float32Array(0), 44_100, 16_000).length).toBe(0);
});
test("output length scales with the rate ratio", () => {
const oneSecondAt48k = new Float32Array(48_000);
const out = resampleLinear(oneSecondAt48k, 48_000, TARGET_SAMPLE_RATE);
expect(out.length).toBe(TARGET_SAMPLE_RATE);
});
test("preserves a constant (DC) signal", () => {
const input = new Float32Array(1000).fill(0.7);
const out = resampleLinear(input, 44_100, 16_000);
for (const sample of out) {
expect(sample).toBeCloseTo(0.7, 5);
}
});
test("downsampling a linear ramp stays a linear ramp", () => {
const n = 800;
const ramp = new Float32Array(n);
for (let i = 0; i < n; i++) ramp[i] = i / (n - 1);
const out = resampleLinear(ramp, 32_000, 16_000);
// Linear interpolation reproduces a linear function exactly at every
// sampled position: out[i] ≈ (i * step) / (n - 1).
const step = 32_000 / 16_000;
for (let i = 0; i < out.length; i++) {
const expected = Math.min((i * step) / (n - 1), 1);
expect(out[i]!).toBeCloseTo(expected, 5);
}
});
test("upsampling interpolates between neighbours", () => {
const input = Float32Array.from([0, 1]);
const out = resampleLinear(input, 1, 2);
expect(out.length).toBe(4);
expect(out[0]).toBeCloseTo(0);
expect(out[1]).toBeCloseTo(0.5);
expect(out[2]).toBeCloseTo(1); // clamped at the last sample
expect(out[3]).toBeCloseTo(1);
});
});
+142
View File
@@ -0,0 +1,142 @@
import { describe, test, expect } from "bun:test";
import {
applyLoadProgress,
initialLoadProgress,
isUiToWorkerMessage,
isWorkerToUiMessage,
type LoadProgressMessage,
type UiToWorkerMessage,
type WorkerToUiMessage,
} from "../src/worker/protocol";
// Protocol handling only — no worker, no network, no model weights.
describe("isUiToWorkerMessage", () => {
test("accepts valid load and transcribe messages", () => {
const load: UiToWorkerMessage = {
type: "load",
modelId: "onnx-community/whisper-tiny.en",
device: "wasm",
};
const transcribe: UiToWorkerMessage = {
type: "transcribe",
audio: new Float32Array(8),
};
expect(isUiToWorkerMessage(load)).toBe(true);
expect(isUiToWorkerMessage(transcribe)).toBe(true);
});
test("rejects malformed messages", () => {
expect(isUiToWorkerMessage(null)).toBe(false);
expect(isUiToWorkerMessage("load")).toBe(false);
expect(isUiToWorkerMessage({ type: "load" })).toBe(false);
expect(isUiToWorkerMessage({ type: "load", modelId: "x", device: "cuda" })).toBe(false);
expect(isUiToWorkerMessage({ type: "transcribe", audio: [1, 2, 3] })).toBe(false);
expect(isUiToWorkerMessage({ type: "result", text: "", chunks: [] })).toBe(false);
});
});
describe("isWorkerToUiMessage", () => {
test("accepts every worker → UI message shape", () => {
const messages: WorkerToUiMessage[] = [
{ type: "load-progress", status: "initiate", file: "model.onnx" },
{ type: "load-progress", status: "download", file: "model.onnx" },
{
type: "load-progress",
status: "progress",
file: "model.onnx",
loaded: 10,
total: 100,
progress: 10,
},
{ type: "load-progress", status: "done", file: "model.onnx" },
{ type: "load-progress", status: "ready" },
{ type: "partial", text: " hello" },
{ type: "result", text: "hello world", chunks: [{ text: "hello world", timestamp: [0, 1.2] }] },
{ type: "error", stage: "load", message: "boom" },
];
for (const message of messages) {
expect(isWorkerToUiMessage(message)).toBe(true);
}
});
test("rejects malformed messages", () => {
expect(isWorkerToUiMessage(undefined)).toBe(false);
expect(isWorkerToUiMessage({})).toBe(false);
expect(isWorkerToUiMessage({ type: "load-progress", status: "progress", file: "f" })).toBe(false);
expect(isWorkerToUiMessage({ type: "partial" })).toBe(false);
expect(isWorkerToUiMessage({ type: "result", text: "x", chunks: "nope" })).toBe(false);
expect(isWorkerToUiMessage({ type: "error", stage: "decode", message: "m" })).toBe(false);
expect(isWorkerToUiMessage({ type: "load", modelId: "x", device: "wasm" })).toBe(false);
});
});
describe("applyLoadProgress", () => {
test("tracks per-file byte progress without mutating previous state", () => {
const p1: LoadProgressMessage = { type: "load-progress", status: "initiate", file: "a.onnx" };
const p2: LoadProgressMessage = {
type: "load-progress",
status: "progress",
file: "a.onnx",
loaded: 50,
total: 200,
progress: 25,
};
const s1 = applyLoadProgress(initialLoadProgress, p1);
const s2 = applyLoadProgress(s1, p2);
expect(initialLoadProgress.files).toEqual({});
expect(s1.files["a.onnx"]).toEqual({ file: "a.onnx", loaded: 0, total: 0, done: false });
expect(s1.files["a.onnx"]!.done).toBe(false); // s1 untouched by s2
expect(s2.files["a.onnx"]).toEqual({ file: "a.onnx", loaded: 50, total: 200, done: false });
expect(s2.ready).toBe(false);
});
test("tracks multiple files independently", () => {
let state = initialLoadProgress;
state = applyLoadProgress(state, {
type: "load-progress",
status: "progress",
file: "a.onnx",
loaded: 10,
total: 100,
progress: 10,
});
state = applyLoadProgress(state, {
type: "load-progress",
status: "progress",
file: "b.json",
loaded: 5,
total: 5,
progress: 100,
});
expect(Object.keys(state.files)).toEqual(["a.onnx", "b.json"]);
expect(state.files["a.onnx"]!.loaded).toBe(10);
expect(state.files["b.json"]!.total).toBe(5);
});
test("done snaps a file's loaded bytes to its total", () => {
let state = applyLoadProgress(initialLoadProgress, {
type: "load-progress",
status: "progress",
file: "a.onnx",
loaded: 199,
total: 200,
progress: 99.5,
});
state = applyLoadProgress(state, { type: "load-progress", status: "done", file: "a.onnx" });
expect(state.files["a.onnx"]).toEqual({ file: "a.onnx", loaded: 200, total: 200, done: true });
});
test("ready flips the ready flag and keeps file state", () => {
let state = applyLoadProgress(initialLoadProgress, {
type: "load-progress",
status: "done",
file: "a.onnx",
});
state = applyLoadProgress(state, { type: "load-progress", status: "ready" });
expect(state.ready).toBe(true);
expect(state.files["a.onnx"]!.done).toBe(true);
});
});
+6
View File
@@ -0,0 +1,6 @@
import { test, expect } from "bun:test";
// M0 smoke test: proves `bun test` runs in this repo.
test("bun test runs", () => {
expect(1 + 1).toBe(2);
});