1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
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 = 90
let isPlaying = false
let lastBeat: undefined | number = undefined
let button = 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()
}
}
},
'▶'
) as HTMLButtonElement
document.addEventListener('keydown', event => {
if (event.key == " ") {
event.preventDefault()
button.click()
}
})
return h('div', {},
button,
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
)
}
)
)
)
}
|