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.