diff options
Diffstat (limited to 'src/view/sequencer')
-rw-r--r-- | src/view/sequencer/addRemoveBeat.ts | 32 | ||||
-rw-r--r-- | src/view/sequencer/block.ts | 28 | ||||
-rw-r--r-- | src/view/sequencer/play.ts | 68 |
3 files changed, 128 insertions, 0 deletions
diff --git a/src/view/sequencer/addRemoveBeat.ts b/src/view/sequencer/addRemoveBeat.ts new file mode 100644 index 0000000..e991d3f --- /dev/null +++ b/src/view/sequencer/addRemoveBeat.ts @@ -0,0 +1,32 @@ +import h, { classNames } from 'lib/h' + +interface Params { + initBeats: number, + onRemove: (index: number) => void, + onAdd: (index: number) => void +} + +export function view({ initBeats, onRemove, onAdd }: Params) { + let beats = initBeats + + return h('div', { className: 'g-Sequencer__Buttons' }, + h('button', + { onclick: () => { + if (beats > 1) { + beats -= 1 + onRemove(beats) + } + } + }, + 'Remove Beat' + ), + h('button', + { onclick: () => { + onAdd(beats) + beats += 1 + } + }, + 'Add Beat' + ) + ) +} diff --git a/src/view/sequencer/block.ts b/src/view/sequencer/block.ts new file mode 100644 index 0000000..5776120 --- /dev/null +++ b/src/view/sequencer/block.ts @@ -0,0 +1,28 @@ +import h, { classNames } from 'lib/h' + +interface Params { + checked: boolean, + onCheck: (checked: boolean) => void +} + +export function view({ checked, onCheck }: Params) { + return h('div', + { className: classNames({ + 'g-Sequencer__Block': true, + 'g-Sequencer__Block--Checked': checked + }), + onclick: (e: Event) => { + checked = !checked + onCheck(checked) + let target = e.target as HTMLElement + if (target !== undefined) { + if (checked) { + target.classList.add('g-Sequencer__Block--Checked') + } else { + target.classList.remove('g-Sequencer__Block--Checked') + } + } + } + } + ) +} 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 + ) + } + ) + ) + ) +} |