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,40 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "@suaveplan/testing/runner";
const PKG_DIR = join(__dirname, "..");
interface PackageJson {
exports: Record<string, unknown>;
sideEffects?: boolean;
}
describe("subpath exports", () => {
const pkg = JSON.parse(
readFileSync(join(PKG_DIR, "package.json"), "utf8")
) as PackageJson;
it("every feature module has a subpath export entry", {
timeout: 5000,
}, () => {
const required = [
".",
"./base/midi-controller-plugin",
"./sequencer/sequencer",
"./arpeggiator/arpeggiator",
"./learn/learn-mode",
"./binding",
"./plugins/kontrol-s88",
"./plugins/maschine",
"./plugins/traktor-s4",
"./error-codes",
];
for (const path of required) {
expect(pkg.exports[path], `missing export: ${path}`).toBeDefined();
}
});
it("sideEffects is false", { timeout: 5000 }, () => {
expect(pkg.sideEffects).toBe(false);
});
});

View File

@@ -0,0 +1,132 @@
/**
* Fake MIDI fixture — supplies the package's spec-mandated injectable seam.
* Lives outside `src/` so it does not appear in coverage reports yet remains
* shared between the unit, integration, and e2e suites.
*/
import type {
MidiAccessAdapter,
MidiAccessLike,
MidiInputLike,
MidiOutputLike,
} from "../src/base/midi-controller-plugin.types.js";
export interface FakeMidiInput extends MidiInputLike {
emit(data: ReadonlyArray<number>, timeStamp?: number): void;
}
export interface FakeMidiOutput extends MidiOutputLike {
readonly sent: number[][];
disconnect(): void;
}
export interface FakeMidiAccess extends MidiAccessLike {
readonly mutableInputs: Map<string, FakeMidiInput>;
readonly mutableOutputs: Map<string, FakeMidiOutput>;
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<number>, 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<number> | 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<string, FakeMidiInput>([[input.id, input]]);
const mutableOutputs = new Map<string, FakeMidiOutput>([
[output.id, output],
]);
const access: FakeMidiAccess = {
get inputs(): ReadonlyMap<string, MidiInputLike> {
return mutableInputs;
},
get outputs(): ReadonlyMap<string, MidiOutputLike> {
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;
}
export function getFirstOutput(access: FakeMidiAccess): FakeMidiOutput {
const first = access.mutableOutputs.values().next().value;
if (!first) throw new Error("no output");
return first;
}

View File

@@ -0,0 +1,131 @@
/**
* Real `TransportLike` implementation backed by an in-memory event log.
* Used in tests so step/arpeggio callbacks can be driven deterministically
* without spinning a real wall-clock scheduler. This is a real object that
* satisfies the `TransportLike` contract — not a runner mock.
*/
import type {
BarsBeatsSixteenths,
SignalLike,
TransportCallback,
TransportLike,
TransportTime,
} from "@suaveplan/audio-transport";
interface ScheduledRepeat {
readonly id: number;
readonly callback: TransportCallback;
readonly interval: TransportTime;
}
interface ScheduledOnce {
readonly id: number;
readonly callback: TransportCallback;
readonly time: number;
}
const NOOP_BBS: BarsBeatsSixteenths = "0:0:0";
function makeFakeSignal(): SignalLike {
let v = 120;
const points: { time: number; value: number; type: "set" }[] = [];
const sig: SignalLike = {
get points() {
return points.slice();
},
get initialValue() {
return 120;
},
get value() {
return v;
},
setValueAtTime(value: number, time: number) {
v = value;
points.push({ time, value, type: "set" });
},
linearRampToValueAtTime(value: number) {
v = value;
},
exponentialRampToValueAtTime(value: number) {
v = value;
},
getValueAtTime() {
return v;
},
cancelScheduledValues() {
points.length = 0;
},
};
return sig;
}
export class FakeTransport implements TransportLike {
public bpm: SignalLike = makeFakeSignal();
public readonly ppq = 192;
public readonly timeSignature = 4;
public swing = 0;
public swingSubdivision: TransportTime = "8n";
public loop = false;
public loopStart: TransportTime = 0;
public loopEnd: TransportTime = 0;
public state: "stopped" | "playing" | "paused" = "stopped";
public position: BarsBeatsSixteenths = NOOP_BBS;
public ticks = 0;
public seconds = 0;
public readonly repeats = new Map<number, ScheduledRepeat>();
public readonly onces = new Map<number, ScheduledOnce>();
private nextId = 1;
start(): void {
this.state = "playing";
}
stop(): void {
this.state = "stopped";
}
pause(): void {
this.state = "paused";
}
schedule(callback: TransportCallback, time: TransportTime): number {
return this.scheduleOnce(callback, time);
}
scheduleOnce(callback: TransportCallback, time: TransportTime): number {
const id = this.nextId++;
const num = typeof time === "number" ? time : 0;
this.onces.set(id, { id, callback, time: num });
return id;
}
scheduleRepeat(
callback: TransportCallback,
interval: TransportTime
): number {
const id = this.nextId++;
this.repeats.set(id, { id, callback, interval });
return id;
}
cancel(eventId: number): void {
this.repeats.delete(eventId);
this.onces.delete(eventId);
}
cancelAfter(_time: TransportTime): void {
// Not used by audio-controllers tests.
}
toSeconds(time: TransportTime): number {
return typeof time === "number" ? time / this.ppq : 0;
}
toTicks(time: TransportTime): number {
return typeof time === "number" ? time * this.ppq : 0;
}
/** Test helper: drive every active repeat once. */
tickAll(time = 0): void {
for (const r of this.repeats.values()) r.callback(time);
}
/** Test helper: fire every scheduled `scheduleOnce`. */
flushOnces(): void {
for (const o of this.onces.values()) o.callback(o.time);
this.onces.clear();
}
}

View File

@@ -0,0 +1,56 @@
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "@suaveplan/testing/runner";
const PKG_DIR = join(__dirname, "..");
const DIST_DIR = join(PKG_DIR, "dist");
function walk(dir: string, out: string[] = []): string[] {
if (!existsSync(dir)) return out;
for (const name of readdirSync(dir)) {
const p = join(dir, name);
const s = statSync(p);
if (s.isDirectory()) walk(p, out);
else if (/\.(js|mjs|cjs)$/.test(p)) out.push(p);
}
return out;
}
describe("purity", () => {
it("dist references no Node globals", { timeout: 30_000 }, () => {
if (!existsSync(DIST_DIR)) {
// dist will exist after build; skip purity if not yet built so the
// test suite can still run on a fresh clone.
expect(true).toBe(true);
return;
}
const offenders: string[] = [];
for (const file of walk(DIST_DIR)) {
const src = readFileSync(file, "utf8");
if (/\bnode:[a-z]+\b/.test(src)) offenders.push(`${file}: node:*`);
if (/\bprocess\.(env|cwd|platform)\b/.test(src))
offenders.push(`${file}: process`);
if (/\bBuffer\.from\b/.test(src)) offenders.push(`${file}: Buffer`);
}
expect(offenders).toEqual([]);
});
it("importing the package mutates no global state", {
timeout: 5000,
}, async () => {
const filter = (keys: string[]): string[] =>
keys
.filter(
(k) =>
!k.startsWith("__zod") &&
!k.startsWith("__vitest") &&
!k.startsWith("__VITEST")
)
.sort();
const beforeKeys = filter(Object.keys(globalThis));
const mod = await import("../src/index.js");
expect(mod.MidiControllerPlugin).toBeDefined();
const afterKeys = filter(Object.keys(globalThis));
expect(afterKeys).toEqual(beforeKeys);
});
});