diff options
-rw-r--r-- | README.md | 29 | ||||
-rw-r--r-- | public/sounds/crash.opus | bin | 0 -> 20320 bytes | |||
-rw-r--r-- | public/sounds/hit-hat-closed.opus | bin | 3920 -> 1753 bytes | |||
-rw-r--r-- | public/sounds/hit-hat-open.opus | bin | 0 -> 4097 bytes | |||
-rw-r--r-- | public/sounds/kick.opus (renamed from public/sounds/bass.opus) | bin | 1743 -> 1743 bytes | |||
-rw-r--r-- | public/sounds/ride.opus | bin | 0 -> 16721 bytes | |||
-rw-r--r-- | public/sounds/snare.opus | bin | 5577 -> 2138 bytes | |||
-rw-r--r-- | public/sounds/tom-floor.opus | bin | 0 -> 3137 bytes | |||
-rw-r--r-- | public/sounds/tom-high.opus | bin | 0 -> 7658 bytes | |||
-rw-r--r-- | public/sounds/tom-medium.opus | bin | 0 -> 6578 bytes | |||
-rw-r--r-- | src/lib/dict.ts | 21 | ||||
-rw-r--r-- | src/sounds.ts | 77 | ||||
-rw-r--r-- | src/view/sequencer.ts | 40 |
13 files changed, 111 insertions, 56 deletions
@@ -10,17 +10,26 @@ Then, open your browser at `http://localhost:8000`. # Sounds -- hit-hat (closed): https://lasonotheque.org/detail-2302-charleston-fermee-7.html -- snare: https://lasonotheque.org/detail-2304-caisse-claire-1.html - bass: https://freesound.org/people/karolist/sounds/371192/ +- snare: https://lasonotheque.org/detail-2304-caisse-claire-1.html +- hit-hat: + - open: https://lasonotheque.org/detail-2295-charleston-1.html + - closed: https://lasonotheque.org/detail-2302-charleston-fermee-7.html +- cymbals: + - 40 cm: https://lasonotheque.org/detail-2311-cymbale-40cm-1.html + - ryde: https://lasonotheque.org/detail-2323-cymbale-ride-51cm-1.html +- tom: + - floor: https://lasonotheque.org/detail-2338-tom-grave-1.html + - medium: https://lasonotheque.org/detail-2333-tom-medium-1.html + - high: https://lasonotheque.org/detail-2329-tom-aigu-1.html # Todo -Multi-sound sequencer: - -- [ ] Sub divide beats ? - -Working on increasing tempo: - -- [ ] Add / Remove beat integrated into sequencer -- [ ] Augment the BPM by X after Y cycles +- Start and stop pressing space +- Rename to `drum.guyonvarch.me` +- Sub divide beats +- Add / Remove beat integrated into sequencer +- Augment the BPM by X after Y cycles +- Prefill with Rock, Jazz, Funk, …, rythms +- Control volume of individual blocks +- Load & save (json files ? URL ?) diff --git a/public/sounds/crash.opus b/public/sounds/crash.opus Binary files differnew file mode 100644 index 0000000..a219130 --- /dev/null +++ b/public/sounds/crash.opus diff --git a/public/sounds/hit-hat-closed.opus b/public/sounds/hit-hat-closed.opus Binary files differindex 1513a5e..c7569b4 100644 --- a/public/sounds/hit-hat-closed.opus +++ b/public/sounds/hit-hat-closed.opus diff --git a/public/sounds/hit-hat-open.opus b/public/sounds/hit-hat-open.opus Binary files differnew file mode 100644 index 0000000..dec1c7b --- /dev/null +++ b/public/sounds/hit-hat-open.opus diff --git a/public/sounds/bass.opus b/public/sounds/kick.opus Binary files differindex 40a8d60..40a8d60 100644 --- a/public/sounds/bass.opus +++ b/public/sounds/kick.opus diff --git a/public/sounds/ride.opus b/public/sounds/ride.opus Binary files differnew file mode 100644 index 0000000..7d2cae9 --- /dev/null +++ b/public/sounds/ride.opus diff --git a/public/sounds/snare.opus b/public/sounds/snare.opus Binary files differindex 1808dd3..0efe32e 100644 --- a/public/sounds/snare.opus +++ b/public/sounds/snare.opus diff --git a/public/sounds/tom-floor.opus b/public/sounds/tom-floor.opus Binary files differnew file mode 100644 index 0000000..b09c607 --- /dev/null +++ b/public/sounds/tom-floor.opus diff --git a/public/sounds/tom-high.opus b/public/sounds/tom-high.opus Binary files differnew file mode 100644 index 0000000..05d6f19 --- /dev/null +++ b/public/sounds/tom-high.opus diff --git a/public/sounds/tom-medium.opus b/public/sounds/tom-medium.opus Binary files differnew file mode 100644 index 0000000..659c531 --- /dev/null +++ b/public/sounds/tom-medium.opus 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) ) } |