mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-14 06:43:47 -04:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12fac86012 | |||
| cea6301ffd | |||
| 0b69ce21c7 | |||
| 4f05674cb3 | |||
| feab3f5c86 |
@@ -1,4 +1,4 @@
|
||||
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||
import { BASE_MIDI_NOTE, getBaseURL, getCommonSampleInfo, noteToFreq, noteToMidi } from './util.mjs';
|
||||
import { registerSound, registerWaveTable } from './index.mjs';
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import {
|
||||
@@ -14,6 +14,13 @@ import { logger } from './logger.mjs';
|
||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||
const loadCache = {}; // string: Promise<ArrayBuffer>
|
||||
|
||||
/**
|
||||
*
|
||||
* @typedef {Object} SampleMetaData
|
||||
* @property {string} url
|
||||
* @property {midi} number
|
||||
*/
|
||||
|
||||
export const getCachedBuffer = (url) => bufferCache[url];
|
||||
|
||||
function humanFileSize(bytes, si) {
|
||||
@@ -33,7 +40,7 @@ function humanFileSize(bytes, si) {
|
||||
export function getSampleInfo(hapValue, bank) {
|
||||
const { speed = 1.0 } = hapValue;
|
||||
const { transpose, url, index, midi, label } = getCommonSampleInfo(hapValue, bank);
|
||||
let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12);
|
||||
const playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12);
|
||||
return { transpose, url, index, midi, label, playbackRate };
|
||||
}
|
||||
|
||||
@@ -156,15 +163,20 @@ export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '')
|
||||
if (baseUrl.startsWith('github:')) {
|
||||
baseUrl = githubPath(baseUrl, '');
|
||||
}
|
||||
const fullUrl = (v) => baseUrl + v;
|
||||
/**
|
||||
*
|
||||
* @param {string} v
|
||||
* @returns {SampleMetaData}
|
||||
*/
|
||||
const getMetaData = (v) => ({ url: baseUrl + v, midi: extractMidiNoteFromString(v) });
|
||||
if (Array.isArray(value)) {
|
||||
//return [key, value.map(replaceUrl)];
|
||||
value = value.map(fullUrl);
|
||||
value = value.map(getMetaData);
|
||||
} else {
|
||||
// must be object
|
||||
value = Object.fromEntries(
|
||||
Object.entries(value).map(([note, samples]) => {
|
||||
return [note, (typeof samples === 'string' ? [samples] : samples).map(fullUrl)];
|
||||
return [note, (typeof samples === 'string' ? [samples] : samples).map(getMetaData)];
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -367,3 +379,16 @@ export function registerSampleSource(key, bank, params) {
|
||||
registerSample(key, bank, params);
|
||||
}
|
||||
}
|
||||
|
||||
export function extractMidiNoteFromString(str) {
|
||||
const regex = /_([a-gA-G])([#b])?([1-9])?\b/;
|
||||
const match = str.match(regex);
|
||||
if (match == null) {
|
||||
return BASE_MIDI_NOTE;
|
||||
}
|
||||
const base = match[1].toUpperCase();
|
||||
const accidental = match[2] ?? '';
|
||||
const octave_str = match[3] ?? '';
|
||||
const parsedVal = base + accidental + octave_str;
|
||||
return noteToMidi(parsedVal);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,12 @@ const accs = { '#': 1, b: -1, s: 1, f: -1 };
|
||||
export const getAccidentalsOffset = (accidentals) => {
|
||||
return accidentals?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} note
|
||||
* @param {number} defaultOctave
|
||||
* @returns {number}
|
||||
*/
|
||||
export const noteToMidi = (note, defaultOctave = 3) => {
|
||||
const [pc, acc, oct = defaultOctave] = tokenizeNote(note);
|
||||
if (!pc) {
|
||||
@@ -29,6 +34,11 @@ export const noteToMidi = (note, defaultOctave = 3) => {
|
||||
const offset = getAccidentalsOffset(acc);
|
||||
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param {number} n
|
||||
* @returns {number}
|
||||
*/
|
||||
export const midiToFreq = (n) => {
|
||||
return Math.pow(2, (n - 69) / 12) * 440;
|
||||
};
|
||||
@@ -38,7 +48,17 @@ export const freqToMidi = (freq) => {
|
||||
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
|
||||
};
|
||||
|
||||
export const valueToMidi = (value, fallbackValue) => {
|
||||
/**
|
||||
*
|
||||
* @param {string} note
|
||||
* @param {number} defaultOctave
|
||||
* @returns {number}
|
||||
*/
|
||||
|
||||
export const noteToFreq = (note, defaultOctave = 3) => {
|
||||
return midiToFreq(noteToMidi(note, defaultOctave));
|
||||
};
|
||||
function __valueToMidi(value) {
|
||||
if (typeof value !== 'object') {
|
||||
throw new Error('valueToMidi: expected object value');
|
||||
}
|
||||
@@ -52,10 +72,15 @@ export const valueToMidi = (value, fallbackValue) => {
|
||||
if (typeof note === 'number') {
|
||||
return note;
|
||||
}
|
||||
if (!fallbackValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
export const valueToMidi = (value, fallbackValue) => {
|
||||
const parsedValue = __valueToMidi(value) ?? fallbackValue;
|
||||
if (parsedValue == null) {
|
||||
throw new Error('valueToMidi: expected freq or note to be set');
|
||||
}
|
||||
return fallbackValue;
|
||||
return parsedValue;
|
||||
};
|
||||
|
||||
export function nanFallback(value, fallback = 0, silent) {
|
||||
@@ -84,15 +109,18 @@ export function secondsToCycle(t, cps) {
|
||||
// deduces relevant info for sample loading from hap.value and sample definition
|
||||
// it encapsulates the core sampler logic into a pure and synchronous function
|
||||
// hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format)
|
||||
export const BASE_MIDI_NOTE = 36;
|
||||
|
||||
export function getCommonSampleInfo(hapValue, bank) {
|
||||
const { s, n = 0 } = hapValue;
|
||||
let midi = valueToMidi(hapValue, 36);
|
||||
let transpose = midi - 36; // C3 is middle C;
|
||||
let url;
|
||||
const maybeMidiNote = __valueToMidi(hapValue);
|
||||
const midi = maybeMidiNote ?? BASE_MIDI_NOTE;
|
||||
let transpose = midi - BASE_MIDI_NOTE; // C3 is middle C;
|
||||
let index = 0;
|
||||
let samplemeta;
|
||||
if (Array.isArray(bank)) {
|
||||
index = getSoundIndex(n, bank.length);
|
||||
url = bank[index];
|
||||
samplemeta = bank[index];
|
||||
} else {
|
||||
const midiDiff = (noteA) => noteToMidi(noteA) - midi;
|
||||
// object format will expect keys as notes
|
||||
@@ -104,10 +132,14 @@ export function getCommonSampleInfo(hapValue, bank) {
|
||||
);
|
||||
transpose = -midiDiff(closest); // semitones to repitch
|
||||
index = getSoundIndex(n, bank[closest].length);
|
||||
url = bank[closest][index];
|
||||
samplemeta = bank[closest][index];
|
||||
}
|
||||
const label = `${s}:${index}`;
|
||||
return { transpose, url, index, midi, label };
|
||||
if (maybeMidiNote != null) {
|
||||
transpose = transpose + (BASE_MIDI_NOTE - samplemeta.midi);
|
||||
}
|
||||
|
||||
return { transpose, index, midi, label, url: samplemeta.url };
|
||||
}
|
||||
|
||||
/** Selects entries from `source` and renames them via `map`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { registerSampleSource } from '@strudel/webaudio';
|
||||
import { extractMidiNoteFromString, registerSampleSource } from '@strudel/webaudio';
|
||||
import { isAudioFile } from './files.mjs';
|
||||
import { logger } from '@strudel/core';
|
||||
|
||||
@@ -47,7 +47,7 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete =
|
||||
Promise.all(
|
||||
[...soundFiles]
|
||||
.sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' }))
|
||||
.map((soundFile, i) => {
|
||||
.map((soundFile) => {
|
||||
const title = soundFile.title;
|
||||
if (!isAudioFile(title)) {
|
||||
return;
|
||||
@@ -58,25 +58,26 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete =
|
||||
splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user';
|
||||
const blob = soundFile.blob;
|
||||
|
||||
return blobToDataUrl(blob).then((soundPath) => {
|
||||
const titlePathMap = sounds.get(parentDirectory) ?? new Map();
|
||||
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);
|
||||
|
||||
titlePathMap.set(title, soundPath);
|
||||
|
||||
sounds.set(parentDirectory, titlePathMap);
|
||||
sounds.set(parentDirectory, sampleInfoMap);
|
||||
return;
|
||||
});
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
sounds.forEach((titlePathMap, key) => {
|
||||
const value = Array.from(titlePathMap.keys())
|
||||
sounds.forEach((sampleInfoMap, key) => {
|
||||
const bank = Array.from(sampleInfoMap.keys())
|
||||
.sort((a, b) => {
|
||||
return a.localeCompare(b);
|
||||
})
|
||||
.map((title) => titlePathMap.get(title));
|
||||
|
||||
registerSampleSource(key, value, { prebake: false });
|
||||
.map((title) => sampleInfoMap.get(title));
|
||||
registerSampleSource(key, bank, { prebake: false });
|
||||
});
|
||||
|
||||
logger('imported sounds registered!', 'success');
|
||||
|
||||
Reference in New Issue
Block a user