From ef585cc779ec0d70b675a3377fd7ba91acf413f4 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 5 Feb 2023 19:15:42 +0100 Subject: [PATCH] basic offline cache --- packages/webaudio/package.json | 3 +- packages/webaudio/sampler.mjs | 61 ++++++++++++++++++++++++++-------- pnpm-lock.yaml | 12 +++++++ 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index 3bc7de38b..a0d8cbdbe 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -34,7 +34,8 @@ }, "homepage": "https://github.com/tidalcycles/strudel#readme", "dependencies": { - "@strudel.cycles/core": "workspace:*" + "@strudel.cycles/core": "workspace:*", + "idb-keyval": "^6.2.0" }, "devDependencies": { "vite": "^3.2.2" diff --git a/packages/webaudio/sampler.mjs b/packages/webaudio/sampler.mjs index f94b6b5a8..260c658e0 100644 --- a/packages/webaudio/sampler.mjs +++ b/packages/webaudio/sampler.mjs @@ -1,5 +1,6 @@ import { logger, toMidi, valueToMidi } from '@strudel.cycles/core'; import { getAudioContext } from './index.mjs'; +import { get, set } from 'idb-keyval'; const bufferCache = {}; // string: Promise const loadCache = {}; // string: Promise @@ -74,22 +75,37 @@ export const getSampleBufferSource = async (s, n, note, speed, freq) => { return bufferSource; }; -export const loadBuffer = (url, ac, s, n = 0) => { +const useOfflineCache = true; + +export const loadBuffer = async (url, ac, s, n = 0) => { const label = s ? `sound "${s}:${n}"` : 'sample'; + // only load if not already requested (loadCache = cache promises loading buffers by url) if (!loadCache[url]) { - logger(`[sampler] load ${label}..`, 'load-sample', { url }); - const timestamp = Date.now(); - loadCache[url] = fetch(url) - .then((res) => res.arrayBuffer()) - .then(async (res) => { - const took = Date.now() - timestamp; - const size = humanFileSize(res.byteLength); - // const downSpeed = humanFileSize(res.byteLength / took); - logger(`[sampler] load ${label}... done! loaded ${size} in ${took}ms`, 'loaded-sample', { url }); - const decoded = await ac.decodeAudioData(res); - bufferCache[url] = decoded; - return decoded; - }); + loadCache[url] = (async () => { + logger(`[sampler] load ${label}..`, 'load-sample', { url }); + const timestamp = Date.now(); + if (useOfflineCache) { + const cachedDataUrl = await get(url); + if (cachedDataUrl) { + // buffer has been loaded into offline cache in a previous session + logger(`[sampler] load ${label}... done! found in offline cache!`, 'loaded-sample', { url }); + return dataUrlToAudioBuffer(ac, cachedDataUrl); + } + } + // buffer not cached offline => fetch from url => + const res = await fetch(url); + const buf = await res.arrayBuffer(); + if (useOfflineCache) { + // we don't need to wait for the offline cache to finish here + bufferToDataUrl(buf).then((dataUrl) => set(url, dataUrl)); + } + const audioBuffer = await ac.decodeAudioData(buf); + const took = Date.now() - timestamp; + const size = humanFileSize(buf.byteLength); + logger(`[sampler] load ${label}... done! loaded ${size} in ${took}ms`, 'loaded-sample', { url }); + bufferCache[url] = audioBuffer; + return audioBuffer; + })(); } return loadCache[url]; }; @@ -179,3 +195,20 @@ export const resetLoadedSamples = () => { }; export const getLoadedSamples = () => sampleCache.current; + +async function bufferToDataUrl(buf) { + return new Promise((resolve) => { + var blob = new Blob([buf], { type: 'application/octet-binary' }); + var reader = new FileReader(); + reader.onload = function (event) { + resolve(event.target.result); + }; + reader.readAsDataURL(blob); + }); +} + +function dataUrlToAudioBuffer(ctx, dataUrl) { + return fetch(dataUrl) + .then((res) => res.arrayBuffer()) + .then((res) => ctx.decodeAudioData(res)); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce03f49cf..d1f9d0464 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -313,9 +313,11 @@ importers: packages/webaudio: specifiers: '@strudel.cycles/core': workspace:* + idb-keyval: ^6.2.0 vite: ^3.2.2 dependencies: '@strudel.cycles/core': link:../core + idb-keyval: 6.2.0 devDependencies: vite: 3.2.5 @@ -6296,6 +6298,12 @@ packages: dev: true optional: true + /idb-keyval/6.2.0: + resolution: {integrity: sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng==} + dependencies: + safari-14-idb-fix: 3.0.0 + dev: false + /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -9968,6 +9976,10 @@ packages: mri: 1.2.0 dev: false + /safari-14-idb-fix/3.0.0: + resolution: {integrity: sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog==} + dev: false + /safe-buffer/5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}