132 lines
3.7 KiB
TypeScript
132 lines
3.7 KiB
TypeScript
/**
|
|
* 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();
|
|
}
|
|
}
|