metronome

This commit is contained in:
“chrisshank” 2024-11-23 14:01:44 -08:00
parent 57253769b4
commit 90825c289d
5 changed files with 146 additions and 1 deletions

74
demo/beats.html Normal file
View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Beats</title>
<style>
html {
height: 100%;
}
body {
min-height: 100%;
position: relative;
margin: 0;
}
event-propagator {
display: block;
position: absolute;
inset: 0 0 0 0;
pointer-events: none;
}
fc-geometry {
border: 1px solid black;
border-radius: 4px;
}
folk-metronome {
padding: 0.5rem;
}
</style>
</head>
<body>
<fc-geometry x="100" y="100">
<folk-metronome bpm="120"></folk-metronome>
</fc-geometry>
<fc-geometry x="200" y="100">
<audio id="kick" src="/Kick_Bouncy.wav" controls></audio>
</fc-geometry>
<fc-geometry x="200" y="200">
<audio id="hat" src="/Hat_Closed.wav" controls></audio>
</fc-geometry>
<event-propagator
source="folk-metronome"
target="#kick"
triggers="beat"
expression="if ($source.beat % 2 === 0) $target.play();"
></event-propagator>
<event-propagator
source="folk-metronome"
target="#hat"
triggers="beat"
expression="if ($source.beat > 2) $target.play();"
></event-propagator>
<script type="module">
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { FolkLLM } from '../src/folk-llm.ts';
import { FolkMetronome } from '../src/folk-metronome.ts';
import { EventPropagator } from '../src/arrows/event-propagator.ts';
FolkGeometry.register();
FolkLLM.register();
FolkMetronome.register();
EventPropagator.register();
</script>
</body>
</html>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Music</title>
<title>Proximity Music</title>
<style>
html {
height: 100%;

BIN
demo/public/Hat_Closed.wav Normal file

Binary file not shown.

BIN
demo/public/Kick_Bouncy.wav Normal file

Binary file not shown.

71
src/folk-metronome.ts Normal file
View File

@ -0,0 +1,71 @@
declare global {
interface HTMLElementTagNameMap {
'folk-metronome': FolkMetronome;
}
}
export class FolkMetronome extends HTMLElement {
static tagName = 'folk-metronome';
static register() {
customElements.define(this.tagName, this);
}
constructor() {
super();
this.addEventListener('click', this);
}
#timeoutId = -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) {
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();
};
}