feat: auto-save and multiple tracks

This commit is contained in:
Daniel Ledda
2022-04-17 14:08:38 +02:00
parent 77b5e25e64
commit 1861403f24
31 changed files with 498 additions and 201 deletions

View File

@@ -2,6 +2,7 @@ import Track, {TrackEvents, TrackInitOptions} from "@/Track";
import {IPublisher, Publisher} from "@/Publisher";
import ISubscriber from "@/Subscriber";
import {greatestCommonDivisor, isPosInt} from "@/utils";
import Ref from "@/Ref";
type BeatGroupInitOptions = {
barCount: number;
@@ -13,6 +14,17 @@ type BeatGroupInitOptions = {
name?: string,
};
export type BeatSerial = {
tracks: Record<string, any>[],
barCount: number,
timeSigUp: number,
globalLoopLength: number,
globalIsLooping: boolean,
useAutoBeatLength: boolean,
barSettingsLocked: boolean,
name: string,
};
export const enum BeatEvents {
TrackOrderChanged="be-0",
TrackListChanged="be-1",
@@ -22,14 +34,17 @@ export const enum BeatEvents {
LockingChanged="be-5",
GlobalLoopLengthChanged="be-5",
GlobalDisplayTypeChanged="be-6",
NameChanged="be-7",
DeepChange="be-7",
}
type EventTypeSubscriptions =
| TrackEvents.LoopLengthChanged
| TrackEvents.DisplayTypeChanged
| TrackEvents.WantsRemoval
| TrackEvents.Baked;
const EventTypeSubscriptions = [
TrackEvents.LoopLengthChanged,
TrackEvents.DisplayTypeChanged,
TrackEvents.WantsRemoval,
TrackEvents.DeepChange,
TrackEvents.Baked,
];
type EventTypeSubscriptions = typeof EventTypeSubscriptions[number];
export default class Beat implements IPublisher<BeatEvents>, ISubscriber<EventTypeSubscriptions> {
private static globalCounter = 0;
@@ -41,14 +56,14 @@ export default class Beat implements IPublisher<BeatEvents>, ISubscriber<EventTy
private globalIsLooping: boolean;
private useAutoBeatLength: boolean;
private barSettingsLocked = false;
private name: string;
private name: Ref<string>;
constructor(options?: BeatGroupInitOptions) {
Beat.globalCounter++;
if (options?.name) {
this.name = options.name;
this.name = Ref.new<string>(options.name);
} else {
this.name = `Pattern ${Beat.globalCounter}`;
this.name = Ref.new<string>(`Pattern ${Beat.globalCounter}`);
}
if (options?.tracks) {
for (const trackOptions of options.tracks) {
@@ -62,6 +77,22 @@ export default class Beat implements IPublisher<BeatEvents>, ISubscriber<EventTy
this.useAutoBeatLength = options?.useAutoBeatLength ?? false;
}
static deserialise(serial: any): Beat {
if (!Beat.isBeatSerial(serial)) {
throw new Error("Not a valid beat serial");
}
const newBeat = new Beat({
loopLength: serial.globalLoopLength,
barCount: serial.barCount,
isLooping: serial.globalIsLooping,
name: serial.name,
timeSigUp: serial.timeSigUp,
useAutoBeatLength: serial.useAutoBeatLength,
});
serial.tracks.forEach(trackSerial => newBeat.addTrack(Track.deserialise(trackSerial)));
return newBeat;
}
notify(publisher: unknown, event: EventTypeSubscriptions): void {
switch (event) {
case TrackEvents.LoopLengthChanged:
@@ -75,9 +106,10 @@ export default class Beat implements IPublisher<BeatEvents>, ISubscriber<EventTy
this.setIsUsingAutoBeatLength(false);
break;
}
this.publisher.notifySubs(BeatEvents.DeepChange);
}
addSubscriber(subscriber: ISubscriber<BeatEvents>, eventType: BeatEvents | BeatEvents[]): { unbind: () => void } {
addSubscriber(subscriber: ISubscriber<BeatEvents>, eventType: BeatEvents | Readonly<BeatEvents[]>): { unbind: () => void } {
return this.publisher.addSubscriber(subscriber, eventType);
}
@@ -220,25 +252,27 @@ export default class Beat implements IPublisher<BeatEvents>, ISubscriber<EventTy
return this.tracks.indexOf(this.getTrackByKey(trackKey)) < this.tracks.length - 1;
}
addTrack(options?: TrackInitOptions): Track {
options = {
timeSig: {
up: this.timeSigUp,
down: 4,
},
bars: this.barCount,
isLooping: this.globalIsLooping,
loopLength: this.globalLoopLength,
...options
};
const newTrack = new Track(options);
addTrack(track: Track): void;
addTrack(options?: TrackInitOptions): Track;
addTrack(optionsOrTrack?: Track | TrackInitOptions): Track | void {
let newTrack: Track;
if (optionsOrTrack instanceof Track) {
newTrack = optionsOrTrack;
} else {
optionsOrTrack = {
timeSig: {
up: this.timeSigUp,
down: 4,
},
bars: this.barCount,
isLooping: this.globalIsLooping,
loopLength: this.globalLoopLength,
...optionsOrTrack
};
newTrack = new Track(optionsOrTrack);
}
this.tracks.push(newTrack);
newTrack.addSubscriber(this, [
TrackEvents.LoopLengthChanged,
TrackEvents.WantsRemoval,
TrackEvents.DisplayTypeChanged,
TrackEvents.Baked,
]);
newTrack.addSubscriber(this, EventTypeSubscriptions);
this.publisher.notifySubs(BeatEvents.TrackListChanged);
return newTrack;
}
@@ -295,11 +329,33 @@ export default class Beat implements IPublisher<BeatEvents>, ISubscriber<EventTy
}
setName(newName: string): void {
this.name = newName;
this.publisher.notifySubs(BeatEvents.NameChanged);
this.name.val = newName;
}
getName(): string {
getName(): Ref<string> {
return this.name;
}
serialise(): Readonly<BeatSerial> {
return {
tracks: this.tracks.map(track => track.serialise()),
barCount: this.barCount,
timeSigUp: this.timeSigUp,
globalLoopLength: this.globalLoopLength,
globalIsLooping: this.globalIsLooping,
useAutoBeatLength: this.useAutoBeatLength,
barSettingsLocked: this.barSettingsLocked,
name: this.name.val,
} as const;
}
static isBeatSerial(serial: any): serial is BeatSerial {
return Array.isArray(serial.tracks) &&
typeof serial.barCount === "number" &&
typeof serial.timeSigUp === "number" &&
typeof serial.globalLoopLength === "number" &&
typeof serial.globalIsLooping === "boolean" &&
typeof serial.useAutoBeatLength === "boolean" &&
typeof serial.barSettingsLocked === "boolean";
}
}