diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f455741f5..a7f65b5bf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -150,6 +150,7 @@ Important: Always publish with `pnpm`, as `npm` does not support overriding main
## useful commands
+
```sh
#regenerate the test snapshots (ex: when updating or creating new pattern functions)
pnpm snapshot
@@ -160,6 +161,81 @@ pnpm run osc
#build the standalone version
pnpm tauri build
```
+
+## version tag patching
+
+here's a little guide on how to patch patterns in the database to prevent breaking old patterns due to breaking changes in newer versions.
+
+the general tactic is to use `// @version x.y` to tag a pattern with a specific strudel version. when a pattern is evaluated, this metadata will de-activate any breaking changes that came after the specified version.
+for example, in version 1.1, the default value for `fanchor` was changed from `0.5` to `0`.
+if play a pattern that was made before that change, sounds that use filter evenlopes can sound very different, so by adding `// @version 1.0` will make it sound like it used to.
+before releasing a new version with breaking changes, we can edit all patterns in the database, inserting the version tag they were created under:
+
+as an example, to release version 1.2, do the following:
+
+1. get date range
+
+```sh
+# get date of last version:
+git log -1 --format=%aI @strudel/core@1.1.0
+# 2024-05-31T23:07:26+02:00
+
+# get date of current version:
+git log -1 --format=%aI @strudel/core@1.2.0
+# 2025-05-01T12:39:24+02:00
+# might also use todays timestamp if version is not yet released
+```
+
+now we know, all patterns between these 2 dates have to receive a version tag (unless they already have one).
+
+2. get patterns in question
+
+```sql
+SELECT *
+FROM code_v1
+WHERE code NOT LIKE '%@version%'
+AND created_at > '2024-05-31T23:07:26+02:00'
+AND created_at < '2025-05-01T12:39:24+02:00'
+ORDER BY created_at ASC;
+```
+
+this gives us all unversioned patterns that were saved between 1.1.0 and 1.2.0. in this case, it's 9373 patterns!
+
+3. insert version tags
+
+we are now ready to insert the version tag to these patterns.
+before updating thousands of patterns, it's probably a good idea to test if a single one gets udpated:
+
+```sql
+UPDATE code_v1
+SET code = code || E'\n// @version 1.1'
+WHERE hash = 'Ns2sMB40yIw4';
+```
+
+after [verifying](https://strudel.cc/?Ns2sMB40yIw4) that the version tag has been added, let's insert it everywhere:
+
+```sql
+UPDATE code_v1
+SET code = code || E'\n// @version 1.1'
+WHERE code NOT LIKE '%@version%'
+AND created_at > '2024-05-31T23:07:26+02:00'
+AND created_at < '2025-05-01T12:39:24+02:00'
+```
+
+4. verify
+
+we can verify that the edits worked by querying all patterns that contain the new version tag:
+
+```sql
+SELECT *
+FROM code_v1
+WHERE code LIKE '%@version 1.1%'
+AND created_at > '2024-05-31T23:07:26+02:00'
+AND created_at < '2025-05-01T12:39:24+02:00'
+ORDER BY created_at ASC;
+```
+
+
## Have Fun
Remember to have fun, and that this project is driven by the passion of volunteers!
diff --git a/README.md b/README.md
index d54302ac6..4aa90fd38 100644
--- a/README.md
+++ b/README.md
@@ -38,13 +38,7 @@ Licensing info for the default sound banks can be found over on the [dough-sampl
## Contributing
-There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
-
-
-
-
-
-Made with [contrib.rocks](https://contrib.rocks).
+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).
## Community
diff --git a/jsdoc/jsdoc-synonyms.js b/jsdoc/jsdoc-synonyms.js
index 0b52420bc..d59c8dac4 100644
--- a/jsdoc/jsdoc-synonyms.js
+++ b/jsdoc/jsdoc-synonyms.js
@@ -1,5 +1,5 @@
/*
-jsdoc-synonyms.js - Add support for @synonym tag
+jsdoc-synonyms.js - Add support for @synonyms tag
Copyright (C) 2023 Strudel contributors - see
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
*/
diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs
index 193d96b55..f47f6eba5 100644
--- a/packages/codemirror/codemirror.mjs
+++ b/packages/codemirror/codemirror.mjs
@@ -1,7 +1,7 @@
import { closeBrackets } from '@codemirror/autocomplete';
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
// import { search, highlightSelectionMatches } from '@codemirror/search';
-import { history } from '@codemirror/commands';
+import { history, indentWithTab } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { defaultHighlightStyle, syntaxHighlighting, bracketMatching } from '@codemirror/language';
import { Compartment, EditorState, Prec } from '@codemirror/state';
@@ -37,6 +37,14 @@ const extensions = {
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
isFlashEnabled,
keybindings,
+ isTabIndentationEnabled: (on) => (on ? keymap.of([indentWithTab]) : []),
+ isMultiCursorEnabled: (on) =>
+ on
+ ? [
+ EditorState.allowMultipleSelections.of(true),
+ EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey),
+ ]
+ : [],
};
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
@@ -51,6 +59,8 @@ export const defaultSettings = {
isFlashEnabled: true,
isTooltipEnabled: false,
isLineWrappingEnabled: false,
+ isTabIndentationEnabled: false,
+ isMultiCursorEnabled: false,
theme: 'strudelTheme',
fontFamily: 'monospace',
fontSize: 18,
diff --git a/packages/codemirror/keybindings.mjs b/packages/codemirror/keybindings.mjs
index 6fe00eda1..1201959a2 100644
--- a/packages/codemirror/keybindings.mjs
+++ b/packages/codemirror/keybindings.mjs
@@ -21,11 +21,11 @@ const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
const keymaps = {
vim,
emacs,
+ codemirror: () => keymap.of(defaultKeymap),
vscode: vscodeExtension,
};
export function keybindings(name) {
const active = keymaps[name];
- return [keymap.of(defaultKeymap), keymap.of(historyKeymap), active ? active() : []];
- // keymap.of(searchKeymap),
+ return [active ? active() : [], keymap.of(historyKeymap)];
}
diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs
index 3a7faf081..326343e10 100644
--- a/packages/core/controls.mjs
+++ b/packages/core/controls.mjs
@@ -444,6 +444,78 @@ export const { crush } = registerControl('crush');
*/
export const { coarse } = registerControl('coarse');
+/**
+ * modulate the amplitude of a sound with a continuous waveform
+ *
+ * @name tremolo
+ * @synonyms trem
+ * @param {number | Pattern} speed modulation speed in HZ
+ * @example
+ * note("d d d# d".fast(4)).s("supersaw").tremolo("<3 2 100> ").tremoloskew("<.5>")
+ *
+ */
+export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremoloskew', 'tremolophase'], 'trem');
+
+/**
+ * modulate the amplitude of a sound with a continuous waveform
+ *
+ * @name tremolosync
+ * @synonyms tremsync
+ * @param {number | Pattern} cycles modulation speed in cycles
+ * @example
+ * note("d d d# d".fast(4)).s("supersaw").tremolosync("4").tremoloskew("<1 .5 0>")
+ *
+ */
+export const { tremolosync } = registerControl(
+ ['tremolosync', 'tremolodepth', 'tremoloskew', 'tremolophase'],
+ 'tremsync',
+);
+
+/**
+ * depth of amplitude modulation
+ *
+ * @name tremolodepth
+ * @synonyms tremdepth
+ * @param {number | Pattern} depth
+ * @example
+ * note("a1 a1 a#1 a1".fast(4)).s("pulse").tremsync(4).tremolodepth("<1 2 .7>")
+ *
+ */
+export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth');
+/**
+ * alter the shape of the modulation waveform
+ *
+ * @name tremoloskew
+ * @synonyms tremskew
+ * @param {number | Pattern} amount between 0 & 1, the shape of the waveform
+ * @example
+ * note("{f a c e}%16").s("sawtooth").tremsync(4).tremoloskew("<.5 0 1>")
+ *
+ */
+export const { tremoloskew } = registerControl('tremoloskew', 'tremskew');
+
+/**
+ * alter the phase of the modulation waveform
+ *
+ * @name tremolophase
+ * @synonyms tremphase
+ * @param {number | Pattern} offset the offset in cycles of the modulation
+ * @example
+ * note("{f a c e}%16").s("sawtooth").tremsync(4).tremolophase("<0 .25 .66>")
+ *
+ */
+export const { tremolophase } = registerControl('tremolophase', 'tremphase');
+
+/**
+ * shape of amplitude modulation
+ *
+ * @name tremoloshape
+ * @param {number | Pattern} shape tri | square | sine | saw | ramp
+ * @example
+ * note("{f g c d}%16").tremsync(4).tremoloshape("").s("sawtooth")
+ *
+ */
+export const { tremoloshape } = registerControl('tremoloshape', 'tremshape');
/**
* filter overdrive for supported filter types
*
@@ -453,6 +525,30 @@ export const { coarse } = registerControl('coarse');
* note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>")
*
*/
+
+// TODO: SUPRADOUGH implement post orbit "pump" sidechain effect
+// /**
+// * modulate the amplitude of an orbit to create a "sidechain" like effect
+// *
+// * @name pump
+// * @param {number | Pattern} speed modulation speed in cycles
+// * @example
+// * note("{f g c d}%16").s("sawtooth").pump(".25:.75")
+// *
+// */
+// export const { pump } = registerControl(['pump', 'pumpdepth']);
+
+// /**
+// * modulate the amplitude of an orbit to create a "sidechain" like effect
+// *
+// * @name pumpdepth
+// * @param {number | Pattern} depth depth of modulation from 0 to 1
+// * @example
+// * note("{f g c d}%16").s("sawtooth").pump(".25").depth("<.25 .5 .75 1>")
+// *
+// */
+// export const { pumpdepth } = registerControl('pumpdepth');
+
export const { drive } = registerControl('drive');
/**
@@ -985,15 +1081,31 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback',
*
*/
export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt');
-/* // TODO: test
+
+/**
+ * Sets the time of the delay effect in cycles.
+ *
+ * @name delaysync
+ * @param {number | Pattern} cycles delay length in cycles
+ * @synonyms delayt, dt
+ * @example
+ * s("bd bd").delay(.25).delaysync("<1 2 3 5>".div(8))
+ *
+ */
+export const { delaysync } = registerControl('delaysync');
+
+/**
* Specifies whether delaytime is calculated relative to cps.
*
* @name lock
* @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle.
+ * @superdirtOnly
* @example
* s("sd").delay().lock(1).osc()
*
+ *
*/
+
export const { lock } = registerControl('lock');
/**
* Set detune for stacked voices of supported oscillators
@@ -1545,18 +1657,6 @@ export const { density } = registerControl('density');
// ['modwheel'],
export const { expression } = registerControl('expression');
export const { sustainpedal } = registerControl('sustainpedal');
-/* // TODO: doesn't seem to do anything
- *
- * Tremolo Audio DSP effect
- *
- * @name tremolodepth
- * @param {number | Pattern} depth between 0 and 1
- * @example
- * n("0,4,7").tremolodepth("<0 .3 .6 .9>").osc()
- *
- */
-export const { tremolodepth, tremdp } = registerControl('tremolodepth', 'tremdp');
-export const { tremolorate, tremr } = registerControl('tremolorate', 'tremr');
export const { fshift } = registerControl('fshift');
export const { fshiftnote } = registerControl('fshiftnote');
@@ -1633,7 +1733,6 @@ export const { zmod } = registerControl('zmod');
// like crush but scaled differently
export const { zcrush } = registerControl('zcrush');
export const { zdelay } = registerControl('zdelay');
-export const { tremolo } = registerControl('tremolo');
export const { zzfx } = registerControl('zzfx');
/**
diff --git a/packages/core/cyclist.mjs b/packages/core/cyclist.mjs
index f28dc604c..ad0961032 100644
--- a/packages/core/cyclist.mjs
+++ b/packages/core/cyclist.mjs
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import createClock from './zyklus.mjs';
-import { logger } from './logger.mjs';
+import { errorLogger, logger } from './logger.mjs';
export class Cyclist {
constructor({
@@ -67,6 +67,7 @@ export class Cyclist {
// the following line is dumb and only here for backwards compatibility
// see https://codeberg.org/uzu/strudel/pulls/1004
const deadline = targetTime - phase;
+ // this onTrigger has another signature
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
this.cps = hap.value.cps;
@@ -75,7 +76,7 @@ export class Cyclist {
}
});
} catch (e) {
- logger(`[cyclist] error: ${e.message}`);
+ errorLogger(e);
onError?.(e);
}
},
diff --git a/packages/core/logger.mjs b/packages/core/logger.mjs
index e13bf86ca..635505c59 100644
--- a/packages/core/logger.mjs
+++ b/packages/core/logger.mjs
@@ -4,6 +4,12 @@ let debounce = 1000,
lastMessage,
lastTime;
+export function errorLogger(e, origin = 'cyclist') {
+ //TODO: add some kind of debug flag that enables this while in dev mode
+ // console.error(e)
+ logger(`[${origin}] error: ${e.message}`);
+}
+
export function logger(message, type, data = {}) {
let t = performance.now();
if (lastMessage === message && t - lastTime < debounce) {
diff --git a/packages/core/neocyclist.mjs b/packages/core/neocyclist.mjs
index 5c175dc1f..261f08acb 100644
--- a/packages/core/neocyclist.mjs
+++ b/packages/core/neocyclist.mjs
@@ -11,7 +11,6 @@ export class NeoCyclist {
constructor({ onTrigger, onToggle, getTime }) {
this.started = false;
this.cps = 0.5;
- this.lastTick = 0; // absolute time when last tick (clock callback) happened
this.getTime = getTime; // get absolute time
this.time_at_last_tick_message = 0;
// the clock of the worker and the audio context clock can drift apart over time
diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs
index 8bb3190c4..d59a1285d 100644
--- a/packages/core/pattern.mjs
+++ b/packages/core/pattern.mjs
@@ -869,6 +869,31 @@ export class Pattern {
console.log(drawLine(this));
return this;
}
+
+ //////////////////////////////////////////////////////////////////////
+ // methods relating to breaking patterns into subcycles
+
+ // Breaks a pattern into a pattern of patterns, according to the structure of the given binary pattern.
+ unjoin(pieces, func = id) {
+ return pieces.withHap((hap) =>
+ hap.withValue((v) => (v ? func(this.ribbon(hap.whole.begin, hap.whole.duration)) : this)),
+ );
+ }
+
+ /**
+ * Breaks a pattern into pieces according to the structure of a given pattern.
+ * True values in the given pattern cause the corresponding subcycle of the
+ * source pattern to be looped, and for an (optional) given function to be
+ * applied. False values result in the corresponding part of the source pattern
+ * to be played unchanged.
+ * @name into
+ * @memberof Pattern
+ * @example
+ * sound("bd sd ht lt").into("1 0", hurry(2))
+ */
+ into(pieces, func) {
+ return this.unjoin(pieces, func).innerJoin();
+ }
}
//////////////////////////////////////////////////////////////////////
@@ -2494,6 +2519,37 @@ export const { fastchunk, fastChunk } = register(
true,
);
+/**
+ * Like `chunk`, but the function is applied to a looped subcycle of the source pattern.
+ * @name chunkInto
+ * @synonyms chunkinto
+ * @memberof Pattern
+ * @example
+ * sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2))
+ * .bank("tr909")
+ */
+export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], function (n, func, pat) {
+ return pat.into(fastcat(true, ...Array(n - 1).fill(false))._iterback(n), func);
+});
+
+/**
+ * Like `chunkInto`, but moves backwards through the chunks.
+ * @name chunkBackInto
+ * @synonyms chunkbackinto
+ * @memberof Pattern
+ * @example
+ * sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2))
+ * .bank("tr909")
+ */
+export const { chunkbackinto, chunkBackInto } = register(['chunkbackinto', 'chunkBackInto'], function (n, func, pat) {
+ return pat.into(
+ fastcat(true, ...Array(n - 1).fill(false))
+ ._iter(n)
+ ._early(1),
+ func,
+ );
+});
+
// TODO - redefine elsewhere in terms of mask
export const bypass = register(
'bypass',
@@ -2509,7 +2565,7 @@ export const bypass = register(
* Loops the pattern inside an `offset` for `cycles`.
* If you think of the entire span of time in cycles as a ribbon, you can cut a single piece and loop it.
* @name ribbon
- * @synonym rib
+ * @synonyms rib
* @param {number} offset start point of loop in cycles
* @param {number} cycles loop length in cycles
* @example
@@ -3207,7 +3263,7 @@ export const slice = register(
* s("bd!8").onTriggerTime((hap) => {console.info(hap)})
*/
Pattern.prototype.onTriggerTime = function (func) {
- return this.onTrigger((t_deprecate, hap, currentTime, cps = 1, targetTime) => {
+ return this.onTrigger((hap, currentTime, _cps, targetTime) => {
const diff = targetTime - currentTime;
window.setTimeout(() => {
func(hap);
diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs
index dcc5fcba4..47231af4d 100644
--- a/packages/core/repl.mjs
+++ b/packages/core/repl.mjs
@@ -1,7 +1,7 @@
import { NeoCyclist } from './neocyclist.mjs';
import { Cyclist } from './cyclist.mjs';
import { evaluate as _evaluate } from './evaluate.mjs';
-import { logger } from './logger.mjs';
+import { errorLogger, logger } from './logger.mjs';
import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs';
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
@@ -245,6 +245,7 @@ export function repl({
export const getTrigger =
({ getTime, defaultOutput }) =>
async (hap, deadline, duration, cps, t) => {
+ // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger)
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
try {
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
@@ -252,9 +253,9 @@ export const getTrigger =
}
if (hap.context.onTrigger) {
// call signature of output / onTrigger is different...
- await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps, t);
+ await hap.context.onTrigger(hap, getTime(), cps, t);
}
} catch (err) {
- logger(`[cyclist] error: ${err.message}`, 'error');
+ errorLogger(err, 'getTrigger');
}
};
diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs
index c933e7fe2..6bd6fde67 100644
--- a/packages/core/signal.mjs
+++ b/packages/core/signal.mjs
@@ -264,7 +264,7 @@ export const randrun = (n) => {
const rands = timeToRands(t.floor().add(0.5), n);
const nums = rands
.map((n, i) => [n, i])
- .sort((a, b) => a[0] > b[0] - a[0] < b[0])
+ .sort((a, b) => (a[0] > b[0]) - (a[0] < b[0]))
.map((x) => x[1]);
const i = t.cyclePos().mul(n).floor() % n;
return nums[i];
diff --git a/packages/core/speak.mjs b/packages/core/speak.mjs
index 7e548a73b..e0f60184e 100644
--- a/packages/core/speak.mjs
+++ b/packages/core/speak.mjs
@@ -32,7 +32,7 @@ function triggerSpeech(words, lang, voice) {
}
export const speak = register('speak', function (lang, voice, pat) {
- return pat.onTrigger((_, hap) => {
+ return pat.onTrigger((hap) => {
triggerSpeech(hap.value, lang, voice);
});
});
diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs
index 874d45132..93c4168c9 100644
--- a/packages/core/test/pattern.test.mjs
+++ b/packages/core/test/pattern.test.mjs
@@ -1271,4 +1271,39 @@ describe('Pattern', () => {
);
});
});
+ describe('unjoin', () => {
+ it('destructures a pattern into subcycles', () => {
+ sameFirst(
+ fastcat('a', 'b', 'c', 'd')
+ .unjoin(fastcat(true, fastcat(true, true)))
+ .fmap(fast(2))
+ .join(),
+ fastcat('a', 'b', 'a', 'b', 'c', 'c', 'd', 'd'),
+ );
+ });
+ });
+ describe('into', () => {
+ it('applies a function to subcycles of a pattern', () => {
+ sameFirst(
+ fastcat('a', 'b', 'c', 'd').into(fastcat(fastcat('true', 'true'), 'true'), fast(2)),
+ fastcat('a', 'a', 'b', 'b', 'c', 'd', 'c', 'd'),
+ );
+ });
+ });
+ describe('chunkinto', () => {
+ it('chunks into subcycles', () => {
+ sameFirst(
+ fastcat('a', 'b', 'c').chunkInto(3, fast(2)).fast(3),
+ fastcat(fastcat('a', 'a'), 'b', 'c', 'a', fastcat('b', 'b'), 'c', 'a', 'b', fastcat('c', 'c')),
+ );
+ });
+ });
+ describe('chunkbackinto', () => {
+ it('chunks into subcycles backwards', () => {
+ sameFirst(
+ fastcat('a', 'b', 'c').chunkBackInto(3, fast(2)).fast(3),
+ fastcat('a', 'b', fastcat('c', 'c'), 'a', fastcat('b', 'b'), 'c', fastcat('a', 'a'), 'b', 'c'),
+ );
+ });
+ });
});
diff --git a/packages/csound/index.mjs b/packages/csound/index.mjs
index 1eeaf6fa5..e44765227 100644
--- a/packages/csound/index.mjs
+++ b/packages/csound/index.mjs
@@ -23,7 +23,7 @@ export const csound = register('csound', (instrument, pat) => {
instrument = instrument || 'triangle';
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
// TODO: find a alternative way to wait for csound to load (to wait with first time playback)
- return pat.onTrigger((time_deprecate, hap, currentTime, _cps, targetTime) => {
+ return pat.onTrigger((hap, currentTime, _cps, targetTime) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
@@ -142,7 +142,7 @@ export const csoundm = register('csoundm', (instrument, pat) => {
p1 = `"${instrument}"`;
}
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
- return pat.onTrigger((tidal_time, hap) => {
+ return pat.onTrigger((hap, currentTime, _cps, targetTime) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
@@ -151,7 +151,7 @@ export const csoundm = register('csoundm', (instrument, pat) => {
throw new Error('csound only support objects as hap values');
}
// Time in seconds counting from now.
- const p2 = tidal_time - getAudioContext().currentTime;
+ const p2 = targetTime - currentTime;
const p3 = hap.duration.valueOf() + 0;
const frequency = getFrequency(hap);
let { gain = 1, velocity = 0.9 } = hap.value;
diff --git a/packages/desktopbridge/midibridge.mjs b/packages/desktopbridge/midibridge.mjs
index 471c826ea..2436d8ad7 100644
--- a/packages/desktopbridge/midibridge.mjs
+++ b/packages/desktopbridge/midibridge.mjs
@@ -6,7 +6,7 @@ const OFF_MESSAGE = 0x80;
const CC_MESSAGE = 0xb0;
Pattern.prototype.midi = function (output) {
- return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
+ return this.onTrigger((hap, currentTime, cps, targetTime) => {
let { note, nrpnn, nrpv, ccn, ccv, velocity = 0.9, gain = 1 } = hap.value;
//magic number to get audio engine to line up, can probably be calculated somehow
const latencyMs = 34;
diff --git a/packages/desktopbridge/oscbridge.mjs b/packages/desktopbridge/oscbridge.mjs
index 9bead6d19..2568c78e5 100644
--- a/packages/desktopbridge/oscbridge.mjs
+++ b/packages/desktopbridge/oscbridge.mjs
@@ -4,7 +4,7 @@ import { Invoke } from './utils.mjs';
const collator = new ClockCollator({});
-export async function oscTriggerTauri(t_deprecate, hap, currentTime, cps = 1, targetTime) {
+export async function oscTriggerTauri(hap, currentTime, cps = 1, targetTime) {
const controls = parseControlsFromHap(hap, cps);
const params = [];
const timestamp = collator.calculateTimestamp(currentTime, targetTime);
diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs
index af2dd3a62..d0e092938 100644
--- a/packages/midi/midi.mjs
+++ b/packages/midi/midi.mjs
@@ -333,7 +333,7 @@ Pattern.prototype.midi = function (midiport, options = {}) {
logger(`Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`),
});
- return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
+ return this.onTrigger((hap, currentTime, cps, targetTime) => {
if (!WebMidi.enabled) {
logger('Midi not enabled');
return;
diff --git a/packages/mondough/mondough.mjs b/packages/mondough/mondough.mjs
index 6e8278939..15feb5d86 100644
--- a/packages/mondough/mondough.mjs
+++ b/packages/mondough/mondough.mjs
@@ -42,6 +42,7 @@ lib['%'] = pace;
lib['?'] = degradeBy; // todo: default 0.5 not working..
lib[':'] = tail;
lib['..'] = range;
+lib['def'] = () => silence;
lib['or'] = (...children) => chooseIn(...children); // always has structure but is cyclewise.. e.g. "s oh*8.dec[.04 | .5]"
//lib['or'] = (...children) => chooseOut(...children); // "s oh*8.dec[.04 | .5]" is better but "dec[.04 | .5].s oh*8" has no struct
diff --git a/packages/osc/osc.mjs b/packages/osc/osc.mjs
index ac70b9e73..8262e5569 100644
--- a/packages/osc/osc.mjs
+++ b/packages/osc/osc.mjs
@@ -60,7 +60,7 @@ export function parseControlsFromHap(hap, cps) {
const collator = new ClockCollator({});
-export async function oscTrigger(t_deprecate, hap, currentTime, cps = 1, targetTime) {
+export async function oscTrigger(hap, currentTime, cps = 1, targetTime) {
const osc = await connect();
const controls = parseControlsFromHap(hap, cps);
const keyvals = Object.entries(controls).flat();
diff --git a/packages/soundfonts/gm.mjs b/packages/soundfonts/gm.mjs
index c0e57dcb0..d8b7687d7 100644
--- a/packages/soundfonts/gm.mjs
+++ b/packages/soundfonts/gm.mjs
@@ -537,7 +537,7 @@ export default {
],
gm_synth_bass_1: [
// Synth Bass 1: Bass
- '0380_Aspirin_sf2_file',
+ // '0380_Aspirin_sf2_file', // broken in safari https://codeberg.org/uzu/strudel/issues/1384
'0380_Chaos_sf2_file',
'0380_FluidR3_GM_sf2_file',
// 0380_GeneralUserGS_sf2_file // laut
diff --git a/packages/soundfonts/sfumato.mjs b/packages/soundfonts/sfumato.mjs
index 6d60f80dd..4d5e7ef45 100644
--- a/packages/soundfonts/sfumato.mjs
+++ b/packages/soundfonts/sfumato.mjs
@@ -3,7 +3,7 @@ import { getAudioContext, registerSound } from '@strudel/webaudio';
import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato';
Pattern.prototype.soundfont = function (sf, n = 0) {
- return this.onTrigger((time_deprecate, h, ct, cps, targetTime) => {
+ return this.onTrigger((h, ct, cps, targetTime) => {
const ctx = getAudioContext();
const note = getPlayableNoteValue(h);
const preset = sf.presets[n % sf.presets.length];
diff --git a/packages/superdough/dspworklet.mjs b/packages/superdough/dspworklet.mjs
index ed5c1e7e0..5b02ad298 100644
--- a/packages/superdough/dspworklet.mjs
+++ b/packages/superdough/dspworklet.mjs
@@ -74,6 +74,6 @@ export const dough = async (code) => {
worklet.node.connect(ac.destination);
};
-export function doughTrigger(time_deprecate, hap, currentTime, cps, targetTime) {
+export function doughTrigger(hap, currentTime, cps, targetTime) {
window.postMessage({ time: targetTime, dough: hap.value, currentTime, duration: hap.duration, cps });
}
diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs
index 90bb5e701..7d90eb15d 100644
--- a/packages/superdough/superdough.mjs
+++ b/packages/superdough/superdough.mjs
@@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
import './feedbackdelay.mjs';
import './reverb.mjs';
import './vowel.mjs';
-import { clamp, nanFallback, _mod } from './util.mjs';
+import { clamp, nanFallback, _mod, cycleToSeconds, secondsToCycle } from './util.mjs';
import workletsUrl from './worklets.mjs?audioworklet';
import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs';
import { map } from 'nanostores';
@@ -28,6 +28,13 @@ export function setMultiChannelOrbits(bool) {
multiChannelOrbits = bool == true;
}
+function getModulationShapeInput(val) {
+ if (typeof val === 'number') {
+ return val % 5;
+ }
+ return { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[val] ?? 0;
+}
+
export const soundMap = map();
export function registerSound(key, onTrigger, data = {}) {
@@ -126,7 +133,7 @@ export const getAudioDevices = async () => {
return devicesMap;
};
-const defaultDefaultValues = {
+let defaultDefaultValues = {
s: 'triangle',
gain: 0.8,
postgain: 1,
@@ -143,13 +150,24 @@ const defaultDefaultValues = {
delay: 0,
byteBeatExpression: '0',
delayfeedback: 0.5,
- delaytime: 0.25,
+ delaysync: 3 / 16,
orbit: 1,
i: 1,
velocity: 1,
fft: 8,
};
+const defaultDefaultDefaultValues = Object.freeze({ ...defaultDefaultValues });
+
+export function setDefault(control, value) {
+ // const main = getControlName(control); // we cant do this because superdough is independent of strudel/core
+ defaultDefaultValues[control] = value;
+}
+
+export function resetDefaults() {
+ defaultDefaultValues = { ...defaultDefaultDefaultValues };
+}
+
let defaultControls = new Map(Object.entries(defaultDefaultValues));
export function setDefaultValue(key, value) {
@@ -325,9 +343,33 @@ function getDelay(orbit, delaytime, delayfeedback, t, channels) {
return delays[orbit];
}
-export function getLfo(audioContext, time, end, properties = {}) {
- return getWorklet(audioContext, 'lfo-processor', {
+export function getLfo(audioContext, begin, end, properties = {}) {
+ const { shape = 0, ...props } = properties;
+ const { dcoffset = -0.5, depth = 1 } = properties;
+ const lfoprops = {
frequency: 1,
+ depth,
+ skew: 0.5,
+ phaseoffset: 0,
+ time: begin,
+ begin,
+ end,
+ shape: getModulationShapeInput(shape),
+ dcoffset,
+ min: dcoffset * depth,
+ max: dcoffset * depth + depth,
+ curve: 1,
+ ...props,
+ };
+
+ return getWorklet(audioContext, 'lfo-processor', lfoprops);
+}
+
+export function getSyncedLfo(audioContext, time, end, cps, cycle, properties = {}) {
+ const frequency = cycle / cps;
+
+ return getWorklet(audioContext, 'lfo-processor', {
+ frequency,
depth: 1,
skew: 0,
phaseoffset: 0,
@@ -450,9 +492,10 @@ function mapChannelNumbers(channels) {
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
}
-export const superdough = async (value, t, hapDuration, cps) => {
+export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => {
+ // new: t is always expected to be the absolute target onset time
const ac = getAudioContext();
- t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t;
+
let { stretch } = value;
if (stretch != null) {
//account for phase vocoder latency
@@ -478,6 +521,12 @@ export const superdough = async (value, t, hapDuration, cps) => {
}
// destructure
let {
+ tremolo,
+ tremolosync,
+ tremolodepth = 1,
+ tremoloskew,
+ tremolophase = 0,
+ tremoloshape,
s = getDefaultValue('s'),
bank,
source,
@@ -487,6 +536,7 @@ export const superdough = async (value, t, hapDuration, cps) => {
// filters
fanchor = getDefaultValue('fanchor'),
drive = 0.69,
+ release = 0,
// low pass
cutoff,
lpenv,
@@ -519,6 +569,7 @@ export const superdough = async (value, t, hapDuration, cps) => {
phasercenter,
//
coarse,
+
crush,
shape,
shapevol = getDefaultValue('shapevol'),
@@ -528,7 +579,8 @@ export const superdough = async (value, t, hapDuration, cps) => {
vowel,
delay = getDefaultValue('delay'),
delayfeedback = getDefaultValue('delayfeedback'),
- delaytime = getDefaultValue('delaytime'),
+ delaysync = getDefaultValue('delaysync'),
+ delaytime,
orbit = getDefaultValue('orbit'),
room,
roomfade,
@@ -547,6 +599,8 @@ export const superdough = async (value, t, hapDuration, cps) => {
compressorRelease,
} = value;
+ delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
+
const orbitChannels = mapChannelNumbers(
multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'),
);
@@ -558,8 +612,11 @@ export const superdough = async (value, t, hapDuration, cps) => {
distortvol = applyGainCurve(distortvol);
delay = applyGainCurve(delay);
velocity = applyGainCurve(velocity);
+ tremolodepth = applyGainCurve(tremolodepth);
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
+ const end = t + hapDuration;
+ const endWithRelease = end + release;
const chainID = Math.round(Math.random() * 1000000);
// oldest audio nodes will be destroyed if maximum polyphony is exceeded
@@ -634,7 +691,7 @@ export const superdough = async (value, t, hapDuration, cps) => {
lprelease,
lpenv,
t,
- t + hapDuration,
+ end,
fanchor,
ftype,
drive,
@@ -658,7 +715,7 @@ export const superdough = async (value, t, hapDuration, cps) => {
hprelease,
hpenv,
t,
- t + hapDuration,
+ end,
fanchor,
);
chain.push(hp());
@@ -669,20 +726,7 @@ export const superdough = async (value, t, hapDuration, cps) => {
if (bandf !== undefined) {
let bp = () =>
- createFilter(
- ac,
- 'bandpass',
- bandf,
- bandq,
- bpattack,
- bpdecay,
- bpsustain,
- bprelease,
- bpenv,
- t,
- t + hapDuration,
- fanchor,
- );
+ createFilter(ac, 'bandpass', bandf, bandq, bpattack, bpdecay, bpsustain, bprelease, bpenv, t, end, fanchor);
chain.push(bp());
if (ftype === '24db') {
chain.push(bp());
@@ -700,6 +744,33 @@ export const superdough = async (value, t, hapDuration, cps) => {
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol }));
distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol }));
+ if (tremolosync != null) {
+ tremolo = cps * tremolosync;
+ }
+
+ if (tremolo !== undefined) {
+ // Allow clipping of modulator for more dynamic possiblities, and to prevent speaker overload
+ // EX: a triangle waveform will clip like this /-\ when the depth is above 1
+ const gain = Math.max(1 - tremolodepth, 0);
+ const amGain = new GainNode(ac, { gain });
+
+ const time = cycle / cps;
+ const lfo = getLfo(ac, t, endWithRelease, {
+ skew: tremoloskew ?? (tremoloshape != null ? 0.5 : 1),
+ frequency: tremolo,
+ depth: tremolodepth,
+ time,
+ dcoffset: 0,
+ shape: tremoloshape,
+ phaseoffset: tremolophase,
+ min: 0,
+ max: 1,
+ curve: 1.5,
+ });
+ lfo.connect(amGain.gain);
+ chain.push(amGain);
+ }
+
compressorThreshold !== undefined &&
chain.push(
getCompressor(ac, compressorThreshold, compressorRatio, compressorKnee, compressorAttack, compressorRelease),
@@ -713,7 +784,7 @@ export const superdough = async (value, t, hapDuration, cps) => {
}
// phaser
if (phaser !== undefined && phaserdepth > 0) {
- const phaserFX = getPhaser(t, t + hapDuration, phaser, phaserdepth, phasercenter, phasersweep);
+ const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep);
chain.push(phaserFX);
}
@@ -725,8 +796,8 @@ export const superdough = async (value, t, hapDuration, cps) => {
// delay
let delaySend;
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
- const delyNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
- delaySend = effectSend(post, delyNode, delay);
+ const delayNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
+ delaySend = effectSend(post, delayNode, delay);
audioNodes.push(delaySend);
}
// reverb
diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs
index 13cfa13a5..88e14e5ab 100644
--- a/packages/superdough/synth.mjs
+++ b/packages/superdough/synth.mjs
@@ -121,7 +121,10 @@ export function registerSynthSounds() {
const gainAdjustment = 1 / Math.sqrt(voices);
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
- const fm = applyFM(o.parameters.get('frequency'), value, begin);
+ // const fm = applyFM(o.parameters.get('frequency'), value, begin);
+ // https://codeberg.org/uzu/strudel/issues/1428
+ // if you think about re-enabling this, please test with fm > 1 first
+ // it's like 10x gain, so it's really dangerous
let envGain = gainNode(1);
envGain = o.connect(envGain);
@@ -133,7 +136,7 @@ export function registerSynthSounds() {
destroyAudioWorkletNode(o);
envGain.disconnect();
onended();
- fm?.stop();
+ // fm?.stop();
vibratoOscillator?.stop();
},
begin,
diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs
index 4e3c7e41e..f4d59024e 100644
--- a/packages/superdough/util.mjs
+++ b/packages/superdough/util.mjs
@@ -68,3 +68,11 @@ export const _mod = (n, m) => ((n % m) + m) % m;
export const getSoundIndex = (n, numSounds) => {
return _mod(Math.round(nanFallback(n, 0)), numSounds);
};
+
+export function cycleToSeconds(cycle, cps) {
+ return cycle / cps;
+}
+
+export function secondsToCycle(t, cps) {
+ return t * cps;
+}
diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs
index 6f526ec8f..e7928a48f 100644
--- a/packages/superdough/worklets.mjs
+++ b/packages/superdough/worklets.mjs
@@ -31,7 +31,7 @@ function polyBlep(phase, dt) {
return 0;
}
}
-
+// The order is important for dough integration
const waveshapes = {
tri(phase, skew = 0.5) {
const x = 1 - skew;
@@ -81,10 +81,12 @@ function getParamValue(block, param) {
}
return param[0];
}
+
const waveShapeNames = Object.keys(waveshapes);
class LFOProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [
+ { name: 'begin', defaultValue: 0 },
{ name: 'time', defaultValue: 0 },
{ name: 'end', defaultValue: 0 },
{ name: 'frequency', defaultValue: 0.5 },
@@ -92,7 +94,10 @@ class LFOProcessor extends AudioWorkletProcessor {
{ name: 'depth', defaultValue: 1 },
{ name: 'phaseoffset', defaultValue: 0 },
{ name: 'shape', defaultValue: 0 },
+ { name: 'curve', defaultValue: 1 },
{ name: 'dcoffset', defaultValue: 0 },
+ { name: 'min', defaultValue: 0 },
+ { name: 'max', defaultValue: 1 },
];
}
@@ -109,10 +114,14 @@ class LFOProcessor extends AudioWorkletProcessor {
}
process(inputs, outputs, parameters) {
+ const begin = parameters['begin'][0];
// eslint-disable-next-line no-undef
if (currentTime >= parameters.end[0]) {
return false;
}
+ if (currentTime <= begin) {
+ return true;
+ }
const output = outputs[0];
const frequency = parameters['frequency'][0];
@@ -122,7 +131,11 @@ class LFOProcessor extends AudioWorkletProcessor {
const skew = parameters['skew'][0];
const phaseoffset = parameters['phaseoffset'][0];
+ const curve = parameters['curve'][0];
+
const dcoffset = parameters['dcoffset'][0];
+ const min = parameters['min'][0];
+ const max = parameters['max'][0];
const shape = waveShapeNames[parameters['shape'][0]];
const blockSize = output[0].length ?? 0;
@@ -134,8 +147,9 @@ class LFOProcessor extends AudioWorkletProcessor {
const dt = frequency / sampleRate;
for (let n = 0; n < blockSize; n++) {
for (let i = 0; i < output.length; i++) {
- const modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
- output[i][n] = modval;
+ let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
+ modval = Math.pow(modval, curve);
+ output[i][n] = clamp(modval, min, max);
}
this.incrementPhase(dt);
}
diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs
index f80f114fd..429d2a26b 100644
--- a/packages/webaudio/webaudio.mjs
+++ b/packages/webaudio/webaudio.mjs
@@ -15,13 +15,10 @@ const hap2value = (hap) => {
return hap.value;
};
-export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps);
-// uses more precise, absolute t if available, see https://codeberg.org/uzu/strudel/pulls/1004
-export const webaudioOutput = (hap, deadline, hapDuration, cps, t) =>
- superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration);
-
-Pattern.prototype.webaudio = function () {
- return this.onTrigger(webaudioOutputTrigger);
+// uses more precise, absolute t if available, see https://github.com/tidalcycles/strudel/pull/1004
+// TODO: refactor output callbacks to eliminate deadline
+export const webaudioOutput = (hap, _deadline, hapDuration, cps, t) => {
+ return superdough(hap2value(hap), t, hapDuration, cps, hap.whole?.begin.valueOf());
};
export function webaudioRepl(options = {}) {
diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap
index f3607cb48..e6bfa44aa 100644
--- a/test/__snapshots__/examples.test.mjs.snap
+++ b/test/__snapshots__/examples.test.mjs.snap
@@ -1889,6 +1889,86 @@ exports[`runs examples > example "chunkBack" example index 0 1`] = `
]
`;
+exports[`runs examples > example "chunkBackInto" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | s:bd speed:2 bank:tr909 ]",
+ "[ 1/16 → 1/8 | s:sd speed:2 bank:tr909 ]",
+ "[ 1/8 → 3/16 | s:bd speed:2 bank:tr909 ]",
+ "[ 3/16 → 1/4 | s:sd speed:2 bank:tr909 ]",
+ "[ 1/4 → 3/8 | s:ht bank:tr909 ]",
+ "[ 3/8 → 1/2 | s:lt bank:tr909 ]",
+ "[ 1/2 → 5/8 | s:bd bank:tr909 ]",
+ "[ 3/4 → 7/8 | s:cp bank:tr909 ]",
+ "[ 7/8 → 1/1 | s:lt bank:tr909 ]",
+ "[ 1/1 → 9/8 | s:bd bank:tr909 ]",
+ "[ 9/8 → 5/4 | s:sd bank:tr909 ]",
+ "[ 5/4 → 21/16 | s:ht speed:2 bank:tr909 ]",
+ "[ 21/16 → 11/8 | s:lt speed:2 bank:tr909 ]",
+ "[ 11/8 → 23/16 | s:ht speed:2 bank:tr909 ]",
+ "[ 23/16 → 3/2 | s:lt speed:2 bank:tr909 ]",
+ "[ 3/2 → 13/8 | s:bd bank:tr909 ]",
+ "[ 7/4 → 15/8 | s:cp bank:tr909 ]",
+ "[ 15/8 → 2/1 | s:lt bank:tr909 ]",
+ "[ 2/1 → 17/8 | s:bd bank:tr909 ]",
+ "[ 17/8 → 9/4 | s:sd bank:tr909 ]",
+ "[ 9/4 → 19/8 | s:ht bank:tr909 ]",
+ "[ 19/8 → 5/2 | s:lt bank:tr909 ]",
+ "[ 5/2 → 41/16 | s:bd speed:2 bank:tr909 ]",
+ "[ 21/8 → 43/16 | s:bd speed:2 bank:tr909 ]",
+ "[ 11/4 → 23/8 | s:cp bank:tr909 ]",
+ "[ 23/8 → 3/1 | s:lt bank:tr909 ]",
+ "[ 3/1 → 25/8 | s:bd bank:tr909 ]",
+ "[ 25/8 → 13/4 | s:sd bank:tr909 ]",
+ "[ 13/4 → 27/8 | s:ht bank:tr909 ]",
+ "[ 27/8 → 7/2 | s:lt bank:tr909 ]",
+ "[ 7/2 → 29/8 | s:bd bank:tr909 ]",
+ "[ 15/4 → 61/16 | s:cp speed:2 bank:tr909 ]",
+ "[ 61/16 → 31/8 | s:lt speed:2 bank:tr909 ]",
+ "[ 31/8 → 63/16 | s:cp speed:2 bank:tr909 ]",
+ "[ 63/16 → 4/1 | s:lt speed:2 bank:tr909 ]",
+]
+`;
+
+exports[`runs examples > example "chunkInto" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | s:bd speed:2 bank:tr909 ]",
+ "[ 1/16 → 1/8 | s:sd speed:2 bank:tr909 ]",
+ "[ 1/8 → 3/16 | s:bd speed:2 bank:tr909 ]",
+ "[ 3/16 → 1/4 | s:sd speed:2 bank:tr909 ]",
+ "[ 1/4 → 3/8 | s:ht bank:tr909 ]",
+ "[ 3/8 → 1/2 | s:lt bank:tr909 ]",
+ "[ 1/2 → 5/8 | s:bd bank:tr909 ]",
+ "[ 3/4 → 7/8 | s:cp bank:tr909 ]",
+ "[ 7/8 → 1/1 | s:lt bank:tr909 ]",
+ "[ 1/1 → 9/8 | s:bd bank:tr909 ]",
+ "[ 9/8 → 5/4 | s:sd bank:tr909 ]",
+ "[ 5/4 → 21/16 | s:ht speed:2 bank:tr909 ]",
+ "[ 21/16 → 11/8 | s:lt speed:2 bank:tr909 ]",
+ "[ 11/8 → 23/16 | s:ht speed:2 bank:tr909 ]",
+ "[ 23/16 → 3/2 | s:lt speed:2 bank:tr909 ]",
+ "[ 3/2 → 13/8 | s:bd bank:tr909 ]",
+ "[ 7/4 → 15/8 | s:cp bank:tr909 ]",
+ "[ 15/8 → 2/1 | s:lt bank:tr909 ]",
+ "[ 2/1 → 17/8 | s:bd bank:tr909 ]",
+ "[ 17/8 → 9/4 | s:sd bank:tr909 ]",
+ "[ 9/4 → 19/8 | s:ht bank:tr909 ]",
+ "[ 19/8 → 5/2 | s:lt bank:tr909 ]",
+ "[ 5/2 → 41/16 | s:bd speed:2 bank:tr909 ]",
+ "[ 21/8 → 43/16 | s:bd speed:2 bank:tr909 ]",
+ "[ 11/4 → 23/8 | s:cp bank:tr909 ]",
+ "[ 23/8 → 3/1 | s:lt bank:tr909 ]",
+ "[ 3/1 → 25/8 | s:bd bank:tr909 ]",
+ "[ 25/8 → 13/4 | s:sd bank:tr909 ]",
+ "[ 13/4 → 27/8 | s:ht bank:tr909 ]",
+ "[ 27/8 → 7/2 | s:lt bank:tr909 ]",
+ "[ 7/2 → 29/8 | s:bd bank:tr909 ]",
+ "[ 15/4 → 61/16 | s:cp speed:2 bank:tr909 ]",
+ "[ 61/16 → 31/8 | s:lt speed:2 bank:tr909 ]",
+ "[ 31/8 → 63/16 | s:cp speed:2 bank:tr909 ]",
+ "[ 63/16 → 4/1 | s:lt speed:2 bank:tr909 ]",
+]
+`;
+
exports[`runs examples > example "clip" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano clip:0.5 ]",
@@ -2505,6 +2585,19 @@ exports[`runs examples > example "delayfeedback" example index 0 1`] = `
]
`;
+exports[`runs examples > example "delaysync" example index 0 1`] = `
+[
+ "[ 0/1 → 1/2 | s:bd delay:0.25 delaysync:0.125 ]",
+ "[ 1/2 → 1/1 | s:bd delay:0.25 delaysync:0.125 ]",
+ "[ 1/1 → 3/2 | s:bd delay:0.25 delaysync:0.25 ]",
+ "[ 3/2 → 2/1 | s:bd delay:0.25 delaysync:0.25 ]",
+ "[ 2/1 → 5/2 | s:bd delay:0.25 delaysync:0.375 ]",
+ "[ 5/2 → 3/1 | s:bd delay:0.25 delaysync:0.375 ]",
+ "[ 3/1 → 7/2 | s:bd delay:0.25 delaysync:0.625 ]",
+ "[ 7/2 → 4/1 | s:bd delay:0.25 delaysync:0.625 ]",
+]
+`;
+
exports[`runs examples > example "delaytime" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:bd delay:0.25 delaytime:0.125 ]",
@@ -4391,6 +4484,35 @@ exports[`runs examples > example "inside" example index 0 1`] = `
]
`;
+exports[`runs examples > example "into" example index 0 1`] = `
+[
+ "[ 0/1 → 1/8 | s:bd speed:2 ]",
+ "[ 1/8 → 1/4 | s:sd speed:2 ]",
+ "[ 1/4 → 3/8 | s:bd speed:2 ]",
+ "[ 3/8 → 1/2 | s:sd speed:2 ]",
+ "[ 1/2 → 3/4 | s:ht ]",
+ "[ 3/4 → 1/1 | s:lt ]",
+ "[ 1/1 → 9/8 | s:bd speed:2 ]",
+ "[ 9/8 → 5/4 | s:sd speed:2 ]",
+ "[ 5/4 → 11/8 | s:bd speed:2 ]",
+ "[ 11/8 → 3/2 | s:sd speed:2 ]",
+ "[ 3/2 → 7/4 | s:ht ]",
+ "[ 7/4 → 2/1 | s:lt ]",
+ "[ 2/1 → 17/8 | s:bd speed:2 ]",
+ "[ 17/8 → 9/4 | s:sd speed:2 ]",
+ "[ 9/4 → 19/8 | s:bd speed:2 ]",
+ "[ 19/8 → 5/2 | s:sd speed:2 ]",
+ "[ 5/2 → 11/4 | s:ht ]",
+ "[ 11/4 → 3/1 | s:lt ]",
+ "[ 3/1 → 25/8 | s:bd speed:2 ]",
+ "[ 25/8 → 13/4 | s:sd speed:2 ]",
+ "[ 13/4 → 27/8 | s:bd speed:2 ]",
+ "[ 27/8 → 7/2 | s:sd speed:2 ]",
+ "[ 7/2 → 15/4 | s:ht ]",
+ "[ 15/4 → 4/1 | s:lt ]",
+]
+`;
+
exports[`runs examples > example "invert" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:bd ]",
@@ -5016,6 +5138,15 @@ exports[`runs examples > example "linger" example index 0 1`] = `
]
`;
+exports[`runs examples > example "lock" example index 0 1`] = `
+[
+ "[ 0/1 → 1/1 | delay:{s:sd} lock:1 ]",
+ "[ 1/1 → 2/1 | delay:{s:sd} lock:1 ]",
+ "[ 2/1 → 3/1 | delay:{s:sd} lock:1 ]",
+ "[ 3/1 → 4/1 | delay:{s:sd} lock:1 ]",
+]
+`;
+
exports[`runs examples > example "loop" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:casio loop:1 ]",
@@ -8682,46 +8813,46 @@ exports[`runs examples > example "shrink" example index 3 1`] = `
exports[`runs examples > example "shuffle" example index 0 1`] = `
[
- "[ 0/1 → 1/4 | note:c s:piano ]",
+ "[ 0/1 → 1/4 | note:e s:piano ]",
"[ 1/4 → 1/2 | note:d s:piano ]",
- "[ 1/2 → 3/4 | note:e s:piano ]",
- "[ 3/4 → 1/1 | note:f s:piano ]",
- "[ 1/1 → 5/4 | note:c s:piano ]",
- "[ 5/4 → 3/2 | note:d s:piano ]",
- "[ 3/2 → 7/4 | note:e s:piano ]",
- "[ 7/4 → 2/1 | note:f s:piano ]",
- "[ 2/1 → 9/4 | note:c s:piano ]",
- "[ 9/4 → 5/2 | note:d s:piano ]",
+ "[ 1/2 → 3/4 | note:f s:piano ]",
+ "[ 3/4 → 1/1 | note:c s:piano ]",
+ "[ 1/1 → 5/4 | note:e s:piano ]",
+ "[ 5/4 → 3/2 | note:c s:piano ]",
+ "[ 3/2 → 7/4 | note:f s:piano ]",
+ "[ 7/4 → 2/1 | note:d s:piano ]",
+ "[ 2/1 → 9/4 | note:d s:piano ]",
+ "[ 9/4 → 5/2 | note:c s:piano ]",
"[ 5/2 → 11/4 | note:e s:piano ]",
"[ 11/4 → 3/1 | note:f s:piano ]",
"[ 3/1 → 13/4 | note:c s:piano ]",
- "[ 13/4 → 7/2 | note:d s:piano ]",
- "[ 7/2 → 15/4 | note:e s:piano ]",
- "[ 15/4 → 4/1 | note:f s:piano ]",
+ "[ 13/4 → 7/2 | note:e s:piano ]",
+ "[ 7/2 → 15/4 | note:f s:piano ]",
+ "[ 15/4 → 4/1 | note:d s:piano ]",
]
`;
exports[`runs examples > example "shuffle" example index 1 1`] = `
[
- "[ 0/1 → 1/8 | note:c s:piano ]",
+ "[ 0/1 → 1/8 | note:e s:piano ]",
"[ 1/8 → 1/4 | note:d s:piano ]",
- "[ 1/4 → 3/8 | note:e s:piano ]",
- "[ 3/8 → 1/2 | note:f s:piano ]",
+ "[ 1/4 → 3/8 | note:f s:piano ]",
+ "[ 3/8 → 1/2 | note:c s:piano ]",
"[ 1/2 → 1/1 | note:g s:piano ]",
- "[ 1/1 → 9/8 | note:c s:piano ]",
- "[ 9/8 → 5/4 | note:d s:piano ]",
- "[ 5/4 → 11/8 | note:e s:piano ]",
- "[ 11/8 → 3/2 | note:f s:piano ]",
+ "[ 1/1 → 9/8 | note:e s:piano ]",
+ "[ 9/8 → 5/4 | note:c s:piano ]",
+ "[ 5/4 → 11/8 | note:f s:piano ]",
+ "[ 11/8 → 3/2 | note:d s:piano ]",
"[ 3/2 → 2/1 | note:g s:piano ]",
- "[ 2/1 → 17/8 | note:c s:piano ]",
- "[ 17/8 → 9/4 | note:d s:piano ]",
+ "[ 2/1 → 17/8 | note:d s:piano ]",
+ "[ 17/8 → 9/4 | note:c s:piano ]",
"[ 9/4 → 19/8 | note:e s:piano ]",
"[ 19/8 → 5/2 | note:f s:piano ]",
"[ 5/2 → 3/1 | note:g s:piano ]",
"[ 3/1 → 25/8 | note:c s:piano ]",
- "[ 25/8 → 13/4 | note:d s:piano ]",
- "[ 13/4 → 27/8 | note:e s:piano ]",
- "[ 27/8 → 7/2 | note:f s:piano ]",
+ "[ 25/8 → 13/4 | note:e s:piano ]",
+ "[ 13/4 → 27/8 | note:f s:piano ]",
+ "[ 27/8 → 7/2 | note:d s:piano ]",
"[ 7/2 → 4/1 | note:g s:piano ]",
]
`;
@@ -9979,6 +10110,420 @@ exports[`runs examples > example "transpose" example index 1 1`] = `
]
`;
+exports[`runs examples > example "tremolo" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 1/16 → 1/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 1/8 → 3/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 3/16 → 1/4 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 1/4 → 5/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 5/16 → 3/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 3/8 → 7/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 7/16 → 1/2 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 1/2 → 9/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 9/16 → 5/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 5/8 → 11/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 11/16 → 3/4 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 3/4 → 13/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 13/16 → 7/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 7/8 → 15/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 15/16 → 1/1 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 1/1 → 17/16 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 17/16 → 9/8 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 9/8 → 19/16 | note:d# s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 19/16 → 5/4 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 5/4 → 21/16 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 21/16 → 11/8 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 11/8 → 23/16 | note:d# s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 23/16 → 3/2 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 3/2 → 25/16 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 25/16 → 13/8 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 13/8 → 27/16 | note:d# s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 27/16 → 7/4 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 7/4 → 29/16 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 29/16 → 15/8 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 15/8 → 31/16 | note:d# s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 31/16 → 2/1 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
+ "[ 2/1 → 33/16 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 33/16 → 17/8 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 17/8 → 35/16 | note:d# s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 35/16 → 9/4 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 9/4 → 37/16 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 37/16 → 19/8 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 19/8 → 39/16 | note:d# s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 39/16 → 5/2 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 5/2 → 41/16 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 41/16 → 21/8 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 21/8 → 43/16 | note:d# s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 43/16 → 11/4 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 11/4 → 45/16 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 45/16 → 23/8 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 23/8 → 47/16 | note:d# s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 47/16 → 3/1 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
+ "[ 3/1 → 49/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 49/16 → 25/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 25/8 → 51/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 51/16 → 13/4 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 13/4 → 53/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 53/16 → 27/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 27/8 → 55/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 55/16 → 7/2 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 7/2 → 57/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 57/16 → 29/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 29/8 → 59/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 59/16 → 15/4 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 15/4 → 61/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 61/16 → 31/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 31/8 → 63/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
+ "[ 63/16 → 4/1 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
+]
+`;
+
+exports[`runs examples > example "tremolodepth" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 1/16 → 1/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 1/8 → 3/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 3/16 → 1/4 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 1/4 → 5/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 5/16 → 3/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 3/8 → 7/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 7/16 → 1/2 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 1/2 → 9/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 9/16 → 5/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 5/8 → 11/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 11/16 → 3/4 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 3/4 → 13/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 13/16 → 7/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 7/8 → 15/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 15/16 → 1/1 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 1/1 → 17/16 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 17/16 → 9/8 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 9/8 → 19/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 19/16 → 5/4 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 5/4 → 21/16 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 21/16 → 11/8 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 11/8 → 23/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 23/16 → 3/2 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 3/2 → 25/16 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 25/16 → 13/8 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 13/8 → 27/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 27/16 → 7/4 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 7/4 → 29/16 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 29/16 → 15/8 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 15/8 → 31/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 31/16 → 2/1 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
+ "[ 2/1 → 33/16 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 33/16 → 17/8 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 17/8 → 35/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 35/16 → 9/4 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 9/4 → 37/16 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 37/16 → 19/8 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 19/8 → 39/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 39/16 → 5/2 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 5/2 → 41/16 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 41/16 → 21/8 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 21/8 → 43/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 43/16 → 11/4 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 11/4 → 45/16 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 45/16 → 23/8 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 23/8 → 47/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 47/16 → 3/1 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
+ "[ 3/1 → 49/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 49/16 → 25/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 25/8 → 51/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 51/16 → 13/4 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 13/4 → 53/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 53/16 → 27/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 27/8 → 55/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 55/16 → 7/2 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 7/2 → 57/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 57/16 → 29/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 29/8 → 59/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 59/16 → 15/4 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 15/4 → 61/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 61/16 → 31/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 31/8 → 63/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
+ "[ 63/16 → 4/1 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
+]
+`;
+
+exports[`runs examples > example "tremolophase" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 1/16 → 1/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 1/8 → 3/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 3/16 → 1/4 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 1/4 → 5/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 5/16 → 3/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 3/8 → 7/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 7/16 → 1/2 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 1/2 → 9/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 9/16 → 5/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 5/8 → 11/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 11/16 → 3/4 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 3/4 → 13/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 13/16 → 7/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 7/8 → 15/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 15/16 → 1/1 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 1/1 → 17/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 17/16 → 9/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 9/8 → 19/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 19/16 → 5/4 | note:e s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 5/4 → 21/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 21/16 → 11/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 11/8 → 23/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 23/16 → 3/2 | note:e s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 3/2 → 25/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 25/16 → 13/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 13/8 → 27/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 27/16 → 7/4 | note:e s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 7/4 → 29/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 29/16 → 15/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 15/8 → 31/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 31/16 → 2/1 | note:e s:sawtooth tremolosync:4 tremolophase:0.25 ]",
+ "[ 2/1 → 33/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 33/16 → 17/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 17/8 → 35/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 35/16 → 9/4 | note:e s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 9/4 → 37/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 37/16 → 19/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 19/8 → 39/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 39/16 → 5/2 | note:e s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 5/2 → 41/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 41/16 → 21/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 21/8 → 43/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 43/16 → 11/4 | note:e s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 11/4 → 45/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 45/16 → 23/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 23/8 → 47/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 47/16 → 3/1 | note:e s:sawtooth tremolosync:4 tremolophase:0.66 ]",
+ "[ 3/1 → 49/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 49/16 → 25/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 25/8 → 51/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 51/16 → 13/4 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 13/4 → 53/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 53/16 → 27/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 27/8 → 55/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 55/16 → 7/2 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 7/2 → 57/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 57/16 → 29/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 29/8 → 59/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 59/16 → 15/4 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 15/4 → 61/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 61/16 → 31/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 31/8 → 63/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
+ "[ 63/16 → 4/1 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
+]
+`;
+
+exports[`runs examples > example "tremoloshape" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 1/16 → 1/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 1/8 → 3/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 3/16 → 1/4 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 1/4 → 5/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 5/16 → 3/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 3/8 → 7/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 7/16 → 1/2 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 1/2 → 9/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 9/16 → 5/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 5/8 → 11/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 11/16 → 3/4 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 3/4 → 13/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 13/16 → 7/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 7/8 → 15/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 15/16 → 1/1 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 1/1 → 17/16 | note:f tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 17/16 → 9/8 | note:g tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 9/8 → 19/16 | note:c tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 19/16 → 5/4 | note:d tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 5/4 → 21/16 | note:f tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 21/16 → 11/8 | note:g tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 11/8 → 23/16 | note:c tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 23/16 → 3/2 | note:d tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 3/2 → 25/16 | note:f tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 25/16 → 13/8 | note:g tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 13/8 → 27/16 | note:c tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 27/16 → 7/4 | note:d tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 7/4 → 29/16 | note:f tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 29/16 → 15/8 | note:g tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 15/8 → 31/16 | note:c tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 31/16 → 2/1 | note:d tremolosync:4 tremoloshape:tri s:sawtooth ]",
+ "[ 2/1 → 33/16 | note:f tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 33/16 → 17/8 | note:g tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 17/8 → 35/16 | note:c tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 35/16 → 9/4 | note:d tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 9/4 → 37/16 | note:f tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 37/16 → 19/8 | note:g tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 19/8 → 39/16 | note:c tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 39/16 → 5/2 | note:d tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 5/2 → 41/16 | note:f tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 41/16 → 21/8 | note:g tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 21/8 → 43/16 | note:c tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 43/16 → 11/4 | note:d tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 11/4 → 45/16 | note:f tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 45/16 → 23/8 | note:g tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 23/8 → 47/16 | note:c tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 47/16 → 3/1 | note:d tremolosync:4 tremoloshape:square s:sawtooth ]",
+ "[ 3/1 → 49/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 49/16 → 25/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 25/8 → 51/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 51/16 → 13/4 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 13/4 → 53/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 53/16 → 27/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 27/8 → 55/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 55/16 → 7/2 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 7/2 → 57/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 57/16 → 29/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 29/8 → 59/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 59/16 → 15/4 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 15/4 → 61/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 61/16 → 31/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 31/8 → 63/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
+ "[ 63/16 → 4/1 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
+]
+`;
+
+exports[`runs examples > example "tremoloskew" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 1/16 → 1/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 1/8 → 3/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 3/16 → 1/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 1/4 → 5/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 5/16 → 3/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 3/8 → 7/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 7/16 → 1/2 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 1/2 → 9/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 9/16 → 5/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 5/8 → 11/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 11/16 → 3/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 3/4 → 13/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 13/16 → 7/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 7/8 → 15/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 15/16 → 1/1 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 1/1 → 17/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 17/16 → 9/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 9/8 → 19/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 19/16 → 5/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 5/4 → 21/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 21/16 → 11/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 11/8 → 23/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 23/16 → 3/2 | note:e s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 3/2 → 25/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 25/16 → 13/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 13/8 → 27/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 27/16 → 7/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 7/4 → 29/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 29/16 → 15/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 15/8 → 31/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 31/16 → 2/1 | note:e s:sawtooth tremolosync:4 tremoloskew:0 ]",
+ "[ 2/1 → 33/16 | note:f s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 33/16 → 17/8 | note:a s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 17/8 → 35/16 | note:c s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 35/16 → 9/4 | note:e s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 9/4 → 37/16 | note:f s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 37/16 → 19/8 | note:a s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 19/8 → 39/16 | note:c s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 39/16 → 5/2 | note:e s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 5/2 → 41/16 | note:f s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 41/16 → 21/8 | note:a s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 21/8 → 43/16 | note:c s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 43/16 → 11/4 | note:e s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 11/4 → 45/16 | note:f s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 45/16 → 23/8 | note:a s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 23/8 → 47/16 | note:c s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 47/16 → 3/1 | note:e s:sawtooth tremolosync:4 tremoloskew:1 ]",
+ "[ 3/1 → 49/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 49/16 → 25/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 25/8 → 51/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 51/16 → 13/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 13/4 → 53/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 53/16 → 27/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 27/8 → 55/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 55/16 → 7/2 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 7/2 → 57/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 57/16 → 29/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 29/8 → 59/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 59/16 → 15/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 15/4 → 61/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 61/16 → 31/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 31/8 → 63/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+ "[ 63/16 → 4/1 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
+]
+`;
+
+exports[`runs examples > example "tremolosync" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 1/16 → 1/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 1/8 → 3/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 3/16 → 1/4 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 1/4 → 5/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 5/16 → 3/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 3/8 → 7/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 7/16 → 1/2 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 1/2 → 9/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 9/16 → 5/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 5/8 → 11/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 11/16 → 3/4 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 3/4 → 13/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 13/16 → 7/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 7/8 → 15/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 15/16 → 1/1 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 1/1 → 17/16 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 17/16 → 9/8 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 9/8 → 19/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 19/16 → 5/4 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 5/4 → 21/16 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 21/16 → 11/8 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 11/8 → 23/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 23/16 → 3/2 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 3/2 → 25/16 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 25/16 → 13/8 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 13/8 → 27/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 27/16 → 7/4 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 7/4 → 29/16 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 29/16 → 15/8 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 15/8 → 31/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 31/16 → 2/1 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
+ "[ 2/1 → 33/16 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 33/16 → 17/8 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 17/8 → 35/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 35/16 → 9/4 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 9/4 → 37/16 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 37/16 → 19/8 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 19/8 → 39/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 39/16 → 5/2 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 5/2 → 41/16 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 41/16 → 21/8 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 21/8 → 43/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 43/16 → 11/4 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 11/4 → 45/16 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 45/16 → 23/8 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 23/8 → 47/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 47/16 → 3/1 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
+ "[ 3/1 → 49/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 49/16 → 25/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 25/8 → 51/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 51/16 → 13/4 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 13/4 → 53/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 53/16 → 27/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 27/8 → 55/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 55/16 → 7/2 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 7/2 → 57/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 57/16 → 29/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 29/8 → 59/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 59/16 → 15/4 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 15/4 → 61/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 61/16 → 31/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 31/8 → 63/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
+ "[ 63/16 → 4/1 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
+]
+`;
+
exports[`runs examples > example "tri" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:C3 ]",
diff --git a/test/testtunes.mjs b/test/testtunes.mjs
index fc887610e..3690d315b 100644
--- a/test/testtunes.mjs
+++ b/test/testtunes.mjs
@@ -359,28 +359,6 @@ stack(
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
).note().piano().slow(2)`;
-/*
-export const customTrigger = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
-// by Felix Roos
-stack(
- freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)").layer(x=>x.mul("1.006,.995"))),
- freq("440(5,8)".clip(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
-).s("/2")
- .onTrigger((t,hap,ct)=>{
- const ac = Tone.getContext().rawContext;
- t = ac.currentTime + t - ct;
- const { freq, s, gain = 1 } = hap.value;
- const master = ac.createGain();
- master.gain.value = 0.1 * gain;
- master.connect(ac.destination);
- const o = ac.createOscillator();
- o.type = s || 'triangle';
- o.frequency.value = Number(freq);
- o.connect(master);
- o.start(t);
- o.stop(t + hap.duration);
-}).stack(s("bd(3,8),hh*4,~ sd").webdirt())`; */
-
export const swimmingWithSoundfonts = `// Koji Kondo - Swimming (Super Mario World)
stack(
n(
diff --git a/undocumented.json b/undocumented.json
index 8a38f205f..3f5a3be30 100644
--- a/undocumented.json
+++ b/undocumented.json
@@ -647,7 +647,6 @@
"evaluate"
],
"/packages/webaudio/webaudio.mjs": [
- "webaudioOutputTrigger",
"webaudioOutput",
"webaudioRepl"
],
diff --git a/website/src/content/blog/release-1.1.0-bananensplit.mdx b/website/src/content/blog/release-1.1.0-bananensplit.mdx
new file mode 100644
index 000000000..dcc547682
--- /dev/null
+++ b/website/src/content/blog/release-1.1.0-bananensplit.mdx
@@ -0,0 +1,317 @@
+---
+title: 'Release Notes v1.1.0'
+description: ''
+date: '2024-06-02'
+tags: ['meta']
+author: froos
+---
+
+These are the release notes for Strudel 1.1.0 aka "Bananensplit".
+
+The last release was over 19 weeks ago, so a lot of things have happened!
+
+First, here's a little demo, teasing some of the new features:
+
+import { Youtube } from '@src/components/Youtube';
+
+
+
+Let's write up some of the highlights:
+
+## New DSP Features
+
+### Stereo Supersaw
+
+with spread, unison, and detune parameters
+
+```plaintext
+note("d f a a# a d3").fast(2)
+.s("supersaw").spread(".8").detune(.3).unison("2 7")
+```
+
+### Analog "ladder" filter type
+
+works great for acid basslines and vibey tones
+
+```plaintext
+note("{d d d a a# d3 f4}%16".sub(12)).gain(1).s("sawtooth")
+.lpf(200).lpenv(slider(1.36,0,8)).lpq(7).distort("1.5:.7")`
+.ftype('ladder')
+```
+
+### stereo distortion effect
+
+```plaintext
+note("{g g a# g g4}%8".add("{0 7 12 0}%8")).lpf(500)
+.s("supersaw").dist("4:.2")
+```
+
+## Editor Features
+
+### inline viz
+
+The editor now supports multiple visuals within the code, using the `_` prefix for viz functions:
+
+
+
+- `._pianoroll()`: inline pianoroll
+- `._punchcard()`: inline punchcard
+- `._scope()`: inline scope
+- `._pitchwheel()`: inline pitchwheel
+
+For more info, check out the new [Visual Feedback Page](https://strudel.cc/learn/visual-feedback/)
+
+### label notation
+
+This new notation simplifies writing patterns at the top level:
+
+```plaintext
+d1: s("bd*4")
+d2: s("[- hh]*4")
+```
+
+This is equivalent to:
+
+```plaintext
+stack(
+ s("bd*4"),
+ s("[- hh]*4")
+)
+```
+
+The labels you choose are arbitrary, the above `d1` and `d2` are a typical thing you'd write in tidal, for example `d1 $ s "bd*4"`.
+If the same label is used multiple times, the last one wins:
+
+```plaintext
+d1: s("bd*4")
+d1: s("[- hh]*4") // <-- only this plays
+```
+
+There is a special label anonymous label `$`, which can appear multiple times without overriding itself:
+
+```plaintext
+// both of these will play:
+$: s("bd*4")
+$: s("[- hh]*4")
+```
+
+You can mute a pattern by prefixing `_`:
+
+```plaintext
+_$: s("bd*4") // <-- this one is muted
+$: s("[- hh]*4")
+```
+
+To run a transformation on all patterns, you can use `all`:
+
+```plaintext
+$: s("bd*4")
+$: s("[- hh]*4")
+all(x=>x.room(.5))
+```
+
+This notation is now the recommended way to [play patterns in parallel](https://strudel.cc/workshop/first-notes/#playing-multiple-patterns)
+
+### Clock sync between multiple instances
+
+timing has received a major overhaul, and is now much more accurate on all browsers. Additionally, you can now sync timing across multiple windows.
+
+
+### Better sample upload support
+
+you can now upload large amounts of samples much faster across all browsers including on IOS devices. supported filetypes now include: ogg flac mp3 wav aac m4a
+
+### experimental tidal syntax
+
+The new `tidal` function allows you to write strudel patterns in tidal syntax:
+
+```plaintext
+await initTidal()
+
+tidal`
+d1 $ s "bd*4"
+
+d2 $ s "[- hh]*4"
+`
+```
+
+As we're looking to improve compatibility with tidal, we're happy to hear feedback.
+
+## breaking changes
+
+This release comes with a bunch of breaking changes. If you find your patterns to sound different, check out the PRs below for guidance on how to update them. Most of these changes shouldn't affect a lot of patterns.
+In case of doubt, add the line `// @version 1.0` to your old pattern.
+If you're having problems, please let us know!
+
+- remove legacy legato + duration implementations by @felixroos in https://codeberg.org/uzu/strudel/pull/965
+- Velocity in value by @felixroos in https://codeberg.org/uzu/strudel/pull/974
+- use ireal as default voicing dict by @felixroos https://codeberg.org/uzu/strudel/pull/967
+- Color in hap value by @felixroos https://codeberg.org/uzu/strudel/pull/1007
+- rename trig -> reset, trigzero -> restart by @felixroos https://codeberg.org/uzu/strudel/pull/1010
+- remove dangerous arithmetic feature by @felixroos https://codeberg.org/uzu/strudel/pull/1030
+- change fanchor to 0 by @daslyfe https://codeberg.org/uzu/strudel/pull/1107
+
+## superdough features
+
+- replace shape with distort in learn doc by @daslyfe https://codeberg.org/uzu/strudel/pull/982
+- Worklet Improvents / fixes by @daslyfe https://codeberg.org/uzu/strudel/pull/963
+- supersaw oscillator by @daslyfe https://codeberg.org/uzu/strudel/pull/978
+- Add analog-style ladder filter by @daslyfe https://codeberg.org/uzu/strudel/pull/1103
+- Calculate phaser modulation phase based on time by @daslyfe https://codeberg.org/uzu/strudel/pull/1110
+- rollback phaser by @daslyfe https://codeberg.org/uzu/strudel/pull/1113
+
+## editor / ui features
+
+- 'Enable Bracket Matching' option in Codemirror by @eefano https://codeberg.org/uzu/strudel/pull/956
+- REPL sync between windows by @daslyfe https://codeberg.org/uzu/strudel/pull/900
+- inline viz / widgets package by @felixroos https://codeberg.org/uzu/strudel/pull/989
+- Inline punchcard + spiral by @felixroos https://codeberg.org/uzu/strudel/pull/1008
+- More fonts by @felixroos https://codeberg.org/uzu/strudel/pull/1023
+- better theme integration for visuals + various fixes by @felixroos https://codeberg.org/uzu/strudel/pull/1024
+- add setting for sync flag by @felixroos https://codeberg.org/uzu/strudel/pull/1025
+- add closeBrackets setting by @felixroos https://codeberg.org/uzu/strudel/pull/1031
+- add font file types to offline cache by @felixroos https://codeberg.org/uzu/strudel/pull/1032
+- pitchwheel visual by @felixroos https://codeberg.org/uzu/strudel/pull/1041
+- repl: set document.title from @title by @kasparsj https://codeberg.org/uzu/strudel/pull/1090
+- Samples tab improvements by @daslyfe https://codeberg.org/uzu/strudel/pull/1102
+
+## language features
+
+- pickOut(), pickRestart(), pickReset() by @eefano https://codeberg.org/uzu/strudel/pull/950
+- Auto await samples by @felixroos https://codeberg.org/uzu/strudel/pull/955
+- feat: can now invert euclid pulses with negative numbers by @felixroos https://codeberg.org/uzu/strudel/pull/959
+- Nested controls by @felixroos https://codeberg.org/uzu/strudel/pull/973
+- alias - for ~ by @yaxu https://codeberg.org/uzu/strudel/pull/981
+- Beat-oriented functionality by @yaxu https://codeberg.org/uzu/strudel/pull/976
+- Labeled statements by @felixroos https://codeberg.org/uzu/strudel/pull/991
+- accidentals in scale degrees by @eefano https://codeberg.org/uzu/strudel/pull/1000
+- Feature: tactus marking by @yaxu https://codeberg.org/uzu/strudel/pull/1021
+- Tactus tidy by @yaxu https://codeberg.org/uzu/strudel/pull/1027
+- Wax, wane, taper and taperlist by @yaxu https://codeberg.org/uzu/strudel/pull/1042
+- transpose: support all combinations of numbers and strings for notes and intervals by @felixroos https://codeberg.org/uzu/strudel/pull/1048
+- anonymous patterns + muting by @felixroos https://codeberg.org/uzu/strudel/pull/1059
+- add swing + swingBy by @felixroos https://codeberg.org/uzu/strudel/pull/1038
+- Stepwise functions from Tidal by @yaxu https://codeberg.org/uzu/strudel/pull/1060
+- Tactus tweaks - fixes for maintaining tactus and highlight locations by @yaxu https://codeberg.org/uzu/strudel/pull/1065
+- Fix stepjoin by @yaxu https://codeberg.org/uzu/strudel/pull/1067
+- More tactus tidying by @yaxu https://codeberg.org/uzu/strudel/pull/1071
+- Tactus calculation toggle and breaking change to tactus calculation in fast/slow/hurry by @yaxu https://codeberg.org/uzu/strudel/pull/1081
+- hs2js package / tidal parser by @felixroos https://codeberg.org/uzu/strudel/pull/870
+- Add the mousex and mousey signal by @Enelg52 https://codeberg.org/uzu/strudel/pull/1112
+- can now access strudelMirror from repl by @felixroos https://codeberg.org/uzu/strudel/pull/1117
+
+## sampler
+
+If you have nodejs installed on your system, you can now use [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler) to serve samples from disk to the REPL or flok.
+
+- local sample server cli by @felixroos https://codeberg.org/uzu/strudel/pull/1033
+- Fix sampler paths by @felixroos https://codeberg.org/uzu/strudel/pull/1034
+- Fix sampler windows by @felixroos https://codeberg.org/uzu/strudel/pull/1108
+- fix sampler on windows by @geikha https://codeberg.org/uzu/strudel/pull/1109
+
+## docs
+
+- V1 release notes by @felixroos https://codeberg.org/uzu/strudel/pull/935
+- Minor documentation error: Update first-sounds.mdx by @mhetrick https://codeberg.org/uzu/strudel/pull/941
+- Update synths.mdx by @andresgottlieb https://codeberg.org/uzu/strudel/pull/984
+- using strudel in your project guide + cleanup examples by @felixroos https://codeberg.org/uzu/strudel/pull/1006
+- Document signals by @ilesinge https://codeberg.org/uzu/strudel/pull/1015
+- improve tutorial + custom samples doc by @felixroos https://codeberg.org/uzu/strudel/pull/1053
+- fix cr typo on first-sounds.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1068
+- fix first sounds typo by @cleary https://codeberg.org/uzu/strudel/pull/1069
+- add `<...>` to first-sounds.mdx recap by @cleary https://codeberg.org/uzu/strudel/pull/1070
+- add nesting to `off` example variation in pattern-effects.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1075
+- fix translation issue in first-effects.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1072
+- add signals to recap in first-effects.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1073
+- fix docs on alignment.mdx by @diegodorado https://codeberg.org/uzu/strudel/pull/1076
+- fix little dub tune example by @lukad https://codeberg.org/uzu/strudel/pull/1104
+- clarify `off` in pattern-effects.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1074
+- Fixes drawPianoroll import in codemirror example by @giohappy https://codeberg.org/uzu/strudel/pull/1116
+- Migrate tutorial fanchor by @felixroos https://codeberg.org/uzu/strudel/pull/1122
+
+## internals
+
+- remove cjs builds by @felixroos https://codeberg.org/uzu/strudel/pull/945
+- controls refactoring: simplify exports by @felixroos https://codeberg.org/uzu/strudel/pull/962
+- move canvas related helpers from core to new draw package by @felixroos https://codeberg.org/uzu/strudel/pull/971
+- remove canvas, externalize samples, delete junk by @felixroos https://codeberg.org/uzu/strudel/pull/1003
+- Improve performance of ! (replicate) by @yaxu https://codeberg.org/uzu/strudel/pull/1084
+- Benchmarks by @yaxu https://codeberg.org/uzu/strudel/pull/1079
+
+## fixes
+
+- fix midi issue on firefox and added quote error by @Enelg52 https://codeberg.org/uzu/strudel/pull/936
+- fix: pianoroll sorting by @felixroos https://codeberg.org/uzu/strudel/pull/938
+- account for cps in midi time duration by @daslyfe https://codeberg.org/uzu/strudel/pull/954
+- fix script importable packages (web + repl) by @felixroos https://codeberg.org/uzu/strudel/pull/957
+- fix: reset global fx on pattern change by @felixroos https://codeberg.org/uzu/strudel/pull/960
+- add debounce to logger by @felixroos https://codeberg.org/uzu/strudel/pull/968
+- fix for transpose(): preserve hap value object structure by @eefano https://codeberg.org/uzu/strudel/pull/966
+- fix: clear hydra on reset by @felixroos https://codeberg.org/uzu/strudel/pull/983
+- little fix for withVal by @eefano https://codeberg.org/uzu/strudel/pull/980
+- fix: share now shares what's visible instead of active by @felixroos https://codeberg.org/uzu/strudel/pull/985
+- Fix pure mini highlight by @yaxu https://codeberg.org/uzu/strudel/pull/994
+- fix: await injectPatternMethods by @felixroos https://codeberg.org/uzu/strudel/pull/1012
+- update undocumented script by @felixroos https://codeberg.org/uzu/strudel/pull/1013
+- eliminate chromium clock jitter by @felixroos https://codeberg.org/uzu/strudel/pull/1004
+- Repl sync fixes by @daslyfe https://codeberg.org/uzu/strudel/pull/1014
+- hotfix for 1017 by @daslyfe https://codeberg.org/uzu/strudel/pull/1020
+- fix cyclist fizzling out by @felixroos https://codeberg.org/uzu/strudel/pull/1046
+- Midi Time hotfix for scheduler updates by @daslyfe https://codeberg.org/uzu/strudel/pull/1047
+- fix: do not reset cc input values on each eval by @felixroos https://codeberg.org/uzu/strudel/pull/1054
+- Fix wchooseCycles not picking the whole pattern by @ilesinge https://codeberg.org/uzu/strudel/pull/1061
+- fix OSC timing for recent scheduler updates by @daslyfe https://codeberg.org/uzu/strudel/pull/1062
+- clarify license by @yaxu https://codeberg.org/uzu/strudel/pull/1064
+- fix failing format test by @daslyfe https://codeberg.org/uzu/strudel/pull/1077
+- fix: url parsing with extra params by @felixroos https://codeberg.org/uzu/strudel/pull/1083
+- fix: csound + dough timing by @felixroos https://codeberg.org/uzu/strudel/pull/1086
+- fix: missing events due to premature worklet cleanup by @felixroos https://codeberg.org/uzu/strudel/pull/1089
+- Use sessionStorage for viewingPatternData and activePattern by @kasparsj https://codeberg.org/uzu/strudel/pull/1091
+- osc: couple of fixes by @kasparsj https://codeberg.org/uzu/strudel/pull/1093
+- web package fixes by @felixroos https://codeberg.org/uzu/strudel/pull/1044
+- Fix audio worklets by @daslyfe https://codeberg.org/uzu/strudel/pull/1114
+- fix: use full repl in web package by @felixroos https://codeberg.org/uzu/strudel/pull/1119
+- [BUG FIX] Audio worklets sometimes dont load by @daslyfe https://codeberg.org/uzu/strudel/pull/1121
+
+## New Contributors
+
+- @mhetrick made their first contribution https://codeberg.org/uzu/strudel/pull/941
+- @eefano made their first contribution https://codeberg.org/uzu/strudel/pull/956
+- @Enelg52 made their first contribution https://codeberg.org/uzu/strudel/pull/936
+- @andresgottlieb made their first contribution https://codeberg.org/uzu/strudel/pull/984
+- @cleary made their first contribution https://codeberg.org/uzu/strudel/pull/1068
+- @diegodorado made their first contribution https://codeberg.org/uzu/strudel/pull/1076
+- @lukad made their first contribution https://codeberg.org/uzu/strudel/pull/1104
+- @giohappy made their first contribution https://codeberg.org/uzu/strudel/pull/1116
+
+A huge thanks to all contributors!!!
+
+## Packages
+
+- @strudel/codemirror@1.1.0
+- @strudel/core@1.1.0
+- @strudel/csound@1.1.0
+- @strudel/draw@1.1.0
+- @strudel/embed@1.1.0
+- hs2js@0.1.0
+- @strudel/hydra@1.1.0
+- @strudel/midi@1.1.0
+- @strudel/mini@1.1.0
+- @strudel/osc@1.1.0
+- @strudel/repl@1.1.0
+- @strudel/sampler@0.1.0
+- @strudel/serial@1.1.0
+- @strudel/soundfonts@1.1.0
+- superdough@1.1.0
+- @strudel/tidal@0.1.0
+- @strudel/tonal@1.1.0
+- @strudel/transpiler@1.1.0
+- @strudel/web@1.1.0
+- @strudel/webaudio@1.1.0
+- @strudel/xen@1.1.0
+
+**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v1.0.0...v1.1.0
diff --git a/website/src/content/blog/release-1.2.0-kardinalschnitten.mdx b/website/src/content/blog/release-1.2.0-kardinalschnitten.mdx
new file mode 100644
index 000000000..e215d4532
--- /dev/null
+++ b/website/src/content/blog/release-1.2.0-kardinalschnitten.mdx
@@ -0,0 +1,186 @@
+---
+title: 'Release Notes v1.2.0'
+description: ''
+date: '2025-05-01'
+tags: ['meta']
+author: froos
+---
+
+## What's Changed
+
+## highlights
+
+- [stepwise functions](https://strudel.cc/learn/stepwise/) ([PR](https://github.com/tidalcycles/strudel/pull/1262))
+- [midimaps](https://strudel.cc/learn/input-output/#midimaps) ([PR](https://github.com/tidalcycles/strudel/pull/1274))
+- [spectrum](https://strudel.cc/learn/visual-feedback/#spectrum) ([PR](https://github.com/tidalcycles/strudel/pull/1213))
+- [mqtt](https://strudel.cc/learn/input-output/#mqtt) ([PR](https://github.com/tidalcycles/strudel/pull/1224))
+- pulse oscillator (todo: https://github.com/tidalcycles/strudel/issues/1336) ([PR](https://github.com/tidalcycles/strudel/pull/1304))
+- theme improvements
+
+## breaking changes
+
+- [breaking change] Sample signals from query onset, rather than midpoint by @yaxu in https://github.com/tidalcycles/strudel/pull/1278
+- change behaviour of polymeter, and remove polymeterSteps by @yaxu in https://github.com/tidalcycles/strudel/pull/1302
+- Polish, rename, and document stepwise functions by @yaxu in https://github.com/tidalcycles/strudel/pull/1262
+
+### superdough
+
+- feat: Create Pulse Oscillator with variable PWM by @daslyfe in https://github.com/tidalcycles/strudel/pull/1304
+- add num samples (edited numbers) by @yaxu in https://github.com/tidalcycles/strudel/pull/1309
+- Add num samples from 0 up to 20 by @yaxu in https://github.com/tidalcycles/strudel/pull/1310
+- feat: add max polyphony feature for superdough by @daslyfe in https://github.com/tidalcycles/strudel/pull/1317
+
+### docs
+
+- doc: visual functions + refactor onPaint by @felixroos in https://github.com/tidalcycles/strudel/pull/1125
+- Labeled statements doc by @felixroos in https://github.com/tidalcycles/strudel/pull/1126
+- Correct spelling mistakes by @EdwardBetts in https://github.com/tidalcycles/strudel/pull/1183
+- remove redundant example for cat, update snapshot by @kdiab in https://github.com/tidalcycles/strudel/pull/1189
+- chore: Edit run locally instructions in README.md by @ChinoUkaegbu in https://github.com/tidalcycles/strudel/pull/1206
+- suggested changes to voicings.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/1231
+- Documentation for all/each, and bugfix for each by @yaxu in https://github.com/tidalcycles/strudel/pull/1233
+- Update documentation for param value modification by @gillespi314 in https://github.com/tidalcycles/strudel/pull/1238
+- fix docs for beat function by @daslyfe in https://github.com/tidalcycles/strudel/pull/1248
+- understand voicings page by @felixroos in https://github.com/tidalcycles/strudel/pull/1230
+- add reference package by @felixroos in https://github.com/tidalcycles/strudel/pull/1252
+- Stepwise documentation tweaks, with mridangam samples by @yaxu in https://github.com/tidalcycles/strudel/pull/1275
+- showcase tweaks by @yaxu in https://github.com/tidalcycles/strudel/pull/1291
+- Signpost licenses for source code and samples a bit more, ref #1277 by @yaxu in https://github.com/tidalcycles/strudel/pull/1289
+- Fix misplaced ending sentence by @makmanalp in https://github.com/tidalcycles/strudel/pull/1296
+- Fix typo pattnr by @ReneNyffenegger in https://github.com/tidalcycles/strudel/pull/1316
+- update docs to reflect import sounds tab change by @hpunq in https://github.com/tidalcycles/strudel/pull/1332
+
+### ui improvements
+
+- Udels (MultiFrame Strudel) Revisited by @daslyfe in https://github.com/tidalcycles/strudel/pull/1132
+- Create audio target selector for OSC/Superdirt by @daslyfe in https://github.com/tidalcycles/strudel/pull/1160
+- Add a search bar to the REPL Reference tab by @netux in https://github.com/tidalcycles/strudel/pull/1165
+- Adding search bar (soundtab.jsx) by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/1185
+- add 2 new ui settings by @felixroos in https://github.com/tidalcycles/strudel/pull/1200
+- Theme glowup by @felixroos in https://github.com/tidalcycles/strudel/pull/1268
+- Create Pattern Page Pagination by @daslyfe in https://github.com/tidalcycles/strudel/pull/1287
+- feat: Theme improvements by @daslyfe in https://github.com/tidalcycles/strudel/pull/1295
+- feat: new themes + theme improvements by @daslyfe in https://github.com/tidalcycles/strudel/pull/1326
+- Add new "import-sounds" tab with explanation on folder import by @hpunq in https://github.com/tidalcycles/strudel/pull/1329
+- Add Icon to import sample button by @daslyfe in https://github.com/tidalcycles/strudel/pull/1331
+- better spacing in zen mode by @felixroos in https://github.com/tidalcycles/strudel/pull/1147
+- Screenreader improvements by @yaxu in https://github.com/tidalcycles/strudel/pull/1158
+- colorize console + tweak header by @felixroos in https://github.com/tidalcycles/strudel/pull/1203
+- Menu Panel Improvements! by @daslyfe in https://github.com/tidalcycles/strudel/pull/1193
+- Make panel hover behavior optional by @daslyfe in https://github.com/tidalcycles/strudel/pull/1199
+- REPL: solo and sync configuration by @bthj in https://github.com/tidalcycles/strudel/pull/1214
+- enhancement: make error messages easier to read by @daslyfe in https://github.com/tidalcycles/strudel/pull/1315
+
+### mqtt
+
+- MQTT support by @yaxu in https://github.com/tidalcycles/strudel/pull/1224
+- MQTT - if password isn't provided, prompt for one by @yaxu in https://github.com/tidalcycles/strudel/pull/1249
+- MQTT - support adding hap duration and cps metadata to JSON messages by @yaxu in https://github.com/tidalcycles/strudel/pull/1279
+- make mqtt topic patternable by @yaxu in https://github.com/tidalcycles/strudel/pull/1280
+- Bugfix: update mqtt connections dictionary by @yaxu in https://github.com/tidalcycles/strudel/pull/1281
+- mqtt bugfix - connection check by @yaxu in https://github.com/tidalcycles/strudel/pull/1282
+
+### new functions
+
+- Add scramble and shuffle by @yaxu in https://github.com/tidalcycles/strudel/pull/1167
+- polyJoin by @yaxu in https://github.com/tidalcycles/strudel/pull/1168
+- Add seqPLoop from Tidal by @yaxu in https://github.com/tidalcycles/strudel/pull/1182
+- add filter + filterWhen + within by @felixroos in https://github.com/tidalcycles/strudel/pull/1039
+- Add bite function by @yaxu in https://github.com/tidalcycles/strudel/pull/1187
+- markcss by @felixroos in https://github.com/tidalcycles/strudel/pull/1202
+- "beat" function for "step sequencer" style rhythm notation by @daslyfe in https://github.com/tidalcycles/strudel/pull/1237
+- Add s_zip for 'cat'-ing patterns together step-by-step, bugfix `steps` by @yaxu in https://github.com/tidalcycles/strudel/pull/1208
+- "stretch" function (phase vocoder) by @daslyfe in https://github.com/tidalcycles/strudel/pull/1130
+- add basic spectrum function by @felixroos in https://github.com/tidalcycles/strudel/pull/1213
+- Add onKey function for custom key commands for patterns by @daslyfe in https://github.com/tidalcycles/strudel/pull/1235
+- Add binary and binaryN by @heerman in https://github.com/tidalcycles/strudel/pull/1226
+- midimaps by @felixroos in https://github.com/tidalcycles/strudel/pull/1274
+- small feat: Add alias for segment and ribbon by @daslyfe in https://github.com/tidalcycles/strudel/pull/1314
+- feat: Create scrub function for scrubbing an audio file by @daslyfe in https://github.com/tidalcycles/strudel/pull/1321
+- feat: Improve gain curve by @daslyfe in https://github.com/tidalcycles/strudel/pull/1318
+- Chop chop by @yaxu in https://github.com/tidalcycles/strudel/pull/1078
+
+### more
+
+- Make `all()` post-stack again, and add `each()` for pre-stack by @yaxu in https://github.com/tidalcycles/strudel/pull/1229
+- Add stepBind, and some toplevel aliases for binds and withValue by @yaxu in https://github.com/tidalcycles/strudel/pull/1241
+- Make cps patternable by @eefano in https://github.com/tidalcycles/strudel/pull/1001
+- Allow wchooseCycles probabilities to be patterned by @yaxu in https://github.com/tidalcycles/strudel/pull/1292
+- @strudel/sampler improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/1288
+
+### refactor
+
+- expose comment commands by @felixroos in https://github.com/tidalcycles/strudel/pull/1136
+- containerize/seperate out boolean checks for repl types/Repl logic into bespoke components. by @daslyfe in https://github.com/tidalcycles/strudel/pull/1163
+- Improve + simplify neocyclist timing by @daslyfe in https://github.com/tidalcycles/strudel/pull/1164
+- Make phaser control consistent with superdirt by @daslyfe in https://github.com/tidalcycles/strudel/pull/1178
+- Revert "Make phaser control consistent with superdirt" by @daslyfe in https://github.com/tidalcycles/strudel/pull/1179
+- make phaser control match superdirt by @daslyfe in https://github.com/tidalcycles/strudel/pull/1180
+- refactor sampler by @felixroos in https://github.com/tidalcycles/strudel/pull/1101
+- update lockfile + minor versions by @felixroos in https://github.com/tidalcycles/strudel/pull/1198
+- Preserve tactus for 'degrade' and friends, and tidy up 'pick' and friends by @yaxu in https://github.com/tidalcycles/strudel/pull/1205
+- Apply `all` function to individual patterns rather than final stack by @yaxu in https://github.com/tidalcycles/strudel/pull/1209
+- Revert "Fix sometimes" by @yaxu in https://github.com/tidalcycles/strudel/pull/1267
+- patchday by @felixroos in https://github.com/tidalcycles/strudel/pull/1264
+- Rename repeat back to extend by @yaxu in https://github.com/tidalcycles/strudel/pull/1285
+- Send delta in OSC message in seconds, to match tidal/superdirt by @yaxu in https://github.com/tidalcycles/strudel/pull/1323
+
+### fixes
+
+- Fix clock worker dependency path in module builds by @matthewkaney in https://github.com/tidalcycles/strudel/pull/1129
+- Fix bug in Fraction.lcm by @yaxu in https://github.com/tidalcycles/strudel/pull/1133
+- Fix tactus marking in mininotation by @yaxu in https://github.com/tidalcycles/strudel/pull/1144
+- Fix loopAt tactus by @yaxu in https://github.com/tidalcycles/strudel/pull/1145
+- Fix OSC clock jitter by @daslyfe in https://github.com/tidalcycles/strudel/pull/1157
+- [CORS HOTFIX] by @daslyfe in https://github.com/tidalcycles/strudel/pull/1162
+- Fixes fit so it works after a chop or slice by @yaxu in https://github.com/tidalcycles/strudel/pull/1171
+- fix sample speed when using splice and fit with superdirt by @daslyfe in https://github.com/tidalcycles/strudel/pull/1172
+- handle midin device not found error by @felixroos in https://github.com/tidalcycles/strudel/pull/1146
+- Fix serial timing by @yaxu in https://github.com/tidalcycles/strudel/pull/1188
+- Fix regression for d1, p1, p(n) by @yaxu in https://github.com/tidalcycles/strudel/pull/1227
+- Fix sometimes by @yaxu in https://github.com/tidalcycles/strudel/pull/1243
+- Fix sf2 timing by @felixroos in https://github.com/tidalcycles/strudel/pull/1272
+- Fix "squeezejoin" and functions using it, including "bite" by @yaxu in https://github.com/tidalcycles/strudel/pull/1286
+- Fixes inverted triangle wave by renaming it to "itri", making non-inverted "tri" by @yaxu in https://github.com/tidalcycles/strudel/pull/1283
+- Hotfix: prevent undefined pattern code from crashing strudel on load by @daslyfe in https://github.com/tidalcycles/strudel/pull/1297
+- Fix test error #1297 by @nkymut in https://github.com/tidalcycles/strudel/pull/1298
+- bugfix zoom stepcount by @yaxu in https://github.com/tidalcycles/strudel/pull/1301
+- bugfix: Allow single param to be used in the as function by @daslyfe in https://github.com/tidalcycles/strudel/pull/1312
+- fix: replace empty spaces in registered sound keys by @daslyfe in https://github.com/tidalcycles/strudel/pull/1319
+- FIX: Multichannel Audio by @daslyfe in https://github.com/tidalcycles/strudel/pull/1322
+- fix: udels header by @daslyfe in https://github.com/tidalcycles/strudel/pull/1325
+- fix: disable astro toolbar by default by @daslyfe in https://github.com/tidalcycles/strudel/pull/1324
+- FIX: sound import order by @daslyfe in https://github.com/tidalcycles/strudel/pull/1333`
+
+## New Contributors
+
+- @EdwardBetts made their first contribution in https://github.com/tidalcycles/strudel/pull/1183
+- @netux made their first contribution in https://github.com/tidalcycles/strudel/pull/1165
+- @kdiab made their first contribution in https://github.com/tidalcycles/strudel/pull/1189
+
+**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v1.1.0...v1.1.1
+
+## packages
+
+- @strudel/codemirror@1.2.0
+- @strudel/core@1.2.0
+- @strudel/csound@1.2.0
+- @strudel/draw@1.2.0
+- @strudel/gamepad@1.2.0
+- @strudel/hydra@1.2.0
+- @strudel/midi@1.2.0
+- @strudel/mini@1.2.0
+- @strudel/motion@1.2.0
+- @strudel/mqtt@1.2.0
+- @strudel/osc@1.2.0
+- @strudel/reference@1.2.0
+- @strudel/repl@1.2.0
+- @strudel/sampler@0.2.0
+- @strudel/serial@1.2.0
+- @strudel/soundfonts@1.2.0
+- superdough@1.2.0
+- @strudel/tonal@1.2.0
+- @strudel/transpiler@1.2.0
+- @strudel/web@1.2.0
+- @strudel/webaudio@1.2.0
+- @strudel/xen@1.2.0
diff --git a/website/src/pages/de/workshop/first-sounds.mdx b/website/src/pages/de/workshop/first-sounds.mdx
index 3695843b0..64a58db02 100644
--- a/website/src/pages/de/workshop/first-sounds.mdx
+++ b/website/src/pages/de/workshop/first-sounds.mdx
@@ -95,7 +95,7 @@ Diese Kombinationen von Buchstaben stehen für verschiedene Teile eines Schlagze
- `mt` = **m**iddle tom
- `ht` = **h**igh tom
- `rd` = **r**i**d**e cymbal
-- `rd` = **cr**ash cymbal
+- `cr` = **cr**ash cymbal
Probier verschiedene Sounds aus!
diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx
index 2f7eaa7f8..8f6a3b8aa 100644
--- a/website/src/pages/learn/effects.mdx
+++ b/website/src/pages/learn/effects.mdx
@@ -57,6 +57,34 @@ Each filter has 2 parameters:
+# Amplitude Modulation
+
+Amplitude modulation changes the amplitude (gain) periodically over time.
+
+## am
+
+
+
+## tremolosync
+
+
+
+## tremolodepth
+
+
+
+## tremoloskew
+
+
+
+## tremolophase
+
+
+
+## tremoloshape
+
+
+
# Amplitude Envelope
The amplitude [envelope]() controls the dynamic contour of a sound.
diff --git a/website/src/pages/learn/mondo-notation.mdx b/website/src/pages/learn/mondo-notation.mdx
index 3b00b9902..e2b0c4027 100644
--- a/website/src/pages/learn/mondo-notation.mdx
+++ b/website/src/pages/learn/mondo-notation.mdx
@@ -176,3 +176,16 @@ $ chord # voicing
/>
The `$` sign is an alias for `,` so it will create a stack behind the scenes.
+
+## variables
+
+using the `def` keyword, you can define variables:
+
+
diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx
index ef97ec0ce..bbe650cf5 100644
--- a/website/src/pages/learn/samples.mdx
+++ b/website/src/pages/learn/samples.mdx
@@ -163,6 +163,21 @@ The last section could be written as:
}
```
+Please note that browsers will often cache `strudel.json` on first load, and keep using the cached
+version even if the orginal has been updated. If this bites you (for example while developing a new
+sample pack), you can force the browser to download a new copy by i.e. changing capitalization of one
+character in the URL, or adding a URL attribute, such as:
+
+```javascript
+samples('https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/strudel.json?version=2');
+```
+
+that gets ignored by GitHub (but changes the URL, forcing the browser to reload every time we increase
+the version number).
+
+It is also possible, of course, to just remove it from cache (deleting cache in browser Privacy settings,
+or from the dev console if you're technically minded, or by using a cache deleting extension).
+
## Github Shortcut
Because loading samples from github is common, there is a shortcut:
diff --git a/website/src/pages/understand/cycles.mdx b/website/src/pages/understand/cycles.mdx
index 1c0a778a6..bcf987c4d 100644
--- a/website/src/pages/understand/cycles.mdx
+++ b/website/src/pages/understand/cycles.mdx
@@ -67,6 +67,8 @@ Or using 2 beats per cycle:
s("bd sd, hh*4")`}
/>
+You can use the `setcps` method to set the global tempo in cycles per second. `setcpm(x)` is the same as `setcps(x / 60)`.
+
To set a specific bpm, use `setcpm(bpm/bpc)`
diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx
index ab925eb91..1b617341b 100644
--- a/website/src/repl/components/panel/Reference.jsx
+++ b/website/src/repl/components/panel/Reference.jsx
@@ -21,7 +21,11 @@ export function Reference() {
return true;
}
- return entry.name.includes(search) || (entry.synonyms?.some((s) => s.includes(search)) ?? false);
+ const lowCaseSearch = search.toLowerCase();
+ return (
+ entry.name.toLowerCase().includes(lowCaseSearch) ||
+ (entry.synonyms?.some((s) => s.includes(lowCaseSearch)) ?? false)
+ );
});
}, [search]);
diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx
index 5be9b602e..cf2978a1a 100644
--- a/website/src/repl/components/panel/SettingsTab.jsx
+++ b/website/src/repl/components/panel/SettingsTab.jsx
@@ -109,6 +109,8 @@ export function SettingsTab({ started }) {
togglePanelTrigger,
maxPolyphony,
multiChannelOrbits,
+ isTabIndentationEnabled,
+ isMultiCursorEnabled,
} = useSettings();
const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@@ -262,6 +264,16 @@ export function SettingsTab({ started }) {
onChange={(cbEvent) => settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)}
value={isLineWrappingEnabled}
/>
+ settingsMap.setKey('isTabIndentationEnabled', cbEvent.target.checked)}
+ value={isTabIndentationEnabled}
+ />
+ settingsMap.setKey('isMultiCursorEnabled', cbEvent.target.checked)}
+ value={isMultiCursorEnabled}
+ />
settingsMap.setKey('isFlashEnabled', cbEvent.target.checked)}
diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx
index 12621c50a..ac30fe342 100644
--- a/website/src/repl/useReplContext.jsx
+++ b/website/src/repl/useReplContext.jsx
@@ -13,6 +13,7 @@ import {
resetGlobalEffects,
resetLoadedSounds,
initAudioOnFirstClick,
+ resetDefaults,
} from '@strudel/webaudio';
import { setVersionDefaultsFrom } from './util.mjs';
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
@@ -181,6 +182,7 @@ export function useReplContext() {
const resetEditor = async () => {
(await getModule('@strudel/tonal'))?.resetVoicings();
+ resetDefaults();
resetGlobalEffects();
clearCanvas();
clearHydra();
diff --git a/website/src/settings.mjs b/website/src/settings.mjs
index 84b433141..3d99b656c 100644
--- a/website/src/settings.mjs
+++ b/website/src/settings.mjs
@@ -21,6 +21,8 @@ export const defaultSettings = {
isSyncEnabled: false,
isLineWrappingEnabled: false,
isPatternHighlightingEnabled: true,
+ isTabIndentationEnabled: false,
+ isMultiCursorEnabled: false,
theme: 'strudelTheme',
fontFamily: 'monospace',
fontSize: 18,
@@ -77,6 +79,8 @@ export function useSettings() {
isLineWrappingEnabled: parseBoolean(state.isLineWrappingEnabled),
isFlashEnabled: parseBoolean(state.isFlashEnabled),
isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled),
+ isTabIndentationEnabled: parseBoolean(state.isTabIndentationEnabled),
+ isMultiCursorEnabled: parseBoolean(state.isMultiCursorEnabled),
fontSize: Number(state.fontSize),
panelPosition: state.activeFooter !== '' && !isUdels() ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
isPanelPinned: parseBoolean(state.isPanelPinned),