Merge branch 'main' into uzu

This commit is contained in:
Felix Roos 2025-06-18 21:26:51 +02:00
commit 28cd7c1681
No known key found for this signature in database
186 changed files with 3188 additions and 2138 deletions

View file

@ -113,7 +113,7 @@ self.onconnect = function (e) {
port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
};
// used to consistently schedule events, for use in a service worker - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/clockworker.mjs>
// used to consistently schedule events, for use in a service worker - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/clockworker.mjs>
function createClock(
getTime,
callback, // called slightly before each cycle

View file

@ -1,6 +1,6 @@
/*
controls.mjs - Registers audio controls for pattern manipulation and effects.
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/controls.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/controls.mjs>
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 <https://www.gnu.org/licenses/>.
*/
@ -106,7 +106,7 @@ export const { source, src } = registerControl('source', 'src');
* @example
* s("bd sd [~ bd] sd,hh*6").n("<0 1>")
*/
// also see https://github.com/tidalcycles/strudel/pull/63
// also see https://codeberg.org/uzu/strudel/pulls/63
export const { n } = registerControl('n');
/**
* Plays the given note name or midi number. A note name consists of
@ -358,7 +358,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], '
* s("bd sd [~ bd] sd").bpf(500).bpq("<0 1 2 3>")
*
*/
// currently an alias of 'bandq' https://github.com/tidalcycles/strudel/issues/496
// currently an alias of 'bandq' https://codeberg.org/uzu/strudel/issues/496
// ['bpq'],
export const { bandq, bpq } = registerControl('bandq', 'bpq');
/**
@ -455,6 +455,32 @@ export const { coarse } = registerControl('coarse');
*/
export const { drive } = registerControl('drive');
/**
* Create byte beats with custom expressions
*
* @name byteBeatExpression
* @synonyms bbexpr
*
* @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat
* @example
* s("bytebeat").bbexpr('t*(t>>15^t>>66)')
*
*/
export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr');
/**
* Create byte beats with custom expressions
*
* @name byteBeatStartTime
* @synonyms bbst
*
* @param {number | Pattern} byteBeatStartTime in samples (t)
* @example
* note("c3!8".add("{0 0 12 0 7 5 3}%8")).s("bytebeat:5").bbst("<3 1>".mul(10000))._scope()
*
*/
export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst');
/**
* Allows you to set the output channels on the interface
*
@ -468,6 +494,41 @@ export const { drive } = registerControl('drive');
*/
export const { channels, ch } = registerControl('channels', 'ch');
/**
* controls the pulsewidth of the pulse oscillator
*
* @name pw
* @param {number | Pattern} pulsewidth
* @example
* note("{f a c e}%16").s("pulse").pw(".8:1:.2")
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1")
*/
export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']);
/**
* controls the lfo rate for the pulsewidth of the pulse oscillator
*
* @name pwrate
* @param {number | Pattern} rate
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
*
*/
export const { pwrate } = registerControl('pwrate');
/**
* controls the lfo sweep for the pulsewidth of the pulse oscillator
*
* @name pwsweep
* @param {number | Pattern} sweep
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
*
*/
export const { pwsweep } = registerControl('pwsweep');
/**
* Phaser audio effect that approximates popular guitar pedals.
*
@ -804,7 +865,7 @@ export const { fanchor } = registerControl('fanchor');
* s("bd sd [~ bd] sd,hh*8").hpf("<2000 2000:25>")
*
*/
// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496
// currently an alias of 'hcutoff' https://codeberg.org/uzu/strudel/issues/496
// ['hpf'],
/**
* Applies a vibrato to the frequency of the oscillator.
@ -871,7 +932,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq');
* s("bd sd [~ bd] sd,hh*8").lpf(2000).lpq("<0 10 20 30>")
*
*/
// currently an alias of 'resonance' https://github.com/tidalcycles/strudel/issues/496
// currently an alias of 'resonance' https://codeberg.org/uzu/strudel/issues/496
export const { resonance, lpq } = registerControl('resonance', 'lpq');
/**
* DJ filter, below 0.5 is low pass filter, above is high pass filter.
@ -1237,7 +1298,7 @@ export const { semitone } = registerControl('semitone');
// TODO: synth param
export const { voice } = registerControl('voice');
// voicings // https://github.com/tidalcycles/strudel/issues/506
// voicings // https://codeberg.org/uzu/strudel/issues/506
// chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings
export const { chord } = registerControl('chord');
// which dictionary to use for the voicings
@ -1471,7 +1532,7 @@ export const { vowel } = registerControl('vowel');
* @name waveloss
*/
export const { waveloss } = registerControl('waveloss');
/*
/**
* Noise crackle density
*
* @name density

View file

@ -1,6 +1,6 @@
/*
cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/neocyclist.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/cyclist.mjs>
cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/neocyclist.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/cyclist.mjs>
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 <https://www.gnu.org/licenses/>.
*/
@ -65,7 +65,7 @@ export class Cyclist {
(hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency;
const duration = hap.duration / this.cps;
// the following line is dumb and only here for backwards compatibility
// see https://github.com/tidalcycles/strudel/pull/1004
// see https://codeberg.org/uzu/strudel/pulls/1004
const deadline = targetTime - phase;
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {

View file

@ -1,6 +1,6 @@
/*
drawLine.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/drawLine.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/drawLine.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -2,7 +2,7 @@
euclid.mjs - Bjorklund/Euclidean/Diaspora rhythms
Copyright (C) 2023 Rohan Drape and strudel contributors
See <https://github.com/tidalcycles/strudel/blob/main/packages/core/euclid.mjs> for authors of this file.
See <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/euclid.mjs> for authors of this file.
The Bjorklund algorithm implementation is ported from the Haskell Music Theory Haskell module by Rohan Drape -
https://rohandrape.net/?t=hmt
@ -10,9 +10,9 @@ https://rohandrape.net/?t=hmt
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 <https://www.gnu.org/licenses/>.
*/
import { Pattern, timeCat, register, silence } from './pattern.mjs';
import { timeCat, register, silence } from './pattern.mjs';
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
import Fraction from './fraction.mjs';
import Fraction, { lcm } from './fraction.mjs';
const left = function (n, x) {
const [ons, offs] = n;
@ -42,29 +42,26 @@ 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]);
const p = flatten(result[1][0]).concat(flatten(result[1][1]));
if (inverted) {
return p.map((x) => (x === 0 ? 1 : 0));
}
return p;
const absOns = Math.abs(ons);
const offs = steps - absOns;
const ones = Array(absOns).fill([1]);
const zeros = Array(offs).fill([0]);
const result = _bjork([absOns, offs], [ones, zeros]);
const pattern = flatten(result[1][0]).concat(flatten(result[1][1]));
return inverted ? pattern.map((x) => 1 - x) : pattern;
};
/**
* Changes the structure of the pattern to form an euclidean rhythm.
* Euclidian rhythms are rhythms obtained using the greatest common
* Changes the structure of the pattern to form an Euclidean rhythm.
* Euclidean rhythms are rhythms obtained using the greatest common
* divisor of two numbers. They were described in 2004 by Godfried
* Toussaint, a canadian computer scientist. Euclidian rhythms are
* Toussaint, a Canadian computer scientist. Euclidean rhythms are
* really useful for computer/algorithmic music because they can
* describe a large number of rhythms with a couple of numbers.
*
* @memberof Pattern
* @name euclid
* @param {number} pulses the number of onsets / beats
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @returns Pattern
* @example
@ -76,7 +73,7 @@ export const bjork = function (ons, steps) {
* Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
* @memberof Pattern
* @name euclidRot
* @param {number} pulses the number of onsets / beats
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param {number} rotation offset in steps
* @returns Pattern
@ -86,13 +83,13 @@ export const bjork = function (ons, steps) {
*/
/**
* @example // A thirteenth century Persian rhythm called Khafif-e-ramal.
* @example // A thirteenth-century Persian rhythm called Khafif-e-ramal.
* note("c3").euclid(2,5)
* @example // The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad.
* note("c3").euclid(3,4)
* @example // Another thirteenth century Persian rhythm by the name of Khafif-e-ramal, as well as a Rumanian folk-dance rhythm.
* note("c3").euclidRot(3,5,2)
* @example // A Ruchenitza rhythm used in a Bulgarian folk-dance.
* @example // A Ruchenitza rhythm used in a Bulgarian folk dance.
* note("c3").euclid(3,7)
* @example // The Cuban tresillo pattern.
* note("c3").euclid(3,8)
@ -151,8 +148,10 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun
* so there will be no gaps.
* @name euclidLegato
* @memberof Pattern
* @param {number} pulses the number of onsets / beats
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param rotation offset in steps
* @param pat
* @example
* note("c3").euclidLegato(3,8)
*/
@ -161,13 +160,13 @@ const _euclidLegato = function (pulses, steps, rotation, pat) {
if (pulses < 1) {
return silence;
}
const bin_pat = _euclidRot(pulses, steps, rotation);
const bin_pat = _euclidRot(pulses, steps, 0);
const gapless = bin_pat
.join('')
.split('1')
.slice(1)
.map((s) => [s.length + 1, true]);
return pat.struct(timeCat(...gapless));
return pat.struct(timeCat(...gapless)).late(Fraction(rotation).div(steps));
};
export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) {
@ -180,7 +179,7 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
* the resulting sequence
* @name euclidLegatoRot
* @memberof Pattern
* @param {number} pulses the number of onsets / beats
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param {number} rotation offset in steps
* @example

View file

@ -1,6 +1,6 @@
/*
evaluate.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/evaluate.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/evaluate.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
fraction.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/fraction.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/fraction.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
hap.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/hap.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/hap.mjs>
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 <https://www.gnu.org/licenses/>.
*/
import Fraction from './fraction.mjs';

View file

@ -1,6 +1,6 @@
/*
index.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/index.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/index.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
neocyclist.mjs - event scheduler like cyclist, except recieves clock pulses from clockworker in order to sync across multiple instances.
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/neocyclist.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/neocyclist.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
{
"name": "@strudel/core",
"version": "1.2.0",
"version": "1.2.2",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
@ -15,7 +15,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"tidalcycles",
@ -27,7 +27,7 @@
"author": "Alex McLean <alex@slab.org> (https://slab.org)",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://strudel.cc",
"dependencies": {

View file

@ -1,6 +1,6 @@
/*
pattern.mjs - Core pattern representation for strudel
Copyright (C) 2025 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/pattern.mjs>
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/pattern.mjs>
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 <https://www.gnu.org/licenses/>.
*/
@ -2444,9 +2444,14 @@ const _chunk = function (n, func, pat, back = false, fast = false) {
return pat.when(binary_pat, func);
};
export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, false);
});
export const { chunk, slowchunk, slowChunk } = register(
['chunk', 'slowchunk', 'slowChunk'],
function (n, func, pat) {
return _chunk(n, func, pat, false, false);
},
true,
true,
);
/**
* Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
@ -2458,9 +2463,14 @@ export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', '
* "0 1 2 3".chunkBack(4, x=>x.add(7))
* .scale("A:minor").note()
*/
export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) {
return _chunk(n, func, pat, true);
});
export const { chunkBack, chunkback } = register(
['chunkBack', 'chunkback'],
function (n, func, pat) {
return _chunk(n, func, pat, true);
},
true,
true,
);
/**
* Like `chunk`, but the cycles of the source pattern aren't repeated
@ -2474,9 +2484,14 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
* .fastChunk(4, x => x.color('red')).slow(2)
* .scale("C2:major").note()
*/
export const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, true);
});
export const { fastchunk, fastChunk } = register(
['fastchunk', 'fastChunk'],
function (n, func, pat) {
return _chunk(n, func, pat, false, true);
},
true,
true,
);
// TODO - redefine elsewhere in terms of mask
export const bypass = register(
@ -3180,6 +3195,25 @@ export const slice = register(
false, // turns off auto-patternification
);
/**
*
* make something happen on event time
* uses browser timeout which is innacurate for audio tasks
* @name onTriggerTime
* @memberof Pattern
* @returns Pattern
* @example
* s("bd!8").onTriggerTime((hap) => {console.info(hap)})
*/
Pattern.prototype.onTriggerTime = function (func) {
return this.onTrigger((t_deprecate, hap, currentTime, cps = 1, targetTime) => {
const diff = targetTime - currentTime;
window.setTimeout(() => {
func(hap);
}, diff * 1000);
}, false);
};
/**
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
* @name splice

View file

@ -1,6 +1,6 @@
/*
pick.mjs - methods that use one pattern to pick events from other patterns.
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
Copyright (C) 2024 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/signal.mjs>
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 <https://www.gnu.org/licenses/>.
*/
@ -156,8 +156,7 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) {
return _pick(lookup, pat, true).resetJoin();
});
/**
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
/** 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

View file

@ -230,7 +230,7 @@ export function repl({
export const getTrigger =
({ getTime, defaultOutput }) =>
async (hap, deadline, duration, cps, t) => {
// TODO: get rid of deadline after https://github.com/tidalcycles/strudel/pull/1004
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
try {
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
await defaultOutput(hap, deadline, duration, cps, t);

View file

@ -1,6 +1,6 @@
/*
signal.mjs - continuous patterns
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
Copyright (C) 2024 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/signal.mjs>
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 <https://www.gnu.org/licenses/>.
*/
@ -488,13 +488,34 @@ export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pair
export const wrandcat = wchooseCycles;
// this function expects pat to be a pattern of floats...
export const perlinWith = (pat) => {
const pata = pat.fmap(Math.floor);
const patb = pat.fmap((t) => Math.floor(t) + 1);
function _perlin(t) {
let ta = Math.floor(t);
let tb = ta + 1;
const smootherStep = (x) => 6.0 * x ** 5 - 15.0 * x ** 4 + 10.0 * x ** 3;
const interp = (x) => (a) => (b) => a + smootherStep(x) * (b - a);
return pat.sub(pata).fmap(interp).appBoth(pata.fmap(timeToRand)).appBoth(patb.fmap(timeToRand));
const v = interp(t - ta)(timeToRand(ta))(timeToRand(tb));
return v;
}
export const perlinWith = (tpat) => {
return tpat.fmap(_perlin);
};
function _berlin(t) {
const prevRidgeStartIndex = Math.floor(t);
const nextRidgeStartIndex = prevRidgeStartIndex + 1;
const prevRidgeBottomPoint = timeToRand(prevRidgeStartIndex);
const nextRidgeTopPoint = timeToRand(nextRidgeStartIndex) + prevRidgeBottomPoint;
const currentPercent = (t - prevRidgeStartIndex) / (nextRidgeStartIndex - prevRidgeStartIndex);
const interp = (a, b, t) => {
return a + (b - a) * t;
};
return interp(prevRidgeBottomPoint, nextRidgeTopPoint, currentPercent) / 2;
}
export const berlinWith = (tpat) => {
return tpat.fmap(_berlin);
};
/**
@ -508,6 +529,18 @@ export const perlinWith = (pat) => {
*/
export const perlin = perlinWith(time.fmap((v) => Number(v)));
/**
* Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful,
* like perlin noise but with sawtooth waves), in the range 0..1.
*
* @name berlin
* @example
* // ascending arpeggios
* n("0!16".add(berlin.fast(4).mul(14))).scale("d:minor")
*
*/
export const berlin = berlinWith(time.fmap((v) => Number(v)));
export const degradeByWith = register(
'degradeByWith',
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),

View file

@ -1,6 +1,6 @@
/*
speak.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/speak.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/speak.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
state.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/state.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/state.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
controls.test.mjs - <short description TODO>
Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/controls.test.mjs>
Copyright (C) 2023 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/controls.test.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
drawLine.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/drawLine.test.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/drawLine.test.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -0,0 +1,89 @@
import { bjork } from '../euclid.mjs';
import { describe, expect, it } from 'vitest';
import { fastcat } from '../pattern.mjs';
describe('bjork', () => {
it('should apply bjorklund to ons and steps', () => {
expect(bjork(3, 8)).toStrictEqual([1, 0, 0, 1, 0, 0, 1, 0]);
expect(bjork(-3, 8)).toStrictEqual([0, 1, 1, 0, 1, 1, 0, 1]);
expect(bjork(8, 8)).toStrictEqual([1, 1, 1, 1, 1, 1, 1, 1]);
expect(bjork(-8, 8)).toStrictEqual([0, 0, 0, 0, 0, 0, 0, 0]);
expect(bjork(5, 8)).toStrictEqual([1, 0, 1, 1, 0, 1, 1, 0]);
});
});
describe('euclid', () => {
it('Can create euclid', () => {
expect(
fastcat('a')
.euclid(3, 8)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/8: a', '3/8 → 1/2: a', '3/4 → 7/8: a']);
expect(
fastcat('a')
.euclid(5, 8)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '3/8 → 1/2: a', '5/8 → 3/4: a', '3/4 → 7/8: a']);
});
});
describe('euclidRot', () => {
it('Can create euclidRot', () => {
expect(
fastcat('a')
.euclidRot(3, 8, 2)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '5/8 → 3/4: a']);
expect(
fastcat('a')
.euclidRot(5, 8, 2)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '1/2 → 5/8: a', '5/8 → 3/4: a', '7/8 → 1/1: a']);
});
});
describe('euclidLegato', () => {
it('Can create euclidLegato', () => {
expect(
fastcat('a')
.euclidLegato(3, 8)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 3/8: a', '3/8 → 3/4: a', '3/4 → 1/1: a']);
expect(
fastcat('a')
.euclidLegato(5, 8)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/4: a', '1/4 → 3/8: a', '3/8 → 5/8: a', '5/8 → 3/4: a', '3/4 → 1/1: a']);
});
});
describe('euclidLegatoRot', () => {
it('Can create euclidLegatoRot', () => {
expect(
fastcat('a')
.euclidLegatoRot(3, 8, 2)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/4: a', '1/4 → 5/8: a', '5/8 → 1/1: a']);
expect(
fastcat('a')
.euclidLegatoRot(5, 8, 2)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/4: a', '1/4 → 1/2: a', '1/2 → 5/8: a', '5/8 → 7/8: a', '7/8 → 1/1: a']);
});
});

View file

@ -1,6 +1,6 @@
/*
fraction.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/fraction.test.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/fraction.test.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
pattern.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/pattern.test.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/pattern.test.mjs>
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 <https://www.gnu.org/licenses/>.
*/
@ -884,7 +884,7 @@ describe('Pattern', () => {
);
});
it('Doesnt drop haps in the 9th cycle', () => {
// fixed with https://github.com/tidalcycles/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
// fixed with https://codeberg.org/uzu/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
expect(sequence(1, 2, 3).ply(2).early(8).firstCycle().length).toBe(6);
});
});

