From 91dfef52ea774ed4309b8ab3864e6b5e50055e76 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 29 May 2022 21:06:07 +0200 Subject: [PATCH] basic sample loader from github --- packages/webdirt/index.mjs | 1 + packages/webdirt/sampler.mjs | 65 ++++++++++++++++++++++++++++++++++++ packages/webdirt/webdirt.mjs | 19 +++++++++-- 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 packages/webdirt/sampler.mjs diff --git a/packages/webdirt/index.mjs b/packages/webdirt/index.mjs index edafebd8..46a6887f 100644 --- a/packages/webdirt/index.mjs +++ b/packages/webdirt/index.mjs @@ -1 +1,2 @@ export * from './webdirt.mjs'; +export * from './sampler.mjs'; diff --git a/packages/webdirt/sampler.mjs b/packages/webdirt/sampler.mjs new file mode 100644 index 00000000..7a6e38b1 --- /dev/null +++ b/packages/webdirt/sampler.mjs @@ -0,0 +1,65 @@ +const bufferCache = {}; // string: Promise + +export const loadBuffer = (url, ac) => { + if (!bufferCache[url]) { + bufferCache[url] = fetch(url) + .then((res) => res.arrayBuffer()) + .then((res) => ac.decodeAudioData(res)); + } + return bufferCache[url]; +}; + +/* export const playBuffer = (buffer, time = ac.currentTime, destination = ac.destination) => { + const src = ac.createBufferSource(); + src.buffer = buffer; + src.connect(destination); + src.start(time); +}; + +export const playSample = async (url) => playBuffer(await loadBuffer(url)); */ + +// https://estuary.mcmaster.ca/samples/resources.json +// Array<{ "url":string, "bank": string, "n": number}> +// ritchse/tidal-drum-machines/tree/main/machines/AkaiLinn +const githubCache = {}; +let loaded; +export const loadGithubSamples = async (path, nameFn) => { + if (githubCache[path]) { + return githubCache[path]; + } + try { + console.log('load github path', path); + const [user, repo, ...folders] = path.split('/'); + const baseUrl = `https://api.github.com/repos/${user}/${repo}/contents`; + const banks = await fetch(`${baseUrl}/${folders.join('/')}`).then((res) => res.json()); + // fetch each subfolder + githubCache[path] = ( + await Promise.all( + banks.map(async ({ name, path }) => ({ + name, + content: await fetch(`${baseUrl}/${path}`) + .then((res) => res.json()) + .catch((err) => { + console.error('could not load path', err); + }), + })), + ) + ) + .filter(({ content }) => !!content) + .reduce( + (acc, { name, content }) => ({ + ...acc, + [nameFn?.(name) || name]: content.map(({ download_url }) => download_url), + }), + {}, + ); + } catch (err) { + console.error('failed to fetch github samples', err); + return; + } + loaded = githubCache[path]; + console.log('loaded github path ', path); + return githubCache[path]; +}; + +export const getLoadedSamples = () => loaded; diff --git a/packages/webdirt/webdirt.mjs b/packages/webdirt/webdirt.mjs index ea4eaa1f..0e6e63b0 100644 --- a/packages/webdirt/webdirt.mjs +++ b/packages/webdirt/webdirt.mjs @@ -1,6 +1,7 @@ import * as strudel from '@strudel.cycles/core'; const { Pattern } = strudel; import * as WebDirt from 'WebDirt'; +import { getLoadedSamples, loadBuffer } from './sampler.mjs'; let webDirt; @@ -62,7 +63,7 @@ export function loadWebDirt(config) { Pattern.prototype.webdirt = function () { // create a WebDirt object and initialize Web Audio context return this._withHap((hap) => { - const onTrigger = (time, e, currentTime) => { + const onTrigger = async (time, e, currentTime) => { if (!webDirt) { throw new Error('WebDirt not initialized!'); } @@ -71,7 +72,21 @@ Pattern.prototype.webdirt = function () { if (!s) { console.warn('webdirt: no "s" was set!'); } - webDirt.playSample({ s, n, ...rest }, deadline); + const samples = getLoadedSamples(); + if (!samples?.[s]) { + // try default samples + webDirt.playSample({ s, n, ...rest }, deadline); + return; + } + if (!samples?.[s]) { + console.warn(`webdirt: sample "${s}" not found in loaded samples`, samples); + } else { + const bank = samples[s]; + const sampleUrl = bank[n % bank.length]; + const buffer = await loadBuffer(sampleUrl, webDirt.ac); + const msg = { buffer: { buffer }, ...rest }; + webDirt.playSample(msg, deadline); + } }; return hap.setContext({ ...hap.context, onTrigger }); });