Compare commits

...

25 Commits

Author SHA1 Message Date
Aria adc3a9ac6c Swap with temp variable 2025-11-22 20:51:30 -06:00
Switch Angel AKA Jade Rose 2518b2aed2 Merge pull request 'FIX: prevent LFO filter modulation from popping from negative values' (#1767) from lpmod_improvements into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1767
2025-11-23 00:52:27 +01:00
Jade (Rose) Rowland 1d5f3a4f30 prevent filter modulation pops 2025-11-22 18:21:24 -05:00
Switch Angel AKA Jade Rose 5fa6cb4653 Merge pull request 'prefix "S" for solo' (#1481) from solo into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1481
2025-11-23 00:03:31 +01:00
Jade (Rose) Rowland 19fd0fc649 rm unessecary file 2025-11-22 17:17:41 -05:00
Jade (Rose) Rowland e72e26eb81 add comment 2025-11-22 17:11:17 -05:00
Jade (Rose) Rowland 1c4b05d55f solo search n = 1 2025-11-22 16:40:31 -05:00
Aria 06bf17f444 Merge pull request 'Bug Fix: Use frac due to negative frequencies from FM' (#1759) from glossing/droponelettertosaveears into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1759
2025-11-21 01:56:10 +01:00
Aria 21543956c6 Also for wavetable 2025-11-20 18:30:53 -06:00
Aria 4758effdc6 Use frac due to negative frequencies from FM 2025-11-20 18:22:44 -06:00
froos 6f58e74bea Merge pull request '[perf] fix connect-leak added by #1742 when noise() is not used' (#1757) from jeromew/strudel:fix-perf5 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1757
2025-11-20 19:48:15 +01:00
jeromew 0435711051 [perf] fix connect-leak added by #1742 when noise() is not used 2025-11-20 17:36:00 +00:00
froos 8f6718b9e8 Merge pull request '[perf] fix connect-leak in delay effect' (#1755) from jeromew/strudel:fix-perf4 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1755
2025-11-20 14:50:59 +01:00
froos aa77255d98 Merge pull request '[perf] fix connect leak when .noise() is in the mix' (#1742) from jeromew/strudel:fix-perf3 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1742
2025-11-20 14:49:40 +01:00
jeromew 7267990e20 Fix codeformat 2025-11-20 13:35:37 +00:00
jeromew 387e5db0fe Merge branch 'fix-perf3' of https://codeberg.org/jeromew/strudel into fix-perf3 2025-11-20 13:24:03 +00:00
jeromew d9f63b8dfa [perf] fix connect leak when .noise() is in the mix 2025-11-20 13:21:09 +00:00
jeromew 8ed72b4573 [perf] fix connect-leak in delay effect 2025-11-20 13:05:41 +00:00
Felix Roos dd2c808cd7 hotfix: reduce sounds-tab click to play latency 2025-11-19 21:55:35 +01:00
Alex McLean 389a63a850 Merge pull request 'wchooseCycles has now notes in an example' (#1748) from scrappy_fiddler/strudel:documentation into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1748
2025-11-18 23:52:43 +01:00
scrappy_fiddler 99beb4454d update snapshot with note example 2025-11-17 20:49:26 +01:00
scrappy_fiddler 30cc46ea66 wchooseCycles has now notes in an example 2025-11-17 20:04:40 +01:00
jeromew bfa8fa3c75 [perf] fix connect leak when .noise() is in the mix 2025-11-16 09:23:11 +00:00
Switch Angel AKA Jade Rose f4cf77f5c6 Merge branch 'main' into solo 2025-09-30 08:09:37 +02:00
Felix Roos 7f103e77e3 prefix "S" for solo 2025-07-28 04:05:21 +02:00
10 changed files with 103 additions and 73 deletions
+14 -3
View File
@@ -152,9 +152,9 @@ export function repl({
// allows muting a pattern x with x_ or _x
return silence;
}
if (id === '$') {
if (id.includes('$')) {
// allows adding anonymous patterns with $:
id = `$${anonymousIndex}`;
id = `${id}${anonymousIndex}`;
anonymousIndex++;
}
pPatterns[id] = this;
@@ -215,8 +215,19 @@ export function repl({
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
if (Object.keys(pPatterns).length) {
let patterns = [];
let soloActive = false;
for (const [key, value] of Object.entries(pPatterns)) {
patterns.push(value.withState((state) => state.setControls({ id: key })));
// handle soloed patterns ex: S$: s("bd!4")
const isSolod = key.length > 1 && key.startsWith('S');
if (isSolod && soloActive === false) {
// first time we see a soloed pattern, clear existing patterns
patterns = [];
soloActive = true;
}
if (!soloActive || (soloActive && isSolod)) {
const valWithState = value.withState((state) => state.setControls({ id: key }));
patterns.push(valWithState);
}
}
if (eachTransform) {
// Explicit lambda so only element (not index and array) are passed
+1 -1
View File
@@ -524,7 +524,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
* @example
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
* wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note()
* @example
* // The probability can itself be a pattern
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
+14 -2
View File
@@ -238,7 +238,7 @@ export function createFilter(context, start, end, params, cps) {
if (sync != null) {
rate = cps * sync;
}
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate };
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate, min: 10, max: 20000 };
getParamLfo(context, frequencyParam, start, end, lfoValues);
return filter;
}
@@ -262,7 +262,15 @@ export function drywet(dry, wet, wetAmount = 0) {
let mix = ac.createGain();
dry_gain.connect(mix);
wet_gain.connect(mix);
return mix;
return {
node: mix,
onended: () => {
dry_gain.disconnect(mix);
wet_gain.disconnect(mix);
dry.disconnect(dry_gain);
wet.disconnect(wet_gain);
},
};
}
let curves = ['linear', 'exponential'];
@@ -297,6 +305,10 @@ export function getVibratoOscillator(param, value, t) {
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(param);
vibratoOscillator.onended = () => {
gain.disconnect(param);
vibratoOscillator.disconnect(gain);
};
vibratoOscillator.start(t);
return vibratoOscillator;
}
+2 -1
View File
@@ -65,8 +65,9 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) {
export function getNoiseMix(inputNode, wet, t) {
const noiseOscillator = getNoiseOscillator('pink', t);
const noiseMix = drywet(inputNode, noiseOscillator.node, wet);
noiseOscillator.node.onended = () => noiseMix.onended();
return {
node: noiseMix,
node: noiseMix.node,
stop: (time) => noiseOscillator?.stop(time),
};
}
+2 -1
View File
@@ -692,7 +692,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
// delay
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
orbitBus.getDelay(delaytime, delayfeedback, t);
orbitBus.sendDelay(post, delay);
const send = orbitBus.sendDelay(post, delay);
audioNodes.push(send);
}
// reverb
if (room > 0) {
+1 -1
View File
@@ -82,7 +82,7 @@ export class Orbit {
}
sendDelay(node, amount) {
effectSend(node, this.delayNode, amount);
return effectSend(node, this.delayNode, amount);
}
duck(t, onsettime = 0, attacktime = 0.1, depth = 1) {
+14 -10
View File
@@ -48,19 +48,17 @@ export function registerSynthSounds() {
[0.001, 0.05, 0.6, 0.01],
);
let sound = getOscillator(s, t, value);
let { node: o, stop, triggerRelease } = sound;
// turn down
const g = gainNode(0.3);
const { duration } = value;
o.onended = () => {
o.disconnect();
let sound = getOscillator(s, t, value, () => {
g.disconnect();
onended();
};
});
let { node: o, stop, triggerRelease } = sound;
const { duration } = value;
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
@@ -460,7 +458,7 @@ export function waveformN(partials, phases, type) {
}
// expects one of waveforms as s
export function getOscillator(s, t, value) {
export function getOscillator(s, t, value, onended) {
const { duration, noise = 0 } = value;
const partials = value.partials ?? value.n;
let o;
@@ -482,7 +480,6 @@ export function getOscillator(s, t, value) {
}
// set frequency
o.frequency.value = getFrequencyFromValue(value);
o.start(t);
let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
@@ -495,6 +492,13 @@ export function getOscillator(s, t, value) {
noiseMix = getNoiseMix(o, noise, t);
}
o.onended = () => {
o.disconnect();
noiseMix?.node.disconnect();
onended();
};
o.start(t);
return {
node: noiseMix?.node || o,
stop: (time) => {
+6 -5
View File
@@ -532,7 +532,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
// We must wrap this here because it is passed into sawblep below which
// has domain [0, 1]
const dt = ffrac(freqVoice * INVSR);
const dt = frac(freqVoice * INVSR);
this.phase[n] = this.phase[n] ?? Math.random();
const v = waveshapes.sawblep(this.phase[n], dt);
@@ -543,8 +543,9 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
if (pn >= 1.0) pn -= 1.0;
this.phase[n] = pn;
// invert right and left gain
const tmp = gainL;
gainL = gainR;
gainR = gainL;
gainR = tmp;
}
}
return true;
@@ -1338,7 +1339,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const tablePos = clamp(pv(parameters.position, i), 0, 1);
const idx = tablePos * (this.numFrames - 1);
const fIdx = idx | 0;
const frac = idx - fIdx;
const interpT = idx - fIdx;
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
const warpMode = pv(parameters.warpMode, i);
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
@@ -1368,13 +1369,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
const s0 = this._sampleFrame(table[fIdx], ph);
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
let s = s0 + (s1 - s0) * frac;
let s = lerp(s0, s1, interpT);
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
s = -s;
}
outL[i] += s * gainL * normalizer;
outR[i] += s * gainR * normalizer;
this.phase[n] = ffrac(this.phase[n] + dPhase);
this.phase[n] = frac(this.phase[n] + dPhase);
}
}
return true;
+48 -48
View File
@@ -12484,54 +12484,54 @@ exports[`runs examples > example "wchooseCycles" example index 0 1`] = `
exports[`runs examples > example "wchooseCycles" example index 1 1`] = `
[
"[ 0/1 → 1/12 | s:bd ]",
"[ 1/12 → 1/6 | s:bd ]",
"[ 1/6 → 1/4 | s:bd ]",
"[ 1/4 → 1/3 | s:bd ]",
"[ 1/3 → 5/12 | s:bd ]",
"[ 5/12 → 1/2 | s:bd ]",
"[ 1/2 → 7/12 | s:sd ]",
"[ 7/12 → 2/3 | s:sd ]",
"[ 2/3 → 3/4 | s:sd ]",
"[ 3/4 → 5/6 | s:bd ]",
"[ 5/6 → 11/12 | s:bd ]",
"[ 11/12 → 1/1 | s:bd ]",
"[ 1/1 → 13/12 | s:bd ]",
"[ 13/12 → 7/6 | s:bd ]",
"[ 7/6 → 5/4 | s:bd ]",
"[ 5/4 → 4/3 | s:bd ]",
"[ 4/3 → 17/12 | s:bd ]",
"[ 17/12 → 3/2 | s:bd ]",
"[ 3/2 → 19/12 | s:hh ]",
"[ 19/12 → 5/3 | s:hh ]",
"[ 5/3 → 7/4 | s:hh ]",
"[ 7/4 → 11/6 | s:bd ]",
"[ 11/6 → 23/12 | s:bd ]",
"[ 23/12 → 2/1 | s:bd ]",
"[ 2/1 → 25/12 | s:hh ]",
"[ 25/12 → 13/6 | s:hh ]",
"[ 13/6 → 9/4 | s:hh ]",
"[ 9/4 → 7/3 | s:hh ]",
"[ 7/3 → 29/12 | s:hh ]",
"[ 29/12 → 5/2 | s:hh ]",
"[ 5/2 → 31/12 | s:bd ]",
"[ 31/12 → 8/3 | s:bd ]",
"[ 8/3 → 11/4 | s:bd ]",
"[ 11/4 → 17/6 | s:bd ]",
"[ 17/6 → 35/12 | s:bd ]",
"[ 35/12 → 3/1 | s:bd ]",
"[ 3/1 → 37/12 | s:bd ]",
"[ 37/12 → 19/6 | s:bd ]",
"[ 19/6 → 13/4 | s:bd ]",
"[ 13/4 → 10/3 | s:sd ]",
"[ 10/3 → 41/12 | s:sd ]",
"[ 41/12 → 7/2 | s:sd ]",
"[ 7/2 → 43/12 | s:hh ]",
"[ 43/12 → 11/3 | s:hh ]",
"[ 11/3 → 15/4 | s:hh ]",
"[ 15/4 → 23/6 | s:bd ]",
"[ 23/6 → 47/12 | s:bd ]",
"[ 47/12 → 4/1 | s:bd ]",
"[ 0/1 → 1/12 | note:c ]",
"[ 1/12 → 1/6 | note:c ]",
"[ 1/6 → 1/4 | note:c ]",
"[ 1/4 → 1/3 | note:c ]",
"[ 1/3 → 5/12 | note:c ]",
"[ 5/12 → 1/2 | note:c ]",
"[ 1/2 → 7/12 | note:f ]",
"[ 7/12 → 2/3 | note:f ]",
"[ 2/3 → 3/4 | note:f ]",
"[ 3/4 → 5/6 | note:c ]",
"[ 5/6 → 11/12 | note:c ]",
"[ 11/12 → 1/1 | note:c ]",
"[ 1/1 → 13/12 | note:c ]",
"[ 13/12 → 7/6 | note:c ]",
"[ 7/6 → 5/4 | note:c ]",
"[ 5/4 → 4/3 | note:c ]",
"[ 4/3 → 17/12 | note:c ]",
"[ 17/12 → 3/2 | note:c ]",
"[ 3/2 → 19/12 | note:a ]",
"[ 19/12 → 5/3 | note:a ]",
"[ 5/3 → 7/4 | note:a ]",
"[ 7/4 → 11/6 | note:c ]",
"[ 11/6 → 23/12 | note:c ]",
"[ 23/12 → 2/1 | note:c ]",
"[ 2/1 → 25/12 | note:a ]",
"[ 25/12 → 13/6 | note:a ]",
"[ 13/6 → 9/4 | note:a ]",
"[ 9/4 → 7/3 | note:a ]",
"[ 7/3 → 29/12 | note:a ]",
"[ 29/12 → 5/2 | note:a ]",
"[ 5/2 → 31/12 | note:c ]",
"[ 31/12 → 8/3 | note:c ]",
"[ 8/3 → 11/4 | note:c ]",
"[ 11/4 → 17/6 | note:c ]",
"[ 17/6 → 35/12 | note:c ]",
"[ 35/12 → 3/1 | note:c ]",
"[ 3/1 → 37/12 | note:c ]",
"[ 37/12 → 19/6 | note:c ]",
"[ 19/6 → 13/4 | note:c ]",
"[ 13/4 → 10/3 | note:f ]",
"[ 10/3 → 41/12 | note:f ]",
"[ 41/12 → 7/2 | note:f ]",
"[ 7/2 → 43/12 | note:a ]",
"[ 43/12 → 11/3 | note:a ]",
"[ 11/3 → 15/4 | note:a ]",
"[ 15/4 → 23/6 | note:c ]",
"[ 23/6 → 47/12 | note:c ]",
"[ 47/12 → 4/1 | note:c ]",
]
`;
@@ -126,7 +126,7 @@ export function SoundsTab() {
try {
// Pre-load the sample by calling onTrigger with a future time
// This triggers the loading but schedules playback for later
const time = ctx.currentTime + 0.5; // Give 500ms for loading
const time = ctx.currentTime + 0.05;
const ref = await onTrigger(time, params, onended);
trigRef.current = ref;
if (ref?.node) {