diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/dict.ts | 21 | ||||
-rw-r--r-- | src/sounds.ts | 77 | ||||
-rw-r--r-- | src/view/sequencer.ts | 40 |
3 files changed, 92 insertions, 46 deletions
diff --git a/src/lib/dict.ts b/src/lib/dict.ts new file mode 100644 index 0000000..43b9f02 --- /dev/null +++ b/src/lib/dict.ts @@ -0,0 +1,21 @@ +type Key = string | number | symbol + +export function fromList<V>(xs: Array<{key: Key, value: V}>): Record<Key, V> { + let res: any = {} + xs.forEach(o => res[o.key] = o.value) + return res as Record<Key, V> +} + +export function toList<V>(record: Record<Key, V>): Array<{key: Key, value: V}> { + return Object.keys(record) + .map(key => ({ key: key, value: record[key] })) +} + +type EnumObject = {[key: string]: number | string}; +type EnumObjectEnum<E extends EnumObject> = E extends {[key: string]: infer ET | string} ? ET : never; + +export function values<E extends EnumObject>(enumObject: E): EnumObjectEnum<E>[] { + return Object.keys(enumObject) + .filter(key => Number.isNaN(Number(key))) + .map(key => enumObject[key] as EnumObjectEnum<E>) +} diff --git a/src/sounds.ts b/src/sounds.ts index 5e4c68a..f906a70 100644 --- a/src/sounds.ts +++ b/src/sounds.ts @@ -1,13 +1,46 @@ +import * as dict from 'lib/dict' + export type Sounds = Record<Sound, AudioBuffer> export enum Sound { - Bass, - Snare, + Crash, + Ride, + TomHigh, + TomMedium, + TomFloor, + HitHatOpen, HitHatClosed, + Snare, + Kick, } export function all(): Array<Sound> { - return [Sound.HitHatClosed, Sound.Snare, Sound.Bass] + return dict.values(Sound) +} + +export function record<T>(f: (sound: Sound) => T): Record<Sound, T> { + let res: any = {} + + all().forEach(async sound => { + res[sound] = f(sound) + }) + + return (res as Record<Sound, T>) +} + +export function toString(sound: Sound): string { + switch (sound) { + case Sound.Kick: return 'Kick' + case Sound.Snare: return 'Snare' + case Sound.HitHatOpen: return 'Hit-hat open' + case Sound.HitHatClosed: return 'Hit-hat closed' + case Sound.Ride: return 'Ride' + case Sound.Crash: return 'Crash' + case Sound.TomFloor: return 'Tom floor' + case Sound.TomMedium: return 'Tom medium' + case Sound.TomHigh: return 'Tom high' + default: throw `Sound ${sound} is unknown.` + } } const audioContext = new AudioContext() @@ -17,27 +50,37 @@ export async function load(): Promise<Sounds> { if (lazy !== undefined) { return lazy } else { - let [bass, snare, hitHatClosed] = await Promise.all([ - fetchSound('/sounds/bass.opus'), - fetchSound('/sounds/snare.opus'), - fetchSound('/sounds/hit-hat-closed.opus') - ]) - - lazy = { - [Sound.Bass]: bass, - [Sound.Snare]: snare, - [Sound.HitHatClosed]: hitHatClosed - } - return lazy + let sounds = dict.fromList(await Promise.all(dict.toList(record(fetchSound)).map(async obj => { + let value = await obj.value + return { key: obj.key, value } + }))) as Sounds + + lazy = sounds + return sounds } } -async function fetchSound(name: string): Promise<AudioBuffer> { - return await fetch(name) +async function fetchSound(sound: Sound): Promise<AudioBuffer> { + return await fetch(path(sound)) .then(res => res.arrayBuffer()) .then(ArrayBuffer => audioContext.decodeAudioData(ArrayBuffer)) } +function path(sound: Sound): string { + switch (sound) { + case Sound.Kick: return 'sounds/kick.opus' + case Sound.Snare: return 'sounds/snare.opus' + case Sound.HitHatOpen: return 'sounds/hit-hat-open.opus' + case Sound.HitHatClosed: return 'sounds/hit-hat-closed.opus' + case Sound.Ride: return 'sounds/ride.opus' + case Sound.Crash: return 'sounds/crash.opus' + case Sound.TomFloor: return 'sounds/tom-floor.opus' + case Sound.TomMedium: return 'sounds/tom-medium.opus' + case Sound.TomHigh: return 'sounds/tom-high.opus' + default: throw `Sound ${sound} is unknown.` + } +} + export function play(sounds: Sounds, sound: Sound): void { const source = audioContext.createBufferSource() source.buffer = sounds[sound] diff --git a/src/view/sequencer.ts b/src/view/sequencer.ts index bc26e69..f7e397f 100644 --- a/src/view/sequencer.ts +++ b/src/view/sequencer.ts @@ -7,27 +7,15 @@ import * as block from 'view/sequencer/block' export function view() { let index = -1 - let columns = [{ - [Sound.Bass]: true, - [Sound.Snare]: false, - [Sound.HitHatClosed]: false, - }] + let columns = [soundsLib.record(sound => sound == Sound.Kick)] let blocksNode = h('div', { className: 'g-Sequencer__Blocks' }, - block.column([ - { - checked: false, - onCheck: checked => columns[0][Sound.HitHatClosed] = checked - }, - { - checked: false, - onCheck: checked => columns[0][Sound.Snare] = checked - }, - { - checked: true, - onCheck: checked => columns[0][Sound.Bass] = checked - } - ]) + block.column( + soundsLib.all().map(sound => ({ + checked: sound == Sound.Kick, + onCheck: checked => columns[0][sound] = checked + })) + ) ) let onNextStep = (sounds: soundsLib.Sounds) => { @@ -69,11 +57,7 @@ export function view() { columns.pop() }, onAdd: index => { - columns.push({ - [Sound.Bass]: false, - [Sound.Snare]: false, - [Sound.HitHatClosed]: false, - }) + columns.push(soundsLib.record(sound => false)) blocksNode.appendChild(block.column( soundsLib.all().map(sound => ({ checked: false, @@ -86,9 +70,7 @@ export function view() { { className: 'g-Sequencer__Grid' }, h('ol', { className: 'g-Sequencer__Column' }, - soundItem('Hit-hat (closed)', Sound.HitHatClosed), - soundItem('Snare', Sound.Snare), - soundItem('Bass', Sound.Bass) + ...soundsLib.all().map(soundItem) ), blocksNode ) @@ -97,13 +79,13 @@ export function view() { return sequencer } -function soundItem(name: string, sound: Sound): Element { +function soundItem(sound: Sound): Element { return h('li', { onclick: async () => { let sounds = await soundsLib.load() soundsLib.play(sounds, sound) } }, - name + soundsLib.toString(sound) ) } |