jevstrudel.git / packages / core / signal.mjs

signal.mjs - continuous patterns 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/.

7import { Hap } from './hap.mjs';
8import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs';
9import { _mod } from './util.mjs';
10import Fraction from './fraction.mjs';
12import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
13
14export function steady(value) {
15  // A continuous value
16  return new Pattern((state) => [new Hap(undefined, state.span, value)]);
17}
18
19export const signal = (func) => {
20  const query = (state) => [new Hap(undefined, state.span, func(state.span.begin, state.controls))];
21  return new Pattern(query);
22};

A sawtooth signal between 0 and 1.

@return {Pattern} @tags generators @example note("<c3 [eb3,g3] g2 [g3,bb3]>*8") .clip(saw.slow(2)) @example n(saw.range(0,8).segment(8)) .scale('C major')

37export const saw = signal((t) => _mod(t, 1));

A sawtooth signal between -1 and 1 (like saw, but bipolar).

@return {Pattern} @tags generators

45export const saw2 = saw.toBipolar();

A sawtooth signal between 1 and 0 (like saw, but flipped).

@return {Pattern} @tags generators @example note("<c3 [eb3,g3] g2 [g3,bb3]>*8") .clip(isaw.slow(2)) @example n(isaw.range(0,8).segment(8)) .scale('C major')

60export const isaw = signal((t) => 1 - _mod(t, 1));

A sawtooth signal between 1 and -1 (like saw2, but flipped).

@return {Pattern} @tags generators

68export const isaw2 = isaw.toBipolar();

A sine signal between -1 and 1 (like sine, but bipolar).

@return {Pattern} @tags generators

76export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));

A sine signal between 0 and 1. @return {Pattern} @tags generators @example n(sine.segment(16).range(0,15)) .scale("C:minor")

87export const sine = sine2.fromBipolar();

A cosine signal between 0 and 1.

@return {Pattern} @tags generators @example n(stack(sine,cosine).segment(16).range(0,15)) .scale("C:minor")

99export const cosine = sine._early(Fraction(1).div(4));

A cosine signal between -1 and 1 (like cosine, but bipolar).

@return {Pattern} @tags generators

107export const cosine2 = sine2._early(Fraction(1).div(4));

A square signal between 0 and 1. @return {Pattern} @tags generators @example n(square.segment(4).range(0,7)).scale("C:minor")

117export const square = signal((t) => Math.floor(_mod(t * 2, 2)));

A square signal between -1 and 1 (like square, but bipolar).

@return {Pattern} @tags generators

125export const square2 = square.toBipolar();

A square signal between 1 and 0 (like square but flipped).

@return {Pattern} @tags generators

133export const isquare = signal((t) => 1 - Math.floor(_mod(t * 2, 2)));

A square signal between 1 and -1 (like isquare, but bipolar).

@return {Pattern} @tags generators

141export const isquare2 = isquare.toBipolar();

A triangle signal between 0 and 1.

@return {Pattern} @tags generators @example n(tri.segment(8).range(0,7)).scale("C:minor")

152export const tri = fastcat(saw, isaw);

A triangle signal between -1 and 1 (like tri, but bipolar).

@return {Pattern} @tags generators

160export const tri2 = fastcat(saw2, isaw2);

An inverted triangle signal between 1 and 0 (like tri, but flipped).

@return {Pattern} @tags generators @example n(itri.segment(8).range(0,7)).scale("C:minor")

171export const itri = fastcat(isaw, saw);

An inverted triangle signal between -1 and 1 (like itri, but bipolar).

@return {Pattern} @tags generators

179export const itri2 = fastcat(isaw2, saw2);

A signal representing the cycle time.

@return {Pattern} @tags generators

187export const time = signal(id);

The mouse's x position value ranges from 0 to 1. @name mousex @return {Pattern} @tags external_io @example n(mousex.segment(4).range(0,7)).scale("C:minor")

The mouse's y position value ranges from 0 to 1. @name mousey @return {Pattern} @tags external_io @example n(mousey.segment(4).range(0,7)).scale("C:minor")

