1import { registerSampleSource } from '@strudel/webaudio'; 2import { isAudioFile } from './files.mjs'; 3import { logger } from '@strudel/core'; 4 5//utilites for writing and reading to the indexdb 6 7export const userSamplesDBConfig = { 8 dbName: 'samples', 9 table: 'usersamples', 10 columns: ['blob', 'title'], 11 version: 1, 12}; 13 14// deletes all of the databases, useful for debugging 15function clearAllIDB() { 16 window.indexedDB 17 .databases() 18 .then((r) => { 19 for (var i = 0; i < r.length; i++) clearIDB(r[i].name); 20 }) 21 .then(() => { 22 alert('All data cleared.'); 23 }); 24} 25 26export function clearIDB(dbName) { 27 return window.indexedDB.deleteDatabase(dbName); 28} 29 30// queries the DB, and registers the sounds so they can be played 31export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete = () => {}) { 32 openDB(config, (objectStore) => { 33 const query = objectStore.getAll(); 34 query.onerror = (e) => { 35 logger('User Samples failed to load ', 'error'); 36 onComplete(); 37 console.error(e?.target?.error); 38 }; 39 40 query.onsuccess = (event) => { 41 const soundFiles = event.target.result; 42 if (!soundFiles?.length) { 43 return; 44 } 45 const sounds = new Map(); 46 47 Promise.all( 48 [...soundFiles] 49 .sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' })) 50 .map((soundFile, i) => { 51 const title = soundFile.title; 52 if (!isAudioFile(title)) { 53 return; 54 } 55 const splitRelativePath = soundFile.id.split('/'); 56 let parentDirectory = 57 //fallback to file name before period and seperator if no parent directory 58 splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user'; 59 const blob = soundFile.blob; 60 61 return blobToDataUrl(blob).then((soundPath) => { 62 const titlePathMap = sounds.get(parentDirectory) ?? new Map(); 63 64 titlePathMap.set(title, soundPath); 65 66 sounds.set(parentDirectory, titlePathMap); 67 return; 68 }); 69 }), 70 ) 71 .then(() => { 72 sounds.forEach((titlePathMap, key) => { 73 const value = Array.from(titlePathMap.keys()) 74 .sort((a, b) => { 75 return a.localeCompare(b); 76 }) 77 .map((title) => titlePathMap.get(title)); 78 79 registerSampleSource(key, value, { prebake: false }); 80 }); 81 82 logger('imported sounds registered!', 'success'); 83 onComplete(); 84 }) 85 .catch((error) => { 86 logger('Something went wrong while registering saved samples from the index db', 'error'); 87 console.error(error); 88 }); 89 }; 90 }); 91} 92 93async function blobToDataUrl(blob) { 94 return new Promise((resolve) => { 95 resolve(URL.createObjectURL(blob)); 96 }); 97} 98 99//open db and initialize it if necessary 100function openDB(config, onOpened) { 101 const { dbName, version, table, columns } = config; 102 if (typeof window === 'undefined') { 103 return; 104 } 105 if (!('indexedDB' in window)) { 106 console.log('IndexedDB is not supported.'); 107 return; 108 } 109 const dbOpen = indexedDB.open(dbName, version); 110 111 dbOpen.onupgradeneeded = (_event) => { 112 const db = dbOpen.result; 113 const objectStore = db.createObjectStore(table, { keyPath: 'id', autoIncrement: false }); 114 columns.forEach((c) => { 115 objectStore.createIndex(c, c, { unique: false }); 116 }); 117 }; 118 dbOpen.onerror = (err) => { 119 logger('Something went wrong while trying to open the the client DB', 'error'); 120 console.error(`indexedDB error: ${err.errorCode}`); 121 }; 122 123 dbOpen.onsuccess = () => { 124 const db = dbOpen.result; 125 // lock store for writing 126 const writeTransaction = db.transaction([table], 'readwrite'); 127 // get object store 128 const objectStore = writeTransaction.objectStore(table); 129 onOpened(objectStore, db); 130 }; 131 return dbOpen; 132} 133 134async function processFilesForIDB(files) { 135 return Promise.all( 136 Array.from(files) 137 .map((s) => { 138 const title = s.name; 139 140 if (!isAudioFile(title)) { 141 return; 142 } 143 //create obscured url to file system that can be fetched 144 const sUrl = URL.createObjectURL(s); 145 //fetch the sound and turn it into a buffer array 146 return fetch(sUrl).then((res) => { 147 return res.blob().then((blob) => { 148 const path = s.webkitRelativePath; 149 let id = path?.length ? path : title; 150 if (id == null || title == null || blob == null) { 151 return; 152 } 153 return { 154 title, 155 blob, 156 id, 157 }; 158 }); 159 }); 160 }) 161 .filter(Boolean), 162 ).catch((error) => { 163 logger('Something went wrong while processing uploaded files', 'error'); 164 console.error(error); 165 }); 166} 167 168export async function uploadSamplesToDB(config, files) { 169 logger('procesing user samples...'); 170 await processFilesForIDB(files).then((files) => { 171 logger('user samples processed... opening db'); 172 const onOpened = (objectStore, _db) => { 173 logger('index db opened... writing files to db'); 174 files.forEach((file) => { 175 if (file == null) { 176 return; 177 } 178 objectStore.put(file); 179 }); 180 logger('user samples written successfully'); 181 }; 182 openDB(config, onOpened); 183 }); 184}