aboutsummaryrefslogtreecommitdiff
path: root/src/view/sequencer
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/sequencer')
-rw-r--r--src/view/sequencer/addRemoveBeat.ts32
-rw-r--r--src/view/sequencer/block.ts28
-rw-r--r--src/view/sequencer/play.ts68
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
+ )
+ }
+ )
+ )
+ )
+}