From fade87173afbfdd51534646ed49844efa2d0e530 Mon Sep 17 00:00:00 2001 From: Joris Date: Mon, 4 Jul 2022 11:32:27 +0200 Subject: Play random major and/or minor chords --- src/view/form.ts | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/view/layout.ts | 13 +++++++++++++ src/view/options.ts | 24 +++++++++++++++++++++++ src/view/play.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 src/view/form.ts create mode 100644 src/view/layout.ts create mode 100644 src/view/options.ts create mode 100644 src/view/play.ts (limited to 'src/view') diff --git a/src/view/form.ts b/src/view/form.ts new file mode 100644 index 0000000..a74a7de --- /dev/null +++ b/src/view/form.ts @@ -0,0 +1,55 @@ +import h, { classNames } from 'lib/h' +import * as dom from 'lib/dom' +import * as play from 'view/play' +import * as layout from 'view/layout' +import * as chord from 'chord' +import * as options from 'view/options' + +// View + +export function view(): Element[] { + let opts = options.load() + + return layout.view( + h('form', + { + className: 'g-Form', + onsubmit + }, + labelInput({ type: 'checkbox', name: 'major', label: 'Major', checked: opts.major }), + labelInput({ type: 'checkbox', name: 'minor', label: 'Minor', checked: opts.minor }), + labelInput({ type: 'number', name: 'bpm', label: 'BPM', value: opts.bpm.toString() }), + labelInput({ type: 'number', name: 'beatsPerChord', label: 'Beats per Chord', value: opts.beatsPerChord.toString() }), + h('input', { type: 'submit', value: 'Play' }) + ) + ) +} + +function onsubmit(event: Event): void { + event.preventDefault() + let input = (name: String) => document.querySelector(`input[name="${name}"]`) as HTMLInputElement + let opts = { + major: input('major').checked, + minor: input('minor').checked, + bpm: parseInt(input('bpm').value), + beatsPerChord: parseInt(input('beatsPerChord').value) + } + options.save(opts) + dom.show(play.view(opts)) +} + +type LabelInputParams = { + type: string, + name: string, + label: string, + checked?: boolean, + value?: string +} + +function labelInput({ type, name, label, checked, value }: LabelInputParams) { + return h('label', + {}, + h('input', { type, name, checked, value }), + label + ) +} diff --git a/src/view/layout.ts b/src/view/layout.ts new file mode 100644 index 0000000..ac62290 --- /dev/null +++ b/src/view/layout.ts @@ -0,0 +1,13 @@ +import h from 'lib/h' +import * as dom from 'lib/dom' +import * as form from 'view/form' + +export function view(main: Element): Element[] { + return [ + h('header', + { onclick: () => dom.show(form.view()) }, + 'Chords' + ), + main + ] +} diff --git a/src/view/options.ts b/src/view/options.ts new file mode 100644 index 0000000..4c71be2 --- /dev/null +++ b/src/view/options.ts @@ -0,0 +1,24 @@ +export type Options = { + major: boolean, + minor: boolean, + bpm: number, + beatsPerChord: number +} + +let defaultOptions: Options = { + major: true, + minor: false, + bpm: 90, + beatsPerChord: 4 +} + +let key: string = 'options' + +export function load(): Options { + let str = localStorage[key] + return str && JSON.parse(str) || defaultOptions +} + +export function save(options: Options): void { + localStorage[key] = JSON.stringify(options) +} diff --git a/src/view/play.ts b/src/view/play.ts new file mode 100644 index 0000000..26558cd --- /dev/null +++ b/src/view/play.ts @@ -0,0 +1,48 @@ +import h, { classNames } from 'lib/h' +import * as dom from 'lib/dom' +import { Options } from 'view/options' +import * as chord from 'chord' +import * as layout from 'view/layout' + +export function view(options: Options): Element[] { + let chords = h('div', + { className: 'g-Play' }, + chordNode(), + chordNode(), + chordNode(options), + chordNode(options) + ) + + let chordBeat = 1 + + dom.triggerAnimation(chords as HTMLElement, 'g-Play--Shift') + dom.triggerAnimation(chords.children[2] as HTMLElement, 'g-Chord--Beat') + setInterval(() => { + if (chordBeat == options.beatsPerChord) { + shiftChords(chords, options) + chords.children[0].classList.remove('g-Chord--Beat') + dom.triggerAnimation(chords as HTMLElement, 'g-Play--Shift') + dom.triggerAnimation(chords.children[2] as HTMLElement, 'g-Chord--Beat') + chordBeat = 1 + } else { + dom.triggerAnimation(chords.children[2] as HTMLElement, 'g-Chord--Beat') + chordBeat += 1 + } + }, 60 / options.bpm * 1000) + + return layout.view(chords) +} + +/* Shift chords and generate a new random one. + */ +function shiftChords(chords: Element, options: Options) { + chords.removeChild(chords.children[0]) + chords.appendChild(chordNode(options)) +} + +function chordNode(options?: Options): Element { + return h('div', + { className: 'g-Chord' }, + options ? chord.generate(options) : '' + ) +} -- cgit v1.2.3