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,139 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
type FrameChord,
matchChord,
mergeRuns,
smoothLabels,
} from "./chord-matching.js";
function chromaFor(pcs: number[]): Float32Array {
const v = new Float32Array(12);
for (const pc of pcs) v[pc] = 1;
return v;
}
describe("matchChord", () => {
it("matches a C major triad to C major", { timeout: 5000 }, () => {
const r = matchChord(chromaFor([0, 4, 7]));
expect(r.root).toBe("C");
expect(r.quality).toBe("major");
expect(r.confidence).toBeGreaterThan(0);
});
it("matches an A minor triad to A minor", { timeout: 5000 }, () => {
const r = matchChord(chromaFor([9, 0, 4]));
expect(r.root).toBe("A");
expect(r.quality).toBe("minor");
});
it("matches a diminished triad", { timeout: 5000 }, () => {
const r = matchChord(chromaFor([0, 3, 6]));
expect(r.quality).toBe("dim");
});
it("matches an augmented triad", { timeout: 5000 }, () => {
const r = matchChord(chromaFor([0, 4, 8]));
expect(r.quality).toBe("aug");
});
it("zero chroma returns the default with zero confidence", {
timeout: 5000,
}, () => {
const r = matchChord(new Float32Array(12));
expect(r.confidence).toBe(0);
});
});
describe("smoothLabels", () => {
it("collapses a single odd label between two identical neighbours", {
timeout: 5000,
}, () => {
const c: FrameChord[] = [
{ root: "C", quality: "major", confidence: 0.9 },
{ root: "G", quality: "major", confidence: 0.4 },
{ root: "C", quality: "major", confidence: 0.9 },
];
const s = smoothLabels(c);
expect(s[1]?.root).toBe("C");
});
it("leaves stable runs unchanged", { timeout: 5000 }, () => {
const c: FrameChord[] = [
{ root: "C", quality: "major", confidence: 0.9 },
{ root: "C", quality: "major", confidence: 0.9 },
];
expect(smoothLabels(c)).toEqual(c);
});
});
describe("mergeRuns", () => {
it("merges adjacent identical labels into single chord records", {
timeout: 5000,
}, () => {
const labels: FrameChord[] = [
{ root: "C", quality: "major", confidence: 0.9 },
{ root: "C", quality: "major", confidence: 0.95 },
{ root: "G", quality: "major", confidence: 0.8 },
{ root: "G", quality: "major", confidence: 0.85 },
];
const merged = mergeRuns(labels, { hopSeconds: 0.1, minDuration: 0 });
expect(merged.length).toBe(2);
expect(merged[0]?.root).toBe("C");
expect(merged[1]?.root).toBe("G");
expect(merged[0]?.timestamp).toBeLessThan(merged[1]?.timestamp ?? 0);
});
it("drops runs shorter than minDuration", { timeout: 5000 }, () => {
const labels: FrameChord[] = [
{ root: "C", quality: "major", confidence: 0.9 },
{ root: "G", quality: "major", confidence: 0.4 },
{ root: "G", quality: "major", confidence: 0.4 },
{ root: "C", quality: "major", confidence: 0.9 },
];
// Smoothing keeps middle G run (length 2), C bookends not — but
// minDuration=0.3 with hopSeconds=0.1 drops the single-frame C bookends.
const merged = mergeRuns(labels, { hopSeconds: 0.1, minDuration: 0.3 });
// Each pure run is too short — all dropped or none survive.
expect(merged.every((c) => c.duration >= 0.3)).toBe(true);
});
it("returns empty array for empty labels", { timeout: 5000 }, () => {
expect(mergeRuns([], { hopSeconds: 0.1, minDuration: 0 })).toEqual([]);
});
it("emits a time-ordered smoothed chord progression", {
timeout: 5000,
}, () => {
const labels: FrameChord[] = [
{ root: "C", quality: "major", confidence: 0.9 },
{ root: "C", quality: "major", confidence: 0.9 },
{ root: "C", quality: "major", confidence: 0.9 },
{ root: "G", quality: "major", confidence: 0.9 },
{ root: "G", quality: "major", confidence: 0.9 },
{ root: "G", quality: "major", confidence: 0.9 },
];
const merged = mergeRuns(labels, { hopSeconds: 0.1, minDuration: 0 });
expect(merged.map((c) => c.root)).toEqual(["C", "G"]);
for (let i = 1; i < merged.length; i++) {
const prev = merged[i - 1];
const cur = merged[i];
expect((cur?.timestamp ?? 0) > (prev?.timestamp ?? 0)).toBe(true);
}
});
it("coalesces non-adjacent identical labels merged by smoothing", {
timeout: 5000,
}, () => {
const labels: FrameChord[] = [
{ root: "C", quality: "major", confidence: 0.5 },
{ root: "C", quality: "major", confidence: 0.5 },
{ root: "G", quality: "major", confidence: 0.4 },
{ root: "C", quality: "major", confidence: 0.5 },
{ root: "C", quality: "major", confidence: 0.5 },
];
const merged = mergeRuns(labels, { hopSeconds: 0.1, minDuration: 0 });
// smoothing converts the lone G to a C — should coalesce to a single C run.
expect(merged.length).toBe(1);
expect(merged[0]?.root).toBe("C");
});
});