Files
arne-drums/src/Beat.ts

148 lines
4.3 KiB
TypeScript

import BeatUnit from "./BeatUnit";
import {IPublisher, Publisher} from "./Publisher";
import ISubscriber from "./Subscriber";
import BeatLike from "./BeatLike";
import {isPosInt} from "./utils";
export type BeatInitOptions = {
timeSig?: {
up: number,
down: number,
},
name?: string,
bars?: number,
isLooping?: boolean,
loopLength?: number,
};
export const enum BeatEvents {
NewTimeSig="BE0",
NewBarCount="BE1",
NewName="BE2",
DisplayTypeChanged="BE3",
LoopLengthChanged="BE4",
}
export default class Beat implements IPublisher<BeatEvents>, BeatLike {
private static count = 0;
private readonly key: string;
private name: string;
private timeSigUp = 4;
private timeSigDown = 4;
private readonly unitRecord: BeatUnit[] = [];
private barCount = 1;
private publisher = new Publisher<BeatEvents>();
private loopLength: number;
private looping: boolean;
constructor(options?: BeatInitOptions) {
this.key = `Beat-${Beat.count}`;
this.name = options?.name ?? this.key;
this.setTimeSignature({up: options?.timeSig?.up ?? 4, down: options?.timeSig?.down ?? 4});
this.setBarCount(options?.bars ?? 4);
Beat.count++;
this.loopLength = options?.loopLength ?? this.timeSigUp * this.barCount;
this.looping = options?.isLooping ?? false;
}
setLoopLength(loopLength: number): void {
if (!isPosInt(loopLength) || loopLength < 2) {
loopLength = this.loopLength;
}
this.loopLength = loopLength;
this.publisher.notifySubs(BeatEvents.LoopLengthChanged);
}
setLooping(isLooping: boolean): void {
this.looping = isLooping;
this.publisher.notifySubs(BeatEvents.DisplayTypeChanged);
}
addSubscriber(subscriber: ISubscriber, eventType: BeatEvents | BeatEvents[] | "all"): { unbind: () => void } {
return this.publisher.addSubscriber(subscriber, eventType);
}
setTimeSignature(timeSig: {up?: number, down?: number}): void {
if (timeSig.up && Beat.isValidTimeSigRange(timeSig.up)) {
this.timeSigUp = timeSig.up | 0;
}
if (timeSig.down && Beat.isValidTimeSigRange(timeSig.down)) {
this.timeSigDown = timeSig.down | 0;
}
this.updateBeatUnitLength();
this.publisher.notifySubs(BeatEvents.NewTimeSig);
}
setTimeSigUp(timeSigUp: number): void {
this.setTimeSignature({up: timeSigUp});
}
setTimeSigDown(timeSigUp: number): void {
this.setTimeSignature({down: timeSigUp});
}
setBarCount(barCount: number): void {
if (!isPosInt(barCount) || barCount == this.barCount) {
barCount = this.barCount;
}
this.barCount = barCount;
this.updateBeatUnitLength();
this.publisher.notifySubs(BeatEvents.NewBarCount);
}
getUnitByIndex(index: number): BeatUnit | null {
if (this.looping) {
index %= this.loopLength;
}
return this.unitRecord[index] ?? null;
}
private updateBeatUnitLength() {
const newBarCount = this.barCount * this.timeSigUp;
if (newBarCount < this.unitRecord.length) {
this.unitRecord.splice(this.barCount * this.timeSigUp, this.unitRecord.length - newBarCount);
} else if (newBarCount > this.unitRecord.length) {
const barsToAdd = newBarCount - this.unitRecord.length;
for (let i = 0; i < barsToAdd; i++) {
this.unitRecord.push(new BeatUnit());
}
}
}
getTimeSigUp(): number {
return this.timeSigUp;
}
getTimeSigDown(): number {
return this.timeSigDown;
}
getBarCount(): number {
return this.barCount;
}
getKey(): string {
return this.key;
}
static isValidTimeSigRange(sig: number): boolean {
return sig >= 2 && sig <= 32;
}
setName(newName: string): void {
this.name = newName;
this.publisher.notifySubs(BeatEvents.NewName);
}
getName(): string {
return this.name;
}
isLooping(): boolean {
return this.looping;
}
getLoopLength(): number {
return this.loopLength;
}
}