aboutsummaryrefslogtreecommitdiff
path: root/src/view/sequencer/play.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/sequencer/play.ts')
-rw-r--r--src/view/sequencer/play.ts68
1 files changed, 68 insertions, 0 deletions
diff --git a/src/view/sequencer/play.ts b/src/view/sequencer/play.ts
new file mode 100644
index 0000000..9ff9c81
--- /dev/null
+++ b/src/view/sequencer/play.ts
@@ -0,0 +1,68 @@
+import h, { classNames } from 'lib/h'
+import * as time from 'lib/time'
+import * as soundsLib from 'sounds'
+
+const MIN_BPM: number = 1
+const MAX_BPM: number = 1000
+
+interface Params {
+ onNextStep: (sounds: soundsLib.Sounds) => void,
+ onStop: () => void
+}
+
+export function view({ onNextStep, onStop }: Params) {
+ let bpm = 60
+ let isPlaying = false
+ let lastBeat: undefined | number = undefined
+
+ return h('div', {},
+ h('button',
+ { className: 'g-PlayStop',
+ onclick: async (e: Event) => {
+ const target = e.target as HTMLButtonElement
+ isPlaying = !isPlaying
+ target.innerText = isPlaying ? '■' : '▶'
+
+ let sounds = await soundsLib.load()
+ let step = (timestamp: number) => {
+ if (lastBeat === undefined || timestamp - lastBeat > 1000 * 60 / bpm) {
+ lastBeat = timestamp
+ onNextStep(sounds)
+ }
+
+ if (isPlaying) window.requestAnimationFrame(step)
+ }
+
+ if (isPlaying) {
+ window.requestAnimationFrame(step)
+ } else {
+ onStop()
+ }
+ }
+ },
+ '▶'
+ )
+ , h('label',
+ { className: 'g-Bpm' },
+ 'BPM',
+ h('input',
+ { className: 'g-Input',
+ type: 'number',
+ value: bpm,
+ min: MIN_BPM,
+ max: MAX_BPM,
+ oninput: time.debounce(
+ (e: Event) => {
+ const target = e.target as HTMLInputElement
+ const n = parseInt(target.value)
+ if (n >= MIN_BPM && n <= MAX_BPM) {
+ bpm = n
+ }
+ },
+ 1000
+ )
+ }
+ )
+ )
+ )
+}