208let _mouseY = 0,
209  _mouseX = 0;
210if (typeof window !== 'undefined') {
211  //document.onmousemove = (e) => {
212  document.addEventListener('mousemove', (e) => {
213    _mouseY = e.clientY / document.body.clientHeight;
214    _mouseX = e.clientX / document.body.clientWidth;
215  });
216}
218export const mousey = signal(() => _mouseY);
219export const mouseY = signal(() => _mouseY);
220export const mousex = signal(() => _mouseX);
221export const mouseX = signal(() => _mouseX);

Random number generators

Produce "Avalanche effect" where flipping a single bit of x results in all output bits flipping with probability 0.5 See e.g. https://github.com/aappleby/smhasher/blob/0ff96f7835817a27d0487325b6c16033e2992eb5/src/MurmurHash3.cpp#L68-L77

228const _murmurHashFinalizer = (x) => {
229  x |= 0;
230  x ^= x >>> 16;
231  x = Math.imul(x, 0x85ebca6b);
232  x ^= x >>> 13;
233  x = Math.imul(x, 0xc2b2ae35);
234  x ^= x >>> 16;
235  return x >>> 0; // unsigned
236};

Convert t to a 32 bit integer, preserving temporal resolution down to 1/2^29

239const _tToT = (t) => {
240  return Math.floor(t * 536870912);
241};

Used to decorrelate nearby T, i, and seed prior to hashing

244const _decorrelate = (T, i = 0, seed = 0) => {
245  const lowBits = (T >>> 0) >>> 0;
246  const highBits = Math.floor(T / 4294967296) >>> 0; // 2^32
247  let key = lowBits ^ Math.imul(highBits ^ 0x85ebca6b, 0xc2b2ae35);
248  key ^= Math.imul(i ^ 0x7f4a7c15, 0x9e3779b9);
249  key ^= Math.imul(seed ^ 0x165667b1, 0x27d4eb2d);
250  return key >>> 0;
251};
253const randAt = (T, i = 0, seed = 0) => {
254  return _murmurHashFinalizer(_decorrelate(T, i, seed)) / 4294967296; // 2^32
255};

n samples at time t

258const timeToRands = (t, n, seed = 0) => {
259  const T = _tToT(t);
260  if (n === 1) {
261    return randAt(T, 0, seed);
262  }
263  const out = new Array(n);
264  for (let i = 0; i < n; i++) out[i] = randAt(T, i, seed);
265  return out;
266};

Old random signals. Currently the default, but can also be chosen via useRNG('legacy')

stretch 300 cycles over the range of [0,2**29 == 536870912) then apply the xorshift algorithm

272const __xorwise = (x) => {
273  const a = (x << 13) ^ x;
274  const b = (a >> 17) ^ a;
275  return (b << 5) ^ b;
276};
277const __frac = (x) => x - Math.trunc(x);
278const __timeToIntSeed = (x) => __xorwise(Math.trunc(__frac(x / 300) * 536870912));
279const __intSeedToRand = (x) => (x % 536870912) / 536870912;
280const __timeToRandsPrime = (seed, n) => {
281  if (n === 1) {
282    return Math.abs(__intSeedToRand(seed));
283  }
284  const result = [];
285  for (let i = 0; i < n; i++) {
286    result.push(__intSeedToRand(seed));
287    seed = __xorwise(seed);
288  }
289  return result;
290};
291const __timeToRands = (t, n) => __timeToRandsPrime(__timeToIntSeed(t), n);

End old random

295let RNG_MODE = 'legacy';
296export const getRandsAtTime = (t, n = 1, seed = 0) => {
297  return RNG_MODE === 'legacy' ? __timeToRands(t + seed, n) : timeToRands(t, n, seed);
298};

Sets which random number generator to use. Historically Strudel would use useRNG('legacy'), which remains the default. To use a new more statistically precise RNG, try useRNG('precise').

