From 6c4aa12c6045fc7a6e0fa086e124124ce194bf0c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 19 Jun 2025 09:09:45 +0200 Subject: [PATCH 1/5] refactor: replace .cpm with setcpm calls + doc setcpm --- packages/core/pattern.mjs | 1 + packages/core/repl.mjs | 11 ++++ packages/gamepad/docs/gamepad.mdx | 15 ++--- .../src/pages/de/workshop/first-sounds.mdx | 56 ++++++++++++------- website/src/pages/de/workshop/recap.mdx | 20 +++---- website/src/pages/learn/samples.mdx | 10 ++-- website/src/pages/understand/cycles.mdx | 49 +++++++++++----- website/src/pages/workshop/first-notes.mdx | 22 +++++--- website/src/pages/workshop/first-sounds.mdx | 44 ++++++++++----- .../src/pages/workshop/pattern-effects.mdx | 10 ++-- website/src/pages/workshop/recap.mdx | 20 +++---- website/src/repl/tunes.mjs | 20 ------- 12 files changed, 169 insertions(+), 109 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 76b02c21e..41b09ea20 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1981,6 +1981,7 @@ export const apply = register('apply', function (func, pat) { /** * Plays the pattern at the given cycles per minute. + * @deprecated * @example * s(",hh*2").cpm(90) // = 90 bpm */ diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 7a8cbbab2..8acd2bbea 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -85,6 +85,17 @@ export function repl({ const pause = () => scheduler.pause(); const toggle = () => scheduler.toggle(); const setCps = (cps) => scheduler.setCps(cps); + + /** + * Changes the global tempo to the given cycles per minute + * + * @name setcpm + * @alias setCpm + * @param {number} cpm cycles per minute + * @example + * setcpm(140/4) // =140 bpm in 4/4 + * $: s("bd*4,[- sd]*2").bank('tr707') + */ const setCpm = (cpm) => scheduler.setCps(cpm / 60); // TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`.. diff --git a/packages/gamepad/docs/gamepad.mdx b/packages/gamepad/docs/gamepad.mdx index 219d8ae0b..e2da594ea 100644 --- a/packages/gamepad/docs/gamepad.mdx +++ b/packages/gamepad/docs/gamepad.mdx @@ -56,14 +56,15 @@ You can use button inputs to control different aspects of your music, such as ga @@ -74,14 +75,13 @@ Analog sticks can be used for continuous control, such as pitch shifting or pann ### Button Sequences @@ -89,6 +89,7 @@ $: note("c4 d3 a3 e3").sound("sawtooth") You can define button sequences to trigger specific actions, like playing a sound when a sequence is detected. -**Tempo ändern mit `cpm`** +**Tempo ändern mit `setcpm`** -*8").cpm(90/4)`} punchcard /> +*8")`} + punchcard +/> @@ -233,8 +238,9 @@ Bolero: @@ -313,18 +319,23 @@ Das haben wir bisher gelernt: Die mit Apostrophen umgebene Mini-Notation benutzt man normalerweise in einer sogenannten Funktion. Die folgenden Funktionen haben wir bereits gesehen: -| Name | Description | Example | -| ----- | -------------------------------------- | ----------------------------------------------------------------------- | -| sound | Spielt den Sound mit dem Namen | | -| bank | Wählt die Soundbank / Drum Machine | | -| cpm | Tempo in **C**ycles **p**ro **M**inute | | -| n | Sample Nummer | | +| Name | Description | Example | +| ------ | -------------------------------------- | ----------------------------------------------------------------------- | +| sound | Spielt den Sound mit dem Namen | | +| bank | Wählt die Soundbank / Drum Machine | | +| setcpm | Tempo in **C**ycles **p**ro **M**inute | | +| n | Sample Nummer | | ## Beispiele **Einfacher Rock Beat** - + **Klassischer House** @@ -339,7 +350,12 @@ Bestimmte Drum Patterns werden oft genreübergreifend wiederverwendet. We Will Rock you - + **Yellow Magic Orchestra - Firecracker** @@ -354,12 +370,13 @@ We Will Rock you @@ -367,12 +384,13 @@ We Will Rock you @@ -380,11 +398,11 @@ We Will Rock you diff --git a/website/src/pages/de/workshop/recap.mdx b/website/src/pages/de/workshop/recap.mdx index 14ef6b252..ec279c084 100644 --- a/website/src/pages/de/workshop/recap.mdx +++ b/website/src/pages/de/workshop/recap.mdx @@ -56,13 +56,13 @@ Diese Seite ist eine Auflistung aller im Workshop vorgestellten Funktionen. ## Pattern-Effekte -| Name | Beschreibung | Beispiel | -| ---- | --------------------------------- | ----------------------------------------------------------------------------------- | -| cpm | Tempo in Cycles pro Minute | | -| fast | schneller | | -| slow | langsamer | | -| rev | rückwärts | | -| jux | einen Stereo-Kanal modifizieren | | -| add | addiert Zahlen oder Noten | ")).scale("C:minor")`} /> | -| ply | jedes Element schneller machen | ")`} /> | -| off | verzögert eine modifizierte Kopie | x.speed(2))`} /> | +| Name | Beschreibung | Beispiel | +| ------ | --------------------------------- | ----------------------------------------------------------------------------------- | +| setcpm | Tempo in Cycles pro Minute | | +| fast | schneller | | +| slow | langsamer | | +| rev | rückwärts | | +| jux | einen Stereo-Kanal modifizieren | | +| add | addiert Zahlen oder Noten | ")).scale("C:minor")`} /> | +| ply | jedes Element schneller machen | ")`} /> | +| off | verzögert eine modifizierte Kopie | x.speed(2))`} /> | diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index cd7944caa..ef97ec0ce 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -247,15 +247,17 @@ We can also declare different samples for different regions of the keyboard: !2, g4 f4]>") - .s('moog').clip(1) - .gain(.5).cpm(60)`} +.s('moog').clip(1) +.gain(.5)`} /> The sampler will always pick the closest matching sample for the current note! diff --git a/website/src/pages/understand/cycles.mdx b/website/src/pages/understand/cycles.mdx index 4694850c3..1c0a778a6 100644 --- a/website/src/pages/understand/cycles.mdx +++ b/website/src/pages/understand/cycles.mdx @@ -43,21 +43,33 @@ This is why the same CPS can produce different perceived tempos. ## Setting CPM -If you're familiar with BPM, you can use the `cpm` method to set the tempo in cycles per minute: +If you're familiar with BPM, you can use the `setcpm` method to set the global tempo in cycles per minute: - + If you want to add more beats per cycle, you might want to divide the cpm: - + Or using 2 beats per cycle: - + -To set a specific bpm, use `.cpm(bpm/bpc)` +To set a specific bpm, use `setcpm(bpm/bpc)` - bpm: the target beats per minute - bpc: the number of perceived beats per cycle @@ -74,22 +86,31 @@ Many music programs use it as a default. Strudel does not a have concept of bars or measures, there are only cycles. How you use them is up to you. Above, we've had this example: - + This could be interpreted as 4/4 time with a tempo of 110bpm. We could write out multiple bars like this: \`).cpm(110/4)`} +>\`)`} /> Instead of writing out each bar separately, we could express this much shorter: ->,hh*4").cpm(110/2)`} /> +>,hh*4")`} +/> Here we can see that thinking in cycles rather than bars simplifies things a lot! These types of simplifications work because of the repetitive nature of rhythm. @@ -109,10 +130,11 @@ We could also write multiple bars with different time signatures: \`).cpm(110*2)`} +>\`)`} /> Here we switch between 3/4 and 4/4, keeping the same tempo. @@ -121,10 +143,11 @@ If we don't specify the length, we get what's called a metric modulation: \`).cpm(110/2)`} +>\`)`} /> Now the 3 elements get the same time as the 4 elements, which is why the tempo changes. diff --git a/website/src/pages/workshop/first-notes.mdx b/website/src/pages/workshop/first-notes.mdx index f3d74ddce..56312a355 100644 --- a/website/src/pages/workshop/first-notes.mdx +++ b/website/src/pages/workshop/first-notes.mdx @@ -216,8 +216,9 @@ Finding the right notes can be difficult.. Scales are here to help: ") -.scale("C:minor").sound("piano").cpm(60)`} + tune={`setcpm(60) +n("0 2 4 <[6,8] [7,9]>") +.scale("C:minor").sound("piano")`} punchcard /> @@ -242,9 +243,10 @@ Just like anything, we can automate the scale with a pattern: , 2 4 <[6,8] [7,9]>") + tune={`setcpm(60) +n("<0 -3>, 2 4 <[6,8] [7,9]>") .scale("/4") -.sound("piano").cpm(60)`} +.sound("piano")`} punchcard /> @@ -275,9 +277,10 @@ Try changing that number! *2") + tune={`setcpm(60) +n("<[4@2 4] [5@2 5] [6@2 6] [5@2 5]>*2") .scale("/4") -.sound("gm_acoustic_bass").cpm(60)`} +.sound("gm_acoustic_bass")`} punchcard /> @@ -291,7 +294,12 @@ This is also sometimes called triplet swing. You'll often find it in blues and j **Replicate** -]").sound("piano").cpm(60)`} punchcard /> +]").sound("piano")`} + punchcard +/> diff --git a/website/src/pages/workshop/first-sounds.mdx b/website/src/pages/workshop/first-sounds.mdx index 9c33cdc3c..083a55390 100644 --- a/website/src/pages/workshop/first-sounds.mdx +++ b/website/src/pages/workshop/first-sounds.mdx @@ -166,9 +166,14 @@ Try also changing the number at the end to change the tempo! -**changing the tempo with cpm** +**changing the tempo with setcpm** -*8").cpm(90/4)`} punchcard /> +*8")`} + punchcard +/> @@ -287,7 +292,7 @@ The Mini-Notation is usually used inside some function. These are the functions | ----- | ----------------------------------- | --------------------------------------------------------------------------------- | | sound | plays the sound of the given name | | | bank | selects the sound bank | | -| cpm | sets the tempo in cycles per minute | | +| cpm | sets the tempo in cycles per minute | | | n | select sample number | | ## Examples @@ -296,8 +301,8 @@ The Mini-Notation is usually used inside some function. These are the functions @@ -314,14 +319,20 @@ Certain drum patterns are reused across genres. We Will Rock you - + **Yellow Magic Orchestra - Firecracker** @@ -329,12 +340,14 @@ We Will Rock you @@ -342,12 +355,13 @@ We Will Rock you @@ -355,11 +369,11 @@ We Will Rock you diff --git a/website/src/pages/workshop/pattern-effects.mdx b/website/src/pages/workshop/pattern-effects.mdx index c9cb7b462..ad84a9ab3 100644 --- a/website/src/pages/workshop/pattern-effects.mdx +++ b/website/src/pages/workshop/pattern-effects.mdx @@ -68,9 +68,10 @@ Try commenting out one or more by adding `//` before a line >")) + tune={`setcpm(60) +note("c2 [eb3,g3] ".add("<0 <1 -1>>")) .color(">").adsr("[.1 0]:.2:[1 0]") -.sound("gm_acoustic_bass").room(.5).cpm(60)`} +.sound("gm_acoustic_bass").room(.5)`} punchcard /> @@ -84,9 +85,10 @@ We can add as often as we like: >").add("0,7")) + tune={`setcpm(60) +note("c2 [eb3,g3]".add("<0 <1 -1>>").add("0,7")) .color(">").adsr("[.1 0]:.2:[1 0]") -.sound("gm_acoustic_bass").room(.5).cpm(60)`} +.sound("gm_acoustic_bass").room(.5)`} punchcard /> diff --git a/website/src/pages/workshop/recap.mdx b/website/src/pages/workshop/recap.mdx index e2b661f56..08e243cef 100644 --- a/website/src/pages/workshop/recap.mdx +++ b/website/src/pages/workshop/recap.mdx @@ -56,13 +56,13 @@ This page is just a listing of all functions covered in the workshop! ## Pattern Effects -| name | description | example | -| ---- | ----------------------------------- | ----------------------------------------------------------------------------------- | -| cpm | sets the tempo in cycles per minute | | -| fast | speed up | | -| slow | slow down | | -| rev | reverse | | -| jux | split left/right, modify right | | -| add | add numbers / notes | ")).scale("C:minor")`} /> | -| ply | speed up each event n times | ")`} /> | -| off | copy, shift time & modify | x.speed(2))`} /> | +| name | description | example | +| ------ | ----------------------------------- | ----------------------------------------------------------------------------------- | +| setcpm | sets the tempo in cycles per minute | | +| fast | speed up | | +| slow | slow down | | +| rev | reverse | | +| jux | split left/right, modify right | | +| add | add numbers / notes | ")).scale("C:minor")`} /> | +| ply | speed up each event n times | ")`} /> | +| off | copy, shift time & modify | x.speed(2))`} /> | diff --git a/website/src/repl/tunes.mjs b/website/src/repl/tunes.mjs index 774e04e49..afaf937df 100644 --- a/website/src/repl/tunes.mjs +++ b/website/src/repl/tunes.mjs @@ -313,26 +313,6 @@ stack( ) .fast(2/3) .pianoroll()`; -/* -export const bridgeIsOver = `// "Bridge is over" -// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ -// @by Felix Roos, bassline by BDP - The Bridge Is Over - -samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'}) -stack( - stack( - note("c3*2 [[c3@1.4 bb2] ab2] gb2*2 <[[gb2@1.4 ab2] bb2] gb2>") - .gain(.8).clip("[.5 1]*2"), - n("<0 1 2 3 4 3 2 1>") - .clip(.5) - .echoWith(8, 1/32, (x,i)=>x.add(n(i)).velocity(Math.pow(.7,i))) - .scale('c4 whole tone') - .echo(3, 1/8, .5) - ).piano(), - s("mad").slow(2) -).cpm(78).slow(4) - .pianoroll() -`; */ export const goodTimes = `// "Good times" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ From 64ab3c9ba73fddfe3d554b376a72a8ac6dfb23f5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 19 Jun 2025 09:12:05 +0200 Subject: [PATCH 2/5] fix: tests --- test/__snapshots__/examples.test.mjs.snap | 29 +++++++++++++++++++++++ test/runtime.mjs | 2 ++ 2 files changed, 31 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 85948f0c0..f3607cb48 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -8450,6 +8450,35 @@ exports[`runs examples > example "seqPLoop" example index 0 1`] = ` ] `; +exports[`runs examples > example "setcpm" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd bank:tr707 ]", + "[ 1/4 → 1/2 | s:bd bank:tr707 ]", + "[ 1/4 → 1/2 | s:sd bank:tr707 ]", + "[ 1/2 → 3/4 | s:bd bank:tr707 ]", + "[ 3/4 → 1/1 | s:bd bank:tr707 ]", + "[ 3/4 → 1/1 | s:sd bank:tr707 ]", + "[ 1/1 → 5/4 | s:bd bank:tr707 ]", + "[ 5/4 → 3/2 | s:bd bank:tr707 ]", + "[ 5/4 → 3/2 | s:sd bank:tr707 ]", + "[ 3/2 → 7/4 | s:bd bank:tr707 ]", + "[ 7/4 → 2/1 | s:bd bank:tr707 ]", + "[ 7/4 → 2/1 | s:sd bank:tr707 ]", + "[ 2/1 → 9/4 | s:bd bank:tr707 ]", + "[ 9/4 → 5/2 | s:bd bank:tr707 ]", + "[ 9/4 → 5/2 | s:sd bank:tr707 ]", + "[ 5/2 → 11/4 | s:bd bank:tr707 ]", + "[ 11/4 → 3/1 | s:bd bank:tr707 ]", + "[ 11/4 → 3/1 | s:sd bank:tr707 ]", + "[ 3/1 → 13/4 | s:bd bank:tr707 ]", + "[ 13/4 → 7/2 | s:bd bank:tr707 ]", + "[ 13/4 → 7/2 | s:sd bank:tr707 ]", + "[ 7/2 → 15/4 | s:bd bank:tr707 ]", + "[ 15/4 → 4/1 | s:bd bank:tr707 ]", + "[ 15/4 → 4/1 | s:sd bank:tr707 ]", +] +`; + exports[`runs examples > example "shape" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:hh shape:0 ]", diff --git a/test/runtime.mjs b/test/runtime.mjs index 6b75fb3be..2a29de3f5 100644 --- a/test/runtime.mjs +++ b/test/runtime.mjs @@ -97,6 +97,7 @@ const toneHelpersMocked = { '_pianoroll', '_spectrum', 'markcss', + 'p', ].forEach((mock) => { strudel.Pattern.prototype[mock] = function () { return this; @@ -163,6 +164,7 @@ evalScope( loadCsound, loadcsound, setcps: id, + setcpm: id, Clock: {}, // whatever }, ); From 66673d211ea60716d83d483b80fdb353c0ba9538 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 19 Jun 2025 09:13:48 +0200 Subject: [PATCH 3/5] fix: cpm -> setcpm --- website/src/pages/workshop/first-sounds.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/src/pages/workshop/first-sounds.mdx b/website/src/pages/workshop/first-sounds.mdx index 083a55390..74daf4bab 100644 --- a/website/src/pages/workshop/first-sounds.mdx +++ b/website/src/pages/workshop/first-sounds.mdx @@ -288,12 +288,12 @@ This is what we've learned so far: The Mini-Notation is usually used inside some function. These are the functions we've seen so far: -| Name | Description | Example | -| ----- | ----------------------------------- | --------------------------------------------------------------------------------- | -| sound | plays the sound of the given name | | -| bank | selects the sound bank | | -| cpm | sets the tempo in cycles per minute | | -| n | select sample number | | +| Name | Description | Example | +| ------ | ----------------------------------- | --------------------------------------------------------------------------------- | +| sound | plays the sound of the given name | | +| bank | selects the sound bank | | +| setcpm | sets the tempo in cycles per minute | | +| n | select sample number | | ## Examples From a8757fecc8554b10274ca27e378fe9bdb2e216fc Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 19 Jun 2025 08:39:05 +0100 Subject: [PATCH 4/5] avoid double action for PRs, try turning on pnpm cache --- .forgejo/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 765f5958d..1266d2ddf 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -1,6 +1,6 @@ name: Strudel tests -on: [push, pull_request] +on: [push] jobs: build: @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - # cache: 'pnpm' + cache: 'pnpm' - run: pnpm install - run: pnpm run format-check - run: pnpm run lint From f3c4afaa54d0c7fa2428ec883052e6643ec725ea Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 19 Jun 2025 08:42:33 +0100 Subject: [PATCH 5/5] apt install zstd for caching --- .forgejo/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 1266d2ddf..90cb7e258 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -11,6 +11,8 @@ jobs: steps: - uses: actions/checkout@v4 + - name: apt install ztd + run: apt update && apt install -y zstd - uses: pnpm/action-setup@v4 with: version: 9.12.2