Compare commits

..

40 Commits

Author SHA1 Message Date
Felix Roos 71e60ed8de add back comment 2025-06-24 05:45:36 +02:00
Felix Roos e2a29914c7 don't interpret note as n 2025-06-24 05:43:06 +02:00
Felix Roos a9a3d491a2 Merge branch 'main' into kasparsj/tonal-fix 2025-06-24 05:39:59 +02:00
yaxu a750764f88 Update CONTRIBUTING.md 2025-06-23 11:13:00 +02:00
froos 5b397009ab Merge pull request 'website intro: fix whitespace and code hosting name' (#1400) from trofi/strudel:website-intro-minor into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1400
2025-06-20 20:00:11 +02:00
Sergei Trofimovich e68df9e789 website intro: fix whitespace and code hosting name 2025-06-20 18:33:58 +01:00
yaxu 4d3bbd5057 Update README.md 2025-06-19 17:44:45 +02:00
Alex McLean c0184dd827 Merge pull request 'fix stepcat #1396' (#1398) from fix-stepcat into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1398
2025-06-19 10:15:06 +02:00
Alex McLean 09fdc45ff2 Merge branch 'main' into fix-stepcat 2025-06-19 10:08:08 +02:00
alex c01b4c651c don't trash lists passed to timecat 2025-06-19 09:06:53 +01:00
froos 634eed1d82 Merge pull request 'deprecate cpm + refactor docs to use setcpm' (#1397) from reference-improvements into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1397
2025-06-19 09:52:36 +02:00
froos 01810f379f Merge branch 'main' into reference-improvements 2025-06-19 09:47:01 +02:00
alex f3c4afaa54 apt install zstd for caching 2025-06-19 08:42:33 +01:00
alex a8757fecc8 avoid double action for PRs, try turning on pnpm cache 2025-06-19 08:39:05 +01:00
alex a1d181d609 format 2025-06-19 08:27:41 +01:00
alex 440f1cb081 reify arguments of stepcat 2025-06-19 08:27:23 +01:00
alex 95193b0bfd a test for #1396 2025-06-19 08:18:20 +01:00
Felix Roos 66673d211e fix: cpm -> setcpm 2025-06-19 09:13:48 +02:00
Felix Roos 64ab3c9ba7 fix: tests 2025-06-19 09:12:05 +02:00
Felix Roos 6c4aa12c60 refactor: replace .cpm with setcpm calls
+ doc setcpm
2025-06-19 09:09:45 +02:00
daslyfe a8f2537e31 Merge pull request 'feat: 3 new codemirror themes' (#1394) from jade/themes into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1394
2025-06-19 02:49:08 +02:00
Jade (Rose) Rowland 7e6876c282 comment 2025-06-18 20:44:09 -04:00
Jade (Rose) Rowland 011139eff8 arch2 2025-06-18 20:12:36 +02:00
Jade (Rose) Rowland 184cb746f3 arch 2025-06-18 19:55:30 +02:00
alex 351314ccdf rename .github to .forgejo 2025-06-15 09:23:29 +01:00
Felix Roos e52017c0d8 fix: reference tab critical fail 2025-06-15 10:12:17 +02:00
alex b253d437d3 Merge branch 'main' of ssh://codeberg.org/uzu/strudel 2025-06-15 08:06:12 +01:00
alex bebbe30b50 codeformat 2025-06-15 08:03:23 +01:00
alex 489d3f8d4d Merge branch 'reorganise-docs' 2025-06-15 08:01:44 +01:00
Felix Roos ee7d783099 fix homepage link 2025-06-14 16:27:54 +02:00
Felix Roos 9becd0a3f0 Merge branch 'main' of https://codeberg.org/uzu/strudel 2025-06-14 16:23:36 +02:00
Felix Roos 9738c90b82 even more degithubbing 2025-06-14 16:23:03 +02:00
Alex McLean f7b58f2e3e Merge pull request 'link back to tech manual in wiki' (#1382) from techmanual-wiki into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1382
2025-06-14 16:22:59 +02:00
alex d856e567c1 link back to tech manual in wiki 2025-06-14 15:22:27 +01:00
Kaspars 81ca199a96 Merge branch 'upstream-main' into tonal-fix 2024-06-02 12:39:14 +03:00
Kaspars 8d8b8ab99e codeformat 2024-05-08 10:42:06 +02:00
Kaspars a4f10d5339 bug fix 2024-05-08 10:38:06 +02:00
Kaspars 33a73c37bc fix tests 2024-05-08 00:46:27 +02:00
Kaspars 4e6d39666a codeformat 2024-05-08 00:00:58 +02:00
Kaspars a5b6022506 - fix scale() to allow both n() and note() at the same time
- fix scale() to allow scale names without tonic and then default it to C
2024-05-07 23:56:39 +02:00
40 changed files with 1348 additions and 1351 deletions
@@ -1,6 +1,6 @@
name: Strudel tests
on: [push, pull_request]
on: [push]
jobs:
build:
@@ -11,13 +11,15 @@ 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
- 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
+1 -1
View File
@@ -17,7 +17,7 @@ git remote set-url origin git@codeberg.org:uzu/strudel.git
To get in touch with the contributors, either
- [join the Tidal Discord Channel](https://discord.gg/remJ6gQA) and go to the #strudel channel
- [join the Tidal Discord Channel](https://discord.com/invite/HGEdXmRkzT) and go to the #strudel channel
- Find related discussions on the [tidal club forum](https://club.tidalcycles.org/)
## Ask a Question
+1 -1
View File
@@ -32,7 +32,7 @@ This project is organized into many [packages](./packages), which are also avail
Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start).
You will need to abide by the terms of the [GNU Affero Public Licence v3](LICENSE.md). As such, Strudel code can only be shared within free/open source projects under the same license -- see the license for details.
You will need to abide by the terms of the [GNU Affero Public Licence v3](LICENSE). As such, Strudel code can only be shared within free/open source projects under the same license -- see the license for details.
Licensing info for the default sound banks can be found over on the [dough-samples](https://github.com/felixroos/dough-samples/blob/main/README.md) repository.
+1 -1
View File
@@ -201,7 +201,7 @@ interfaces.
The Strudel REPL is available at <https://strudel.cc>,
including an interactive tutorial. The repository is at
<https://github.com/tidalcycles/strudel>, all the code is open source
<https://codeberg.org/uzu/strudel/releases>, all the code is open source
under the GPL-3.0 License.
# Acknowledgments
+1 -1
View File
@@ -128,7 +128,7 @@ For the future, it is planned to integrate alternative sound engines such as Gli
# Links
The Strudel REPL is available at <https://strudel.cc>, including an interactive tutorial.
The repository is at <https://github.com/tidalcycles/strudel>, all the code is open source under the GPL-3.0 License.
The repository is at <https://codeberg.org/uzu/strudel/releases>, all the code is open source under the GPL-3.0 License.
# Acknowledgments
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -451,7 +451,7 @@ While Haskell's type system makes it a great language for the ongoing developmen
# Links
The Strudel REPL is available at <https://strudel.cc>, including an interactive tutorial.
The repository is at <https://github.com/tidalcycles/strudel>, all the code is open source under the AGPL-3.0 License.
The repository is at <https://codeberg.org/uzu/strudel>, all the code is open source under the AGPL-3.0 License.
# Acknowledgments
+59 -76
View File
@@ -1,80 +1,63 @@
$if(false)$
This is a pandoc template and should not be edited.
$endif$
$if(false)$ This is a pandoc template and should not be edited. $endif$
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"$if(lang)$ lang="$lang$" xml:lang="$lang$"$endif$>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
$for(author-meta)$
<meta name="author" content="$author-meta$" />
$endfor$
$if(date-meta)$
<meta name="date" content="$date-meta$" />
$endif$
<title>$if(title-prefix)$$title-prefix$ - $endif$$pagetitle$</title>
<style type="text/css">code{white-space: pre;}</style>
$if(quotes)$
<style type="text/css">q { quotes: "“" "”" "" ""; }</style>
$endif$
$if(highlighting-css)$
<style type="text/css">
$highlighting-css$
</style>
$endif$
<link rel="stylesheet" href="css/iclc.css" $if(html5)$$else$type="text/css" $endif$/>
$for(css)$
<link rel="stylesheet" href="$css$" $if(html5)$$else$type="text/css" $endif$/>
$endfor$
$if(math)$
$math$
$endif$
$for(header-includes)$
$header-includes$
$endfor$
</head>
<body>
$for(include-before)$
$include-before$
$endfor$
$if(title)$
<div id="$idprefix$header">
<h1 class="title">$title$</h1>
$if(subtitle)$
<h1 class="subtitle">$subtitle$</h1>
$endif$
<ul id="authorlist">
$for(author)$
<li>$author$</li>
$endfor$
</ul>
$if(date)$
<h3 class="date">$date$</h3>
$endif$
</div>
$endif$
$if(toc)$
<div id="$idprefix$TOC">
$toc$
</div>
$endif$
<html xmlns="http://www.w3.org/1999/xhtml" $if(lang)$ lang="$lang$" xml:lang="$lang$" $endif$>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
$for(author-meta)$
<meta name="author" content="$author-meta$" />
$endfor$ $if(date-meta)$
<meta name="date" content="$date-meta$" />
$endif$
<title>$if(title-prefix)$$title-prefix$ - $endif$$pagetitle$</title>
<style type="text/css">
code {
white-space: pre;
}
</style>
$if(quotes)$
<style type="text/css">
q {
quotes: '“' '”' '' '';
}
</style>
$endif$ $if(highlighting-css)$
<style type="text/css">
$highlighting-css$
</style>
$endif$
<link rel="stylesheet" href="css/iclc.css" $if(html5)$$else$type="text/css" $endif$ />
$for(css)$
<link rel="stylesheet" href="$css$" $if(html5)$$else$type="text/css" $endif$ />
$endfor$ $if(math)$ $math$ $endif$ $for(header-includes)$ $header-includes$ $endfor$
</head>
<body>
$for(include-before)$ $include-before$ $endfor$ $if(title)$
<div id="$idprefix$header">
<h1 class="title">$title$</h1>
$if(subtitle)$
<h1 class="subtitle">$subtitle$</h1>
$endif$
<ul id="authorlist">
$for(author)$
<li>$author$</li>
$endfor$
</ul>
$if(date)$
<h3 class="date">$date$</h3>
$endif$
</div>
$endif$ $if(toc)$
<div id="$idprefix$TOC">$toc$</div>
$endif$
<h2 class="abstract">Abstract</h2>
<div id="abstract">
$if(abstract)$
$abstract$
$else$
Please provide an abstract in the metadata block at the top of your
markdown document. Refer to template.txt for details.
$endif$
</div>
<h2 class="abstract">Abstract</h2>
<div id="abstract">
$if(abstract)$ $abstract$ $else$ Please provide an abstract in the metadata block at the top of your markdown
document. Refer to template.txt for details. $endif$
</div>
$body$
$for(include-after)$
$include-after$
$endfor$
</body>
$body$ $for(include-after)$ $include-after$ $endfor$
</body>
</html>
+1 -1
View File
@@ -2,4 +2,4 @@
Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel).
To understand how those pieces connect, refer to the [Technical Manual](https://codeberg.org/uzu/strudel/src/branch/main/technical-manual.md) or the individual READMEs.
To understand how those pieces connect, refer to the [Technical Manual](https://codeberg.org/uzu/strudel/wiki/Technical-Manual) or the individual READMEs.
+12
View File
@@ -8,6 +8,11 @@ import CutiePi, { settings as CutiePiSettings } from './themes/CutiePi.mjs';
import sonicPink, { settings as sonicPinkSettings } from './themes/sonic-pink.mjs';
import redText, { settings as redTextSettings } from './themes/red-text.mjs';
import greenText, { settings as greenTextSettings } from './themes/green-text.mjs';
import archBtw, { settings as archBtwSettings } from './themes/archBtw.mjs';
import fruitDaw, { settings as fruitDawSettings } from './themes/fruitDaw.mjs';
import bluescreenlight, { settings as bluescreenlightsettings } from './themes/bluescreenlight.mjs';
import androidstudio, { settings as androidstudioSettings } from './themes/androidstudio.mjs';
import atomone, { settings as atomOneSettings } from './themes/atomone.mjs';
import aura, { settings as auraSettings } from './themes/aura.mjs';
@@ -39,17 +44,20 @@ import { setTheme } from '@strudel/draw';
export const themes = {
strudelTheme,
algoboy,
archBtw,
androidstudio,
atomone,
aura,
bbedit,
blackscreen,
bluescreen,
bluescreenlight,
CutiePi,
darcula,
dracula,
duotoneDark,
eclipse,
fruitDaw,
githubDark,
githubLight,
greenText,
@@ -78,10 +86,12 @@ export const themes = {
export const settings = {
strudelTheme: strudelThemeSettings,
bluescreen: bluescreenSettings,
bluescreenlight: bluescreenlightsettings,
blackscreen: blackscreenSettings,
whitescreen: whitescreenSettings,
teletext: teletextSettings,
algoboy: algoboySettings,
archBtw: archBtwSettings,
androidstudio: androidstudioSettings,
atomone: atomOneSettings,
aura: auraSettings,
@@ -92,9 +102,11 @@ export const settings = {
eclipse: eclipseSettings,
CutiePi: CutiePiSettings,
sonicPink: sonicPinkSettings,
fruitDaw: fruitDawSettings,
githubLight: githubLightSettings,
githubDark: githubDarkSettings,
greenText: greenTextSettings,
gruvboxDark: gruvboxDarkSettings,
gruvboxLight: gruvboxLightSettings,
materialDark: materialDarkSettings,
+38
View File
@@ -0,0 +1,38 @@
/*
* Arch Btw
* Modern terminal inspired theme
* made by Jade
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
const hex = ['rgb(0, 0, 0)', 'rgb(82, 208, 250)', 'rgba(113, 208, 250, .4)', 'rgba(113, 208, 250, .15)'];
export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[1],
selection: hex[2],
selectionMatch: hex[0],
gutterBackground: hex[0],
gutterForeground: hex[2],
gutterBorder: 'transparent',
lineHighlight: hex[0],
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: hex[1],
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
{ tag: [t.comment, t.brace, t.bracket], color: hex[2] },
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[1] },
{ tag: [t.attributeName, t.number], color: hex[1] },
{ tag: t.keyword, color: hex[1] },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
],
});
+37
View File
@@ -0,0 +1,37 @@
/*
* A lighter blue screen theme
* made by Jade
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
const hex = ['rgb(75, 130, 247)', 'rgb(47, 108, 246)', 'rgb(255, 255, 255)', 'rgba(255, 255, 255,.3)'];
export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[2],
selection: hex[3],
selectionMatch: hex[0],
gutterBackground: hex[0],
gutterForeground: hex[2],
gutterBorder: 'transparent',
lineHighlight: hex[1],
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: hex[2],
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[2] },
{ tag: [t.comment, t.bracket, t.brace, t.compareOperator], color: hex[3] },
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
{ tag: [t.attributeName, t.number], color: hex[2] },
{ tag: t.keyword, color: hex[2] },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] },
],
});
+50
View File
@@ -0,0 +1,50 @@
/*
* Fruit Daw
* made by Jade
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
const hex = [
'rgb(84, 93, 98)',
'rgb(255, 255, 255)',
'rgba(255, 255, 255, .25)',
'rgb(67, 76, 81)',
'rgb(186, 230, 115)',
'rgb(252, 184, 67)',
'rgb(124, 206, 254)',
'rgb(83, 101, 102)',
'rgba(46, 62, 72,.5)',
'rgb(94, 100, 108)',
'rgb(167, 216, 177)',
];
export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[10],
selection: hex[8],
selectionMatch: hex[0],
gutterBackground: hex[3],
gutterForeground: hex[2],
gutterBorder: 'transparent',
lineHighlight: hex[3],
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: hex[1],
},
{ tag: [t.bool, t.special(t.variableName)], color: hex[1] },
{ tag: [t.comment, t.brace, t.bracket], color: hex[2] },
{ tag: [t.variableName], color: hex[1] },
{ tag: [t.labelName, t.propertyName, t.self, t.atom], color: hex[5] },
{ tag: [t.attributeName, t.number], color: hex[6] },
{ tag: t.keyword, color: hex[5] },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[4] },
],
});
+2 -1
View File
@@ -1981,6 +1981,7 @@ export const apply = register('apply', function (func, pat) {
/**
* Plays the pattern at the given cycles per minute.
* @deprecated
* @example
* s("<bd sd>,hh*2").cpm(90) // = 90 bpm
*/
@@ -2719,7 +2720,7 @@ export function stepcat(...timepats) {
if (timepats.length === 0) {
return nothing;
}
const findsteps = (x) => (Array.isArray(x) ? x : [x._steps, x]);
const findsteps = (x) => (Array.isArray(x) ? x : [x._steps ?? 1, x]);
timepats = timepats.map(findsteps);
if (timepats.find((x) => x[0] === undefined)) {
const times = timepats.map((a) => a[0]).filter((x) => x !== undefined);
+11
View File
@@ -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`..
+5 -1
View File
@@ -52,6 +52,7 @@ import {
stackCentre,
stepcat,
sometimes,
expand,
} from '../index.mjs';
import { steady } from '../signal.mjs';
@@ -884,7 +885,7 @@ describe('Pattern', () => {
);
});
it('Doesnt drop haps in the 9th cycle', () => {
// fixed with https://github.com/tidalcycles/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
// fixed with https://codeberg.org/uzu/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
expect(sequence(1, 2, 3).ply(2).early(8).firstCycle().length).toBe(6);
});
});
@@ -1179,6 +1180,9 @@ describe('Pattern', () => {
it('calculates undefined steps as the average', () => {
expect(sameFirst(stepcat(pure(1), pure(2), pure(3).setSteps(undefined)), fastcat(1, 2, 3)));
});
it('works with auto-reified values', () => {
expect(sameFirst(stepcat(expand(3, 'bd'), 'rim'), stepcat(expand(3, 'bd'), pure('rim'))));
});
});
describe('shrink', () => {
it('can shrink', () => {
+8 -7
View File
@@ -56,14 +56,15 @@ You can use button inputs to control different aspects of your music, such as ga
<MiniRepl
client:idle
tune={`const gp = gamepad(0)
// Use button values to control amplitude
setcpm(120)
// Use button values to control amplitude
$: stack(
s("[[hh hh] oh hh oh]/2").mask(gp.tglX).bank("RolandTR909"), // X btn for HH
s("cr*1").mask(gp.Y).bank("RolandTR909"), // LB btn for CR
s("bd").mask(gp.tglA).bank("RolandTR909"), // A btn for BD
s("[ht - - mt - - lt - ]/2").mask(gp.tglB).bank("RolandTR909"), // B btn for Toms
s("sd*4").mask(gp.RB).bank("RolandTR909"), // RB btn for SD
).cpm(120)
)
`}
/>
@@ -74,14 +75,13 @@ Analog sticks can be used for continuous control, such as pitch shifting or pann
<MiniRepl
client:idle
tune={`const gp = gamepad(0)
// Use analog stick for continuous control
setcpm(120)
// Use analog stick for continuous control
$: note("c4 d3 a3 e3").sound("sawtooth")
.lpf(gp.x1.range(100,4000))
.lpq(gp.y1.range(5,30))
.decay(gp.y2.range(0.1,2))
.lpenv(gp.x2.range(-5,5))
.cpm(120)
`}
.lpenv(gp.x2.range(-5,5))`}
/>
### 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.
<MiniRepl client:idle tune={`const gp = gamepad(0)
setcpm(120)
// Define button sequences
const HADOUKEN = [
'd', // Down
@@ -99,7 +100,7 @@ const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA
// Check butto-n sequence (returns 1 while detected, 0 when not within last 1 second)
$: s("free_hadouken -").slow(2)
.mask(gp.btnSequence(HADOUKEN)).room(1).cpm(120)
.mask(gp.btnSequence(HADOUKEN)).room(1)
// hadouken.wav by Syna-Max
//https://freesound.org/people/Syna-Max/sounds/67674/
+1 -1
View File
@@ -17,7 +17,7 @@ You can also pin the version like this:
```
This has the advantage that your code will always work, regardless of potential breaking changes in the strudel codebase.
See [releases](https://github.com/tidalcycles/strudel/releases) for the latest versions.
See [releases](https://codeberg.org/uzu/strudel/releases) for the latest versions.
## Use Web Component
+7
View File
@@ -37,6 +37,13 @@ describe('tonal', () => {
.firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']);
});
it('scale without tonic', () => {
expect(
n(0, 1, 2)
.scale('major')
.firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']);
});
it('scale with mininotation colon', () => {
expect(
n(0, 1, 2)
+1 -1
View File
@@ -14,7 +14,7 @@ function scaleStep(step, scale) {
scale = scale.replaceAll(':', ' ');
step = Math.ceil(step);
let { intervals, tonic, empty } = Scale.get(scale);
if ((empty && isNote(scale)) || (!empty && !tonic)) {
if ((empty && isNote(scale)) || (empty && !tonic)) {
throw new Error(`incomplete scale. Make sure to use ":" instead of spaces, example: .scale("C:major")`);
} else if (empty) {
throw new Error(`invalid scale "${scale}"`);
-193
View File
@@ -1,193 +0,0 @@
This document introduces you to Strudel in a technical sense. If you just want to *use* Strudel, have a look at the [Tutorial](https://strudel.tidalcycles.org/tutorial/).
## Strudel Packages
There are different packages for different purposes. They..
- split up the code into smaller chunks
- can be selectively used to implement some sort of time based system
Please refer to the individual README files in the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages)
## REPL
The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library.
More info in the [REPL README](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl/README.md)
# High Level Overview
<img src="https://codeberg.org/uzu/strudel/raw/branch/talk/talk/public/strudelflow.png" width="600" />
## 1. End User Code
The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://codeberg.org/uzu/strudel/src/branch/main/packages/eval#strudelcycleseval) evaluates the user code
after a transpilation step, which resolves the syntax sugar. If you don't want the syntax sugar, you can omit the eval package and call the native javascript `eval` instead.
### 🍭 Syntax Sugar
JavaScript Transpilation = converting valid JavaScript to valid JavaScript:
```js
"c3 [e3 g3]".fast(2)
```
becomes
```js
mini('c3 [e3 g3]')
.withMiniLocation([1, 0, 0], [1, 11, 11]) // source location
.fast(2);
```
- double quoted strings and backtick strings are turned into `mini` calls (single quoted strings are left as is)
- The source location is added by chaining `withMiniLocation`, which enables the real time highlighting later
- (psuedo) variable names that look like notes (like `c4`, `bb2` or `fs3`) are turned into strings
- support for top level await
- operator overloading could be implemented in the future
This is how it works:
<img src="https://codeberg.org/uzu/strudel/raw/branch/talk/talk/public/shiftflow.png" width="800" />
- The user code is parsed with a [shift parser](https://github.com/shapesecurity/shift-parser-js), generating an AST
- The AST is transformed to resolve the syntax sugar
- The AST is used to generate code again (shift-codegen)
Shift will most likely be replaced with acorn in the future, see https://codeberg.org/uzu/strudel/issues/174
### Mini Notation
Another important part of the user code is the mini notation, which allows to express rhythms in a short manner.
- the mini notation is [implemented as a PEG grammar](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini/krill.pegjs), living in the [mini package](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini)
- it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn
- the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/)
- the generated parser takes a mini notation string and outputs an AST
- the AST can then be used to construct a pattern using the regular Strudel API
Here's an example AST:
```json
{
"type_": "pattern",
"arguments_": { "alignment": "h" },
"source_": [
{
"type_": "element", "source_": "c3",
"location_": { "start": { "offset": 1, "line": 1, "column": 2 }, "end": { "offset": 4, "line": 1, "column": 5 } }
},
{
"type_": "element",
"location_": { "start": { "offset": 4, "line": 1, "column": 5 }, "end": { "offset": 11, "line": 1, "column": 12 } }
"source_": {
"type_": "pattern", "arguments_": { "alignment": "h" },
"source_": [
{
"type_": "element", "source_": "e3",
"location_": { "start": { "offset": 5, "line": 1, "column": 6 }, "end": { "offset": 8, "line": 1, "column": 9 } }
},
{
"type_": "element", "source_": "g3",
"location_": { "start": { "offset": 8, "line": 1, "column": 9 }, "end": { "offset": 10, "line": 1, "column": 11 } }
}
]
},
}
]
}
```
which translates to `seq(c3, seq(e3, g3))`
## 2. Querying & Scheduling
When the user code has been evaluated, we hopefully get a Pattern instance, which we can use to query events from.
These events can then be used to trigger side effects in the real world. On that note, Events are mostly called Hap(s) in the codebase, because JS already has a built in `Event` class.
### Querying
> Querying = Asking a Pattern for Events within a certain time span
```js
seq('c3', ['e3', 'g3']) // <--- Pattern
.queryArc(0, 2) // query events within 0 and 2 cycles
.map((hap) => hap.showWhole()); // make readable
```
yields
```js
[
'0/1 -> 1/2: c3', // cycle 0
'1/2 -> 3/4: e3',
'3/4 -> 1/1: g3',
'1/1 -> 3/2: c3', // cycle 1
'3/2 -> 7/4: e3',
'7/4 -> 2/1: g3',
];
```
### 🗓️ Scheduling
The scheduler will query events repeatedly, creating a possibly endless loop of time slices.
Here is a simplified example of how it works
```js
let step = 0.5; // query interval in seconds
let tick = 0; // how many intervals have passed
let pattern = seq('c3', ['e3', 'g3']); // pattern from user
setInterval(() => {
const events = pattern.queryArc(tick * step, ++tick * step);
events.forEach((event) => {
console.log(event.showWhole());
const o = getAudioContext().createOscillator();
o.frequency.value = getFreq(event.value);
o.start(event.whole.begin);
o.stop(event.whole.begin + event.duration);
o.connect(getAudioContext().destination);
});
}, step * 1000); // query each "step" seconds
```
## 3. Sound Output
The third and last step is to use the scheduled events to make sound.
Patterns are wrapped with param functions to compose different properties of the sound.
```js
note("[c2(3,8) [<eb2 g1> bb1]]") // sets frequency
.s("<sawtooth square>") // sound source
.gain(.5) // turn down volume
.cutoff(sine.range(200,1000).slow(4)) // modulated cutoff
.slow(2)
.out().logValues()`,
]}
/>
```
Here is an example Hap value with different properties:
```js
{ note: 'a4', s: 'sawtooth', gain: 0.5, cutoff: 267 }
```
<img src="https://codeberg.org/uzu/strudel/raw/branch/talk/talk/public/waa-nodes.png" width="600" />
<div className="text-left">
- Patterns represent just values in time!
- Suitable for any time based output (music, visuals, movement, .. ?)
### Supported Outputs
At the time of writing this doc, the following outputs are supported:
- Web Audio API `.out()` see [/webaudio](https://codeberg.org/uzu/strudel/src/branch/main/packages/webaudio)
- MIDI `.midi()` see [/midi](https://codeberg.org/uzu/strudel/src/branch/main/packages/midi)
- OSC `.osc()` see [/osc](https://codeberg.org/uzu/strudel/src/branch/main/packages/osc)
- Serial `.serial()` see [/serial](https://codeberg.org/uzu/strudel/src/branch/main/packages/serial)
- Tone.js `.tone()` (deprecated?) [/tone](https://codeberg.org/uzu/strudel/src/branch/main/packages/tone)
- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://codeberg.org/uzu/strudel/src/branch/main/packages/webdirt)
- Speech `.speak()` (experimental) part of [/core](https://codeberg.org/uzu/strudel/src/branch/main/packages/core)
These could change, so make sure to check the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages).
+29
View File
@@ -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 ]",
+2
View File
@@ -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
},
);
@@ -1,169 +0,0 @@
---
// fetch all commits for just this page's path
type Props = {
path: string;
};
const { path } = Astro.props as Props;
const resolvedPath = `website/src/pages${path}.mdx`;
const url = `https://api.github.com/repos/tidalcycles/strudel/commits?path=${resolvedPath}`;
const commitsURL = `https://github.com/tidalcycles/strudel/commits/main/${resolvedPath}`;
type Commit = {
author: {
id: string;
login: string;
};
};
async function getCommits(url: string) {
try {
const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello';
if (!token) {
throw new Error('Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.');
}
const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`;
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: auth,
'User-Agent': 'astro-docs/1.0',
},
});
const data = await res.json();
if (!res.ok) {
throw new Error(
`Request to fetch commits failed. Reason: ${res.statusText}
Message: ${data.message}`,
);
}
return data as Commit[];
} catch (e) {
console.warn(`[error] /src/components/AvatarList.astro
${(e as any)?.message ?? e}`);
return [] as Commit[];
}
}
function removeDups(arr: Commit[]) {
const map = new Map<string, Commit['author']>();
for (let item of arr) {
const author = item.author;
// Deduplicate based on author.id
//map.set(author.id, { login: author.login, id: author.id });
author && map.set(author.id, { login: author.login, id: author.id });
}
return [...map.values()];
}
const data = await getCommits(url);
const unique = removeDups(data);
const recentContributors = unique.slice(0, 3); // only show avatars for the 3 most recent contributors
const additionalContributors = unique.length - recentContributors.length; // list the rest of them as # of extra contributors
---
<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
<div class="contributors px-4 mb-8">
<ul class="avatar-list" style={`--avatar-count: ${recentContributors.length}`}>
{
recentContributors.map((item) => (
<li>
<a href={`https://github.com/${item.login}`}>
<img
alt={`Contributor ${item.login}`}
title={`Contributor ${item.login}`}
width="64"
height="64"
src={`https://avatars.githubusercontent.com/u/${item.id}`}
/>
</a>
</li>
))
}
</ul>
{
additionalContributors > 0 && (
<span>
<a href={commitsURL}>{`and ${additionalContributors} additional contributor${
additionalContributors > 1 ? 's' : ''
}.`}</a>
</span>
)
}
{unique.length === 0 && <a href={commitsURL}>Contributors</a>}
</div>
<style>
.avatar-list {
--avatar-size: 2.5rem;
--avatar-count: 3;
display: grid;
list-style: none;
/* Default to displaying most of the avatar to
enable easier access on touch devices, ensuring
the WCAG touch target size is met or exceeded */
grid-template-columns: repeat(var(--avatar-count), max(44px, calc(var(--avatar-size) / 1.15)));
/* `padding` matches added visual dimensions of
the `box-shadow` to help create a more accurate
computed component size */
padding: 0.08em;
font-size: var(--avatar-size);
}
@media (any-hover: hover) and (any-pointer: fine) {
.avatar-list {
/* We create 1 extra cell to enable the computed
width to match the final visual width */
grid-template-columns: repeat(calc(var(--avatar-count) + 1), calc(var(--avatar-size) / 1.75));
}
}
.avatar-list li {
width: var(--avatar-size);
height: var(--avatar-size);
}
.avatar-list li:hover ~ li a,
.avatar-list li:focus-within ~ li a {
transform: translateX(33%);
}
.avatar-list img,
.avatar-list a {
display: block;
border-radius: 50%;
}
.avatar-list a {
transition: transform 180ms ease-in-out;
}
.avatar-list img {
width: 100%;
height: 100%;
object-fit: cover;
background-color: #fff;
box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
}
.avatar-list a:focus {
outline: 2px solid transparent;
/* Double-layer trick to work for dark and light backgrounds */
box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
}
.contributors {
display: flex;
align-items: center;
}
.contributors > * + * {
margin-left: 0.75rem;
}
</style>
+1 -1
View File
@@ -82,7 +82,7 @@ export default function Search() {
appId={ALGOLIA.appId}
apiKey={ALGOLIA.apiKey}
getMissingResultsUrl={({ query }) => {
return `https://github.com/tidalcycles/strudel/issues/new?title=Missing doc for ${query}`;
return `https://codeberg.org/uzu/strudel/issues/new?title=Missing%20doc%20for${encodeURIComponent(query)}`;
}}
transformItems={(items) => {
return items.map((item) => {
@@ -18,5 +18,4 @@ currentPage = currentPage.endsWith('/') ? currentPage.slice(0, -1) : currentPage
<nav aria-labelledby="grid-right" class="w-64 text-foreground">
<TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} />
<MoreMenu editHref={githubEditUrl} />
<!-- <AvatarList path={currentPage} /> -->
</nav>
+37 -19
View File
@@ -168,9 +168,14 @@ Korrekt, der echte Vorteil dieser Schreibweise zeigt sich wenn du Elemente entfe
</Box>
**Tempo ändern mit `cpm`**
**Tempo ändern mit `setcpm`**
<MiniRepl client:visible tune={`sound("<bd hh rim hh>*8").cpm(90/4)`} punchcard />
<MiniRepl
client:visible
tune={`setcpm(90/4)
sound("<bd hh rim hh>*8")`}
punchcard
/>
<Box>
@@ -233,8 +238,9 @@ Bolero:
<MiniRepl
client:visible
tune={`sound("sd sd*3 sd sd*3 sd sd sd sd*3 sd sd*3 sd*3 sd*3")
.cpm(10)`}
tune={`
setcpm(10)
sound("sd sd*3 sd sd*3 sd sd sd sd*3 sd sd*3 sd*3 sd*3")`}
punchcard
/>
@@ -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 | <MiniRepl client:visible tune={`sound("bd sd")`} /> |
| bank | Wählt die Soundbank / Drum Machine | <MiniRepl client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| cpm | Tempo in **C**ycles **p**ro **M**inute | <MiniRepl client:visible tune={`sound("bd sd").cpm(90)`} /> |
| n | Sample Nummer | <MiniRepl client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
| Name | Description | Example |
| ------ | -------------------------------------- | ----------------------------------------------------------------------- |
| sound | Spielt den Sound mit dem Namen | <MiniRepl client:visible tune={`sound("bd sd")`} /> |
| bank | Wählt die Soundbank / Drum Machine | <MiniRepl client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| setcpm | Tempo in **C**ycles **p**ro **M**inute | <MiniRepl client:visible tune={`setcpm(90); sound("bd sd")`} /> |
| n | Sample Nummer | <MiniRepl client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
## Beispiele
**Einfacher Rock Beat**
<MiniRepl client:visible tune={`sound("bd sd, hh*4").bank("RolandTR505").cpm(100/2)`} punchcard />
<MiniRepl
client:visible
tune={`setcpm(100/2)
sound("bd sd, hh*4").bank("RolandTR505")`}
punchcard
/>
**Klassischer House**
@@ -339,7 +350,12 @@ Bestimmte Drum Patterns werden oft genreübergreifend wiederverwendet.
We Will Rock you
<MiniRepl client:visible tune={`sound("bd*2 cp").bank("RolandTR707").cpm(81/2)`} punchcard />
<MiniRepl
client:visible
tune={`setcpm(81/2)
sound("bd*2 cp").bank("RolandTR707")`}
punchcard
/>
**Yellow Magic Orchestra - Firecracker**
@@ -354,12 +370,13 @@ We Will Rock you
<MiniRepl
client:visible
tune={`sound(\`
tune={`setcpm(90/4)
sound(\`
[- - oh - ] [- - - - ] [- - - - ] [- - - - ],
[hh hh - - ] [hh - hh - ] [hh - hh - ] [hh - hh - ],
[- - - - ] [cp - - - ] [- - - - ] [cp - - - ],
[bd - - - ] [- - - bd] [- - bd - ] [- - - bd]
\`).cpm(90/4)`}
\`)`}
punchcard
/>
@@ -367,12 +384,13 @@ We Will Rock you
<MiniRepl
client:visible
tune={`sound(\`
tune={`setcpm(88/4)
sound(\`
[- - - - ] [- - - - ] [- - - - ] [- - oh:1 - ],
[hh hh hh hh] [hh hh hh hh] [hh hh hh hh] [hh hh - - ],
[- - - - ] [cp - - - ] [- - - - ] [- cp - - ],
[bd bd - - ] [- - bd - ] [bd bd - bd ] [- - - - ]
\`).bank("RolandTR808").cpm(88/4)`}
\`).bank("RolandTR808")`}
punchcard
/>
@@ -380,11 +398,11 @@ We Will Rock you
<MiniRepl
client:visible
tune={`s(\`jazz*2,
tune={`setcpm(100/2)
s(\`jazz*2,
insect [crow metal] - -,
- space:4 - space:1,
- wind\`)
.cpm(100/2)`}
- wind\`)`}
punchcard
/>
+10 -10
View File
@@ -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 | <MiniRepl client:visible tune={`sound("bd sd").cpm(90)`} /> |
| fast | schneller | <MiniRepl client:visible tune={`sound("bd sd").fast(2)`} /> |
| slow | langsamer | <MiniRepl client:visible tune={`sound("bd sd").slow(2)`} /> |
| rev | rückwärts | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | einen Stereo-Kanal modifizieren | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | addiert Zahlen oder Noten | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | jedes Element schneller machen | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | verzögert eine modifizierte Kopie | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
| Name | Beschreibung | Beispiel |
| ------ | --------------------------------- | ----------------------------------------------------------------------------------- |
| setcpm | Tempo in Cycles pro Minute | <MiniRepl client:visible tune={`setcpm(90); sound("bd sd")`} /> |
| fast | schneller | <MiniRepl client:visible tune={`sound("bd sd").fast(2)`} /> |
| slow | langsamer | <MiniRepl client:visible tune={`sound("bd sd").slow(2)`} /> |
| rev | rückwärts | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | einen Stereo-Kanal modifizieren | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | addiert Zahlen oder Noten | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | jedes Element schneller machen | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | verzögert eine modifizierte Kopie | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
+1 -1
View File
@@ -14,7 +14,7 @@ These pages will introduce you to [Strudel](https://strudel.cc/), a web-based [l
# What is Strudel?
[Strudel](https://strudel.cc/) is a version of [Tidal Cycles](https://tidalcycles.org) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), initiated by [Alex McLean](https://slab.org) and [Felix Roos](https://github.com/felixroos) in 2022.
[Strudel](https://strudel.cc/) is a version of [Tidal Cycles](https://tidalcycles.org) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), initiated by [Alex McLean](https://slab.org) and [Felix Roos](https://froos.cc/) in 2022.
Tidal Cycles, also known as Tidal, is a language for [algorithmic pattern](https://algorithmicpattern.org), and though it is most commonly used for [making music](https://tidalcycles.org/docs/showcase), it can be used for any kind of pattern making activity, including [weaving](https://www.youtube.com/watch?v=TfEmEsusXjU).
Tidal was first implemented as a library written in the [Haskell](https://www.haskell.org/) functional programming language, and by itself it does not make any sound.
+6 -4
View File
@@ -247,15 +247,17 @@ We can also declare different samples for different regions of the keyboard:
<MiniRepl
client:idle
tune={`samples({
tune={`setcpm(60)
samples({
'moog': {
'g2': 'moog/004_Mighty%20Moog%20G2.wav',
'g3': 'moog/005_Mighty%20Moog%20G3.wav',
'g4': 'moog/006_Mighty%20Moog%20G4.wav',
}}, 'github:tidalcycles/dirt-samples');
}}, 'github:tidalcycles/dirt-samples')
note("g2!2 <bb2 c3>!2, <c4@3 [<eb4 bb3> 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!
+36 -13
View File
@@ -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:
<MiniRepl client:visible tune={`s("bd hh").cpm(110)`} />
<MiniRepl
client:visible
tune={`setcpm(110)
s("bd hh")`}
/>
If you want to add more beats per cycle, you might want to divide the cpm:
<MiniRepl client:visible tune={`s("bd sd bd rim, hh*8").cpm(110/4)`} />
<MiniRepl
client:visible
tune={`setcpm(110/4)
s("bd sd bd rim, hh*8")`}
/>
Or using 2 beats per cycle:
<MiniRepl client:visible tune={`s("bd sd, hh*4").cpm(110/2)`} />
<MiniRepl
client:visible
tune={`setcpm(110/2)
s("bd sd, hh*4")`}
/>
<Box>
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:
<MiniRepl client:visible tune={`s("bd sd bd rim, hh*8").cpm(110/4)`} />
<MiniRepl
client:visible
tune={`setcpm(110/4)
s("bd sd bd rim, hh*8")`}
/>
This could be interpreted as 4/4 time with a tempo of 110bpm.
We could write out multiple bars like this:
<MiniRepl
client:visible
tune={`s(\`<
tune={`setcpm(110/4)
s(\`<
[bd sd bd rim, hh*8]
[bd sd bd rim*2, hh*8]
>\`).cpm(110/4)`}
>\`)`}
/>
Instead of writing out each bar separately, we could express this much shorter:
<MiniRepl client:visible tune={`s("bd <sd rim*<1 2>>,hh*4").cpm(110/2)`} />
<MiniRepl
client:visible
tune={`setcpm(110/2)
s("bd <sd rim*<1 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:
<MiniRepl
client:visible
tune={`s(\`<
tune={`setcpm(110*2)
s(\`<
[bd hh rim]@3
[bd hh rim sd]@4
>\`).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:
<MiniRepl
client:visible
tune={`s(\`<
tune={`setcpm(110/2)
s(\`<
[bd hh rim]
[bd hh rim sd]
>\`).cpm(110/2)`}
>\`)`}
/>
Now the 3 elements get the same time as the 4 elements, which is why the tempo changes.
+15 -7
View File
@@ -216,8 +216,9 @@ Finding the right notes can be difficult.. Scales are here to help:
<MiniRepl
client:visible
tune={`n("0 2 4 <[6,8] [7,9]>")
.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:
<MiniRepl
client:visible
tune={`n("<0 -3>, 2 4 <[6,8] [7,9]>")
tune={`setcpm(60)
n("<0 -3>, 2 4 <[6,8] [7,9]>")
.scale("<C:major D:mixolydian>/4")
.sound("piano").cpm(60)`}
.sound("piano")`}
punchcard
/>
@@ -275,9 +277,10 @@ Try changing that number!
<MiniRepl
client:visible
tune={`n("<[4@2 4] [5@2 5] [6@2 6] [5@2 5]>*2")
tune={`setcpm(60)
n("<[4@2 4] [5@2 5] [6@2 6] [5@2 5]>*2")
.scale("<C2:mixolydian F2:mixolydian>/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**
<MiniRepl client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano").cpm(60)`} punchcard />
<MiniRepl
client:visible
tune={`setcpm(60)
note("c!2 [eb,<g a bb a>]").sound("piano")`}
punchcard
/>
<Box>
+34 -20
View File
@@ -166,9 +166,14 @@ Try also changing the number at the end to change the tempo!
</Box>
**changing the tempo with cpm**
**changing the tempo with setcpm**
<MiniRepl client:visible tune={`sound("<bd hh rim hh>*8").cpm(90/4)`} punchcard />
<MiniRepl
client:visible
tune={`setcpm(90/4)
sound("<bd hh rim hh>*8")`}
punchcard
/>
<Box>
@@ -283,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 | <MiniRepl client:visible tune={`sound("bd sd [- bd] sd")`} /> |
| bank | selects the sound bank | <MiniRepl client:visible tune={`sound("bd sd [- bd] sd").bank("RolandTR909")`} /> |
| cpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`sound("bd sd [- bd] sd").cpm(45)`} /> |
| n | select sample number | <MiniRepl client:visible tune={`n("0 1 4 2 0 6 3 2").sound("jazz")`} /> |
| Name | Description | Example |
| ------ | ----------------------------------- | --------------------------------------------------------------------------------- |
| sound | plays the sound of the given name | <MiniRepl client:visible tune={`sound("bd sd [- bd] sd")`} /> |
| bank | selects the sound bank | <MiniRepl client:visible tune={`sound("bd sd [- bd] sd").bank("RolandTR909")`} /> |
| setcpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`setcpm(45); sound("bd sd [- bd] sd")`} /> |
| n | select sample number | <MiniRepl client:visible tune={`n("0 1 4 2 0 6 3 2").sound("jazz")`} /> |
## Examples
@@ -296,8 +301,8 @@ The Mini-Notation is usually used inside some function. These are the functions
<MiniRepl
client:visible
tune={`sound("[bd sd]*2, hh*8").bank("RolandTR505")
.cpm(100/4)`}
tune={`setcpm(100/4)
sound("[bd sd]*2, hh*8").bank("RolandTR505")`}
punchcard
/>
@@ -314,14 +319,20 @@ Certain drum patterns are reused across genres.
We Will Rock you
<MiniRepl client:visible tune={`sound("bd*2 cp").bank("RolandTR707").cpm(81/2)`} punchcard />
<MiniRepl
client:visible
tune={`setcpm(81/2)
sound("bd*2 cp").bank("RolandTR707")`}
punchcard
/>
**Yellow Magic Orchestra - Firecracker**
<MiniRepl
client:visible
tune={`sound("bd sd, - - - hh - hh - -, - perc - perc:1*2")
.bank("RolandCompurhythm1000").cpm(120/2)`}
tune={`setcpm(120/2)
sound("bd sd, - - - hh - hh - -, - perc - perc:1*2")
.bank("RolandCompurhythm1000")`}
punchcard
/>
@@ -329,12 +340,14 @@ We Will Rock you
<MiniRepl
client:visible
tune={`sound(\`
tune={`
setcpm(90/4)
sound(\`
[- - oh - ] [- - - - ] [- - - - ] [- - - - ],
[hh hh - - ] [hh - hh - ] [hh - hh - ] [hh - hh - ],
[- - - - ] [cp - - - ] [- - - - ] [cp - - - ],
[bd - - - ] [- - - bd] [- - bd - ] [- - - bd]
\`).cpm(90/4)`}
\`)`}
punchcard
/>
@@ -342,12 +355,13 @@ We Will Rock you
<MiniRepl
client:visible
tune={`sound(\`
tune={`setcpm(88/4)
sound(\`
[- - - - ] [- - - - ] [- - - - ] [- - oh:1 - ],
[hh hh hh hh] [hh hh hh hh] [hh hh hh hh] [hh hh - - ],
[- - - - ] [cp - - - ] [- - - - ] [~ cp - - ],
[bd bd - - ] [- - bd - ] [bd bd - bd ] [- - - - ]
\`).bank("RolandTR808").cpm(88/4)`}
\`).bank("RolandTR808")`}
punchcard
/>
@@ -355,11 +369,11 @@ We Will Rock you
<MiniRepl
client:visible
tune={`s(\`jazz*2,
tune={`setcpm(100/2)
s(\`jazz*2,
insect [crow metal] - -,
- space:4 - space:1,
- wind\`)
.cpm(100/2)`}
- wind\`)`}
punchcard
/>
@@ -68,9 +68,10 @@ Try commenting out one or more by adding `//` before a line
<MiniRepl
client:visible
tune={`note("c2 [eb3,g3] ".add("<0 <1 -1>>"))
tune={`setcpm(60)
note("c2 [eb3,g3] ".add("<0 <1 -1>>"))
.color("<cyan <magenta yellow>>").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:
<MiniRepl
client:visible
tune={`note("c2 [eb3,g3]".add("<0 <1 -1>>").add("0,7"))
tune={`setcpm(60)
note("c2 [eb3,g3]".add("<0 <1 -1>>").add("0,7"))
.color("<cyan <magenta yellow>>").adsr("[.1 0]:.2:[1 0]")
.sound("gm_acoustic_bass").room(.5).cpm(60)`}
.sound("gm_acoustic_bass").room(.5)`}
punchcard
/>
+10 -10
View File
@@ -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 | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").cpm(45)`} /> |
| fast | speed up | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").fast(2)`} /> |
| slow | slow down | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").slow(2)`} /> |
| rev | reverse | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | split left/right, modify right | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | add numbers / notes | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | speed up each event n times | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | copy, shift time & modify | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
| name | description | example |
| ------ | ----------------------------------- | ----------------------------------------------------------------------------------- |
| setcpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`setcpm(45); sound("bd sd [~ bd] sd")`} /> |
| fast | speed up | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").fast(2)`} /> |
| slow | slow down | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").slow(2)`} /> |
| rev | reverse | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | split left/right, modify right | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | add numbers / notes | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | speed up each event n times | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | copy, shift time & modify | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
@@ -70,7 +70,7 @@ export function Reference() {
<ul>
{entry.params?.map(({ name, type, description }, i) => (
<li key={i}>
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
{name} : {type?.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
</li>
))}
</ul>
@@ -20,7 +20,7 @@ export function WelcomeTab({ context }) {
</p>
<p>
{/* To learn more about what this all means, check out the{' '} */}
To get started, check out the
To get started, check out the{' '}
<a href={`${baseNoTrailing}/workshop/getting-started/`} target="_blank">
interactive tutorial
</a>
@@ -42,8 +42,8 @@ export function WelcomeTab({ context }) {
GNU Affero General Public License
</a>
. You can find the source code at{' '}
<a href="https://github.com/tidalcycles/strudel" target="_blank">
github
<a href="https://codeberg.org/uzu/strudel" target="_blank">
codeberg
</a>
. You can also find <a href="https://github.com/felixroos/dough-samples/blob/main/README.md">licensing info</a>{' '}
for the default sound banks there. Please consider to{' '}
-20
View File
@@ -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/