@name useRNG @tags generators, math @param {string} mod - Mode. One of 'legacy', 'precise' @example useRNG('legacy') // Repeats every 300 cycles $: n(irand(50)).seg(16).scale("C:minor").ribbon(88, 32) $: n(irand(50)).seg(16).scale("C:minor").ribbon(388, 32)

314export const useRNG = (mode = 'legacy') => (RNG_MODE = mode);

A discrete pattern of numbers from 0 to n-1 @tags generators @example n(run(4)).scale("C4:pentatonic") // n("0 1 2 3").scale("C4:pentatonic")

323export const run = (n) => saw.range(0, n).round().segment(n);

Creates a binary pattern from a number.

@name binary @tags generators @param {number} n - input number to convert to binary @example "hh".s().struct(binary(5)) // "hh".s().struct("1 0 1")

335export const binary = (n) => {
336  const nBits = reify(n).log2().floor().add(1);
337  return binaryN(n, nBits);
338};

Creates a binary pattern from a number, padded to n bits long.

@name binaryN @tags generators @param {number} n - input number to convert to binary @param {number} nBits - pattern length, defaults to 16 @example "hh".s().struct(binaryN(55532, 16)) // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0")

351export const binaryN = (n, nBits = 16) => {
352  nBits = reify(nBits);
353  // Shift and mask, putting msb on the right-side
354  const bitPos = run(nBits).mul(-1).add(nBits.sub(1));
355  return reify(n).segment(nBits).brshift(bitPos).band(pure(1));
356};

Creates a binary list pattern from a number.

@name binaryL @tags generators @param {number} n - input number to convert to binary s("saw").seg(8) .partials(binaryL(irand(4096).add(1)))

367export const binaryL = (n) => {
368  const nBits = reify(n).log2().floor().add(1);
369  return binaryNL(n, nBits);
370};

Creates a binary list pattern from a number, padded to n bits long.

@name binaryNL @tags generators @param {number} n - input number to convert to binary @param {number} nBits - pattern length, defaults to 16

380export const binaryNL = (n, nBits = 16) => {
381  return reify(n)
382    .withValue((v) => (bits) => {
383      const bList = [];
384      for (let i = bits - 1; i >= 0; i--) {
385        bList.push((v >> i) & 1);
386      }
387      return bList;
388    })
389    .appLeft(reify(nBits));
390};

Creates a list of random numbers of the given length

@name randL @tags generators @param {number} n Number of random numbers to sample @example s("saw").seg(16).n(irand(12)).scale("F1:minor") .partials(randL(8))

402export const randL = (n) => {
403  return signal((t) => (nVal) => getRandsAtTime(t, nVal).map(Math.abs)).appLeft(reify(n));
404};
406export const randrun = (n) => {
407  return signal((t, controls) => {
408    // Without adding 0.5, the first cycle is always 0,1,2,3,...
409    let rands = getRandsAtTime(t.floor().add(0.5), n, controls.randSeed);
410    // Support n = 1
411    if (!Array.isArray(rands)) rands = [rands];
412    const nums = rands
413      .map((n, i) => [n, i])
414      .sort((a, b) => (a[0] > b[0]) - (a[0] < b[0]))
415      .map((x) => x[1]);
416    const i = _mod(t.cyclePos().mul(n).floor(), n);
417    return nums[i];
418  })._segment(n);
419};
420
421const _rearrangeWith = (ipat, n, pat) => {
422  const pats = [...Array(n).keys()].map((i) => pat.zoom(Fraction(i).div(n), Fraction(i + 1).div(n)));
423  return ipat.fmap((i) => pats[i].repeatCycles(n)._fast(n)).innerJoin();
424};

Slices a pattern into the given number of parts, then plays those parts in random order. Each part will be played exactly once per cycle. @name shuffle @tags temporal @example note("c d e f").sound("piano").shuffle(4) @example seq("c d e f".shuffle(4), "g").note().sound("piano")

436export const shuffle = register('shuffle', (n, pat) => {
437  return _rearrangeWith(randrun(n), n, pat);
438});

