Compare commits

..

6 Commits

Author SHA1 Message Date
Alex McLean 7bcd5897da format 2025-10-13 15:17:46 +01:00
Aria a7adebe036 Let pos or neg run until sign switches 2025-10-11 23:25:56 -05:00
Aria 62379269f0 Polarity approach 2025-10-11 15:12:10 -05:00
Aria 1162a42218 One more example 2025-10-11 01:28:31 -05:00
Aria 32d11d0332 Move timeline back to pattern file 2025-10-11 01:11:05 -05:00
Aria e5eb3b8d7b First pass at timelines 2025-10-10 18:47:33 -05:00
21 changed files with 170 additions and 489 deletions
+3 -36
View File
@@ -1887,52 +1887,19 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size',
export const { shape } = registerControl(['shape', 'shapevol']); export const { shape } = registerControl(['shape', 'shapevol']);
/** /**
* Wave shaping distortion. CAUTION: it can get loud. * Wave shaping distortion. CAUTION: it can get loud.
* Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output. Third option sets the waveshaping type. * Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output.
* Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;) * Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;)
* *
* @name distort * @name distort
* @synonyms dist * @synonyms dist
* @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} distortion
* @param {number | Pattern} volume linear postgain of the distortion
* @param {number | string | Pattern} type type of distortion to apply
* @example * @example
* s("bd sd [~ bd] sd,hh*8").distort("<0 2 3 10:.5>") * s("bd sd [~ bd] sd,hh*8").distort("<0 2 3 10:.5>")
* @example * @example
* note("d1!8").s("sine").penv(36).pdecay(.12).decay(.23).distort("8:.4") * note("d1!8").s("sine").penv(36).pdecay(.12).decay(.23).distort("8:.4")
* @example
* s("bd:4*4").bank("tr808").distort("3:0.5:diode")
* *
*/ */
export const { distort, dist } = registerControl(['distort', 'distortvol', 'distorttype'], 'dist'); export const { distort, dist } = registerControl(['distort', 'distortvol'], 'dist');
/**
* Postgain for waveshaping distortion.
*
* @name distortvol
* @synonyms distvol
* @param {number | Pattern} volume linear postgain of the distortion
* @example
* s("bd*4").bank("tr909").distort(2).distortvol(0.8)
*/
export const { distortvol } = registerControl('distortvol', 'distvol');
/**
* Type of waveshaping distortion to apply.
*
* @name distorttype
* @synonyms disttype
* @param {number | string | Pattern} type type of distortion to apply
* @example
* s("bd*4").bank("tr909").distort(2).distorttype("<0 1 2>")
*
* @example
* s("sine").note("F1*2").release(1)
* .penv(24).pdecay(0.05)
* .distort(rand.range(1, 8))
* .distorttype("<fold chebyshev scurve diode asym sinefold>")
*/
export const { distorttype } = registerControl('distorttype', 'disttype');
/** /**
* Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")` * Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")`
* More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) * More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties)
+51 -68
View File
@@ -8,6 +8,7 @@ import TimeSpan from './timespan.mjs';
import Fraction, { isFraction, lcm } from './fraction.mjs'; import Fraction, { isFraction, lcm } from './fraction.mjs';
import Hap from './hap.mjs'; import Hap from './hap.mjs';
import State from './state.mjs'; import State from './state.mjs';
import { getTime } from './time.mjs';
import { unionWithObj } from './value.mjs'; import { unionWithObj } from './value.mjs';
import { import {
@@ -3534,75 +3535,57 @@ export const morph = (frompat, topat, bypat) => {
return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by)))); return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))));
}; };
export const TIMELINES = {
state: {},
polarities: {},
};
/** /**
* Soft-clipping distortion * Aligns the pattern with the specified `timeline` by ID.
* *
* @name soft * More specifically, if a timeline ID is encountered for the first time, that moment
* @param {number | Pattern} distortion amount of distortion to apply * in time is marked as the "start" of that pattern. Thereafter, any other patterns aligned
* @param {number | Pattern} volume linear postgain of the distortion * with that same id will begin at that same moment in time.
* *
* If the sign of the id changes (positive to negative or vice versa), we reset the timeline.
*
* @param {number | Pattern} id Timeline id. Must be a non-zero number. Switch signs to reset.
* @returns Pattern
* @example
* s("tri").seg(8).n(irand(12)).scale("G#:minor").lpf(400).room(2)
* .timeline("<1 -1 1 2>")
* @example
* $: n("[0 .. 6]/4").scale("F:minor")
* // Execute the following line at any point in the session
* // and it will always start on note 0
* // $: n("[0 .. 6]/4").scale("F:minor").timeline(2)
*/ */
/** export const timeline = register('timeline', (id, pat) => {
* Hard-clipping distortion if (typeof id !== 'number' || id === 0) {
* logger(
* @name hard `[query] ${id} is not a valid timeline id. Please ensure it is a non-zero number. Defaulting to timeline 1.`,
* @param {number | Pattern} distortion amount of distortion to apply );
* @param {number | Pattern} volume linear postgain of the distortion id = 1;
* }
*/ const { state, polarities } = TIMELINES;
/** const key = Math.abs(id);
* Cubic polynomial distortion // We let the pattern run until the sign switches
* const p = id > 0 ? 1 : -1;
* @name cubic let t = polarities[key] === p ? state[key] : Math.ceil(getTime());
* @param {number | Pattern} distortion amount of distortion to apply return (
* @param {number | Pattern} volume linear postgain of the distortion pat
* .late(t)
*/ .onTrigger((hap) => {
/** const T = Number(hap.part.begin);
* Diode-emulating distortion // Set state on the first trigger
* state[key] ??= T;
* @name diode polarities[key] ??= p;
* @param {number | Pattern} distortion amount of distortion to apply if (polarities[key] !== p) {
* @param {number | Pattern} volume linear postgain of the distortion state[key] = T;
* }
*/ polarities[key] = p;
/** }, false)
* Asymmetrical diode distortion // Add labels
* .withValue((v) => ({ ...v, timeline: id, offset: t }))
* @name asym );
* @param {number | Pattern} distortion amount of distortion to apply });
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Wavefolding distortion
*
* @name fold
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Wavefolding distortion composed with sinusoid
*
* @name sinefold
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Distortion via Chebyshev polynomials
*
* @name chebyshev
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
const distAlgoNames = ['scurve', 'soft', 'hard', 'cubic', 'diode', 'asym', 'fold', 'sinefold', 'chebyshev'];
for (const name of distAlgoNames) {
// Add aliases for distortion algorithms
Pattern.prototype[name] = function (args) {
const argsPat = reify(args).fmap((v) => (Array.isArray(v) ? [...v, name] : [v, 1, name]));
return this.distort(argsPat);
};
}
+6 -1
View File
@@ -4,7 +4,7 @@ import { evaluate as _evaluate } from './evaluate.mjs';
import { errorLogger, logger } from './logger.mjs'; import { errorLogger, logger } from './logger.mjs';
import { setTime } from './time.mjs'; import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs'; import { evalScope } from './evaluate.mjs';
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs'; import { register, Pattern, isPattern, silence, stack, TIMELINES } from './pattern.mjs';
export function repl({ export function repl({
defaultOutput, defaultOutput,
@@ -52,6 +52,11 @@ export function repl({
onToggle: (started) => { onToggle: (started) => {
updateState({ started }); updateState({ started });
onToggle?.(started); onToggle?.(started);
if (!started) {
// Reset timeline state
TIMELINES.state = {};
TIMELINES.polarities = {};
}
}, },
setInterval, setInterval,
clearInterval, clearInterval,
+3 -6
View File
@@ -20,13 +20,10 @@ export async function prebake() {
// import('@strudel/osc'), // import('@strudel/osc'),
); );
// load samples // load samples
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main'; const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
// TODO: move this onto the strudel repo // TODO: move this onto the strudel repo
const ts = 'https://raw.githubusercontent.com/todepond/samples/main'; const ts = 'https://raw.githubusercontent.com/todepond/samples/main/';
const tc = 'https://raw.githubusercontent.com/tidalcycles/uzu-drumkit/main';
await Promise.all([ await Promise.all([
modulesLoading, modulesLoading,
registerSynthSounds(), registerSynthSounds(),
@@ -39,9 +36,9 @@ export async function prebake() {
samples(`${ds}/tidal-drum-machines.json`), samples(`${ds}/tidal-drum-machines.json`),
samples(`${ds}/piano.json`), samples(`${ds}/piano.json`),
samples(`${ds}/Dirt-Samples.json`), samples(`${ds}/Dirt-Samples.json`),
samples(`${ds}/uzu-drumkit.json`),
samples(`${ds}/vcsl.json`), samples(`${ds}/vcsl.json`),
samples(`${ds}/mridangam.json`), samples(`${ds}/mridangam.json`),
samples(`${tc}/strudel.json`),
]); ]);
aliasBank(`${ts}/tidal-drum-machines-alias.json`); aliasBank(`${ts}/tidal-drum-machines-alias.json`);
-1
View File
@@ -153,7 +153,6 @@ samples('github:tidalcycles/dirt-samples')
The format is `github:<user>/<repo>/<branch>`. The format is `github:<user>/<repo>/<branch>`.
If `<repo>` and `<branch>` are not specified, they will default to `samples` and `main` respectively.
It expects a `strudel.json` file to be present at the root of the given repository, which declares the sample paths in the repo. It expects a `strudel.json` file to be present at the root of the given repository, which declares the sample paths in the repo.
The format is also expected to be the same as explained above. The format is also expected to be the same as explained above.
-18
View File
@@ -1,18 +0,0 @@
let audioContext;
export const setDefaultAudioContext = () => {
audioContext = new AudioContext();
return audioContext;
};
export const getAudioContext = () => {
if (!audioContext) {
return setDefaultAudioContext();
}
return audioContext;
};
export function getAudioContextCurrentTime() {
return getAudioContext().currentTime;
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { getAudioContext } from './audioContext.mjs'; import { getAudioContext } from './superdough.mjs';
let worklet; let worklet;
export async function dspWorklet(ac, code) { export async function dspWorklet(ac, code) {
+1 -98
View File
@@ -1,7 +1,6 @@
import { getAudioContext } from './audioContext.mjs'; import { getAudioContext } from './superdough.mjs';
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs'; import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
import { getNoiseBuffer } from './noise.mjs'; import { getNoiseBuffer } from './noise.mjs';
import { logger } from './logger.mjs';
export const noises = ['pink', 'white', 'brown', 'crackle']; export const noises = ['pink', 'white', 'brown', 'crackle'];
@@ -382,102 +381,6 @@ export function applyFM(param, value, begin) {
return { stop }; return { stop };
} }
// Saturation curves
const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1)
const _mod = (n, m) => ((n % m) + m) % m;
const _scurve = (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x));
const _soft = (x, k) => Math.tanh(x * (1 + k));
const _hard = (x, k) => clamp((1 + k) * x, -1, 1);
const _fold = (x, k) => {
// Closed form folding for audio rate
let y = (1 + 0.5 * k) * x;
const window = _mod(y + 1, 4);
return 1 - Math.abs(window - 2);
};
const _sineFold = (x, k) => Math.sin((Math.PI / 2) * _fold(x, k));
const _cubic = (x, k) => {
const t = __squash(Math.log1p(k));
const cubic = (x - (t / 3) * x * x * x) / (1 - t / 3); // normalized to go from (-1, 1)
return _soft(cubic, k);
};
const _diode = (x, k, asym = false) => {
const g = 1 + 2 * k; // gain
const t = __squash(Math.log1p(k));
const bias = 0.07 * t;
const pos = _soft(x + bias, 2 * k);
const neg = _soft(asym ? bias : -x + bias, 2 * k);
const y = pos - neg;
// We divide by the derivative at 0 so that the distortion is roughly
// the identity map near 0 => small values are preserved and undistorted
const sech = 1 / Math.cosh(g * bias);
const sech2 = sech * sech; // derivative of soft (i.e. tanh) is sech^2
const denom = Math.max(1e-8, (asym ? 1 : 2) * g * sech2); // g from chain rule; 2 if both pos/neg have x
return _soft(y / denom, k);
};
const _asym = (x, k) => _diode(x, k, true);
const _chebyshev = (x, k) => {
const kl = 10 * Math.log1p(k);
let tnm1 = 1;
let tnm2 = x;
let tn;
let y = 0;
for (let i = 1; i < 64; i++) {
if (i < 2) {
// Already set inital conditions
y += i == 0 ? tnm1 : tnm2;
continue;
}
tn = 2 * x * tnm1 - tnm2; // https://en.wikipedia.org/wiki/Chebyshev_polynomials#Recurrence_definition
tnm2 = tnm1;
tnm1 = tn;
if (i % 2 === 0) {
y += Math.min((1.3 * kl) / i, 2) * tn;
}
}
// Soft clip
return _soft(y, kl / 20);
};
export const distortionAlgorithms = {
scurve: _scurve,
soft: _soft,
hard: _hard,
cubic: _cubic,
diode: _diode,
asym: _asym,
fold: _fold,
sinefold: _sineFold,
chebyshev: _chebyshev,
};
const _algoNames = Object.freeze(Object.keys(distortionAlgorithms));
export const getDistortionAlgorithm = (algo) => {
let index = algo;
if (typeof algo === 'string') {
index = _algoNames.indexOf(algo);
if (index === -1) {
logger(`[superdough] Could not find waveshaping algorithm ${algo}.
Available options are ${_algoNames.join(', ')}.
Defaulting to ${_algoNames[0]}.`);
index = 0;
}
}
const name = _algoNames[index % _algoNames.length]; // allow for wrapping if algo was a number
return distortionAlgorithms[name];
};
export const getDistortion = (distort, postgain, algorithm) => {
return getWorklet(getAudioContext(), 'distort-processor', { distort, postgain }, { processorOptions: { algorithm } });
};
export const getFrequencyFromValue = (value, defaultNote = 36) => { export const getFrequencyFromValue = (value, defaultNote = 36) => {
let { note, freq } = value; let { note, freq } = value;
note = note || defaultNote; note = note || defaultNote;
-1
View File
@@ -11,5 +11,4 @@ export * from './synth.mjs';
export * from './zzfx.mjs'; export * from './zzfx.mjs';
export * from './logger.mjs'; export * from './logger.mjs';
export * from './dspworklet.mjs'; export * from './dspworklet.mjs';
export * from './audioContext.mjs';
export * from './wavetable.mjs'; export * from './wavetable.mjs';
+1 -1
View File
@@ -1,5 +1,5 @@
import { drywet } from './helpers.mjs'; import { drywet } from './helpers.mjs';
import { getAudioContext } from './audioContext.mjs'; import { getAudioContext } from './superdough.mjs';
let noiseCache = {}; let noiseCache = {};
+23 -37
View File
@@ -1,6 +1,5 @@
import { getCommonSampleInfoFromBank } from './util.mjs'; import { noteToMidi, valueToMidi, getSoundIndex, getCommonSampleInfo } from './util.mjs';
import { registerSound, registerWaveTable } from './index.mjs'; import { getAudioContext, registerSound, registerWaveTable } from './index.mjs';
import { getAudioContext } from './audioContext.mjs';
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
@@ -23,34 +22,35 @@ function humanFileSize(bytes, si) {
return bytes.toFixed(1) + ' ' + units[u]; return bytes.toFixed(1) + ' ' + units[u];
} }
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);
return { transpose, url, index, midi, label, playbackRate };
}
export const getSampleBuffer = async (label, sampleUrl, resolveUrl) => { // takes hapValue and returns buffer + playbackRate.
export const getSampleBuffer = async (hapValue, bank, resolveUrl) => {
let { url: sampleUrl, label, playbackRate } = getSampleInfo(hapValue, bank);
if (resolveUrl) { if (resolveUrl) {
sampleUrl = await resolveUrl(sampleUrl); sampleUrl = await resolveUrl(sampleUrl);
} }
const ac = getAudioContext(); const ac = getAudioContext();
const buffer = await loadBuffer(sampleUrl, ac, label); const buffer = await loadBuffer(sampleUrl, ac, label);
return buffer
};
function getBufferPlaybackRate(hapValue, buffer, transpose) {
const { speed = 1.0 } = hapValue;
let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12);
if (hapValue.unit === 'c') { if (hapValue.unit === 'c') {
playbackRate = playbackRate * buffer.duration; playbackRate = playbackRate * buffer.duration;
} }
return playbackRate; return { buffer, playbackRate };
} };
// creates playback ready AudioBufferSourceNode from hapValue // creates playback ready AudioBufferSourceNode from hapValue
export function getSampleBufferSource(hapValue, buffer,transpose) { export const getSampleBufferSource = async (hapValue, bank, resolveUrl) => {
let { buffer, playbackRate } = await getSampleBuffer(hapValue, bank, resolveUrl);
if (hapValue.speed < 0) { if (hapValue.speed < 0) {
// should this be cached? // should this be cached?
buffer = reverseBuffer(buffer); buffer = reverseBuffer(buffer);
} }
const playbackRate = getBufferPlaybackRate(hapValue, buffer, transpose)
const ac = getAudioContext(); const ac = getAudioContext();
const bufferSource = ac.createBufferSource(); const bufferSource = ac.createBufferSource();
bufferSource.buffer = buffer; bufferSource.buffer = buffer;
@@ -120,20 +120,13 @@ function githubPath(base, subpath = '') {
if (!base.startsWith('github:')) { if (!base.startsWith('github:')) {
throw new Error('expected "github:" at the start of pseudoUrl'); throw new Error('expected "github:" at the start of pseudoUrl');
} }
let path = base.slice('github:'.length); let [_, path] = base.split('github:');
path = path.endsWith('/') ? path.slice(0, -1) : path; path = path.endsWith('/') ? path.slice(0, -1) : path;
if (path.split('/').length === 2) {
let components = path.split('/'); // assume main as default branch if none set
let user = components[0]; path += '/main';
let repo = components.length >= 2 ? components[1] : 'samples';
let branch = components.length >= 3 ? components[2] : 'main';
let other = components.slice(3);
if (subpath) {
other.push(subpath);
} }
other = other.join('/'); return `https://raw.githubusercontent.com/${path}/${subpath}`;
return `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${other}`;
} }
export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '') => { export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '') => {
@@ -263,7 +256,7 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
const cutGroups = []; const cutGroups = [];
export async function onTriggerSample(t, value, onended, bufferSrc) { export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
let { let {
s, s,
nudge = 0, // TODO: is this in seconds? nudge = 0, // TODO: is this in seconds?
@@ -285,7 +278,7 @@ export async function onTriggerSample(t, value, onended, bufferSrc) {
// destructure adsr here, because the default should be different for synths and samples // destructure adsr here, because the default should be different for synths and samples
let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
const { bufferSource, sliceDuration, offset } = bufferSrc const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl);
// asny stuff above took too long? // asny stuff above took too long?
if (ac.currentTime > t) { if (ac.currentTime > t) {
@@ -347,21 +340,14 @@ export async function onTriggerSample(t, value, onended, bufferSrc) {
return handle; return handle;
} }
async function getBufferSrcFromBank(hapValue, bank, resolveUrl = undefined) {
const { transpose, url, label } = getCommonSampleInfoFromBank(hapValue, bank)
let buffer = await getSampleBuffer(label,url, resolveUrl);
return getSampleBufferSource(hapValue, buffer, transpose)
}
function registerSample(key, bank, params) { function registerSample(key, bank, params) {
registerSound(key, async (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, await getBufferSrcFromBank(hapValue, bank, undefined)), { registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank), {
type: 'sample', type: 'sample',
samples: bank, samples: bank,
...params, ...params,
}); });
} }
export function registerSampleSource(key, bank, params) { export function registerSampleSource(key, bank, params) {
const isWavetable = key.startsWith('wt_'); const isWavetable = key.startsWith('wt_');
if (isWavetable) { if (isWavetable) {
+22 -5
View File
@@ -9,11 +9,10 @@ import './reverb.mjs';
import './vowel.mjs'; import './vowel.mjs';
import { nanFallback, _mod, cycleToSeconds } from './util.mjs'; import { nanFallback, _mod, cycleToSeconds } from './util.mjs';
import workletsUrl from './worklets.mjs?audioworklet'; import workletsUrl from './worklets.mjs?audioworklet';
import { createFilter, gainNode, getCompressor, getDistortion, getLfo, getWorklet, effectSend } from './helpers.mjs'; import { createFilter, gainNode, getCompressor, getLfo, getWorklet, effectSend } from './helpers.mjs';
import { map } from 'nanostores'; import { map } from 'nanostores';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
import { loadBuffer } from './sampler.mjs'; import { loadBuffer } from './sampler.mjs';
import { getAudioContext } from './audioContext.mjs';
import { SuperdoughAudioController } from './superdoughoutput.mjs'; import { SuperdoughAudioController } from './superdoughoutput.mjs';
export const DEFAULT_MAX_POLYPHONY = 128; export const DEFAULT_MAX_POLYPHONY = 128;
@@ -155,7 +154,6 @@ let defaultDefaultValues = {
phaserdepth: 0.75, phaserdepth: 0.75,
shapevol: 1, shapevol: 1,
distortvol: 1, distortvol: 1,
distorttype: 0,
delay: 0, delay: 0,
byteBeatExpression: '0', byteBeatExpression: '0',
delayfeedback: 0.5, delayfeedback: 0.5,
@@ -202,6 +200,25 @@ export function setVersionDefaults(version) {
export const resetLoadedSounds = () => soundMap.set({}); export const resetLoadedSounds = () => soundMap.set({});
let audioContext;
export const setDefaultAudioContext = () => {
audioContext = new AudioContext({ latencyHint: 'playback' });
return audioContext;
};
export const getAudioContext = () => {
if (!audioContext) {
return setDefaultAudioContext();
}
return audioContext;
};
export function getAudioContextCurrentTime() {
return getAudioContext().currentTime;
}
let externalWorklets = []; let externalWorklets = [];
export function registerWorklet(url) { export function registerWorklet(url) {
externalWorklets.push(url); externalWorklets.push(url);
@@ -456,7 +473,6 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
shapevol = getDefaultValue('shapevol'), shapevol = getDefaultValue('shapevol'),
distort, distort,
distortvol = getDefaultValue('distortvol'), distortvol = getDefaultValue('distortvol'),
distorttype = getDefaultValue('distorttype'),
pan, pan,
vowel, vowel,
delay = getDefaultValue('delay'), delay = getDefaultValue('delay'),
@@ -630,7 +646,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
// effects // effects
coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })); coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse }));
crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush }));
distort !== undefined && chain.push(getDistortion(distort, distortvol, distorttype)); shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol }));
distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol }));
if (tremolosync != null) { if (tremolosync != null) {
tremolo = cps * tremolosync; tremolo = cps * tremolosync;
+1 -2
View File
@@ -1,6 +1,5 @@
import { clamp } from './util.mjs'; import { clamp } from './util.mjs';
import { registerSound, soundMap } from './superdough.mjs'; import { registerSound, getAudioContext, soundMap } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
import { import {
applyFM, applyFM,
destroyAudioWorkletNode, destroyAudioWorkletNode,
+4 -19
View File
@@ -80,13 +80,12 @@ export function secondsToCycle(t, cps) {
// deduces relevant info for sample loading from hap.value and sample definition // deduces relevant info for sample loading from hap.value and sample definition
// it encapsulates the core sampler logic into a pure and synchronous function // 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) // hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format)
export function getCommonSampleInfoFromBank(hapValue, bank) { export function getCommonSampleInfo(hapValue, bank) {
const { s, n = 0 } = hapValue; const { s, n = 0 } = hapValue;
let midi = valueToMidi(hapValue, 36); let midi = valueToMidi(hapValue, 36);
let transpose = midi - 36; // C3 is middle C;
let url; let url;
let index = 0; let index = 0;
let {transpose, label} = getCommonSampleInfo(hapValue)
if (Array.isArray(bank)) { if (Array.isArray(bank)) {
index = getSoundIndex(n, bank.length); index = getSoundIndex(n, bank.length);
url = bank[index]; url = bank[index];
@@ -103,20 +102,6 @@ export function getCommonSampleInfoFromBank(hapValue, bank) {
index = getSoundIndex(n, bank[closest].length); index = getSoundIndex(n, bank[closest].length);
url = bank[closest][index]; url = bank[closest][index];
} }
label = `${s}:${index}`; const label = `${s}:${index}`;
return { transpose, url, label }; return { transpose, url, index, midi, label };
} }
export function getCommonSampleInfo(hapValue) {
const { s, n = 0 } = hapValue;
let midi = valueToMidi(hapValue, 36);
let transpose = midi - 36; // C3 is middle C;
const label = `${s}:${n}`;
return { transpose, label };
}
+4 -7
View File
@@ -1,7 +1,6 @@
import { getAudioContext, registerSound } from './index.mjs'; import { getAudioContext, registerSound } from './index.mjs';
import { getCommonSampleInfoFromBank } from './util.mjs'; import { getCommonSampleInfo } from './util.mjs';
import { import {
applyFM,
applyParameterModulators, applyParameterModulators,
destroyAudioWorkletNode, destroyAudioWorkletNode,
getADSRValues, getADSRValues,
@@ -216,7 +215,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
warpmode = Warpmode[warpmode.toUpperCase()] ?? Warpmode.NONE; warpmode = Warpmode[warpmode.toUpperCase()] ?? Warpmode.NONE;
} }
const frequency = getFrequencyFromValue(value); const frequency = getFrequencyFromValue(value);
const { url, label } = getCommonSampleInfoFromBank(value, tables); const { url, label } = getCommonSampleInfo(value, tables);
const payload = await getPayload(url, label, frameLen); const payload = await getPayload(url, label, frameLen);
let holdEnd = t + duration; let holdEnd = t + duration;
if (clip !== undefined) { if (clip !== undefined) {
@@ -231,12 +230,12 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
begin: t, begin: t,
end: envEnd, end: envEnd,
frequency, frequency,
freqspread: value.detune, detune: value.detune,
position: value.wt, position: value.wt,
warp: value.warp, warp: value.warp,
warpMode: warpmode, warpMode: warpmode,
voices: Math.max(value.unison ?? 1, 1), voices: Math.max(value.unison ?? 1, 1),
panspread: value.spread, spread: value.spread,
phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0, phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0,
}, },
{ outputChannelCount: [2] }, { outputChannelCount: [2] },
@@ -309,7 +308,6 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
}, },
); );
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t); const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
const fm = applyFM(source.parameters.get('frequency'), value, t);
const envGain = ac.createGain(); const envGain = ac.createGain();
const node = source.connect(envGain); const node = source.connect(envGain);
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear'); getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
@@ -320,7 +318,6 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
() => { () => {
destroyAudioWorkletNode(source); destroyAudioWorkletNode(source);
vibratoOscillator?.stop(); vibratoOscillator?.stop();
fm?.stop();
node.disconnect(); node.disconnect();
wtPosModulators?.disconnect(); wtPosModulators?.disconnect();
wtWarpModulators?.disconnect(); wtWarpModulators?.disconnect();
+20 -25
View File
@@ -4,7 +4,6 @@
import OLAProcessor from './ola-processor'; import OLAProcessor from './ola-processor';
import FFT from './fft.js'; import FFT from './fft.js';
import { getDistortionAlgorithm } from './helpers.mjs';
const clamp = (num, min, max) => Math.min(Math.max(num, min), max); const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
const mod = (n, m) => ((n % m) + m) % m; const mod = (n, m) => ((n % m) + m) % m;
@@ -431,10 +430,9 @@ class DistortProcessor extends AudioWorkletProcessor {
]; ];
} }
constructor({ processorOptions }) { constructor() {
super(); super();
this.started = false; this.started = false;
this.algorithm = getDistortionAlgorithm(processorOptions.algorithm);
} }
process(inputs, outputs, parameters) { process(inputs, outputs, parameters) {
@@ -446,12 +444,13 @@ class DistortProcessor extends AudioWorkletProcessor {
return false; return false;
} }
this.started = hasInput; this.started = hasInput;
const shape = Math.expm1(parameters.distort[0]);
const postgain = Math.max(0.001, Math.min(1, parameters.postgain[0]));
for (let n = 0; n < blockSize; n++) { for (let n = 0; n < blockSize; n++) {
const postgain = clamp(pv(parameters.postgain, n), 0.001, 1); for (let i = 0; i < input.length; i++) {
const shape = Math.expm1(pv(parameters.distort, n)); output[i][n] = (((1 + shape) * input[i][n]) / (1 + shape * Math.abs(input[i][n]))) * postgain;
for (let ch = 0; ch < input.length; ch++) {
const x = input[ch][n];
output[ch][n] = postgain * this.algorithm(x, shape);
} }
} }
return true; return true;
@@ -1050,15 +1049,14 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
return [ return [
{ name: 'begin', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY }, { name: 'begin', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY },
{ name: 'end', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY }, { name: 'end', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY },
{ name: 'frequency', defaultValue: 440, min: Number.EPSILON }, { name: 'frequency', defaultValue: 220, minValue: 0.01, maxValue: 20000 },
{ name: 'detune', defaultValue: 0 }, { name: 'detune', defaultValue: 0.18 },
{ name: 'freqspread', defaultValue: 0.18, min: 0 }, { name: 'position', defaultValue: 0, minValue: 0, maxValue: 1 },
{ name: 'position', defaultValue: 0, min: 0, max: 1 }, { name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 },
{ name: 'warp', defaultValue: 0, min: 0, max: 1 },
{ name: 'warpMode', defaultValue: 0 }, { name: 'warpMode', defaultValue: 0 },
{ name: 'voices', defaultValue: 1, min: 1 }, { name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 },
{ name: 'panspread', defaultValue: 0.7, min: 0, max: 1 }, { name: 'spread', defaultValue: 0.7, minValue: 0, maxValue: 1 },
{ name: 'phaserand', defaultValue: 0, min: 0, max: 1 }, { name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 },
]; ];
} }
@@ -1236,12 +1234,10 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
_sampleFrame(frame, phase) { _sampleFrame(frame, phase) {
const len = frame.length; const len = frame.length;
const pos = phase * len; const pos = phase * len;
let i = pos | 0; const i = pos | 0;
if (i >= len) i = 0; // fast wrap
const frac = pos - i; const frac = pos - i;
const a = frame[i]; const a = frame[i];
let i1 = i + 1; const i1 = i + 1 < len ? i + 1 : 0; // fast wrap
if (i1 >= len) i1 = 0;
const b = frame[i1]; const b = frame[i1];
return a + (b - a) * frac; return a + (b - a) * frac;
} }
@@ -1271,7 +1267,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
} }
for (let i = 0; i < outL.length; i++) { for (let i = 0; i < outL.length; i++) {
const detune = pv(parameters.detune, i); const detune = pv(parameters.detune, i);
const freqspread = pv(parameters.freqspread, i);
const tablePos = clamp(pv(parameters.position, i), 0, 1); const tablePos = clamp(pv(parameters.position, i), 0, 1);
const idx = tablePos * (this.numFrames - 1); const idx = tablePos * (this.numFrames - 1);
const fIdx = idx | 0; const fIdx = idx | 0;
@@ -1280,9 +1275,9 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const warpMode = pv(parameters.warpMode, i); const warpMode = pv(parameters.warpMode, i);
const voices = pv(parameters.voices, i); const voices = pv(parameters.voices, i);
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1); const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0; const spread = voices > 1 ? clamp(pv(parameters.spread, i), 0, 1) : 0;
const gain1 = Math.sqrt(0.5 - 0.5 * panspread); const gain1 = Math.sqrt(0.5 - 0.5 * spread);
const gain2 = Math.sqrt(0.5 + 0.5 * panspread); const gain2 = Math.sqrt(0.5 + 0.5 * spread);
let f = pv(parameters.frequency, i); let f = pv(parameters.frequency, i);
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
const normalizer = 1 / Math.sqrt(voices); const normalizer = 1 / Math.sqrt(voices);
@@ -1295,7 +1290,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
gainL = gain2; gainL = gain2;
gainR = gain1; gainR = gain1;
} }
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, freqspread, n)); // voice detune const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
const dPhase = fVoice * this.invSR; const dPhase = fVoice * this.invSR;
const level = this._chooseMip(dPhase); const level = this._chooseMip(dPhase);
const table = this.tables[level]; const table = this.tables[level];
+1 -2
View File
@@ -1,7 +1,6 @@
//import { ZZFX } from 'zzfx'; //import { ZZFX } from 'zzfx';
import { midiToFreq, noteToMidi } from './util.mjs'; import { midiToFreq, noteToMidi } from './util.mjs';
import { registerSound } from './superdough.mjs'; import { registerSound, getAudioContext } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
import { buildSamples } from './zzfx_fork.mjs'; import { buildSamples } from './zzfx_fork.mjs';
export const getZZFX = (value, t) => { export const getZZFX = (value, t) => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { getAudioContext } from './audioContext.mjs'; import { getAudioContext } from './superdough.mjs';
// https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6 // https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6
// changes: replaced this.volume with 1 + using sampleRate from getAudioContext() // changes: replaced this.volume with 1 + using sampleRate from getAudioContext()
+17 -76
View File
@@ -2829,82 +2829,6 @@ exports[`runs examples > example "distort" example index 1 1`] = `
] ]
`; `;
exports[`runs examples > example "distort" example index 2 1`] = `
[
"[ 0/1 → 1/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 1/4 → 1/2 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 1/2 → 3/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 3/4 → 1/1 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 1/1 → 5/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 5/4 → 3/2 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 3/2 → 7/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 7/4 → 2/1 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 2/1 → 9/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 9/4 → 5/2 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 5/2 → 11/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 11/4 → 3/1 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 3/1 → 13/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 13/4 → 7/2 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 7/2 → 15/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 15/4 → 4/1 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
]
`;
exports[`runs examples > example "distorttype" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 1/4 → 1/2 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 1/2 → 3/4 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 3/4 → 1/1 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 1/1 → 5/4 | s:bd bank:tr909 distort:2 distorttype:1 ]",
"[ 5/4 → 3/2 | s:bd bank:tr909 distort:2 distorttype:1 ]",
"[ 3/2 → 7/4 | s:bd bank:tr909 distort:2 distorttype:1 ]",
"[ 7/4 → 2/1 | s:bd bank:tr909 distort:2 distorttype:1 ]",
"[ 2/1 → 9/4 | s:bd bank:tr909 distort:2 distorttype:2 ]",
"[ 9/4 → 5/2 | s:bd bank:tr909 distort:2 distorttype:2 ]",
"[ 5/2 → 11/4 | s:bd bank:tr909 distort:2 distorttype:2 ]",
"[ 11/4 → 3/1 | s:bd bank:tr909 distort:2 distorttype:2 ]",
"[ 3/1 → 13/4 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 13/4 → 7/2 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 7/2 → 15/4 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 15/4 → 4/1 | s:bd bank:tr909 distort:2 distorttype:0 ]",
]
`;
exports[`runs examples > example "distorttype" example index 1 1`] = `
[
"[ (0/1 → 1/2) ⇝ 1/1 | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:1 distorttype:fold ]",
"[ 0/1 ⇜ (1/2 → 1/1) | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:1 distorttype:fold ]",
"[ (1/1 → 3/2) ⇝ 2/1 | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:4.6367951557040215 distorttype:chebyshev ]",
"[ 1/1 ⇜ (3/2 → 2/1) | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:4.6367951557040215 distorttype:chebyshev ]",
"[ (2/1 → 5/2) ⇝ 3/1 | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:7.716689839959145 distorttype:scurve ]",
"[ 2/1 ⇜ (5/2 → 3/1) | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:7.716689839959145 distorttype:scurve ]",
"[ (3/1 → 7/2) ⇝ 4/1 | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:2.5210237745195627 distorttype:diode ]",
"[ 3/1 ⇜ (7/2 → 4/1) | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:2.5210237745195627 distorttype:diode ]",
]
`;
exports[`runs examples > example "distortvol" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 1/4 → 1/2 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 1/2 → 3/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 3/4 → 1/1 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 1/1 → 5/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 5/4 → 3/2 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 3/2 → 7/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 7/4 → 2/1 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 2/1 → 9/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 9/4 → 5/2 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 5/2 → 11/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 11/4 → 3/1 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 3/1 → 13/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 13/4 → 7/2 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 7/2 → 15/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 15/4 → 4/1 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
]
`;
exports[`runs examples > example "djf" example index 0 1`] = ` exports[`runs examples > example "djf" example index 0 1`] = `
[ [
"[ 0/1 → 1/8 | note:D3 s:supersaw djf:0.5 ]", "[ 0/1 → 1/8 | note:D3 s:supersaw djf:0.5 ]",
@@ -10920,6 +10844,23 @@ exports[`runs examples > example "take" example index 2 1`] = `
] ]
`; `;
exports[`runs examples > example "timeline" example index 0 1`] = `[]`;
exports[`runs examples > example "timeline" example index 1 1`] = `
[
"[ 0/1 → 4/7 | note:F3 ]",
"[ (4/7 → 1/1) ⇝ 8/7 | note:G3 ]",
"[ 4/7 ⇜ (1/1 → 8/7) | note:G3 ]",
"[ 8/7 → 12/7 | note:Ab3 ]",
"[ (12/7 → 2/1) ⇝ 16/7 | note:Bb3 ]",
"[ 12/7 ⇜ (2/1 → 16/7) | note:Bb3 ]",
"[ 16/7 → 20/7 | note:C4 ]",
"[ (20/7 → 3/1) ⇝ 24/7 | note:Db4 ]",
"[ 20/7 ⇜ (3/1 → 24/7) | note:Db4 ]",
"[ 24/7 → 4/1 | note:Eb4 ]",
]
`;
exports[`runs examples > example "tour" example index 0 1`] = ` exports[`runs examples > example "tour" example index 0 1`] = `
[ [
"[ 0/1 → 1/8 | note:e s:folkharp ]", "[ 0/1 → 1/8 | note:e s:folkharp ]",
-17
View File
@@ -60,23 +60,6 @@ Strudel makes heavy use of chained functions. Here is a more sophisticated examp
.room(0.5)`} .room(0.5)`}
/> />
## Write your own chained function
You can write your own chained function using `register`. Here's the above chain but registered as a reusable, chained function.
<MiniRepl
client:idle
tune={`const effectChain = register('effectChain', (pat) => pat
.s("sawtooth")
.cutoff(500)
//.delay(0.5)
.room(0.5)
)
note("a3 c#4 e4 a4").effectChain()`}
/>
Try adding `.rev()` after `effectChain()` to hear further effects added.
# Comments # Comments
The `//` in the example above is a line comment, resulting in the `delay` function being ignored. The `//` in the example above is a line comment, resulting in the `delay` function being ignored.
+11 -67
View File
@@ -1,8 +1,6 @@
import { getSampleBufferSource, onTriggerSample, registerSampleSource } from '@strudel/webaudio'; import { registerSampleSource } from '@strudel/webaudio';
import { isAudioFile } from './files.mjs'; import { isAudioFile } from './files.mjs';
import { getSoundIndex, logger } from '@strudel/core'; import { logger } from '@strudel/core';
import { registerSound } from '@strudel/webaudio';
import { getCommonSampleInfo } from '../../../packages/superdough/util.mjs';
//utilites for writing and reading to the indexdb //utilites for writing and reading to the indexdb
@@ -27,15 +25,6 @@ function clearAllIDB() {
export function clearIDB(dbName) { export function clearIDB(dbName) {
return window.indexedDB.deleteDatabase(dbName); return window.indexedDB.deleteDatabase(dbName);
}
function registerSampleFromIdb(key, bank, params) {
} }
// queries the DB, and registers the sounds so they can be played // queries the DB, and registers the sounds so they can be played
@@ -63,76 +52,31 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete =
if (!isAudioFile(title)) { if (!isAudioFile(title)) {
return; return;
} }
const splitRelativePath = soundFile.id.split('/'); const splitRelativePath = soundFile.id.split('/');
let parentDirectory = let parentDirectory =
//fallback to file name before period and seperator if no parent directory //fallback to file name before period and seperator if no parent directory
splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user'; splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user';
const blob = soundFile.blob;
// const blob = soundFile.blob;
return blobToDataUrl(blob).then((soundPath) => {
const titlePathMap = sounds.get(parentDirectory) ?? new Map(); const titlePathMap = sounds.get(parentDirectory) ?? new Map();
titlePathMap.set(title, soundFile.id); titlePathMap.set(title, soundPath);
sounds.set(parentDirectory, titlePathMap); sounds.set(parentDirectory, titlePathMap);
return;
});
// return blobToDataUrl(blob).then((soundPath) => {
// const titlePathMap = sounds.get(parentDirectory) ?? new Map();
// titlePathMap.set(title, soundPath);
// sounds.set(parentDirectory, titlePathMap);
// return;
// });
}), }),
) )
.then(() => { .then(() => {
sounds.forEach((titlePathMap, key) => { sounds.forEach((titlePathMap, key) => {
const bank = Array.from(titlePathMap.keys()) const value = Array.from(titlePathMap.keys())
.sort((a, b) => { .sort((a, b) => {
return a.localeCompare(b); return a.localeCompare(b);
}) })
.map((title) => titlePathMap.get(title)); .map((title) => titlePathMap.get(title));
registerSampleSource(key, value, { prebake: false });
registerSound(key, async (t, hapValue, onended) => {
const { s, n = 0 } = hapValue;
const index = getSoundIndex(n, bank.length);
let {transpose, label} = getCommonSampleInfo(hapValue)
const storeKey = bank[index];
openDB(config, (objectStore) => {
const getRequest = objectStore.get(storeKey);
getRequest.onsuccess = async (event) => {
const result = event.target.result;
let buffer = result?.blob.arrayBuffer ? await result.blob.arrayBuffer() : null;
console.info(buffer)
if (buffer) {
const bufferSource = getSampleBufferSource(hapValue,buffer,transpose)
onTriggerSample(t, hapValue, onended, bufferSource)
} else {
logger(`Could not load sample for ${storeKey}`, 'error');
}
};
})
// const buffer = objectStore.getKey(key)
// const bufferSource = getSampleBufferSource(hapValue,buffer,transpose)
// onTriggerSample(t, hapValue, onended, await getBufferSrcFromBank(hapValue, bank, undefined))
}, {
type: 'sample',
samples: bank,
});
// registerSampleSource(key, value, { prebake: false });
}); });
logger('imported sounds registered!', 'success'); logger('imported sounds registered!', 'success');