diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index bbed050e..213839e1 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -7,26 +7,32 @@ This program is free software: you can redistribute it and/or modify it under th import { Pattern, register, sequence } from './pattern.mjs'; export function createParam(names) { - const name = Array.isArray(names) ? names[0] : names; + let isMulti = Array.isArray(names); + names = !isMulti ? [names] : names; + const name = names[0]; - var withVal; - if (Array.isArray(names)) { - withVal = (xs) => { - if (Array.isArray(xs)) { - const result = {}; - xs.forEach((x, i) => { - if (i < names.length) { - result[names[i]] = x; - } - }); - return result; - } else { - return { [name]: xs }; - } - }; - } else { - withVal = (x) => ({ [name]: x }); - } + const withVal = (xs) => { + let bag; + // check if we have an object with an unnamed control (.value) + if (typeof xs === 'object' && xs.value !== undefined) { + bag = xs; // grab props that are already there + xs = xs.value; // grab the unnamed control for this one + delete bag.value; + } + if (isMulti && Array.isArray(xs)) { + const result = bag || {}; + xs.forEach((x, i) => { + if (i < names.length) { + result[names[i]] = x; + } + }); + return result; + } else if (bag) { + return { ...bag, [name]: xs }; + } else { + return { [name]: xs }; + } + }; const func = (...pats) => sequence(...pats).withValue(withVal); @@ -400,19 +406,6 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); * s(",hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>") * */ -// TODO: currently duplicated with "native" legato -// TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419 -/** - * a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample. - * - * @name legato - * @param {number | Pattern} duration between 0 and 1, where 1 is the length of the whole hap time - * @noAutocomplete - * @example - * "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>") - * - */ -// ['legato'], // ['clhatdecay'], export const { crush } = registerControl('crush'); /** @@ -1307,7 +1300,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") * */ -export const { shape } = registerControl('shape'); +export const { shape } = registerControl(['shape', 'shapevol']); /** * Wave shaping distortion. CAUTION: it can get loud. * Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output. @@ -1322,7 +1315,7 @@ export const { shape } = registerControl('shape'); * note("d1!8").s("sine").penv(36).pdecay(.12).decay(.23).distort("8:.4") * */ -export const { distort, dist } = registerControl('distort', 'dist'); +export const { distort, dist } = registerControl(['distort', 'distortvol'], 'dist'); /** * Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")` * More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) @@ -1427,8 +1420,6 @@ export const { waveloss } = registerControl('waveloss'); * */ export const { density } = registerControl('density'); -// TODO: midi effects? -export const { dur } = registerControl('dur'); // ['modwheel'], export const { expression } = registerControl('expression'); export const { sustainpedal } = registerControl('sustainpedal'); @@ -1492,16 +1483,27 @@ export const { val } = registerControl('val'); export const { cps } = registerControl('cps'); /** * Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. - * In tidal, this would be done with legato, [which has a complicated history in strudel](https://github.com/tidalcycles/strudel/issues/111). - * For now, if you're coming from tidal, just think clip = legato. * * @name clip + * @synonyms legato * @param {number | Pattern} factor >= 0 * @example * note("c a f e").s("piano").clip("<.5 1 2>") * */ -export const { clip } = registerControl('clip'); +export const { clip, legato } = registerControl('clip', 'legato'); + +/** + * Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration. + * + * @name duration + * @synonyms dur + * @param {number | Pattern} seconds >= 0 + * @example + * note("c a f e").s("piano").dur("<.5 1 2>") + * + */ +export const { duration, dur } = registerControl('duration', 'dur'); // ZZFX export const { zrand } = registerControl('zrand'); diff --git a/packages/core/draw.mjs b/packages/core/draw.mjs index 7a9454f2..30de7ba8 100644 --- a/packages/core/draw.mjs +++ b/packages/core/draw.mjs @@ -134,7 +134,7 @@ export class Drawer { this.lastFrame = phase; this.visibleHaps = (this.visibleHaps || []) // filter out haps that are too far in the past (think left edge of screen for pianoroll) - .filter((h) => h.whole?.end >= phase - lookbehind - lookahead) + .filter((h) => h.endClipped >= phase - lookbehind - lookahead) // add new haps with onset (think right edge bars scrolling in) .concat(haps.filter((h) => h.hasOnset())); const time = phase - lookahead; diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index 7a952cf8..ad0b0148 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -41,11 +41,17 @@ const _bjork = function (n, x) { }; export const bjork = function (ons, steps) { + const inverted = ons < 0; + ons = Math.abs(ons); const offs = steps - ons; const x = Array(ons).fill([1]); const y = Array(offs).fill([0]); const result = _bjork([ons, offs], [x, y]); - return flatten(result[1][0]).concat(flatten(result[1][1])); + const p = flatten(result[1][0]).concat(flatten(result[1][1])); + if (inverted) { + return p.map((x) => (x === 0 ? 1 : 0)); + } + return p; }; /** diff --git a/packages/core/hap.mjs b/packages/core/hap.mjs index 6a1eb987..ab5f0c97 100644 --- a/packages/core/hap.mjs +++ b/packages/core/hap.mjs @@ -3,6 +3,7 @@ hap.mjs - Copyright (C) 2022 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 . */ +import Fraction from './fraction.mjs'; export class Hap { /* @@ -32,7 +33,16 @@ export class Hap { } get duration() { - return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1); + let duration; + if (typeof this.value?.duration === 'number') { + duration = Fraction(this.value.duration); + } else { + duration = this.whole.end.sub(this.whole.begin); + } + if (typeof this.value?.clip === 'number') { + return duration.mul(this.value.clip); + } + return duration; } get endClipped() { diff --git a/packages/core/logger.mjs b/packages/core/logger.mjs index 16d38f91..e13bf86c 100644 --- a/packages/core/logger.mjs +++ b/packages/core/logger.mjs @@ -1,6 +1,16 @@ export const logKey = 'strudel.log'; +let debounce = 1000, + lastMessage, + lastTime; + export function logger(message, type, data = {}) { + let t = performance.now(); + if (lastMessage === message && t - lastTime < debounce) { + return; + } + lastMessage = message; + lastTime = t; console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px'); if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') { document.dispatchEvent( diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 3d859728..00f20adf 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -896,16 +896,15 @@ addToPrototype('weaveWith', function (t, ...funcs) { ////////////////////////////////////////////////////////////////////// // compose matrix functions -// TODO - adopt value.mjs fully.. +function _nonArrayObject(x) { + return !Array.isArray(x) && typeof x === 'object'; +} function _composeOp(a, b, func) { - function _nonFunctionObject(x) { - return x instanceof Object && !(x instanceof Function); - } - if (_nonFunctionObject(a) || _nonFunctionObject(b)) { - if (!_nonFunctionObject(a)) { + if (_nonArrayObject(a) || _nonArrayObject(b)) { + if (!_nonArrayObject(a)) { a = { value: a }; } - if (!_nonFunctionObject(b)) { + if (!_nonArrayObject(b)) { b = { value: b }; } return unionWithObj(a, b, func); @@ -2203,12 +2202,6 @@ export const bypass = register('bypass', function (on, pat) { */ export const ribbon = register('ribbon', (offset, cycles, pat) => pat.early(offset).restart(pure(1).slow(cycles))); -// sets absolute duration of haps -// TODO - fix -export const duration = register('duration', function (value, pat) { - return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value))); -}); - export const hsla = register('hsla', (h, s, l, a, pat) => { return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`); }); @@ -2241,21 +2234,6 @@ export const velocity = register('velocity', function (velocity, pat) { return pat.withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity })); }); -/** - * - * Multiplies the hap duration with the given factor. - * With samples, `clip` might be a better function to use ([more info](https://github.com/tidalcycles/strudel/pull/598)) - * @name legato - * @memberof Pattern - * @example - * note("c3 eb3 g3 c4").legato("<.25 .5 1 2>") - */ -// TODO - fix -export const legato = register('legato', function (value, pat) { - value = Fraction(value); - return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value)))); -}); - ////////////////////////////////////////////////////////////////////// // Control-related functions, i.e. ones that manipulate patterns of // objects diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index c70805d8..7e9a9099 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -244,9 +244,68 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { return pat.apply(pickmod(lookup, funcs)); }); +/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickOut = register('pickOut', function (lookup, pat) { + return _pick(lookup, pat, false).outerJoin(); +}); + +/** * The same as `pickOut`, but if you pick a number greater than the size of the list, + * it wraps around, rather than sticking at the maximum value. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickmodOut = register('pickmodOut', function (lookup, pat) { + return _pick(lookup, pat, true).outerJoin(); +}); + +/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickRestart = register('pickRestart', function (lookup, pat) { + return _pick(lookup, pat, false).trigzeroJoin(); +}); + +/** * The same as `pickRestart`, but if you pick a number greater than the size of the list, + * it wraps around, rather than sticking at the maximum value. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickmodRestart = register('pickmodRestart', function (lookup, pat) { + return _pick(lookup, pat, true).trigzeroJoin(); +}); + +/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickReset = register('pickReset', function (lookup, pat) { + return _pick(lookup, pat, false).trigJoin(); +}); + +/** * The same as `pickReset`, but if you pick a number greater than the size of the list, + * it wraps around, rather than sticking at the maximum value. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickmodReset = register('pickmodReset', function (lookup, pat) { + return _pick(lookup, pat, true).trigJoin(); +}); + /** /** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). * Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern. + * @name inhabit + * @synonyms pickSqueeze * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -257,21 +316,23 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { * @example * s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4) */ -export const inhabit = register('inhabit', function (lookup, pat) { - return _pick(lookup, pat, true).squeezeJoin(); +export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) { + return _pick(lookup, pat, false).squeezeJoin(); }); /** * The same as `inhabit`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. * For example, if you pick the fifth pattern of a list of three, you'll get the * second one. + * @name inhabitmod + * @synonyms pickmodSqueeze * @param {Pattern} pat * @param {*} xs * @returns {Pattern} */ -export const inhabitmod = register('inhabit', function (lookup, pat) { - return _pick(lookup, pat, false).squeezeJoin(); +export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) { + return _pick(lookup, pat, true).squeezeJoin(); }); /** diff --git a/packages/core/test/controls.test.mjs b/packages/core/test/controls.test.mjs index aa66bf98..69d63645 100644 --- a/packages/core/test/controls.test.mjs +++ b/packages/core/test/controls.test.mjs @@ -25,4 +25,8 @@ describe('controls', () => { { s: 'sd', n: 4, gain: 0.5 }, ]); }); + it('should support nested controls', () => { + expect(s(mini('bd').pan(1)).firstCycleValues).toEqual([{ s: 'bd', pan: 1 }]); + expect(s(mini('bd:1').pan(1)).firstCycleValues).toEqual([{ s: 'bd', n: 1, pan: 1 }]); + }); }); diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index e7d1dae3..85fccc97 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -251,7 +251,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { nudge = 0, // TODO: is this in seconds? cut, loop, - clip = undefined, // if 1, samples will be cut off when the hap ends + clip = undefined, // if set, samples will be cut off when the hap ends n = 0, note, speed = 1, // sample playback speed diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index f180f671..3f24d0bf 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -316,7 +316,9 @@ export const superdough = async (value, deadline, hapDuration) => { coarse, crush, shape, + shapevol = 1, distort, + distortvol = 1, pan, vowel, delay = 0, @@ -458,14 +460,8 @@ export const superdough = async (value, deadline, hapDuration) => { // effects coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })); crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); - if (shape !== undefined) { - const input = Array.isArray(shape) ? { shape: shape[0], postgain: shape[1] } : { shape }; - chain.push(getWorklet(ac, 'shape-processor', input)); - } - if (distort !== undefined) { - const input = Array.isArray(distort) ? { distort: distort[0], postgain: distort[1] } : { distort }; - chain.push(getWorklet(ac, 'distort-processor', input)); - } + shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol })); + distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol })); compressorThreshold !== undefined && chain.push( diff --git a/packages/tonal/tonal.mjs b/packages/tonal/tonal.mjs index 48cfdb9f..78ec1101 100644 --- a/packages/tonal/tonal.mjs +++ b/packages/tonal/tonal.mjs @@ -103,6 +103,8 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval; return hap.withValue(() => hap.value + semitones); } + if (typeof hap.value === 'object') + return hap.withValue(() => ({ ...hap.value, note: Note.simplify(Note.transpose(hap.value.note, interval)) })); // TODO: move simplify to player to preserve enharmonics // tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3 return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval))); @@ -133,6 +135,11 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n if (!hap.context.scale) { throw new Error('can only use scaleTranspose after .scale'); } + if (typeof hap.value === 'object') + return hap.withValue(() => ({ + ...hap.value, + note: scaleOffset(hap.context.scale, Number(offset), hap.value.note), + })); if (typeof hap.value !== 'string') { throw new Error('can only use scaleTranspose with notes'); } diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 19dbb804..a176d2cc 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -15,7 +15,6 @@ const hap2value = (hap) => { return { ...hap.value, velocity: hap.context.velocity }; }; -// TODO: bind logger export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps); export const webaudioOutput = (hap, deadline, hapDuration) => superdough(hap2value(hap), deadline, hapDuration); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index d6c0fe70..08335f1b 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1971,55 +1971,55 @@ exports[`runs examples > example "distort" example index 0 1`] = ` "[ 11/4 → 23/8 | s:hh distort:3 ]", "[ 11/4 → 3/1 | s:sd distort:3 ]", "[ 23/8 → 3/1 | s:hh distort:3 ]", - "[ 3/1 → 25/8 | s:hh distort:[10 0.5] ]", - "[ 3/1 → 13/4 | s:bd distort:[10 0.5] ]", - "[ 25/8 → 13/4 | s:hh distort:[10 0.5] ]", - "[ 13/4 → 27/8 | s:hh distort:[10 0.5] ]", - "[ 13/4 → 7/2 | s:sd distort:[10 0.5] ]", - "[ 27/8 → 7/2 | s:hh distort:[10 0.5] ]", - "[ 7/2 → 29/8 | s:hh distort:[10 0.5] ]", - "[ 29/8 → 15/4 | s:bd distort:[10 0.5] ]", - "[ 29/8 → 15/4 | s:hh distort:[10 0.5] ]", - "[ 15/4 → 31/8 | s:hh distort:[10 0.5] ]", - "[ 15/4 → 4/1 | s:sd distort:[10 0.5] ]", - "[ 31/8 → 4/1 | s:hh distort:[10 0.5] ]", + "[ 3/1 → 25/8 | s:hh distort:10 distortvol:0.5 ]", + "[ 3/1 → 13/4 | s:bd distort:10 distortvol:0.5 ]", + "[ 25/8 → 13/4 | s:hh distort:10 distortvol:0.5 ]", + "[ 13/4 → 27/8 | s:hh distort:10 distortvol:0.5 ]", + "[ 13/4 → 7/2 | s:sd distort:10 distortvol:0.5 ]", + "[ 27/8 → 7/2 | s:hh distort:10 distortvol:0.5 ]", + "[ 7/2 → 29/8 | s:hh distort:10 distortvol:0.5 ]", + "[ 29/8 → 15/4 | s:bd distort:10 distortvol:0.5 ]", + "[ 29/8 → 15/4 | s:hh distort:10 distortvol:0.5 ]", + "[ 15/4 → 31/8 | s:hh distort:10 distortvol:0.5 ]", + "[ 15/4 → 4/1 | s:sd distort:10 distortvol:0.5 ]", + "[ 31/8 → 4/1 | s:hh distort:10 distortvol:0.5 ]", ] `; exports[`runs examples > example "distort" example index 1 1`] = ` [ - "[ 0/1 → 1/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 1/8 → 1/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 1/4 → 3/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 3/8 → 1/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 1/2 → 5/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 5/8 → 3/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 3/4 → 7/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 7/8 → 1/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 1/1 → 9/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 9/8 → 5/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 5/4 → 11/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 11/8 → 3/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 3/2 → 13/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 13/8 → 7/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 7/4 → 15/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 15/8 → 2/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 2/1 → 17/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 17/8 → 9/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 9/4 → 19/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 19/8 → 5/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 5/2 → 21/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 21/8 → 11/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 11/4 → 23/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 23/8 → 3/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 3/1 → 25/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 25/8 → 13/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 13/4 → 27/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 27/8 → 7/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 7/2 → 29/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 29/8 → 15/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 15/4 → 31/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 31/8 → 4/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", + "[ 0/1 → 1/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 1/8 → 1/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 1/4 → 3/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 3/8 → 1/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 1/2 → 5/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 5/8 → 3/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 3/4 → 7/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 7/8 → 1/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 1/1 → 9/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 9/8 → 5/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 5/4 → 11/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 11/8 → 3/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 3/2 → 13/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 13/8 → 7/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 7/4 → 15/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 15/8 → 2/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 2/1 → 17/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 17/8 → 9/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 9/4 → 19/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 19/8 → 5/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 5/2 → 21/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 21/8 → 11/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 11/4 → 23/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 23/8 → 3/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 3/1 → 25/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 25/8 → 13/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 13/4 → 27/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 27/8 → 7/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 7/2 → 29/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 29/8 → 15/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 15/4 → 31/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 31/8 → 4/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", ] `; @@ -2091,6 +2091,27 @@ exports[`runs examples > example "dry" example index 0 1`] = ` ] `; +exports[`runs examples > example "duration" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:c s:piano duration:0.5 ]", + "[ 1/4 → 1/2 | note:a s:piano duration:0.5 ]", + "[ 1/2 → 3/4 | note:f s:piano duration:0.5 ]", + "[ 3/4 → 1/1 | note:e s:piano duration:0.5 ]", + "[ 1/1 → 5/4 | note:c s:piano duration:1 ]", + "[ 5/4 → 3/2 | note:a s:piano duration:1 ]", + "[ 3/2 → 7/4 | note:f s:piano duration:1 ]", + "[ 7/4 → 2/1 | note:e s:piano duration:1 ]", + "[ 2/1 → 9/4 | note:c s:piano duration:2 ]", + "[ 9/4 → 5/2 | note:a s:piano duration:2 ]", + "[ 5/2 → 11/4 | note:f s:piano duration:2 ]", + "[ 11/4 → 3/1 | note:e s:piano duration:2 ]", + "[ 3/1 → 13/4 | note:c s:piano duration:0.5 ]", + "[ 13/4 → 7/2 | note:a s:piano duration:0.5 ]", + "[ 7/2 → 15/4 | note:f s:piano duration:0.5 ]", + "[ 15/4 → 4/1 | note:e s:piano duration:0.5 ]", +] +`; + exports[`runs examples > example "early" example index 0 1`] = ` [ "[ -1/10 ⇜ (0/1 → 2/5) | s:hh ]", @@ -3611,48 +3632,6 @@ exports[`runs examples > example "layer" example index 0 1`] = ` ] `; -exports[`runs examples > example "legato" example index 0 1`] = ` -[ - "[ 0/1 → 1/32 | c4 ]", - "[ 1/4 → 9/32 | eb4 ]", - "[ 1/2 → 17/32 | g4 ]", - "[ 3/4 → 25/32 | bb4 ]", - "[ 1/1 → 17/16 | c4 ]", - "[ 5/4 → 21/16 | eb4 ]", - "[ 3/2 → 25/16 | g4 ]", - "[ 7/4 → 29/16 | bb4 ]", - "[ 2/1 → 17/8 | c4 ]", - "[ 9/4 → 19/8 | eb4 ]", - "[ 5/2 → 21/8 | g4 ]", - "[ 11/4 → 23/8 | bb4 ]", - "[ 3/1 → 51/16 | c4 ]", - "[ 13/4 → 55/16 | eb4 ]", - "[ 7/2 → 59/16 | g4 ]", - "[ 15/4 → 63/16 | bb4 ]", -] -`; - -exports[`runs examples > example "legato" example index 0 2`] = ` -[ - "[ 0/1 → 1/16 | note:c3 ]", - "[ 1/4 → 5/16 | note:eb3 ]", - "[ 1/2 → 9/16 | note:g3 ]", - "[ 3/4 → 13/16 | note:c4 ]", - "[ 1/1 → 9/8 | note:c3 ]", - "[ 5/4 → 11/8 | note:eb3 ]", - "[ 3/2 → 13/8 | note:g3 ]", - "[ 7/4 → 15/8 | note:c4 ]", - "[ 2/1 → 9/4 | note:c3 ]", - "[ 9/4 → 5/2 | note:eb3 ]", - "[ 5/2 → 11/4 | note:g3 ]", - "[ 11/4 → 3/1 | note:c4 ]", - "[ 3/1 → 7/2 | note:c3 ]", - "[ 13/4 → 15/4 | note:eb3 ]", - "[ 7/2 → 4/1 | note:g3 ]", - "[ 15/4 → 17/4 | note:c4 ]", -] -`; - exports[`runs examples > example "leslie" example index 0 1`] = ` [ "[ 0/1 → 1/1 | n:0 s:supersquare leslie:0 ]", diff --git a/website/src/pages/learn/time-modifiers.mdx b/website/src/pages/learn/time-modifiers.mdx index e1226512..1baef9ea 100644 --- a/website/src/pages/learn/time-modifiers.mdx +++ b/website/src/pages/learn/time-modifiers.mdx @@ -34,11 +34,7 @@ Some of these have equivalent operators in the Mini Notation: -## legato - - - -## clip +## clip / legato