Slices a pattern into the given number of parts, then plays those parts at random. Similar to shuffle, but parts might be played more than once, or not at all, per cycle. @name scramble @tags temporal @example note("c d e f").sound("piano").scramble(4) @example seq("c d e f".scramble(4), "g").note().sound("piano")

450export const scramble = register('scramble', (n, pat) => {
451  return _rearrangeWith(_irand(n)._segment(n), n, pat);
452});

Modify a pattern by applying a function to the randomSeed control if present

@tags math @param {Function} func Function from seed (or undefined) to seed (or undefined) @param {Pattern} pat Pattern to update @returns Pattern

462export const withSeed = (func, pat) => {
463  return new Pattern((state) => {
464    let { randSeed, ...controls } = state.controls;
465    randSeed = func(randSeed);
466    return pat.query(state.setControls({ ...controls, randSeed }));
467  }, pat._steps);
468};

Change the seed for random signals. Normally, random signals depend on time, so two patterns at the same time will have the same random values. Specifying a new seed changes the signal output by rand. This also affects other functions that use randomness, like shuffle and sometimes.

@name seed @tags math @param {number} n A new seed. Can be any number. @example $: s("hh4").degrade(); $: s("bd4").degrade().seed(1); // Will degrade different events from the hi-hat

483export const seed = register('seed', (n, pat) => {
484  return withSeed(() => n, pat);
485});

A continuous pattern of random numbers, between 0 and 1.

@name rand @tags generators @example // randomly change the cutoff s("bd4,hh8").cutoff(rand.range(500,8000))

497export const rand = signal((t, controls) => getRandsAtTime(t, 1, controls.randSeed));

A continuous pattern of random numbers, between -1 and 1 @tags generators

502export const rand2 = rand.toBipolar();
504export const _brandBy = (p) => rand.fmap((x) => x < p);

A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1

@name brandBy @tags generators @param {number} probability - a number between 0 and 1 @example s("hh*10").pan(brandBy(0.2))

515export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();

A continuous pattern of 0 or 1 (binary random)

@name brand @tags generators @example s("hh*10").pan(brand)

525export const brand = _brandBy(0.5);
527export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));

A continuous pattern of random integers, between 0 and n-1.

@name irand @tags generators @param {number} n max value (exclusive) @example // randomly select scale notes from 0 - 7 (= C to C) n(irand(8)).struct("x x2 x x3").scale("C:minor")

540export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
542export const __chooseWith = (pat, xs) => {
543  xs = xs.map(reify);
544  if (xs.length == 0) {
545    return silence;
546  }
547
548  return pat.range(0, xs.length).fmap((i) => {
549    const key = Math.min(Math.max(Math.floor(i), 0), xs.length - 1);
550    return xs[key];
551  });
552};

Choose from the list of values (or patterns of values) using the given pattern of numbers, which should be in the range of 0..1 @tags temporal @param {Pattern} pat @param {*} xs @returns {Pattern} @example note("c2 g2!2 d2 f1").s(chooseWith(sine.fast(2), ["sawtooth", "triangle", "bd:6"]))

563export const chooseWith = (pat, xs) => {
564  return __chooseWith(pat, xs).outerJoin();
565};

As with {chooseWith}, but the structure comes from the chosen values, rather than the pattern you're using to choose with. @tags temporal @param {Pattern} pat @param {*} xs @returns {Pattern}

575export const chooseInWith = (pat, xs) => {
576  return __chooseWith(pat, xs).innerJoin();
577};

Chooses randomly from the given list of elements. @tags temporal @param {...any} xs values / patterns to choose from. @returns {Pattern} - a continuous pattern. @example note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6"))

587export const choose = (...xs) => chooseWith(rand, xs);

todo: doc

590export const chooseIn = (...xs) => chooseInWith(rand, xs);
591export const chooseOut = choose;

Chooses from the given list of values (or patterns of values), according to the pattern that the method is called on. The pattern should be in the range 0 .. 1. @tags temporal @param {...any} xs @returns {Pattern}

