mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-23 13:42:56 -04:00
186 lines
5.6 KiB
JavaScript
186 lines
5.6 KiB
JavaScript
import { extractMidiNoteFromString, registerSampleSource } from '@strudel/webaudio';
|
|
import { isAudioFile } from './files.mjs';
|
|
import { logger } from '@strudel/core';
|
|
|
|
//utilites for writing and reading to the indexdb
|
|
|
|
export const userSamplesDBConfig = {
|
|
dbName: 'samples',
|
|
table: 'usersamples',
|
|
columns: ['blob', 'title'],
|
|
version: 1,
|
|
};
|
|
|
|
// deletes all of the databases, useful for debugging
|
|
function clearAllIDB() {
|
|
window.indexedDB
|
|
.databases()
|
|
.then((r) => {
|
|
for (var i = 0; i < r.length; i++) clearIDB(r[i].name);
|
|
})
|
|
.then(() => {
|
|
alert('All data cleared.');
|
|
});
|
|
}
|
|
|
|
export function clearIDB(dbName) {
|
|
return window.indexedDB.deleteDatabase(dbName);
|
|
}
|
|
|
|
// queries the DB, and registers the sounds so they can be played
|
|
export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete = () => {}) {
|
|
openDB(config, (objectStore) => {
|
|
const query = objectStore.getAll();
|
|
query.onerror = (e) => {
|
|
logger('User Samples failed to load ', 'error');
|
|
onComplete();
|
|
console.error(e?.target?.error);
|
|
};
|
|
|
|
query.onsuccess = (event) => {
|
|
const soundFiles = event.target.result;
|
|
if (!soundFiles?.length) {
|
|
return;
|
|
}
|
|
const sounds = new Map();
|
|
|
|
Promise.all(
|
|
[...soundFiles]
|
|
.sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' }))
|
|
.map((soundFile) => {
|
|
const title = soundFile.title;
|
|
if (!isAudioFile(title)) {
|
|
return;
|
|
}
|
|
const splitRelativePath = soundFile.id.split('/');
|
|
let parentDirectory =
|
|
//fallback to file name before period and seperator if no parent directory
|
|
splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user';
|
|
const blob = soundFile.blob;
|
|
|
|
return blobToDataUrl(blob).then((path) => {
|
|
const sampleInfoMap = sounds.get(parentDirectory) ?? new Map();
|
|
const midi = extractMidiNoteFromString(title);
|
|
/** @type {import('@strudel/webaudio').SampleMetaData} */
|
|
const samplemetadata = { url: path, midi };
|
|
sampleInfoMap.set(title, samplemetadata);
|
|
|
|
sounds.set(parentDirectory, sampleInfoMap);
|
|
return;
|
|
});
|
|
}),
|
|
)
|
|
.then(() => {
|
|
sounds.forEach((sampleInfoMap, key) => {
|
|
const bank = Array.from(sampleInfoMap.keys())
|
|
.sort((a, b) => {
|
|
return a.localeCompare(b);
|
|
})
|
|
.map((title) => sampleInfoMap.get(title));
|
|
registerSampleSource(key, bank, { prebake: false });
|
|
});
|
|
|
|
logger('imported sounds registered!', 'success');
|
|
onComplete();
|
|
})
|
|
.catch((error) => {
|
|
logger('Something went wrong while registering saved samples from the index db', 'error');
|
|
console.error(error);
|
|
});
|
|
};
|
|
});
|
|
}
|
|
|
|
async function blobToDataUrl(blob) {
|
|
return new Promise((resolve) => {
|
|
resolve(URL.createObjectURL(blob));
|
|
});
|
|
}
|
|
|
|
//open db and initialize it if necessary
|
|
function openDB(config, onOpened) {
|
|
const { dbName, version, table, columns } = config;
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
if (!('indexedDB' in window)) {
|
|
console.log('IndexedDB is not supported.');
|
|
return;
|
|
}
|
|
const dbOpen = indexedDB.open(dbName, version);
|
|
|
|
dbOpen.onupgradeneeded = (_event) => {
|
|
const db = dbOpen.result;
|
|
const objectStore = db.createObjectStore(table, { keyPath: 'id', autoIncrement: false });
|
|
columns.forEach((c) => {
|
|
objectStore.createIndex(c, c, { unique: false });
|
|
});
|
|
};
|
|
dbOpen.onerror = (err) => {
|
|
logger('Something went wrong while trying to open the the client DB', 'error');
|
|
console.error(`indexedDB error: ${err.errorCode}`);
|
|
};
|
|
|
|
dbOpen.onsuccess = () => {
|
|
const db = dbOpen.result;
|
|
// lock store for writing
|
|
const writeTransaction = db.transaction([table], 'readwrite');
|
|
// get object store
|
|
const objectStore = writeTransaction.objectStore(table);
|
|
onOpened(objectStore, db);
|
|
};
|
|
return dbOpen;
|
|
}
|
|
|
|
async function processFilesForIDB(files) {
|
|
return Promise.all(
|
|
Array.from(files)
|
|
.map((s) => {
|
|
const title = s.name;
|
|
|
|
if (!isAudioFile(title)) {
|
|
return;
|
|
}
|
|
//create obscured url to file system that can be fetched
|
|
const sUrl = URL.createObjectURL(s);
|
|
//fetch the sound and turn it into a buffer array
|
|
return fetch(sUrl).then((res) => {
|
|
return res.blob().then((blob) => {
|
|
const path = s.webkitRelativePath;
|
|
let id = path?.length ? path : title;
|
|
if (id == null || title == null || blob == null) {
|
|
return;
|
|
}
|
|
return {
|
|
title,
|
|
blob,
|
|
id,
|
|
};
|
|
});
|
|
});
|
|
})
|
|
.filter(Boolean),
|
|
).catch((error) => {
|
|
logger('Something went wrong while processing uploaded files', 'error');
|
|
console.error(error);
|
|
});
|
|
}
|
|
|
|
export async function uploadSamplesToDB(config, files) {
|
|
logger('procesing user samples...');
|
|
await processFilesForIDB(files).then((files) => {
|
|
logger('user samples processed... opening db');
|
|
const onOpened = (objectStore, _db) => {
|
|
logger('index db opened... writing files to db');
|
|
files.forEach((file) => {
|
|
if (file == null) {
|
|
return;
|
|
}
|
|
objectStore.put(file);
|
|
});
|
|
logger('user samples written successfully');
|
|
};
|
|
openDB(config, onOpened);
|
|
});
|
|
}
|