aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoris2022-06-21 07:59:57 +0200
committerJoris2022-06-21 07:59:57 +0200
commit46f39fa93fa99eef2691d6dc905b9d083eb170cb (patch)
tree1d39b1b358a49a4c194dbf852cf80b5f07629361 /src
parent5944be94dfd221f41cfb5e60e007c159b617f5dc (diff)
Add more drum sounds
Diffstat (limited to 'src')
-rw-r--r--src/lib/dict.ts21
-rw-r--r--src/sounds.ts77
-rw-r--r--src/view/sequencer.ts40
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)
)
}