601Pattern.prototype.choose = function (...xs) {
602  return chooseWith(this, xs);
603};

As with choose, but the pattern that this method is called on should be in the range -1 .. 1 @tags temporal @param {...any} xs @returns {Pattern}

612Pattern.prototype.choose2 = function (...xs) {
613  return chooseWith(this.fromBipolar(), xs);
614};

Picks one of the elements at random each cycle. @tags temporal @synonyms randcat @returns {Pattern} @example chooseCycles("bd", "hh", "sd").s().fast(8) @example s("bd | hh | sd").fast(8)

626export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
628export const randcat = chooseCycles;
629
630const _wchooseWith = function (pat, ...pairs) {
631  // A list of patterns of values
632  const values = pairs.map((pair) => reify(pair[0]));

A list of weight patterns

635  const weights = [];
637  let total = pure(0);
638  for (const pair of pairs) {
639    // 'add' accepts either values or patterns of values here, so no need
640    // to explicitly reify
641    total = total.add(pair[1]);
642    // accumulate our list of weight patterns
643    weights.push(total);
644  }
645  // a pattern of lists of weights
646  const weightspat = sequenceP(weights);

Takes a number from 0-1, returns a pattern of patterns of values

649  const match = function (r) {
650    const findpat = total.mul(r);
651    return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat);
652  };
653  // This returns a pattern of patterns.. The innerJoin is in wchooseCycles
654  return pat.bind(match);
655};
657const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();

Chooses randomly from the given list of elements by giving a probability to each element @tags temporal @param {...any} pairs arrays of value and weight @returns {Pattern} - a continuous pattern. @example note("c2 g2!2 d2 f1").s(wchoose(["sine",10], ["triangle",1], ["bd:6",1]))

667export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);

Picks one of the elements at random each cycle by giving a probability to each element @tags temporal @synonyms wrandcat @returns {Pattern} @example wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8) @example wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note() @example // The probability can itself be a pattern wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()

682export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin();
684export const wrandcat = wchooseCycles;
685
686function _perlin(t, seed = 0) {
687  let ta = Math.floor(t);
688  let tb = ta + 1;
689  const smootherStep = (x) => 6.0 * x ** 5 - 15.0 * x ** 4 + 10.0 * x ** 3;
690  const interp = (x) => (a) => (b) => a + smootherStep(x) * (b - a);
691  const ra = getRandsAtTime(ta, 1, seed);
692  const rb = getRandsAtTime(tb, 1, seed);
693  const v = interp(t - ta)(ra)(rb);
694  return v;
695}
696
697function _berlin(t, seed = 0) {
698  const prevRidgeStartIndex = Math.floor(t);
699  const nextRidgeStartIndex = prevRidgeStartIndex + 1;
700
701  const prevRidgeBottomPoint = getRandsAtTime(prevRidgeStartIndex, 1, seed);
702  const height = getRandsAtTime(nextRidgeStartIndex, 1, seed);
703  const nextRidgeTopPoint = prevRidgeBottomPoint + height;
704
705  const currentPercent = (t - prevRidgeStartIndex) / (nextRidgeStartIndex - prevRidgeStartIndex);
706  const interp = (a, b, t) => {
707    return a + t * (b - a);
708  };
709  return interp(prevRidgeBottomPoint, nextRidgeTopPoint, currentPercent) / 2;
710}

Generates a continuous pattern of perlin noise, in the range 0..1.

@tags generators @name perlin @example // randomly change the cutoff s("bd4,hh8").cutoff(perlin.range(500,8000))

722export const perlin = signal((t, controls) => _perlin(t, controls.randSeed));

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.

@tags generators @name berlin @example // ascending arpeggios n("0!16".add(berlin.fast(4).mul(14))).scale("d:minor")

