From 46f39fa93fa99eef2691d6dc905b9d083eb170cb Mon Sep 17 00:00:00 2001 From: Joris Date: Tue, 21 Jun 2022 07:59:57 +0200 Subject: Add more drum sounds --- src/lib/dict.ts | 21 ++++++++++++++ src/sounds.ts | 77 +++++++++++++++++++++++++++++++++++++++------------ src/view/sequencer.ts | 40 ++++++++------------------ 3 files changed, 92 insertions(+), 46 deletions(-) create mode 100644 src/lib/dict.ts (limited to 'src') 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(xs: Array<{key: Key, value: V}>): Record { + let res: any = {} + xs.forEach(o => res[o.key] = o.value) + return res as Record +} + +export function toList(record: Record): 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 {[key: string]: infer ET | string} ? ET : never; + +export function values(enumObject: E): EnumObjectEnum[] { + return Object.keys(enumObject) + .filter(key => Number.isNaN(Number(key))) + .map(key => enumObject[key] as EnumObjectEnum) +} 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 export enum Sound { - Bass, - Snare, + Crash, + Ride, + TomHigh, + TomMedium, + TomFloor, + HitHatOpen, HitHatClosed, + Snare, + Kick, } export function all(): Array { - return [Sound.HitHatClosed, Sound.Snare, Sound.Bass] + return dict.values(Sound) +} + +export function record(f: (sound: Sound) => T): Record { + let res: any = {} + + all().forEach(async sound => { + res[sound] = f(sound) + }) + + return (res as Record) +} + +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 { 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 { - return await fetch(name) +async function fetchSound(sound: Sound): Promise { + 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) ) } -- cgit v1.2.3