72 lines
1.3 KiB
TypeScript
72 lines
1.3 KiB
TypeScript
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'folk-metronome': FolkMetronome;
|
|
}
|
|
}
|
|
|
|
export class FolkMetronome extends HTMLElement {
|
|
static tagName = 'folk-metronome';
|
|
|
|
static define() {
|
|
customElements.define(this.tagName, this);
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.addEventListener('click', this);
|
|
}
|
|
|
|
#timeoutId: NodeJS.Timeout | -1 = -1;
|
|
|
|
get isPlaying() {
|
|
return this.#timeoutId !== -1;
|
|
}
|
|
|
|
// default to 100ms per beat
|
|
#bpm = Number(this.getAttribute('bpm') || 100);
|
|
get bpm() {
|
|
return this.#bpm;
|
|
}
|
|
set bpm(bpm) {
|
|
this.#bpm = bpm;
|
|
}
|
|
|
|
#beat = 0;
|
|
get beat() {
|
|
return this.#beat;
|
|
}
|
|
|
|
get #intervalMs() {
|
|
return (60 * 1000) / this.#bpm;
|
|
}
|
|
|
|
handleEvent(e: Event) {
|
|
if (e.type === 'click' && e.target === this) {
|
|
this.isPlaying ? this.pause() : this.play();
|
|
}
|
|
}
|
|
|
|
play() {
|
|
this.#timeoutId = setInterval(() => {
|
|
this.#updateBeat(this.#beat + 1);
|
|
this.dispatchEvent(new Event('beat'));
|
|
}, this.#intervalMs);
|
|
}
|
|
|
|
pause() {
|
|
clearInterval(this.#timeoutId);
|
|
this.#timeoutId = -1;
|
|
}
|
|
|
|
reset() {
|
|
this.pause();
|
|
this.#updateBeat(0);
|
|
}
|
|
|
|
#updateBeat = (nextBeat: number) => {
|
|
this.#beat = ((nextBeat - 1) % 4) + 1;
|
|
this.textContent = this.#beat.toString();
|
|
};
|
|
}
|