diff --git a/packages/core/index.mjs b/packages/core/index.mjs index e4daf445a..2a17ccb9a 100644 --- a/packages/core/index.mjs +++ b/packages/core/index.mjs @@ -25,6 +25,7 @@ export * from './cyclist.mjs'; export * from './logger.mjs'; export * from './time.mjs'; export * from './ui.mjs'; +export * from './ui.mjs'; export { default as drawLine } from './drawLine.mjs'; // below won't work with runtime.mjs (json import fails) /* import * as p from './package.json'; diff --git a/website/src/repl/idbutils.mjs b/packages/repl/idbutils.mjs similarity index 97% rename from website/src/repl/idbutils.mjs rename to packages/repl/idbutils.mjs index 5fc62c576..9af168d13 100644 --- a/website/src/repl/idbutils.mjs +++ b/packages/repl/idbutils.mjs @@ -1,9 +1,12 @@ import { registerSound, onTriggerSample } from '@strudel/webaudio'; -import { isAudioFile } from './files.mjs'; import { logger } from '@strudel/core'; //utilites for writing and reading to the indexdb + +export const isAudioFile = (filename) => + ['wav', 'mp3', 'flac', 'ogg', 'm4a', 'aac'].includes(filename.split('.').slice(-1)[0]); + export const userSamplesDBConfig = { dbName: 'samples', table: 'usersamples', diff --git a/packages/repl/prebake.mjs b/packages/repl/prebake.mjs index 3f421f985..a2667b565 100644 --- a/packages/repl/prebake.mjs +++ b/packages/repl/prebake.mjs @@ -1,5 +1,5 @@ -import { noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core'; -import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; +import { noteToMidi, valueToMidi, Pattern, evalScope,registerSamplesFromDB } from '@strudel/core'; +import { aliasBank, registerSynthSounds, registerZZFXSounds, samples, } from '@strudel/webaudio'; import * as core from '@strudel/core'; export async function prebake() { @@ -28,6 +28,7 @@ export async function prebake() { modulesLoading, registerSynthSounds(), registerZZFXSounds(), + registerSamplesFromDB(), //registerSoundfonts(), // need dynamic import here, because importing @strudel/soundfonts fails on server: // => getting "window is not defined", as soon as "@strudel/soundfonts" is imported statically @@ -36,9 +37,10 @@ export async function prebake() { samples(`${ds}/tidal-drum-machines.json`), samples(`${ds}/piano.json`), samples(`${ds}/Dirt-Samples.json`), - samples(`${ds}/uzu-drumkit.json`), + samples(`https://raw.githubusercontent.com/tidalcycles/uzu-drumkit/main/strudel.json`), samples(`${ds}/vcsl.json`), samples(`${ds}/mridangam.json`), + registerSamplesFromDB(), ]); aliasBank(`${ts}/tidal-drum-machines-alias.json`); diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 18d1b7797..f764be42f 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -216,50 +216,62 @@ function getSamplesPrefixHandler(url) { */ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', options = {}) => { + if (typeof sampleMap === 'string') { - // check if custom prefix handler - const handler = getSamplesPrefixHandler(sampleMap); - if (handler) { - return handler(sampleMap); - } - sampleMap = resolveSpecialPaths(sampleMap); - if (sampleMap.startsWith('github:')) { - sampleMap = githubPath(sampleMap, 'strudel.json'); - } - if (sampleMap.startsWith('local:')) { - sampleMap = `http://localhost:5432`; - } - if (sampleMap.startsWith('shabda:')) { - let [_, path] = sampleMap.split('shabda:'); - sampleMap = `https://shabda.ndre.gr/${path}.json?strudel=1`; - } - if (sampleMap.startsWith('shabda/speech')) { - let [_, path] = sampleMap.split('shabda/speech'); - path = path.startsWith('/') ? path.substring(1) : path; - let [params, words] = path.split(':'); - let gender = 'f'; - let language = 'en-GB'; - if (params) { - [language, gender] = params.split('/'); - } - sampleMap = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'`; - } - if (typeof fetch !== 'function') { - // not a browser - return; - } const base = sampleMap.split('/').slice(0, -1).join('/'); - if (typeof fetch === 'undefined') { - // skip fetch when in node / testing - return; + const savedJSON = localStorage.getItem(sampleMap) + if (savedJSON) { + samples(JSON.parse(savedJSON), baseUrl || json._base || base, options) + } else { + + + // check if custom prefix handler + const handler = getSamplesPrefixHandler(sampleMap); + if (handler) { + return handler(sampleMap); + } + sampleMap = resolveSpecialPaths(sampleMap); + if (sampleMap.startsWith('github:')) { + sampleMap = githubPath(sampleMap, 'strudel.json'); + } + if (sampleMap.startsWith('local:')) { + sampleMap = `http://localhost:5432`; + } + if (sampleMap.startsWith('shabda:')) { + let [_, path] = sampleMap.split('shabda:'); + sampleMap = `https://shabda.ndre.gr/${path}.json?strudel=1`; + } + if (sampleMap.startsWith('shabda/speech')) { + let [_, path] = sampleMap.split('shabda/speech'); + path = path.startsWith('/') ? path.substring(1) : path; + let [params, words] = path.split(':'); + let gender = 'f'; + let language = 'en-GB'; + if (params) { + [language, gender] = params.split('/'); + } + sampleMap = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'`; + } + if (typeof fetch !== 'function') { + // not a browser + return; + } + + if (typeof fetch === 'undefined') { + // skip fetch when in node / testing + return; + } + return fetch(sampleMap) + .then((res) => res.json()) + .then((json) => { + localStorage.setItem(sampleMap, JSON.stringify(json)) + samples(json, baseUrl || json._base || base, options) + }) + .catch((error) => { + console.error(error); + throw new Error(`error loading "${sampleMap}"`); + }); } - return fetch(sampleMap) - .then((res) => res.json()) - .then((json) => samples(json, baseUrl || json._base || base, options)) - .catch((error) => { - console.error(error); - throw new Error(`error loading "${sampleMap}"`); - }); } const { prebake, tag } = options; processSampleMap( diff --git a/website/public/uzu-drumkit.json b/website/public/uzu-drumkit.json deleted file mode 100644 index b9c7250d5..000000000 --- a/website/public/uzu-drumkit.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "_base": "https://raw.githubusercontent.com/tidalcycles/uzu-drumkit/main/", - "bd": [ - "bd/10_bd_switchangel.wav", - "bd/11_bd_mot4i.wav", - "bd/12_bd_mot4i.wav", - "bd/13_bd_mot4i.wav", - "bd/14_bd_switchangel.wav", - "bd/15_bd_switchangel.wav", - "bd/16_bd_switchangel.wav", - "bd/17_bd_switchangel.wav" - ], - "brk": [ - "brk/10_break_amen_pprocessed.wav" - ], - "cb": [ - "cb/10_perc_switchangel.wav" - ], - "cp": [ - "cp/10_cp_switchangel.wav", - "cp/11_cp_mot4i.wav" - ], - "cr": [ - "cr/10_cr_switchangel.wav", - "cr/11_cr_mot4i.wav" - ], - "hh": [ - "hh/10_hh_switchangel.wav", - "hh/11_hh_mot4i.wav", - "hh/12_hh_switchangel.wav", - "hh/13_hh_switchangel.wav", - "hh/14_hh_mot4i.wav" - ], - "ht": [ - "ht/10_ht_mot4i.wav" - ], - "lt": [ - "lt/10_lt_mot4i.wav" - ], - "misc": [ - "misc/10_misc_switchangel_ludens.wav", - "misc/11_misc_switchangel_ludens.wav", - "misc/12_misc_switchangel_ludens.wav", - "misc/13_misc_switchangel_ludens.wav", - "misc/14_misc_switchangel_ludens.wav" - ], - "mt": [ - "mt/10_mt_mot4i.wav" - ], - "oh": [ - "oh/10_oh_switchangel.wav", - "oh/11_oh_switchangel.wav", - "oh/12_oh_switchangel.wav", - "oh/13_oh_switchangel.wav" - ], - "rd": [ - "rd/10_rd_switchangel.wav" - ], - "rim": [ - "rim/10_rim_switchangel.wav", - "rim/11_rim_switch_angel.wav" - ], - "sd": [ - "sd/10_sd_switchangel-bounce-2.wav", - "sd/11_sd_switchangel_3.wav", - "sd/12_sd_switchangel_2.wav", - "sd/13_sd_switchangel_2.wav", - "sd/14_sd.wav" - ], - "sh": [ - "sh/10_sh_switchangel.wav" - ], - "tb": [ - "tb/10_tb.wav" - ] - } \ No newline at end of file diff --git a/website/src/repl/components/panel/FilesTab.jsx b/website/src/repl/components/panel/FilesTab.jsx index 2e121fb59..8e00133d2 100644 --- a/website/src/repl/components/panel/FilesTab.jsx +++ b/website/src/repl/components/panel/FilesTab.jsx @@ -1,7 +1,7 @@ import { Fragment, useEffect } from 'react'; -import React, { useMemo, useState } from 'react'; -import { isAudioFile, readDir, dir, playFile } from '../../files.mjs'; - +import { useMemo, useState } from 'react'; +import { readDir, dir, playFile } from '../../files.mjs'; +import { isAudioFile } from '@strudel/core'; export function FilesTab() { const [path, setPath] = useState([]); useEffect(() => { diff --git a/website/src/repl/components/panel/ImportSoundsButton.jsx b/website/src/repl/components/panel/ImportSoundsButton.jsx index 5574c396c..c9e19c203 100644 --- a/website/src/repl/components/panel/ImportSoundsButton.jsx +++ b/website/src/repl/components/panel/ImportSoundsButton.jsx @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react'; -import { registerSamplesFromDB, uploadSamplesToDB, userSamplesDBConfig } from '../../idbutils.mjs'; +import { registerSamplesFromDB, uploadSamplesToDB, userSamplesDBConfig } from '@strudel/core/idbutils.mjs'; //choose a directory to locally import samples export default function ImportSoundsButton({ onComplete }) { diff --git a/website/src/repl/files.mjs b/website/src/repl/files.mjs index bd83f071d..bfd0c6d36 100644 --- a/website/src/repl/files.mjs +++ b/website/src/repl/files.mjs @@ -75,8 +75,7 @@ export const walkFileTree = (node, fn) => { } }; -export const isAudioFile = (filename) => - ['wav', 'mp3', 'flac', 'ogg', 'm4a', 'aac'].includes(filename.split('.').slice(-1)[0]); + function uint8ArrayToDataURL(uint8Array) { const blob = new Blob([uint8Array], { type: 'audio/*' }); diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index fac6f5bb6..5704f9776 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -1,6 +1,6 @@ import { Pattern, noteToMidi, valueToMidi } from '@strudel/core'; import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; -import { registerSamplesFromDB } from './idbutils.mjs'; +import { registerSamplesFromDB } from '@strudel/core/idbutils.mjs'; import './piano.mjs'; import './files.mjs'; @@ -10,6 +10,8 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL export async function prebake() { // https://archive.org/details/SalamanderGrandPianoV3 // License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm + + await Promise.all([ registerSynthSounds(), registerZZFXSounds(), @@ -28,7 +30,7 @@ export async function prebake() { prebake: true, tag: 'drum-machines', }), - samples(`${baseNoTrailing}/uzu-drumkit.json`, undefined, { + samples(`https://raw.githubusercontent.com/tidalcycles/uzu-drumkit/main/strudel.json`, undefined, { prebake: true, tag: 'drum-machines', }),