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: + +Screenshot 2024-06-01 at 01 23 51 + +- `._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. +![Screenshot 2024-06-02 at 11 24 40 PM](https://codeberg.org/uzu/strudel/assets/47068718/840be744-a13e-4d7b-ab09-50d3a70b1f85) + +### 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),