From 61919a1b3e87d5e66957351d11409130ffc6f0cd Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 3 Dec 2023 23:33:04 -0500 Subject: [PATCH 01/12] upload samples --- website/src/repl/FileUpload.jsx | 35 +++++++++++++++++++++++++++++++++ website/src/repl/Footer.jsx | 2 ++ 2 files changed, 37 insertions(+) create mode 100644 website/src/repl/FileUpload.jsx diff --git a/website/src/repl/FileUpload.jsx b/website/src/repl/FileUpload.jsx new file mode 100644 index 00000000..ab606657 --- /dev/null +++ b/website/src/repl/FileUpload.jsx @@ -0,0 +1,35 @@ +import React from 'react'; + +export default function FileUpload({ onUpload }) { + let fileUploadRef = React.createRef(); + function mapFiles(soundFiles) { + const files = Array.from(soundFiles).map((soundFile) => { + const file = { name: soundFile.name, path: URL.createObjectURL(soundFile) }; + return file; + }); + onUpload(files); + } + return ( + <> + { + mapFiles(fileUploadRef.current.files); + }} + /> + fileUploadRef.current.click()} + /> + + ); +} diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index b03f62d0..833a7a75 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -10,6 +10,7 @@ import { useSettings, settingsMap, setActiveFooter, defaultSettings } from '../s import { getAudioContext, soundMap } from '@strudel.cycles/webaudio'; import { useStore } from '@nanostores/react'; import { FilesTab } from './FilesTab'; +import FileUpload from './FileUpload'; const TAURI = window.__TAURI__; @@ -424,6 +425,7 @@ function SettingsTab({ scheduler }) { onChange={(fontFamily) => settingsMap.setKey('fontFamily', fontFamily)} /> + console.log(files)} /> Date: Mon, 4 Dec 2023 19:11:38 -0500 Subject: [PATCH 02/12] style input --- packages/superdough/sampler.mjs | 7 +-- website/src/repl/FileUpload.jsx | 35 --------------- website/src/repl/Footer.jsx | 6 +-- website/src/repl/ImportSoundsButton.jsx | 58 +++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 41 deletions(-) delete mode 100644 website/src/repl/FileUpload.jsx create mode 100644 website/src/repl/ImportSoundsButton.jsx diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index b8f10d5d..d3e6a5a4 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -209,14 +209,15 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option const { prebake, tag } = options; processSampleMap( sampleMap, - (key, value) => - registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { + (key, value) => { + return registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { type: 'sample', samples: value, baseUrl, prebake, tag, - }), + }); + }, baseUrl, ); }; diff --git a/website/src/repl/FileUpload.jsx b/website/src/repl/FileUpload.jsx deleted file mode 100644 index ab606657..00000000 --- a/website/src/repl/FileUpload.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -export default function FileUpload({ onUpload }) { - let fileUploadRef = React.createRef(); - function mapFiles(soundFiles) { - const files = Array.from(soundFiles).map((soundFile) => { - const file = { name: soundFile.name, path: URL.createObjectURL(soundFile) }; - return file; - }); - onUpload(files); - } - return ( - <> - { - mapFiles(fileUploadRef.current.files); - }} - /> - fileUploadRef.current.click()} - /> - - ); -} diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index 833a7a75..7854d25e 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -10,7 +10,7 @@ import { useSettings, settingsMap, setActiveFooter, defaultSettings } from '../s import { getAudioContext, soundMap } from '@strudel.cycles/webaudio'; import { useStore } from '@nanostores/react'; import { FilesTab } from './FilesTab'; -import FileUpload from './FileUpload'; +import ImportSoundsButton from './ImportSoundsButton'; const TAURI = window.__TAURI__; @@ -243,7 +243,7 @@ function SoundsTab() { }); return (
-
+
settingsMap.setKey('soundsFilter', value)} @@ -254,6 +254,7 @@ function SoundsTab() { user: 'User', }} > + settingsMap.setKey('soundsFilter', 'user')} />
{soundEntries.map(([name, { data, onTrigger }]) => ( @@ -425,7 +426,6 @@ function SettingsTab({ scheduler }) { onChange={(fontFamily) => settingsMap.setKey('fontFamily', fontFamily)} /> - console.log(files)} /> a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' })) + .forEach((soundFile) => { + const name = soundFile.name; + if (!isAudioFile(name)) { + return; + } + const splitRelativePath = soundFile.webkitRelativePath?.split('/'); + const parentDirectory = splitRelativePath[splitRelativePath.length - 2]; + const soundPath = URL.createObjectURL(soundFile); + const soundPaths = sounds.get(parentDirectory) ?? new Set(); + soundPaths.add(soundPath); + + sounds.set(parentDirectory, soundPaths); + }); + sounds.forEach((soundPaths, key) => { + const value = Array.from(soundPaths); + registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { + type: 'sample', + samples: value, + baseUrl: undefined, + prebake: false, + tag: undefined, + }); + }); + onComplete(); + } + + return ( + + ); +} From 8146725f89042944496c85643ec6eec49baa1cc1 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Mon, 4 Dec 2023 19:20:28 -0500 Subject: [PATCH 03/12] removed unessecary change --- packages/superdough/sampler.mjs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index d3e6a5a4..b8f10d5d 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -209,15 +209,14 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option const { prebake, tag } = options; processSampleMap( sampleMap, - (key, value) => { - return registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { + (key, value) => + registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { type: 'sample', samples: value, baseUrl, prebake, tag, - }); - }, + }), baseUrl, ); }; From a7713213f93736cd86d26480ef2a6185e4ebf0a3 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 10 Dec 2023 00:44:34 -0500 Subject: [PATCH 04/12] indexdb --- website/src/repl/ImportSoundsButton.jsx | 172 ++++++++++++++++++++---- 1 file changed, 143 insertions(+), 29 deletions(-) diff --git a/website/src/repl/ImportSoundsButton.jsx b/website/src/repl/ImportSoundsButton.jsx index 10f2ea48..502f25e4 100644 --- a/website/src/repl/ImportSoundsButton.jsx +++ b/website/src/repl/ImportSoundsButton.jsx @@ -1,38 +1,152 @@ -import React from 'react'; +import React, { useCallback, useEffect } from 'react'; +import { registerSound, onTriggerSample } from '@strudel.cycles/webaudio'; import { isAudioFile } from './files.mjs'; //choose a directory to locally import samples -export default function ImportSoundsButton({ onComplete }) { - let fileUploadRef = React.createRef(); - function mapFiles(soundFiles) { - const sounds = new Map(); - Array.from(soundFiles) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' })) - .forEach((soundFile) => { - const name = soundFile.name; - if (!isAudioFile(name)) { +const userSamplesDB = 'testdb3'; +const sampleObject = 'testsamples'; + +function clearData() { + window.indexedDB + .databases() + .then((r) => { + for (var i = 0; i < r.length; i++) window.indexedDB.deleteDatabase(r[i].name); + }) + .then(() => { + alert('All data cleared.'); + }); +} + +const registerSamples = () => { + openDB((objectStore) => { + let query = objectStore.getAll(); + query.onsuccess = (event) => { + const soundFiles = event.target.result; + const sounds = new Map(); + [...soundFiles] + .sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' })) + .forEach((soundFile) => { + const title = soundFile.title; + if (!isAudioFile(title)) { + return; + } + const splitRelativePath = soundFile.id?.split('/'); + const parentDirectory = splitRelativePath[splitRelativePath.length - 2]; + const soundPath = soundFile.blob; + const soundPaths = sounds.get(parentDirectory) ?? new Set(); + soundPaths.add(soundPath); + sounds.set(parentDirectory, soundPaths); + }); + + sounds.forEach((soundPaths, key) => { + const value = Array.from(soundPaths); + registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { + type: 'sample', + samples: value, + baseUrl: undefined, + prebake: false, + tag: undefined, + }); + }); + }; + }); +}; + +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); + }); +} + +const openDB = (onOpened) => { + if ('indexedDB' in window) { + // indexedDB supported + } else { + console.log('IndexedDB is not supported.'); + } + const dbOpen = indexedDB.open(userSamplesDB, 6); + + dbOpen.onupgradeneeded = (_event) => { + const db = dbOpen.result; + const objectStore = db.createObjectStore(sampleObject, { keyPath: 'id', autoIncrement: false }); + // objectStore.createIndex('name', 'name', { unique: false }); + objectStore.createIndex('blob', 'blob', { unique: false }); + objectStore.createIndex('title', 'title', { unique: false }); + }; + dbOpen.onerror = (err) => { + console.error(`indexedDB error: ${err.errorCode}`); + }; + + dbOpen.onsuccess = () => { + const db = dbOpen.result; + + const // lock store for writing + writeTransaction = db.transaction([sampleObject], 'readwrite'), + // get object store + objectStore = writeTransaction.objectStore(sampleObject); + // objectStore.put({ title: 'test', blob: 'test' }); + + onOpened(objectStore, db); + }; +}; + +const processFilesForIDB = async (files) => { + return await Promise.all( + Array.from(files).map(async (s) => { + const title = s.name; + if (!isAudioFile(title)) { + return; + } + // const id = crypto.randomUUID(); + const sUrl = URL.createObjectURL(s); + const buf = await fetch(sUrl).then((res) => res.arrayBuffer()); + const base64 = await bufferToDataUrl(buf); + const f = { + title, + blob: base64, + id: s.webkitRelativePath, + }; + return f; + }), + ).catch((error) => { + console.error(error); + }); +}; + +const setupUserSamplesDB = async (files, onComplete) => { + //clearData(); + await processFilesForIDB(files).then((files) => { + const onOpened = (objectStore, db) => { + files.forEach((file) => { + if (file == null) { return; } - const splitRelativePath = soundFile.webkitRelativePath?.split('/'); - const parentDirectory = splitRelativePath[splitRelativePath.length - 2]; - const soundPath = URL.createObjectURL(soundFile); - const soundPaths = sounds.get(parentDirectory) ?? new Set(); - soundPaths.add(soundPath); + const mutation = objectStore.put(file); + mutation.onsuccess = () => {}; + }); + onComplete(objectStore, db); + }; + openDB(onOpened); + }); +}; - sounds.set(parentDirectory, soundPaths); - }); - sounds.forEach((soundPaths, key) => { - const value = Array.from(soundPaths); - registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { - type: 'sample', - samples: value, - baseUrl: undefined, - prebake: false, - tag: undefined, - }); +export default function ImportSoundsButton({ onComplete }) { + let fileUploadRef = React.createRef(); + const onChange = useCallback(async () => { + await setupUserSamplesDB(fileUploadRef.current.files, (objectStore, db) => {}).then(async () => { + registerSamples(); + onComplete(); }); - onComplete(); - } + }); + + useEffect(() => { + registerSamples(); + }, []); return (
{soundEntries.map(([name, { data, onTrigger }]) => ( From 145510266acef9736f421f26ba27345b3c141d00 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 10 Dec 2023 11:26:46 -0500 Subject: [PATCH 08/12] fixing merge conflicts --- website/src/repl/Footer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index 5d1faf27..65eeaf7f 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -242,7 +242,7 @@ function SoundsTab() { }); return (
-
+
settingsMap.setKey('soundsFilter', value)} From 4051276d74f3f2f8b200fa496d4bf0777c97ff4f Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 10 Dec 2023 11:27:39 -0500 Subject: [PATCH 09/12] fixing merge conflicts --- website/src/repl/Footer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index 65eeaf7f..b03f62d0 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -242,7 +242,7 @@ function SoundsTab() { }); return (
-
+
settingsMap.setKey('soundsFilter', value)} From 59a4cd9cf8a6dae5f044190759629fd67787bf48 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 10 Dec 2023 11:37:18 -0500 Subject: [PATCH 10/12] updating buton location --- website/src/repl/{ => panel}/ImportSoundsButton.jsx | 2 +- website/src/repl/panel/SoundsTab.jsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) rename website/src/repl/{ => panel}/ImportSoundsButton.jsx (97%) diff --git a/website/src/repl/ImportSoundsButton.jsx b/website/src/repl/panel/ImportSoundsButton.jsx similarity index 97% rename from website/src/repl/ImportSoundsButton.jsx rename to website/src/repl/panel/ImportSoundsButton.jsx index 2ce208ae..2d9c37a1 100644 --- a/website/src/repl/ImportSoundsButton.jsx +++ b/website/src/repl/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 '../idbutils.mjs'; //choose a directory to locally import samples export default function ImportSoundsButton({ onComplete }) { diff --git a/website/src/repl/panel/SoundsTab.jsx b/website/src/repl/panel/SoundsTab.jsx index fe32212d..af9aabca 100644 --- a/website/src/repl/panel/SoundsTab.jsx +++ b/website/src/repl/panel/SoundsTab.jsx @@ -5,6 +5,7 @@ import { getAudioContext, soundMap, connectToDestination } from '@strudel.cycles import React, { useMemo, useRef } from 'react'; import { settingsMap, useSettings } from '../../settings.mjs'; import { ButtonGroup } from './Forms.jsx'; +import ImportSoundsButton from './ImportSoundsButton.jsx'; const getSamples = (samples) => Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1; @@ -43,7 +44,7 @@ export function SoundsTab() { }); return (
-
+
settingsMap.setKey('soundsFilter', value)} @@ -54,6 +55,7 @@ export function SoundsTab() { user: 'User', }} > + settingsMap.setKey('soundsFilter', 'user')} />
{soundEntries.map(([name, { data, onTrigger }]) => ( From 79e8e430f1fab853990cc52f53c604b0e388730c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 10 Dec 2023 19:50:20 +0100 Subject: [PATCH 11/12] fix: style for small screen --- website/src/repl/panel/Forms.jsx | 2 +- website/src/repl/panel/ImportSoundsButton.jsx | 2 +- website/src/repl/panel/SoundsTab.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/repl/panel/Forms.jsx b/website/src/repl/panel/Forms.jsx index e45305e5..49d0e913 100644 --- a/website/src/repl/panel/Forms.jsx +++ b/website/src/repl/panel/Forms.jsx @@ -9,7 +9,7 @@ export function ButtonGroup({ value, onChange, items }) { key={key} onClick={() => onChange(key)} className={cx( - 'px-2 border-b h-8', + 'px-2 border-b h-8 whitespace-nowrap', // i === 0 && 'rounded-l-md', // i === arr.length - 1 && 'rounded-r-md', // value === key ? 'bg-background' : 'bg-lineHighlight', diff --git a/website/src/repl/panel/ImportSoundsButton.jsx b/website/src/repl/panel/ImportSoundsButton.jsx index 2d9c37a1..20a1d5e3 100644 --- a/website/src/repl/panel/ImportSoundsButton.jsx +++ b/website/src/repl/panel/ImportSoundsButton.jsx @@ -21,7 +21,7 @@ export default function ImportSoundsButton({ onComplete }) { return (