View file

@ -1,26 +1,30 @@
/*
util.test.mjs - Tests for the core 'util' module
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/util.test.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/util.test.mjs>
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 <https://www.gnu.org/licenses/>.
*/
import { pure } from '../pattern.mjs';
import {
isNote,
tokenizeNote,
noteToMidi,
midiToFreq,
freqToMidi,
_mod,
compose,
flatten,
fractionalArgs,
freqToMidi,
getFrequency,
getPlayableNoteValue,
parseNumeral,
parseFractional,
isNote,
midiToFreq,
noteToMidi,
numeralArgs,
fractionalArgs,
parseFractional,
parseNumeral,
rotate,
splitAt,
tokenizeNote,
zipWith,
} from '../util.mjs';
import { describe, it, expect } from 'vitest';
import { describe, expect, it } from 'vitest';
describe('isNote', () => {
it('should recognize notes without accidentals', () => {
@ -233,3 +237,46 @@ describe('fractionalArgs', () => {
expect(add('q', 2)).toBe(2.25);
});
});
describe('rotate', () => {
it('should rotate array to the left', () => {
expect(rotate([0, 1, 2, 3], 2)).toStrictEqual([2, 3, 0, 1]);
expect(rotate([0, 1, 2, 3], 0)).toStrictEqual([0, 1, 2, 3]);
expect(rotate([0, 1, 2, 3], -3)).toStrictEqual([1, 2, 3, 0]);
expect(rotate([0, 1, 2, 3], 3)).toStrictEqual([3, 0, 1, 2]);
expect(rotate([0], 3)).toStrictEqual([0]);
expect(rotate([], 3)).toStrictEqual([]);
});
});
describe('flatten', () => {
it('should flatten array by one level', () => {
expect(flatten([0, 1, 2, 3])).toStrictEqual([0, 1, 2, 3]);
expect(flatten([0, 1, [2, 3]])).toStrictEqual([0, 1, 2, 3]);
expect(flatten([0, [1, [2, 3]]])).toStrictEqual([0, 1, [2, 3]]);
expect(flatten([0])).toStrictEqual([0]);
expect(flatten([])).toStrictEqual([]);
});
});
describe('splitAt', () => {
it('should split array into two', () => {
expect(splitAt(2, [0, 1, 2, 3])).toStrictEqual([
[0, 1],
[2, 3],
]);
expect(splitAt(0, [0, 1, 2, 3])).toStrictEqual([[], [0, 1, 2, 3]]);
expect(splitAt(-3, [0, 1, 2, 3])).toStrictEqual([[0], [1, 2, 3]]);
expect(splitAt(3, [0, 1, 2, 3])).toStrictEqual([[0, 1, 2], [3]]);
});
});
describe('zipWith', () => {
it('should use the function to combine the two arrays element-wise', () => {
expect(zipWith((a, b) => a + b, [0, 1, 2, 3], [0, 1, 2, 3])).toStrictEqual([0, 2, 4, 6]);
expect(zipWith((a, b) => a + b, [0, 1, 2, 3], [0, 1, 2])).toStrictEqual([0, 2, 4, NaN]);
expect(zipWith((a, b) => a + b, [0, 1, 2], [0, 1, 2, 3])).toStrictEqual([0, 2, 4]);
expect(zipWith((a) => a, [0, 1, 2], [1, 2, 3, 0])).toStrictEqual([0, 1, 2]);
expect(zipWith((a, b) => b, [0, 1, 2], [1, 2, 3, 0])).toStrictEqual([1, 2, 3]);
});
});

View file

@ -1,6 +1,6 @@
/*
value.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/value.test.mjs>
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/activity/contributors>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
timespan.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/timespan.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/timespan.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
ui.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/ui.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/ui.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
util.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/util.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/util.mjs>
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 <https://www.gnu.org/licenses/>.
*/
@ -162,6 +162,7 @@ export const compose = (...funcs) => pipe(...funcs.reverse());
// Removes 'None' values from given list
export const removeUndefineds = (xs) => xs.filter((x) => x != undefined);
// flattens by one level
export const flatten = (arr) => [].concat(...arr);
export const id = (a) => a;
@ -237,6 +238,7 @@ export const splitAt = function (index, value) {
return [value.slice(0, index), value.slice(index)];
};
// Uses the function f to combine the arrays xs, ys element-wise
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
export const pairs = function (xs) {

View file

@ -1,6 +1,6 @@
/*
value.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/value.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/value.mjs>
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 <https://www.gnu.org/licenses/>.
*/
@ -9,7 +9,7 @@ import { logger } from './logger.mjs';
export function unionWithObj(a, b, func) {
if (b?.value !== undefined && Object.keys(b).length === 1) {
// https://github.com/tidalcycles/strudel/issues/1026
// https://codeberg.org/uzu/strudel/issues/1026
logger(`[warn]: Can't do arithmetic on control pattern.`);
return a;
}