diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs
index a13674f1..9a4b5fe5 100644
--- a/packages/core/signal.mjs
+++ b/packages/core/signal.mjs
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Hap } from './hap.mjs';
-import { Pattern, fastcat, reify, silence, stack } from './pattern.mjs';
+import { Pattern, fastcat, reify, silence, stack, isPattern } from './pattern.mjs';
import Fraction from './fraction.mjs';
import { id } from './util.mjs';
@@ -156,8 +156,8 @@ export const chooseInWith = (pat, xs) => {
};
/**
- * Chooses randomly from the given list of values.
- * @param {...any} xs
+ * Chooses randomly from the given list of elements.
+ * @param {...any} xs values / patterns to choose from.
* @returns {Pattern} - a continuous pattern.
*/
export const choose = (...xs) => chooseWith(rand, xs);
@@ -183,6 +183,14 @@ Pattern.prototype.choose2 = function (...xs) {
return chooseWith(this._fromBipolar(), xs);
};
+/**
+ * Picks one of the elements at random each cycle.
+ * @returns {Pattern}
+ * @example
+ * chooseCycles("bd", "hh", "sd").s().fast(4).out()
+ * @example
+ * "bd | hh | sd".s().fast(4).out()
+ */
export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
export const randcat = chooseCycles;
@@ -223,14 +231,53 @@ Pattern.prototype._degradeByWith = function (withPat, x) {
return this.fmap((a) => (_) => a).appLeft(withPat._filterValues((v) => v > x));
};
+/**
+ * Randomly removes events from the pattern by a given amount.
+ * 0 = 0% chance of removal
+ * 1 = 100% chance of removal
+ *
+ * @name degradeBy
+ * @memberof Pattern
+ * @param {number} amount - a number between 0 and 1
+ * @returns Pattern
+ * @example
+ * s("hh*8").degradeBy(0.2).out()
+ * @example
+ * s("[hh?0.2]*8").out()
+ */
Pattern.prototype._degradeBy = function (x) {
return this._degradeByWith(rand, x);
};
+/**
+ *
+ * Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)`
+ *
+ * @name degrade
+ * @memberof Pattern
+ * @returns Pattern
+ * @example
+ * s("hh*8").degrade().out()
+ * @example
+ * s("[hh?]*8").out()
+ */
Pattern.prototype.degrade = function () {
return this._degradeBy(0.5);
};
+/**
+ * Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
+ * 0 = 100% chance of removal
+ * 1 = 0% chance of removal
+ * Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example).
+ *
+ * @name undegradeBy
+ * @memberof Pattern
+ * @param {number} amount - a number between 0 and 1
+ * @returns Pattern
+ * @example
+ * s("hh*8").undegradeBy(0.2).out()
+ */
Pattern.prototype._undegradeBy = function (x) {
return this._degradeByWith(
rand.fmap((r) => 1 - r),
@@ -246,6 +293,25 @@ Pattern.prototype._sometimesBy = function (x, func) {
return stack(this._degradeBy(x), func(this._undegradeBy(1 - x)));
};
+// https://github.com/tidalcycles/strudel/discussions/198
+/* Pattern.prototype._sometimesBy = function (x, other) {
+ other = typeof other === 'function' ? other(this._undegradeBy(1 - x)) : reify(other)._undegradeBy(1 - x);
+ return stack(this._degradeBy(x), other);
+}; */
+
+/**
+ *
+ * Randomly applies the given function by the given probability.
+ * Similar to {@link Pattern#someCyclesBy}
+ *
+ * @name sometimesBy
+ * @memberof Pattern
+ * @param {number | Pattern} probability - a number between 0 and 1
+ * @param {function} function - the transformation to apply
+ * @returns Pattern
+ * @example
+ * s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5")).out()
+ */
Pattern.prototype.sometimesBy = function (patx, func) {
const pat = this;
return reify(patx)
@@ -253,6 +319,7 @@ Pattern.prototype.sometimesBy = function (patx, func) {
.innerJoin();
};
+// why does this exist? it is identical to sometimesBy
Pattern.prototype._sometimesByPre = function (x, func) {
return stack(this._degradeBy(x), func(this).undegradeBy(1 - x));
};
@@ -264,6 +331,17 @@ Pattern.prototype.sometimesByPre = function (patx, func) {
.innerJoin();
};
+/**
+ *
+ * Applies the given function with a 50% chance
+ *
+ * @name sometimes
+ * @memberof Pattern
+ * @param {function} function - the transformation to apply
+ * @returns Pattern
+ * @example
+ * s("hh*4").sometimes(x=>x.speed("0.5")).out()
+ */
Pattern.prototype.sometimes = function (func) {
return this._sometimesBy(0.5, func);
};
@@ -279,6 +357,19 @@ Pattern.prototype._someCyclesBy = function (x, func) {
);
};
+/**
+ *
+ * Randomly applies the given function by the given probability on a cycle by cycle basis.
+ * Similar to {@link Pattern#sometimesBy}
+ *
+ * @name someCyclesBy
+ * @memberof Pattern
+ * @param {number | Pattern} probability - a number between 0 and 1
+ * @param {function} function - the transformation to apply
+ * @returns Pattern
+ * @example
+ * s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5")).out()
+ */
Pattern.prototype.someCyclesBy = function (patx, func) {
const pat = this;
return reify(patx)
@@ -286,30 +377,100 @@ Pattern.prototype.someCyclesBy = function (patx, func) {
.innerJoin();
};
+/**
+ *
+ * Shorthand for `.someCyclesBy(0.5, fn)`
+ *
+ * @name someCycles
+ * @memberof Pattern
+ * @returns Pattern
+ * @example
+ * s("hh(3,8)").someCycles(x=>x.speed("0.5")).out()
+ */
Pattern.prototype.someCycles = function (func) {
return this._someCyclesBy(0.5, func);
};
+/**
+ *
+ * Shorthand for `.sometimesBy(0.75, fn)`
+ *
+ * @name often
+ * @memberof Pattern
+ * @returns Pattern
+ * @example
+ * s("hh*8").often(x=>x.speed("0.5")).out()
+ */
Pattern.prototype.often = function (func) {
return this.sometimesBy(0.75, func);
};
+/**
+ *
+ * Shorthand for `.sometimesBy(0.25, fn)`
+ *
+ * @name rarely
+ * @memberof Pattern
+ * @returns Pattern
+ * @example
+ * s("hh*8").rarely(x=>x.speed("0.5")).out()
+ */
Pattern.prototype.rarely = function (func) {
return this.sometimesBy(0.25, func);
};
+/**
+ *
+ * Shorthand for `.sometimesBy(0.1, fn)`
+ *
+ * @name almostNever
+ * @memberof Pattern
+ * @returns Pattern
+ * @example
+ * s("hh*8").almostNever(x=>x.speed("0.5")).out()
+ */
Pattern.prototype.almostNever = function (func) {
return this.sometimesBy(0.1, func);
};
+/**
+ *
+ * Shorthand for `.sometimesBy(0.9, fn)`
+ *
+ * @name almostAlways
+ * @memberof Pattern
+ * @returns Pattern
+ * @example
+ * s("hh*8").almostAlways(x=>x.speed("0.5")).out()
+ */
Pattern.prototype.almostAlways = function (func) {
return this.sometimesBy(0.9, func);
};
+/**
+ *
+ * Shorthand for `.sometimesBy(0, fn)` (never calls fn)
+ *
+ * @name never
+ * @memberof Pattern
+ * @returns Pattern
+ * @example
+ * s("hh*8").never(x=>x.speed("0.5")).out()
+ */
Pattern.prototype.never = function (func) {
return this;
};
+/**
+ *
+ * Shorthand for `.sometimesBy(1, fn)` (always calls fn)
+ *
+ * @name always
+ * @memberof Pattern
+ * @returns Pattern
+ * @example
+ * s("hh*8").always(x=>x.speed("0.5")).out()
+ */
Pattern.prototype.always = function (func) {
return func(this);
};
diff --git a/packages/react/vite.config.js b/packages/react/vite.config.js
index d51ef97a..6786d022 100644
--- a/packages/react/vite.config.js
+++ b/packages/react/vite.config.js
@@ -38,7 +38,9 @@ export default defineConfig({
'@codemirror/commands',
'@lezer/highlight',
'@codemirror/language',
- '@uiw/codemirror-themes'
+ '@uiw/codemirror-themes',
+ '@uiw/react-codemirror',
+ '@lezer/highlight',
],
},
target: 'esnext',
diff --git a/tutorial/MiniRepl.jsx b/tutorial/MiniRepl.jsx
index af21f362..0f18c2b6 100644
--- a/tutorial/MiniRepl.jsx
+++ b/tutorial/MiniRepl.jsx
@@ -2,8 +2,8 @@ import { Tone } from '@strudel.cycles/tone';
import { evalScope } from '@strudel.cycles/eval';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import controls from '@strudel.cycles/core/controls.mjs';
-import * as WebDirt from 'WebDirt';
import { loadWebDirt } from '@strudel.cycles/webdirt';
+import { samples } from '@strudel.cycles/webaudio';
export const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
oscillator: { type: 'triangle' },
@@ -12,6 +12,15 @@ export const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.
},
});
+samples(
+ {
+ bd: '808bd/BD0000.WAV',
+ sd: ['808sd/SD0010.WAV', '808sd/SD0050.WAV', '808sd/SD0000.WAV'],
+ hh: ['hh27/000_hh27closedhh.wav', 'hh/000_hh3closedhh.wav'],
+ },
+ 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/',
+);
+
evalScope(
Tone,
controls,
diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx
index f8995a33..8e1fc75e 100644
--- a/tutorial/tutorial.mdx
+++ b/tutorial/tutorial.mdx
@@ -177,11 +177,11 @@ In essence, the `x!n` is like a shortcut for `[x*n]@n`.
## Euclidian
-Using round brackets, we can create rhythmical sub-divisions based on three parameters: beats, segments and offset.
-The first parameter controls how may beats will be played.
-The second parameter controls the total amount of segments the beats will be distributed over.
+Using round brackets, we can create rhythmical sub-divisions based on three parameters: beats, segments and offset.
+The first parameter controls how may beats will be played.
+The second parameter controls the total amount of segments the beats will be distributed over.
The third (optional) parameter controls the starting position for distributing the beats.
-One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,1)" or simply "(3,8)",
+One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,1)" or simply "(3,8)",
resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segments, starting on position 1).
@@ -450,6 +450,38 @@ Stacks the given pattern to the current pattern:
+## Randomness
+
+These methods add random behavior to your Patterns.
+
+{{ 'chooseCycles' | jsdoc }}
+
+{{ 'Pattern.degradeBy' | jsdoc }}
+
+{{ 'Pattern.degrade' | jsdoc }}
+
+{{ 'Pattern.undegradeBy' | jsdoc }}
+
+{{ 'Pattern.sometimesBy' | jsdoc }}
+
+{{ 'Pattern.sometimes' | jsdoc }}
+
+{{ 'Pattern.someCyclesBy' | jsdoc }}
+
+{{ 'Pattern.someCycles' | jsdoc }}
+
+{{ 'Pattern.often' | jsdoc }}
+
+{{ 'Pattern.rarely' | jsdoc }}
+
+{{ 'Pattern.almostNever' | jsdoc }}
+
+{{ 'Pattern.almostAlways' | jsdoc }}
+
+{{ 'Pattern.never' | jsdoc }}
+
+{{ 'Pattern.always' | jsdoc }}
+
## Tone API
To make the sounds more interesting, we can use Tone.js instruments ands effects.