Compare commits

...

15 Commits

Author SHA1 Message Date
alex b523205590 Merge branch 'revv' of ssh://codeberg.org/uzu/strudel into revv 2025-11-28 22:03:32 +00:00
alex 956598da18 snapshot 2025-11-28 22:03:21 +00:00
Alex McLean e011f3b982 Merge branch 'main' into revv 2025-11-28 22:37:03 +01:00
alex cb591815ed fix copy paste errors 2025-11-28 21:35:35 +00:00
Aria d30df8d4cf Merge pull request '[perf] Disconnect lfos for phaser and filters' (#1787) from glossing/perf-lfos into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1787
2025-11-28 20:19:14 +01:00
Aria e10cd22c26 Merge branch 'main' into glossing/perf-lfos 2025-11-28 19:59:51 +01:00
Aria 18dc4944bf Typos and cleanup 2025-11-28 12:36:23 -06:00
froos 2f8cabbb6b Merge pull request 'fix: return silence when no pattern is returned' (#1795) from fix-empty-pattern into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1795
2025-11-27 23:08:50 +01:00
Felix Roos ec26392509 fix: return silence when no pattern is returned 2025-11-27 22:53:38 +01:00
froos 30cd486f37 Merge pull request 'fix(tool/dbpatch): add missing package name' (#1765) from peterpf/strudel:bugfix/add-package-name-for-dbpatch into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1765
2025-11-27 22:47:01 +01:00
froos cde0520ed9 Merge pull request 'add CHANGELOG.md + basic script to generate' (#1794) from changelog into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1794
2025-11-27 22:38:04 +01:00
Alex McLean f4b1baac87 add revv() for reversing whole patterns 2025-11-27 15:34:18 +00:00
Peter Egger d217119391 fix(tool/dbpatch): add missing package name 2025-11-26 19:48:48 +01:00
Aria 692359309f Typo and formatting 2025-11-25 17:31:11 -06:00
Aria d65b908bda Disconnect lfos for phaser and filters 2025-11-25 17:27:37 -06:00
7 changed files with 77 additions and 21 deletions
+18 -1
View File
@@ -2234,7 +2234,7 @@ export const brak = register('brak', function (pat) {
}); });
/** /**
* Reverse all haps in a pattern * Reverse all cycles in a pattern. See also `revv` for reversing a whole pattern.
* *
* @name rev * @name rev
* @memberof Pattern * @memberof Pattern
@@ -2266,6 +2266,23 @@ export const rev = register(
true, true,
); );
/**
* Reverse a whole pattern. See also `rev` for reversing each cycle.
*
* @name revv
* @memberof Pattern
* @returns Pattern
* @example
* // This is the same as `<[g e] [d c]>`. If `rev()` is used, you get
* // the same as `<[d c] [g e]>`, where each cycle reverses, but the order of
* // cycles stays the same.
* note("<[c d] [e g]>").revv()
*/
export const revv = register('revv', function (pat) {
const negateSpan = (span) => new TimeSpan(Fraction(0).sub(span.end), Fraction(0).sub(span.begin));
return pat.withQuerySpan(negateSpan).withHapSpan(negateSpan);
});
/** Like press, but allows you to specify the amount by which each /** Like press, but allows you to specify the amount by which each
* event is shifted. pressBy(0.5) is the same as press, while * event is shifted. pressBy(0.5) is the same as press, while
* pressBy(1/3) shifts each event by a third of its timespan. * pressBy(1/3) shifts each event by a third of its timespan.
+1 -2
View File
@@ -244,8 +244,7 @@ export function repl({
} }
if (!isPattern(pattern)) { if (!isPattern(pattern)) {
const message = `got "${typeof evaluated}" instead of pattern`; pattern = silence;
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
} }
logger(`[eval] code updated`); logger(`[eval] code updated`);
pattern = await setPattern(pattern, autostart); pattern = await setPattern(pattern, autostart);
+12
View File
@@ -586,6 +586,18 @@ describe('Pattern', () => {
.map((a) => a.value), .map((a) => a.value),
).toStrictEqual(['c', 'b', 'a']); ).toStrictEqual(['c', 'b', 'a']);
}); });
it('Does not reverse the order of cycles', () => {
expect(fastcat('a', 'b', 'c', 'd').slow(2).rev().fast(2).sortHapsByPart().firstCycle()).toStrictEqual(
fastcat('b', 'a', 'd', 'c').firstCycle(),
);
});
});
describe('revv()', () => {
it('Does reverse the order of cycles', () => {
expect(fastcat('a', 'b', 'c', 'd').slow(2).revv().fast(2).sortHapsByPart().firstCycle()).toStrictEqual(
fastcat('d', 'c', 'b', 'a').firstCycle(),
);
});
}); });
describe('sequence()', () => { describe('sequence()', () => {
it('Can work like fastcat', () => { it('Can work like fastcat', () => {
+3 -2
View File
@@ -240,6 +240,7 @@ export function createFilter(context, start, end, params, cps, cycle) {
rate = cps * sync; rate = cps * sync;
} }
const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined); const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined);
let lfo;
if (hasLFO) { if (hasLFO) {
depth = depth ?? 1; depth = depth ?? 1;
const time = cycle / cps; const time = cycle / cps;
@@ -255,10 +256,10 @@ export function createFilter(context, start, end, params, cps, cycle) {
time, time,
curve: 1, curve: 1,
}; };
getParamLfo(context, frequencyParam, start, end, lfoValues); lfo = getParamLfo(context, frequencyParam, start, end, lfoValues);
} }
return filter; return { filter, lfo };
} }
// stays 1 until .5, then fades out // stays 1 until .5, then fades out
+29 -16
View File
@@ -287,7 +287,7 @@ export function connectToDestination(input, channels) {
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) { function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
const ac = getAudioContext(); const ac = getAudioContext();
const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 }); const lfo = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
//filters //filters
const numStages = 2; //num of filters in series const numStages = 2; //num of filters in series
@@ -300,14 +300,14 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000
filter.frequency.value = centerFrequency + fOffset; filter.frequency.value = centerFrequency + fOffset;
filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9); filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9);
lfoGain.connect(filter.detune); lfo.connect(filter.detune);
fOffset += 282; fOffset += 282;
if (i > 0) { if (i > 0) {
filterChain[i - 1].connect(filter); filterChain[i - 1].connect(filter);
} }
filterChain.push(filter); filterChain.push(filter);
} }
return filterChain[filterChain.length - 1]; return { phaser: filterChain[filterChain.length - 1], lfo };
} }
function getFilterType(ftype) { function getFilterType(ftype) {
@@ -413,7 +413,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
release = 0, release = 0,
//phaser //phaser
phaserrate: phaser, phaserrate,
phaserdepth = getDefaultValue('phaserdepth'), phaserdepth = getDefaultValue('phaserdepth'),
phasersweep, phasersweep,
phasercenter, phasercenter,
@@ -560,10 +560,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
}; };
const lpParams = pickAndRename(value, lpMap); const lpParams = pickAndRename(value, lpMap);
lpParams.type = 'lowpass'; lpParams.type = 'lowpass';
let lp = () => createFilter(ac, t, end, lpParams, cps, cycle); const lp = () => createFilter(ac, t, end, lpParams, cps, cycle);
chain.push(lp()); const { filter: lpf1, lfo: lfo1 } = lp();
chain.push(lpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') { if (ftype === '24db') {
chain.push(lp()); const { filter: lpf2, lfo: lfo2 } = lp();
chain.push(lpf2);
lfo2 && audioNodes.push(lfo2);
} }
} }
@@ -589,10 +593,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
}; };
const hpParams = pickAndRename(value, hpMap); const hpParams = pickAndRename(value, hpMap);
hpParams.type = 'highpass'; hpParams.type = 'highpass';
let hp = () => createFilter(ac, t, end, hpParams, cps, cycle); const hp = () => createFilter(ac, t, end, hpParams, cps, cycle);
chain.push(hp()); const { filter: hpf1, lfo: lfo1 } = hp();
chain.push(hpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') { if (ftype === '24db') {
chain.push(hp()); const { filter: hpf2, lfo: lfo2 } = hp();
chain.push(hpf2);
lfo2 && audioNodes.push(lfo2);
} }
} }
@@ -618,10 +626,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
}; };
const bpParams = pickAndRename(value, bpMap); const bpParams = pickAndRename(value, bpMap);
bpParams.type = 'bandpass'; bpParams.type = 'bandpass';
let bp = () => createFilter(ac, t, end, bpParams, cps, cycle); const bp = () => createFilter(ac, t, end, bpParams, cps, cycle);
chain.push(bp()); const { filter: bpf1, lfo: lfo1 } = bp();
chain.push(bpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') { if (ftype === '24db') {
chain.push(bp()); const { filter: bpf2, lfo: lfo2 } = bp();
chain.push(bpf2);
lfo2 && audioNodes.push(lfo2);
} }
} }
@@ -684,9 +696,10 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
chain.push(panner); chain.push(panner);
} }
// phaser // phaser
if (phaser !== undefined && phaserdepth > 0) { if (phaserrate !== undefined && phaserdepth > 0) {
const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep); const { phaser, lfo } = getPhaser(t, endWithRelease, phaserrate, phaserdepth, phasercenter, phasersweep);
chain.push(phaserFX); audioNodes.push(lfo);
chain.push(phaser);
} }
// last gain // last gain
+13
View File
@@ -9256,6 +9256,19 @@ exports[`runs examples > example "rev" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "revv" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:g ]",
"[ 1/2 → 1/1 | note:e ]",
"[ 1/1 → 3/2 | note:d ]",
"[ 3/2 → 2/1 | note:c ]",
"[ 2/1 → 5/2 | note:g ]",
"[ 5/2 → 3/1 | note:e ]",
"[ 3/1 → 7/2 | note:d ]",
"[ 7/2 → 4/1 | note:c ]",
]
`;
exports[`runs examples > example "ribbon" example index 0 1`] = ` exports[`runs examples > example "ribbon" example index 0 1`] = `
[ [
"[ 0/1 → 1/1 | note:d ]", "[ 0/1 → 1/1 | note:d ]",
+1
View File
@@ -1,4 +1,5 @@
{ {
"name": "dbpatch",
"dependencies": { "dependencies": {
"csv": "^6.3.11" "csv": "^6.3.11"
}, },