From 5697ee6524e349eff26a589181897aa76fa41611 Mon Sep 17 00:00:00 2001 From: Daniel Ledda Date: Sat, 4 Sep 2021 08:55:46 +0200 Subject: [PATCH] feat: added autobeat for full bars --- src/Beat.ts | 86 +++----------- src/BeatGroup.ts | 107 ++++++++++++++---- .../Beat/BeatSettings/BeatSettingsView.ts | 1 - src/ui/BeatGroup/Beat/BeatView.ts | 20 ++-- .../BeatGroupSettings/BeatGroupSettings.css | 7 ++ .../BeatGroupSettingsView.ts | 59 ++++++++-- .../BeatLikeLoopSettingsView.ts | 5 +- 7 files changed, 174 insertions(+), 111 deletions(-) diff --git a/src/Beat.ts b/src/Beat.ts index 0af9f08..91b716f 100644 --- a/src/Beat.ts +++ b/src/Beat.ts @@ -1,4 +1,4 @@ -import BeatUnit, {BeatUnitType} from "./BeatUnit"; +import BeatUnit from "./BeatUnit"; import {IPublisher, Publisher} from "./Publisher"; import ISubscriber from "./Subscriber"; import BeatLike from "./BeatLike"; @@ -15,13 +15,12 @@ export type BeatInitOptions = { loopLength?: number, }; -export enum BeatEvents { - NewTimeSig, - NewBarCount, - NewName, - UnitChanged, - DisplayTypeChanged, - LoopLengthChanged, +export const enum BeatEvents { + NewTimeSig="BE0", + NewBarCount="BE1", + NewName="BE2", + DisplayTypeChanged="BE3", + LoopLengthChanged="BE4", } export default class Beat implements IPublisher, BeatLike { @@ -48,9 +47,9 @@ export default class Beat implements IPublisher, BeatLike { setLoopLength(loopLength: number): void { if (!isPosInt(loopLength) || loopLength < 2) { - return; + loopLength = this.loopLength; } - this.loopLength = loopLength | 0; + this.loopLength = loopLength; this.publisher.notifySubs(BeatEvents.LoopLengthChanged); } @@ -59,14 +58,13 @@ export default class Beat implements IPublisher, BeatLike { this.publisher.notifySubs(BeatEvents.DisplayTypeChanged); } - addSubscriber(subscriber: ISubscriber, eventType: BeatEvents | "all"): { unbind: () => void } { + 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; - this.loopLength = this.timeSigUp * this.barCount; } if (timeSig.down && Beat.isValidTimeSigRange(timeSig.down)) { this.timeSigDown = timeSig.down | 0; @@ -77,17 +75,16 @@ export default class Beat implements IPublisher, BeatLike { setBarCount(barCount: number): void { if (!isPosInt(barCount) || barCount == this.barCount) { - return; + barCount = this.barCount; } this.barCount = barCount; - this.loopLength = this.timeSigUp * this.barCount; this.updateBeatUnitLength(); this.publisher.notifySubs(BeatEvents.NewBarCount); } getUnitByIndex(index: number): BeatUnit | null { if (this.looping) { - return this.unitRecord[index % this.loopLength]; + index %= this.loopLength; } return this.unitRecord[index] ?? null; } @@ -112,63 +109,6 @@ export default class Beat implements IPublisher, BeatLike { return this.timeSigDown; } - turnUnitOn(index: number): void { - if (!isPosInt(index)) { - return; - } - const unit = this.getUnit(index); - if (unit) { - unit.setOn(true); - this.publisher.notifySubs(BeatEvents.UnitChanged); - } - } - - turnUnitOff(index: number): void { - if (!isPosInt(index)) { - return; - } - const unit = this.getUnit(index); - if (unit) { - unit.setOn(false); - this.publisher.notifySubs(BeatEvents.UnitChanged); - } - } - - - toggleUnit(index: number): void { - if (!isPosInt(index)) { - return; - } - const unit = this.getUnit(index); - if (unit) { - unit.toggle(); - this.publisher.notifySubs(BeatEvents.UnitChanged); - } - } - - setUnitType(index: number, type: BeatUnitType): void { - if (!isPosInt(index)) { - return; - } - this.getUnit(index).setType(type); - this.publisher.notifySubs(BeatEvents.UnitChanged); - } - - unitIsOn(index: number): boolean { - return this.getUnit(index)?.isOn(); - } - - unitType(index: number): BeatUnitType { - return this.getUnit(index)?.getType(); - } - - private getUnit(index: number): BeatUnit { - if (!this.unitRecord[index]) { - throw new Error(`Invalid beat unit index! - ${index}`); - } - return this.unitRecord[index]; - } - getBarCount(): number { return this.barCount; } @@ -178,7 +118,7 @@ export default class Beat implements IPublisher, BeatLike { } static isValidTimeSigRange(sig: number): boolean { - return sig >= 2 && sig <= 64; + return sig >= 2 && sig <= 32; } setName(newName: string): void { diff --git a/src/BeatGroup.ts b/src/BeatGroup.ts index b46ebd2..50ff644 100644 --- a/src/BeatGroup.ts +++ b/src/BeatGroup.ts @@ -10,23 +10,28 @@ type BeatGroupInitOptions = { timeSigUp: number; beats: BeatInitOptions[], loopLength?: number, + forceFullBars?: boolean, + useAutoBeatLength?: boolean, } export const enum BeatGroupEvents { - BeatOrderChanged, - BeatListChanged, - GlobalBarCountChanged, - GlobalTimeSigUpChanged, + BeatOrderChanged="BGE0", + BeatListChanged="BGE1", + BarCountChanged="BGE2", + TimeSigUpChanged="BGE3", + AutoBeatSettingsChanged="BGE4", } -export default class BeatGroup implements IPublisher, BeatLike { +export default class BeatGroup implements IPublisher, BeatLike, ISubscriber { private beats: Beat[] = []; private beatKeyMap: Record = {}; private publisher: Publisher = new Publisher(); - private globalBarCount: number; - private globalTimeSigUp: number; + private barCount: number; + private timeSigUp: number; private globalLoopLength: number; private globalIsLooping: boolean; + private forceFullBars: boolean; + private useAutoBeatLength: boolean; constructor(options?: BeatGroupInitOptions) { if (options?.beats) { @@ -36,10 +41,18 @@ export default class BeatGroup implements IPublisher(publisher: IPublisher, event: "all" | T[] | T): void { + if (event === BeatEvents.LoopLengthChanged) { + this.autoBeatLength(); + } } addSubscriber(subscriber: ISubscriber, eventType: "all" | BeatGroupEvents | BeatEvents | (BeatGroupEvents | BeatEvents)[]): { unbind: () => void } { @@ -47,18 +60,18 @@ export default class BeatGroup implements IPublisher prev * curr, 1); + } + + setTimeSigUp(timeSigUp: number): void { + if (!Beat.isValidTimeSigRange(timeSigUp)) { + timeSigUp = this.timeSigUp; + } + this.timeSigUp = timeSigUp; for (const beat of this.beats) { beat.setTimeSignature({up: timeSigUp}); } - this.publisher.notifySubs(BeatGroupEvents.GlobalTimeSigUpChanged); + this.publisher.notifySubs(BeatGroupEvents.TimeSigUpChanged); } getBeatByKey(beatKey: string): Beat { @@ -165,6 +205,7 @@ export default class BeatGroup implements IPublisher this.onBeatUnitClick(event.button, i)); - window.addEventListener("mouseup", (event: MouseEvent) => { - BeatView.selectingUnits = false; - BeatView.deselectingUnits = false; - }); view.onHover(() => { this.lastHoveredBeatUnitView = view; if (BeatView.selectingUnits) { @@ -81,6 +76,7 @@ export default class BeatView extends UINode implements ISubscriber { this.lastHoveredBeatUnitView.turnOff(); } }); + view.onMouseDown((event: MouseEvent) => this.onBeatUnitClick(event.button, i)); } } } @@ -149,6 +145,9 @@ export default class BeatView extends UINode implements ISubscriber { classes: ["beat-title"], }); this.setupBeatUnits(); + if (!this.beatUnitViewBlock) { + throw new Error("Beat unit block setup failed!"); + } this.settingsView = new BeatSettingsView({beat: this.beat}); this.settingsToggleButton = UINode.make("div", { classes: ["beat-settings-btn"], @@ -162,7 +161,7 @@ export default class BeatView extends UINode implements ISubscriber { classes: ["beat-main"], subs: [ this.title, - this.beatUnitViewBlock!, + this.beatUnitViewBlock, ] }), this.settingsToggleButton, @@ -175,3 +174,8 @@ export default class BeatView extends UINode implements ISubscriber { return this.node; } } + +window.addEventListener("mouseup", () => { + BeatView.selectingUnits = false; + BeatView.deselectingUnits = false; +}); \ No newline at end of file diff --git a/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettings.css b/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettings.css index 5f3ca84..b834c59 100644 --- a/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettings.css +++ b/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettings.css @@ -12,3 +12,10 @@ display: inline-block; text-align: center; } + +.beat-group-settings-option-group { + display: none; +} +.beat-group-settings-option-group.visible { + display: inline-block; +} diff --git a/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettingsView.ts b/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettingsView.ts index 1366d82..36fd928 100644 --- a/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettingsView.ts +++ b/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettingsView.ts @@ -5,6 +5,7 @@ import {IPublisher} from "../../../Publisher"; import {BeatGroupEvents} from "../../../BeatGroup"; import BeatLikeLoopSettingsView from "../BeatLikeLoopSettings/BeatLikeLoopSettingsView"; import "./BeatGroupSettings.css"; +import {BeatEvents} from "../../../Beat"; export type BeatGroupSettingsUINodeOptions = UINodeOptions & { beatGroup: BeatGroup, @@ -15,21 +16,31 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber private barCountInput!: HTMLInputElement; private timeSigUpInput!: HTMLInputElement; private loopSettingsView!: BeatLikeLoopSettingsView; + private autoBeatLengthCheckbox!: HTMLInputElement; + private forceFullBarsCheckbox!: HTMLInputElement; + private autoBeatOptions!: HTMLElement; constructor(options: BeatGroupSettingsUINodeOptions) { super(options); this.beatGroup = options.beatGroup; this.beatGroup.addSubscriber(this, [ - BeatGroupEvents.GlobalBarCountChanged, - BeatGroupEvents.GlobalTimeSigUpChanged + BeatGroupEvents.BarCountChanged, + BeatGroupEvents.TimeSigUpChanged, + BeatEvents.DisplayTypeChanged, ]); } notify(publisher: IPublisher, event: "all" | T[] | T): void { - if (event === BeatGroupEvents.GlobalBarCountChanged) { - this.barCountInput.value = this.beatGroup.getBeatByIndex(0).getBarCount().toString(); - } else if (event === BeatGroupEvents.GlobalTimeSigUpChanged) { - this.barCountInput.value = this.beatGroup.getBeatByIndex(0).getBarCount().toString(); + if (event === BeatGroupEvents.BarCountChanged) { + this.barCountInput.valueAsNumber = this.beatGroup.getBeatByIndex(0).getBarCount(); + } else if (event === BeatGroupEvents.TimeSigUpChanged) { + this.timeSigUpInput.valueAsNumber = this.beatGroup.getBeatByIndex(0).getTimeSigUp(); + } else if (event === BeatEvents.DisplayTypeChanged) { + if (this.beatGroup.isLooping()) { + this.autoBeatOptions.classList.add("visible"); + } else { + this.autoBeatOptions.classList.remove("visible"); + } } } @@ -46,13 +57,44 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber type: "number", value: this.beatGroup.getBeatByIndex(0).getTimeSigUp().toString(), oninput: () => { - this.beatGroup.setGlobalTimeSigUp(Number(this.timeSigUpInput.value)); + this.beatGroup.setTimeSigUp(Number(this.timeSigUpInput.value)); }, }); + this.autoBeatLengthCheckbox = UINode.make("input", { + type: "checkbox", + checked: this.beatGroup.autoBeatLengthOn(), + oninput: () => { + this.beatGroup.setIsUsingAutoBeatLength(this.autoBeatLengthCheckbox.checked); + }, + }); + this.forceFullBarsCheckbox = UINode.make("input", { + type: "checkbox", + checked: this.beatGroup.forcesFullBars(), + oninput: () => { + this.beatGroup.setForcesFullBars(this.forceFullBarsCheckbox.checked); + }, + }); + this.autoBeatOptions = UINode.make("div", { + classes: ["beat-group-settings-option-group"], + subs: [ + UINode.make("div", { + subs: [ + UINode.make("label", { innerText: "Auto beat length:"}), + this.autoBeatLengthCheckbox, + ], + }), + UINode.make("div", { + subs: [ + UINode.make("label", { innerText: "Force full bars:"}), + this.forceFullBarsCheckbox, + ], + }), + ] + }); this.node = UINode.make("div", { classes: ["beat-group-settings"], subs: [ - UINode.make("h4", { innerText: "Settings for beat" }), + UINode.make("div", { innerText: "Settings for beat" }), UINode.make("div", { classes: ["beat-group-settings-options"], subs: [ @@ -71,6 +113,7 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber ], }), this.loopSettingsView.render(), + this.autoBeatOptions, ], }), ], diff --git a/src/ui/BeatGroup/BeatLikeLoopSettings/BeatLikeLoopSettingsView.ts b/src/ui/BeatGroup/BeatLikeLoopSettings/BeatLikeLoopSettingsView.ts index 92698eb..7556078 100644 --- a/src/ui/BeatGroup/BeatLikeLoopSettings/BeatLikeLoopSettingsView.ts +++ b/src/ui/BeatGroup/BeatLikeLoopSettings/BeatLikeLoopSettingsView.ts @@ -21,7 +21,10 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri } private setupBindings() { - this.beatLike.addSubscriber(this, "all"); + this.beatLike.addSubscriber(this, [ + BeatEvents.LoopLengthChanged, + BeatEvents.DisplayTypeChanged + ]); } notify(publisher: IPublisher, event: "all" | T[] | T): void {