735export const berlin = signal((t, controls) => _berlin(t, controls.randSeed));
737export const degradeByWith = register(
738  'degradeByWith',
739  (withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
740  true,
741  true,
742);

Randomly removes events from the pattern by a given amount. 0 = 0% chance of removal 1 = 100% chance of removal

@tags temporal @name degradeBy @memberof Pattern @param {number} amount - a number between 0 and 1 @returns Pattern @example s("hh*8").degradeBy(0.2) @example s("[hh?0.2]*8") @example //beat generator s("bd").segment(16).degradeBy(.5).ribbon(16,1)

762export const degradeBy = register(
763  'degradeBy',
764  function (x, pat) {
765    return pat._degradeByWith(rand, x);
766  },
767  true,
768  true,
769);

Randomly removes 50% of events from the pattern. Shorthand for .degradeBy(0.5)

@tags temporal @name degrade @memberof Pattern @returns Pattern @example s("hh*8").degrade() @example s("[hh?]*8")

784export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, true);

Inverse of 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).

@tags temporal @name undegradeBy @memberof Pattern @param {number} amount - a number between 0 and 1 @returns Pattern @example s("hh8").undegradeBy(0.2) @example s("hh10").layer( x => x.degradeBy(0.2).pan(0), x => x.undegradeBy(0.8).pan(1) )

805export const undegradeBy = register(
806  'undegradeBy',
807  function (x, pat) {
808    return pat._degradeByWith(
809      rand.fmap((r) => 1 - r),
810      x,
811    );
812  },
813  true,
814  true,
815);

Inverse of degrade: Randomly removes 50% of events from the pattern. Shorthand for .undegradeBy(0.5) Events that would be removed by degrade are let through by undegrade and vice versa (see second example).

@tags temporal @name undegrade @memberof Pattern @returns Pattern @example s("hh8").undegrade() @example s("hh10").layer( x => x.degrade().pan(0), x => x.undegrade().pan(1) )

833export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), true, true);

Randomly applies the given function by the given probability. Similar to someCyclesBy

@tags temporal @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*8").sometimesBy(.4, x=>x.speed("0.5"))

850export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
851  return reify(patx)
852    .fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x))))
853    .innerJoin();
854});

Applies the given function with a 50% chance

@tags temporal @name sometimes @memberof Pattern @param {function} function - the transformation to apply @returns Pattern @example s("hh*8").sometimes(x=>x.speed("0.5"))

868export const sometimes = register('sometimes', function (func, pat) {
869  return pat._sometimesBy(0.5, func);
870});

Randomly applies the given function by the given probability on a cycle by cycle basis. Similar to sometimesBy

@name someCyclesBy @memberof Pattern @param {number | Pattern} probability - a number between 0 and 1 @param {function} function - the transformation to apply @returns Pattern @tags temporal @example s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5"))

887export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) {
888  return reify(patx)
889    .fmap((x) =>
890      stack(
891        pat._degradeByWith(rand._segment(1), x),
892        func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
893      ),
894    )
895    .innerJoin();
896});

Shorthand for .someCyclesBy(0.5, fn)

@name someCycles @memberof Pattern @returns Pattern @tags temporal @example s("bd,hh*8").someCycles(x=>x.speed("0.5"))

909export const someCycles = register('someCycles', function (func, pat) {
910  return pat._someCyclesBy(0.5, func);
911});

Shorthand for .sometimesBy(0.75, fn)

@name often @memberof Pattern @returns Pattern @tags temporal @example s("hh*8").often(x=>x.speed("0.5"))

924export const often = register('often', function (func, pat) {
925  return pat.sometimesBy(0.75, func);
926});

Shorthand for .sometimesBy(0.25, fn)

@name rarely @memberof Pattern @returns Pattern @tags temporal @example s("hh*8").rarely(x=>x.speed("0.5"))

939export const rarely = register('rarely', function (func, pat) {
940  return pat.sometimesBy(0.25, func);
941});

Shorthand for .sometimesBy(0.1, fn)

@tags temporal @name almostNever @memberof Pattern @returns Pattern @example s("hh*8").almostNever(x=>x.speed("0.5"))

