mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 14:26:58 -04:00
freq
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||
import { getBaseURL, getCommonSampleInfo, noteToFreq } from './util.mjs';
|
||||
import { registerSound, registerWaveTable } from './index.mjs';
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import {
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
releaseAudioNode,
|
||||
} from './helpers.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
import { note2midi } from 'node_modules/@strudel/tonal/tonleiter.mjs';
|
||||
|
||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||
const loadCache = {}; // string: Promise<ArrayBuffer>
|
||||
@@ -30,10 +31,13 @@ function humanFileSize(bytes, si) {
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
||||
|
||||
export function getSampleInfo(hapValue, bank) {
|
||||
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 { transpose, url, index, midi, label, baseFrequency } = getCommonSampleInfo(hapValue, bank);
|
||||
const relativeBaseFreq = noteToFreq('C');
|
||||
|
||||
const frequencyAdjustment = relativeBaseFreq / baseFrequency;
|
||||
const playbackRate = (Math.abs(speed) * Math.pow(2, transpose / 12)) * frequencyAdjustment;
|
||||
return { transpose, url, index, midi, label, playbackRate };
|
||||
}
|
||||
|
||||
@@ -360,6 +364,7 @@ function registerSample(key, bank, params) {
|
||||
}
|
||||
|
||||
export function registerSampleSource(key, bank, params) {
|
||||
|
||||
const isWavetable = key.startsWith('wt_');
|
||||
if (isWavetable) {
|
||||
registerWaveTable(key, bank, params);
|
||||
@@ -367,3 +372,23 @@ export function registerSampleSource(key, bank, params) {
|
||||
registerSample(key, bank, params);
|
||||
}
|
||||
}
|
||||
|
||||
function extractNoteFromString(str) {
|
||||
const regex = /_([a-gA-G])([#b])?([1-9])?\b/;
|
||||
const match = str.match(regex);
|
||||
if (match == null) {
|
||||
return { note: "C", base: "C", accidental: undefined, octave: 3 }
|
||||
}
|
||||
const base = match[1].toUpperCase();
|
||||
const accidental = match[2] ?? ""
|
||||
const octave_str = match[3] ?? "";
|
||||
const octave = Number(octave_str)
|
||||
return { note: base + accidental + octave_str, base, accidental, octave }
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function extractBaseFrequencyFromString(str) {
|
||||
const { note } = extractNoteFromString(str);
|
||||
return noteToFreq(note)
|
||||
}
|
||||
@@ -2,7 +2,12 @@ import { logger } from './logger.mjs';
|
||||
|
||||
// currently duplicate with core util.mjs to skip dependency
|
||||
// TODO: add separate util module?
|
||||
|
||||
/**
|
||||
*
|
||||
* @typedef {Object} SampleMetaData
|
||||
* @property {string} url
|
||||
* @property {baseFrequency} number
|
||||
*/
|
||||
export const tokenizeNote = (note) => {
|
||||
if (typeof note !== 'string') {
|
||||
return [];
|
||||
@@ -19,7 +24,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,17 +39,34 @@ 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;
|
||||
};
|
||||
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
|
||||
|
||||
export const freqToMidi = (freq) => {
|
||||
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
|
||||
};
|
||||
|
||||
export const valueToMidi = (value, fallbackValue) => {
|
||||
if (typeof value !== 'object') {
|
||||
/**
|
||||
*
|
||||
* @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');
|
||||
}
|
||||
let { freq, note } = value;
|
||||
@@ -52,10 +79,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) {
|
||||
@@ -88,11 +120,11 @@ 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;
|
||||
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 +136,13 @@ 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 };
|
||||
|
||||
|
||||
|
||||
return { transpose, index, midi, label, ...samplemeta };
|
||||
}
|
||||
|
||||
/** Selects entries from `source` and renames them via `map`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { registerSampleSource } from '@strudel/webaudio';
|
||||
import { extractBaseFrequencyFromString, registerSampleSource } from '@strudel/webaudio';
|
||||
import { isAudioFile } from './files.mjs';
|
||||
import { logger } from '@strudel/core';
|
||||
|
||||
@@ -28,7 +28,7 @@ export function clearIDB(dbName) {
|
||||
}
|
||||
|
||||
// queries the DB, and registers the sounds so they can be played
|
||||
export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete = () => {}) {
|
||||
export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete = () => { }) {
|
||||
openDB(config, (objectStore) => {
|
||||
const query = objectStore.getAll();
|
||||
query.onerror = (e) => {
|
||||
@@ -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,24 @@ 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 baseFrequency = extractBaseFrequencyFromString(title)
|
||||
sampleInfoMap.set(title, { url: path, baseFrequency });
|
||||
|
||||
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