Compare commits

..

1 Commits

Author SHA1 Message Date
alex 24ac74e0ca make cpm a global control (like cps), and deal with it in cyclist 2026-11-01 11:25:06 +00:00
6 changed files with 96 additions and 47 deletions
-2
View File
@@ -41,8 +41,6 @@ Licensing info for the default sound banks can be found over on the [dough-sampl
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md). You can find the full list of contributors [here](https://codeberg.org/uzu/strudel/activity/contributors).
Please see [POLICY.md](POLICY.md) for our code of conduct and humane AI/LLM policy.
## Community
Strudel is a member of the 'Uzu' family of pattern-based live coding languages, along with TidalCycles and friends.
+25
View File
@@ -2702,7 +2702,32 @@ export const { seconds } = registerControl('seconds');
export const { songPtr } = registerControl('songPtr');
export const { uid } = registerControl('uid');
export const { val } = registerControl('val');
/**
* Plays the pattern at the given cycles per second. The cps can be patterned. This changes the global tempo, so it generally
* makes sense to have either `cps` (or `cpm` - cycles per minute) set by only one of your patterns.
* Note that this currently does not work when the 'Sync across Browser Tabs / Windows' setting is switched on.
* @tags temporal
* @example
* s("bd hh sd hh").cps(0.5) // = 1 cycle every 2 seconds
* @example
* // a smooth change over 4 cycles
* s("bd hh sd hh").cps(sine.slow(4).range(0.5,1.5))
*/
export const { cps } = registerControl('cps');
/**
* Plays the pattern at the given cycles per minute. The cpm can be patterned. This changes the global tempo, so it generally
* makes sense to have either `cpm` (or `cps` - cycles per second) set by only one of your patterns.
* Note that this currently does not work when the 'Sync across Browser Tabs / Windows' setting is switched on.
* @tags temporal
* @example
* s("bd hh sd hh").cpm(90) // = feels like 180 bpm
* // a smooth change over 4 cycles
* s("bd hh sd hh").cpm(sine.slow(4).range(30,70))
*/
export const { cpm } = registerControl('cpm');
/**
* Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration.
*
+8 -6
View File
@@ -84,10 +84,10 @@ export class Cyclist {
// cps changes first, so the durations of the simultaneous haps
// are adjusted accordingly.
if (aBegin.eq(bBegin)) {
if (a.value.cps !== undefined) {
return b.value.cps !== undefined ? 0 : -1;
if (a.value.cps !== undefined || a.value.cpm !== undefined) {
return b.value.cps !== undefined || b.value.cpm !== undefined ? 0 : -1;
}
if (b.value.cps !== undefined) {
if (b.value.cps !== undefined || b.value.cpm !== undefined) {
return 1;
}
return 0;
@@ -97,6 +97,8 @@ export class Cyclist {
haps.forEach((hap) => {
const targetTime = (hap.whole.begin - this.checkpoint.cycles) / this.cps + this.checkpoint.seconds;
let hapCps = hap.value.cps;
hapCps ??= hap.value.cpm != null ? hap.value.cpm / 60 : undefined;
if (tickEndTime < targetTime) {
// If the targetTime exceeds the tickEndTime, this means
// the cps has slowed down, and the latter haps from the query
@@ -104,10 +106,10 @@ export class Cyclist {
// They'll be scheduled during the next tick.
return;
}
onSchedule?.(hap, targetTime + this.latency, hap.value.cps ?? this.cps);
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
onSchedule?.(hap, targetTime + this.latency, hapCps ?? this.cps);
if (hapCps !== undefined && this.cps != hapCps) {
cpsChanged = true;
this.cps = hap.value.cps;
this.cps = hapCps;
this.num_ticks_since_checkpoint = 0;
this.checkpoint = {
cycles: hap.whole.begin,
-12
View File
@@ -2476,18 +2476,6 @@ export const apply = register('apply', function (func, pat) {
return func(pat);
});
/**
* Plays the pattern at the given cycles per minute.
* @tags temporal
* @deprecated
* @example
* s("<bd sd>,hh*2").cpm(90) // = 90 bpm
*/
// this is redefined in repl.mjs, using the current cps as divisor
export const cpm = register('cpm', function (cpm, pat) {
return pat._fast(cpm / 60 / 1);
});
/**
* Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator
*
-4
View File
@@ -217,14 +217,10 @@ export function repl({
} catch (err) {
console.warn('injectPatternMethods: error:', err);
}
const cpm = register('cpm', function (cpm, pat) {
return pat._fast(cpm / 60 / scheduler.cps);
});
return evalScope({
all,
each,
hush,
cpm,
setCps,
setcps: setCps,
setCpm,
+63 -23
View File
@@ -1,5 +1,68 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`runs examples > example "" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd cps:0.5 ]",
"[ 1/4 → 1/2 | s:hh cps:0.5 ]",
"[ 1/2 → 3/4 | s:sd cps:0.5 ]",
"[ 3/4 → 1/1 | s:hh cps:0.5 ]",
"[ 1/1 → 5/4 | s:bd cps:0.5 ]",
"[ 5/4 → 3/2 | s:hh cps:0.5 ]",
"[ 3/2 → 7/4 | s:sd cps:0.5 ]",
"[ 7/4 → 2/1 | s:hh cps:0.5 ]",
"[ 2/1 → 9/4 | s:bd cps:0.5 ]",
"[ 9/4 → 5/2 | s:hh cps:0.5 ]",
"[ 5/2 → 11/4 | s:sd cps:0.5 ]",
"[ 11/4 → 3/1 | s:hh cps:0.5 ]",
"[ 3/1 → 13/4 | s:bd cps:0.5 ]",
"[ 13/4 → 7/2 | s:hh cps:0.5 ]",
"[ 7/2 → 15/4 | s:sd cps:0.5 ]",
"[ 15/4 → 4/1 | s:hh cps:0.5 ]",
]
`;
exports[`runs examples > example "" example index 0 2`] = `
[
"[ 0/1 → 1/4 | s:bd cpm:50 ]",
"[ 1/4 → 1/2 | s:hh cpm:57.6536686473018 ]",
"[ 1/2 → 3/4 | s:sd cpm:64.14213562373095 ]",
"[ 3/4 → 1/1 | s:hh cpm:68.47759065022574 ]",
"[ 1/1 → 5/4 | s:bd cpm:70 ]",
"[ 5/4 → 3/2 | s:hh cpm:68.47759065022574 ]",
"[ 3/2 → 7/4 | s:sd cpm:64.14213562373095 ]",
"[ 7/4 → 2/1 | s:hh cpm:57.6536686473018 ]",
"[ 2/1 → 9/4 | s:bd cpm:50 ]",
"[ 9/4 → 5/2 | s:hh cpm:42.34633135269821 ]",
"[ 5/2 → 11/4 | s:sd cpm:35.85786437626905 ]",
"[ 11/4 → 3/1 | s:hh cpm:31.52240934977427 ]",
"[ 3/1 → 13/4 | s:bd cpm:30 ]",
"[ 13/4 → 7/2 | s:hh cpm:31.522409349774268 ]",
"[ 7/2 → 15/4 | s:sd cpm:35.85786437626905 ]",
"[ 15/4 → 4/1 | s:hh cpm:42.34633135269819 ]",
]
`;
exports[`runs examples > example "" example index 1 1`] = `
[
"[ 0/1 → 1/4 | s:bd cps:1 ]",
"[ 1/4 → 1/2 | s:hh cps:1.1913417161825448 ]",
"[ 1/2 → 3/4 | s:sd cps:1.3535533905932737 ]",
"[ 3/4 → 1/1 | s:hh cps:1.4619397662556435 ]",
"[ 1/1 → 5/4 | s:bd cps:1.5 ]",
"[ 5/4 → 3/2 | s:hh cps:1.4619397662556435 ]",
"[ 3/2 → 7/4 | s:sd cps:1.3535533905932737 ]",
"[ 7/4 → 2/1 | s:hh cps:1.1913417161825448 ]",
"[ 2/1 → 9/4 | s:bd cps:1 ]",
"[ 9/4 → 5/2 | s:hh cps:0.8086582838174552 ]",
"[ 5/2 → 11/4 | s:sd cps:0.6464466094067263 ]",
"[ 11/4 → 3/1 | s:hh cps:0.5380602337443567 ]",
"[ 3/1 → 13/4 | s:bd cps:0.5 ]",
"[ 13/4 → 7/2 | s:hh cps:0.5380602337443567 ]",
"[ 7/2 → 15/4 | s:sd cps:0.6464466094067262 ]",
"[ 15/4 → 4/1 | s:hh cps:0.8086582838174547 ]",
]
`;
exports[`runs examples > example "_euclidRot" example index 0 1`] = `
[
"[ 0/1 → 1/5 | note:c3 ]",
@@ -2789,29 +2852,6 @@ exports[`runs examples > example "cosine" example index 0 1`] = `
]
`;
exports[`runs examples > example "cpm" example index 0 1`] = `
[
"[ 0/1 → 1/3 | s:hh ]",
"[ 0/1 → 2/3 | s:bd ]",
"[ 1/3 → 2/3 | s:hh ]",
"[ 2/3 → 1/1 | s:hh ]",
"[ 2/3 → 4/3 | s:sd ]",
"[ 1/1 → 4/3 | s:hh ]",
"[ 4/3 → 5/3 | s:hh ]",
"[ 4/3 → 2/1 | s:bd ]",
"[ 5/3 → 2/1 | s:hh ]",
"[ 2/1 → 7/3 | s:hh ]",
"[ 2/1 → 8/3 | s:sd ]",
"[ 7/3 → 8/3 | s:hh ]",
"[ 8/3 → 3/1 | s:hh ]",
"[ 8/3 → 10/3 | s:bd ]",
"[ 3/1 → 10/3 | s:hh ]",
"[ 10/3 → 11/3 | s:hh ]",
"[ 10/3 → 4/1 | s:sd ]",
"[ 11/3 → 4/1 | s:hh ]",
]
`;
exports[`runs examples > example "crush" example index 0 1`] = `
[
"[ 0/1 → 1/6 | s:hh crush:16 ]",