954export const almostNever = register('almostNever', function (func, pat) {
955  return pat.sometimesBy(0.1, func);
956});

Shorthand for .sometimesBy(0.9, fn)

@tags temporal @name almostAlways @memberof Pattern @returns Pattern @example s("hh*8").almostAlways(x=>x.speed("0.5"))

969export const almostAlways = register('almostAlways', function (func, pat) {
970  return pat.sometimesBy(0.9, func);
971});

Shorthand for .sometimesBy(0, fn) (never calls fn)

@tags temporal @name never @memberof Pattern @returns Pattern @example s("hh*8").never(x=>x.speed("0.5"))

984export const never = register('never', function (_, pat) {
985  return pat;
986});

Shorthand for .sometimesBy(1, fn) (always calls fn)

@tags temporal @name always @memberof Pattern @returns Pattern @example s("hh*8").always(x=>x.speed("0.5"))

999export const always = register('always', function (func, pat) {
1000  return func(pat);
1001});

keyname: string | Array<string> keyname reference: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values

1005export function _keyDown(keyname) {
1006  if (Array.isArray(keyname) === false) {
1007    keyname = [keyname];
1008  }
1009  const keyState = getCurrentKeyboardState();
1010  return keyname.every((x) => {
1011    const keyName = keyAlias.get(x) ?? x;
1012    return keyState[keyName];
1013  });
1014}

Do something on a keypress, or array of keypresses Key name reference

@tags external_io @name whenKey @memberof Pattern @returns Pattern @example s("bd(5,8)").whenKey("Control:j", x => x.segment(16).color("red")).whenKey("Control:i", x => x.fast(2).color("blue"))

1029export const whenKey = register('whenKey', function (input, func, pat) {
1030  return pat.when(_keyDown(input), func);
1031});

returns true when a key or array of keys is held Key name reference

@tags external_io @name keyDown @memberof Pattern @returns Pattern @example keyDown("Control:j").pick([s("bd(5,8)"), s("cp(3,8)")])

1046export const keyDown = register('keyDown', function (pat) {
1047  return pat.fmap(_keyDown);
1048});

A pattern measuring the duration of events, in cycles per event. cyclesPer doesn't have structure itself, but takes structure, and therefore event durations, from the pattern that it is combined with. For example cyclesPer.struct("1 1 [1 1] 1") would give the same as "0.25 0.25 [0.125 0.125] 0.25". See also its reciprocal, per, also known as perCycle.

@tags temporal @example // Shorter events are lower in pitch sound("saw saw [saw saw] saw") .note(cyclesPer.range(50, 100)) @example sound("bd sd [bd bd] sd*4 [- sd] [bd [bd bd]]") .note(cyclesPer.add(20))

1066export const cyclesPer = new Pattern(function (state) {
1067  return [new Hap(undefined, state.span, state.span.duration)];
1068});

A pattern measuring the 'shortness' of events, or in other words, the duration of pattern events, in events per cycle. per doesn't have structure itself, but takes structure, and therefore event durations, from the pattern that it is combined with. For example per.struct("1 1 [1 1] 1") would give the same as "4 4 [8 8] 4". See also its reciprocal, cyclesPer. @tags temporal @synonyms perCycle @example // Shorter events are more distorted n("0 02 0 02 0 [0 0 0]@2").sound("bd") .distort(per.div(2))

1083export const per = new Pattern(function (state) {
1084  return [new Hap(undefined, state.span, Fraction(1).div(state.span.duration))];
1085});
1087export const perCycle = per;

Like per but measures the shortness of events according to an exponential curve. In particular, where the event duration halves, the returned value increases by one. perx.struct("1 1 [1 [1 1]] 1") would therefore be the same as "3 3 [4 [5 5]] 3". @tags temporal

1096export const perx = new Pattern(function (state) {
1097  const n = Fraction(1).div(state.span.duration);
1098  return [new Hap(undefined, state.span, Math.log(n) / Math.log(2) + 1)];
1099});