diff --git a/.prettierignore b/.prettierignore index 010ad28a..950e59f1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,3 +11,5 @@ pnpm-lock.yaml pnpm-workspace.yaml **/dev-dist website/.astro +!tidal-drum-machines.json +!tidal-drum-machines-alias.json diff --git a/packages/repl/README.md b/packages/repl/README.md index b82f3434..46819712 100644 --- a/packages/repl/README.md +++ b/packages/repl/README.md @@ -94,3 +94,15 @@ or The `.editor` property on the `strudel-editor` web component gives you the instance of [StrudelMirror](https://github.com/tidalcycles/strudel/blob/a46bd9b36ea7d31c9f1d3fca484297c7da86893f/packages/codemirror/codemirror.mjs#L124) that runs the REPL. For example, you could use `setCode` to change the code from the outside, `start` / `stop` to toggle playback or `evaluate` to evaluate the code. + +## Development: How to Test + +```sh +cd packages/repl +pnpm build +cd ../.. # back to root folder +# edit ./examples/buildless/web-component-no-iframe.html +# use +pnpx serve # from root folder +# go to http://localhost:3000/examples/buildless/web-component-no-iframe +``` diff --git a/packages/repl/prebake.mjs b/packages/repl/prebake.mjs index 9fc1c881..9642cdd6 100644 --- a/packages/repl/prebake.mjs +++ b/packages/repl/prebake.mjs @@ -1,5 +1,5 @@ import { noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core'; -import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; +import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; import * as core from '@strudel/core'; export async function prebake() { @@ -21,6 +21,9 @@ export async function prebake() { ); // load samples const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/'; + + // TODO: move this onto the strudel repo + const ts = 'https://raw.githubusercontent.com/todepond/samples/main/'; await Promise.all([ modulesLoading, registerSynthSounds(), @@ -36,6 +39,8 @@ export async function prebake() { samples(`${ds}/EmuSP12.json`), samples(`${ds}/vcsl.json`), ]); + + aliasBank(`${ts}/tidal-drum-machines-alias.json`); } const maxPan = noteToMidi('C8'); diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index b61f4263..d17cd8f7 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -17,11 +17,72 @@ import { loadBuffer } from './sampler.mjs'; export const soundMap = map(); export function registerSound(key, onTrigger, data = {}) { - soundMap.setKey(key, { onTrigger, data }); + soundMap.setKey(key.toLowerCase(), { onTrigger, data }); +} + +function aliasBankMap(aliasMap) { + // Make all bank keys lower case for case insensitivity + for (const key in aliasMap) { + aliasMap[key.toLowerCase()] = aliasMap[key]; + } + + // Look through every sound... + const soundDictionary = soundMap.get(); + for (const key in soundDictionary) { + // Check if the sound is part of a bank... + const [bank, suffix] = key.split('_'); + if (!suffix) continue; + + // Check if the bank is aliased... + const aliasValue = aliasMap[bank]; + if (aliasValue) { + if (typeof aliasValue === 'string') { + // Alias a single alias + soundDictionary[`${aliasValue}_${suffix}`.toLowerCase()] = soundDictionary[key]; + } else if (Array.isArray(aliasValue)) { + // Alias multiple aliases + for (const alias of aliasValue) { + soundDictionary[`${alias}_${suffix}`.toLowerCase()] = soundDictionary[key]; + } + } + } + } + + // Update the sound map! + // We need to destructure here to trigger the update + soundMap.set({ ...soundDictionary }); +} + +async function aliasBankPath(path) { + const response = await fetch(path); + const aliasMap = await response.json(); + aliasBankMap(aliasMap); +} + +/** + * Register an alias for a bank of sounds. + * Optionally accepts a single argument map of bank aliases. + * Optionally accepts a single argument string of a path to a JSON file containing bank aliases. + * @param {string} bank - The bank to alias + * @param {string} alias - The alias to use for the bank + */ +export async function aliasBank(...args) { + switch (args.length) { + case 1: + if (typeof args[0] === 'string') { + return aliasBankPath(args[0]); + } else { + return aliasBankMap(args[0]); + } + case 2: + return aliasBankMap({ [args[0]]: args[1] }); + default: + throw new Error('aliasMap expects 1 or 2 arguments, received ' + args.length); + } } export function getSound(s) { - return soundMap.get()[s]; + return soundMap.get()[s.toLowerCase()]; } const defaultDefaultValues = { diff --git a/website/public/tidal-drum-machines-alias.json b/website/public/tidal-drum-machines-alias.json new file mode 100644 index 00000000..460cc6ba --- /dev/null +++ b/website/public/tidal-drum-machines-alias.json @@ -0,0 +1,68 @@ +{ + "AJKPercusyn": "Percysyn", + "AkaiLinn": "Linn", + "AkaiMPC60": "MPC60", + "AkaiXR10": "XR10", + "AlesisHR16": "HR16", + "AlesisSR16": "SR16", + "BossDR110": "DR110", + "BossDR220": "DR220", + "BossDR55": "DR55", + "BossDR550": "DR550", + "CasioRZ1": "RZ1", + "CasioSK1": "SK1", + "CasioVL1": "VL1", + "DoepferMS404": "MS404", + "EmuDrumulator": "Drumulator", + "EmuSP12": "SP12", + "KorgDDM110": "DDM110", + "KorgKPR77": "KPR77", + "KorgKR55": "KR55", + "KorgKRZ": "KRZ", + "KorgM1": "M1", + "KorgMinipops": "Minipops", + "KorgPoly800": "Poly800", + "KorgT3": "T3", + "Linn9000": "9000", + "LinnLM1": "LM1", + "LinnLM2": "LM2", + "MoogConcertMateMG1": "ConcertMateMG1", + "OberheimDMX": "DMX", + "RhodesPolaris": "Polaris", + "RhythmAce": "Ace", + "RolandCompurhythm1000": "Compurhythm1000", + "RolandCompurhythm78": "Compurhythm78", + "RolandCompurhythm8000": "Compurhythm8000", + "RolandD110": "D110", + "RolandD70": "D70", + "RolandDDR30": "DDR30", + "RolandJD990": "JD990", + "RolandMC202": "MC202", + "RolandMC303": "MC303", + "RolandMT32": "MT32", + "RolandR8": "R8", + "RolandS50": "S50", + "RolandSH09": "SH09", + "RolandSystem100": "System100", + "RolandTR505": "TR505", + "RolandTR606": "TR606", + "RolandTR626": "TR626", + "RolandTR707": "TR707", + "RolandTR727": "TR727", + "RolandTR808": "TR808", + "RolandTR909": "TR909", + "SakataDPM48": "DPM48", + "SequentialCircuitsDrumtracks": "CircuitsDrumtracks", + "SequentialCircuitsTom": "CircuitsTom", + "SimmonsSDS400": "SDS400", + "SimmonsSDS5": "SDS5", + "SoundmastersR88": "R88", + "UnivoxMicroRhythmer12": "MicroRhythmer12", + "ViscoSpaceDrum": "SpaceDrum", + "XdrumLM8953": "LM8953", + "YamahaRM50": "RM50", + "YamahaRX21": "RX21", + "YamahaRX5": "RX5", + "YamahaRY30": "RY30", + "YamahaTG33": "TG33" +} diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 55b534a6..b0833bd5 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -1,5 +1,5 @@ import { Pattern, noteToMidi, valueToMidi } from '@strudel/core'; -import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; +import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; import { registerSamplesFromDB } from './idbutils.mjs'; import './piano.mjs'; import './files.mjs'; @@ -121,6 +121,8 @@ export async function prebake() { }, ), ]); + + aliasBank(`${baseNoTrailing}/tidal-drum-machines-alias.json`); } const maxPan = noteToMidi('C8');