jevstrudel.git / packages / core / pattern.mjs

pattern.mjs - Core pattern representation for strudel 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/.

7import TimeSpan from './timespan.mjs';
8import Fraction, { isFraction, lcm } from './fraction.mjs';
9import Hap from './hap.mjs';
10import State from './state.mjs';
11import { unionWithObj } from './value.mjs';
13import {
14  uniqsortr,
15  removeUndefineds,
16  flatten,
17  id,
18  listRange,
19  curry,
20  _mod,
21  numeralArgs,
22  parseNumeral,
23  pairs,
24  zipWith,
25  stringifyValues,
26} from './util.mjs';
27import drawLine from './drawLine.mjs';
28import { errorLogger, logger } from './logger.mjs';
29import { strudelScope } from './evaluate.mjs';
30
31let stringParser;
32
33let __steps = true;
34
35export const calculateSteps = function (x) {
36  __steps = x ? true : false;
37};

parser is expected to turn a string into a pattern if set, the reify function will parse all strings with it intended to use with mini to automatically interpret all strings as mini notation

42export const setStringParser = (parser) => (stringParser = parser);

@class Class representing a pattern.

45export class Pattern {
46  /**
47   * Create a pattern. As an end user, you will most likely not create a Pattern directly.
48   *
49   * @param {function} query - The function that maps a `State` to an array of `Hap`.
50   * @noAutocomplete
51   */
52  constructor(query, steps = undefined) {
53    this.query = query;
54    this._Pattern = true; // this property is used to detectinstance of another Pattern
55    this._steps = steps; // in terms of number of steps per cycle
56  }
57
58  get _steps() {
59    return this.__steps;
60  }
61
62  set _steps(steps) {
63    this.__steps = steps === undefined ? undefined : Fraction(steps);
64  }
65
66  setSteps(steps) {
67    this._steps = steps;
68    return this;
69  }
70
71  withSteps(f) {
72    if (!__steps) {
73      return this;
74    }
75    return new Pattern(this.query, this._steps === undefined ? undefined : f(this._steps));
76  }
77
78  get hasSteps() {
79    return this._steps !== undefined;
80  }

//////////////////////////////////////////////////////////////////// Haskell-style functor, applicative and monadic operations

Returns a new pattern, with the function applied to the value of each hap. It has the alias fmap. @tags functional @synonyms fmap @param {Function} func to to apply to the value @returns Pattern @example "0 1 2".withValue(v => v + 10).log()

95  withValue(func) {
96    const result = new Pattern((state) => this.query(state).map((hap) => hap.withValue(func)));
97    result._steps = this._steps;
98    return result;
99  }

runs func on query state

102  withState(func) {
103    return new Pattern((state) => this.query(func(state)));
104  }

see withValue @noAutocomplete

110  fmap(func) {
111    return this.withValue(func);
112  }

Assumes 'this' is a pattern of functions, and given a function to resolve wholes, applies a given pattern of values to that pattern of functions. @tags functional @param {Function} whole_func @param {Function} func @noAutocomplete @returns Pattern

124  appWhole(whole_func, pat_val) {
125    const pat_func = this;
126    const query = function (state) {
127      const hap_funcs = pat_func.query(state);
128      const hap_vals = pat_val.query(state);
129      const apply = function (hap_func, hap_val) {
130        const s = hap_func.part.intersection(hap_val.part);
131        if (s == undefined) {
132          return undefined;
133        }
134        return new Hap(
135          whole_func(hap_func.whole, hap_val.whole),
136          s,
137          hap_func.value(hap_val.value),
138          hap_val.combineContext(hap_func),
139        );
140      };
141      return flatten(
142        hap_funcs.map((hap_func) => removeUndefineds(hap_vals.map((hap_val) => apply(hap_func, hap_val)))),
143      );
144    };
145    return new Pattern(query);
146  }

When this method is called on a pattern of functions, it matches its haps with those in the given pattern of values. A new pattern is returned, with each matching value applied to the corresponding function.

In this _appBoth variant, where timespans of the function and value haps are not the same but do intersect, the resulting hap has a timespan of the intersection. This applies to both the part and the whole timespan. @tags functional @param {Pattern} pat_val @noAutocomplete @returns Pattern

161  appBoth(pat_val) {
162    const pat_func = this;

Tidal's <*>

165    const whole_func = function (span_a, span_b) {
166      if (span_a == undefined || span_b == undefined) {
167        return undefined;
168      }
169      return span_a.intersection_e(span_b);
170    };
171    const result = pat_func.appWhole(whole_func, pat_val);
172    if (__steps) {
173      result._steps = lcm(pat_val._steps, pat_func._steps);
174    }
175    return result;
176  }

As with appBoth, but the whole timespan is not the intersection, but the timespan from the function of patterns that this method is called on. In practice, this means that the pattern structure, including onsets, are preserved from the pattern of functions (often referred to as the left hand or inner pattern). @tags functional @param {Pattern} pat_val @noAutocomplete @returns Pattern

189  appLeft(pat_val) {
190    const pat_func = this;
191
192    const query = function (state) {
193      const haps = [];
194      for (const hap_func of pat_func.query(state)) {
195        const hap_vals = pat_val.query(state.setSpan(hap_func.wholeOrPart()));
196        for (const hap_val of hap_vals) {
197          const new_whole = hap_func.whole;
198          const new_part = hap_func.part.intersection(hap_val.part);
199          if (new_part) {
200            const new_value = hap_func.value(hap_val.value);
201            const new_context = hap_val.combineContext(hap_func);
202            const hap = new Hap(new_whole, new_part, new_value, new_context);
203            haps.push(hap);
204          }
205        }
206      }
207      return haps;
208    };
209    const result = new Pattern(query);
210    result._steps = this._steps;
211    return result;
212  }

As with appLeft, but whole timespans are instead taken from the pattern of values, i.e. structure is preserved from the right hand/outer pattern. @tags functional @param {Pattern} pat_val @noAutocomplete @returns Pattern

223  appRight(pat_val) {
224    const pat_func = this;
225
226    const query = function (state) {
227      const haps = [];
228      for (const hap_val of pat_val.query(state)) {
229        const hap_funcs = pat_func.query(state.setSpan(hap_val.wholeOrPart()));
230        for (const hap_func of hap_funcs) {
231          const new_whole = hap_val.whole;
232          const new_part = hap_func.part.intersection(hap_val.part);
233          if (new_part) {
234            const new_value = hap_func.value(hap_val.value);
235            const new_context = hap_val.combineContext(hap_func);
236            const hap = new Hap(new_whole, new_part, new_value, new_context);
237            haps.push(hap);
238          }
239        }
240      }
241      return haps;
242    };
243    const result = new Pattern(query);
244    result._steps = pat_val._steps;
245    return result;
246  }
248  bindWhole(choose_whole, func) {
249    const pat_val = this;
250    const query = function (state) {
251      const withWhole = function (a, b) {
252        return new Hap(
253          choose_whole(a.whole, b.whole),
254          b.part,
255          b.value,
256          Object.assign({}, a.context, b.context, {
257            locations: (a.context.locations || []).concat(b.context.locations || []),
258          }),
259        );
260      };
261      const match = function (a) {
262        return func(a.value)
263          .query(state.setSpan(a.part))
264          .map((b) => withWhole(a, b));
265      };
266      return flatten(pat_val.query(state).map((a) => match(a)));
267    };
268    return new Pattern(query);
269  }
270
271  bind(func) {
272    const whole_func = function (a, b) {
273      if (a == undefined || b == undefined) {
274        return undefined;
275      }
276      return a.intersection_e(b);
277    };
278    return this.bindWhole(whole_func, func);
279  }
280
281  join() {
282    // Flattens a pattern of patterns into a pattern, where wholes are
283    // the intersection of matched inner and outer haps.
284    return this.bind(id);
285  }
286
287  outerBind(func) {
288    return this.bindWhole((a) => a, func).setSteps(this._steps);
289  }
290
291  outerJoin() {
292    // Flattens a pattern of patterns into a pattern, where wholes are
293    // taken from outer haps.
294    return this.outerBind(id);
295  }
296
297  innerBind(func) {
298    return this.bindWhole((_, b) => b, func);
299  }
300
301  innerJoin() {
302    // Flattens a pattern of patterns into a pattern, where wholes are
303    // taken from inner haps.
304    return this.innerBind(id);
305  }

Flatterns patterns of patterns, by retriggering/resetting inner patterns at onsets of outer pattern haps

308  resetJoin(restart = false) {
309    const pat_of_pats = this;
310    return new Pattern((state) => {
311      return (
312        pat_of_pats
313          // drop continuous haps from the outer pattern.
314          .discreteOnly()
315          .query(state)
316          .map((outer_hap) => {
317            return (
318              outer_hap.value
319                // reset = align the inner pattern cycle start to outer pattern haps
320                // restart = align the inner pattern cycle zero to outer pattern haps
321                .late(restart ? outer_hap.whole.begin : outer_hap.whole.begin.cyclePos())
322                .query(state)
323                .map((inner_hap) =>
324                  new Hap(
325                    // Supports continuous haps in the inner pattern
326                    inner_hap.whole ? inner_hap.whole.intersection(outer_hap.whole) : undefined,
327                    inner_hap.part.intersection(outer_hap.part),
328                    inner_hap.value,
329                  ).setContext(outer_hap.combineContext(inner_hap)),
330                )
331                // Drop haps that didn't intersect
332                .filter((hap) => hap.part)
333            );
334          })
335          .flat()
336      );
337    });
338  }
340  restartJoin() {
341    return this.resetJoin(true);
342  }

Like the other joins above, joins a pattern of patterns of values, into a flatter pattern of values. In this case it takes whole cycles of the inner pattern to fit each event in the outer pattern.

347  squeezeJoin() {
348    // A pattern of patterns, which we call the 'outer' pattern, with patterns
349    // as values which we call the 'inner' patterns.
350    const pat_of_pats = this;
351    function query(state) {
352      // Get the events with the inner patterns. Ignore continuous events (without 'wholes')
353      const haps = pat_of_pats.discreteOnly().query(state);
354      // A function to map over the events from the outer pattern.
355      function flatHap(outerHap) {
356        // Get the inner pattern, slowed and shifted so that the 'whole'
357        // timespan of the outer event corresponds to the first cycle of the
358        // inner event
359        const inner_pat = outerHap.value._focusSpan(outerHap.wholeOrPart());
360        // Get the inner events, from the timespan of the outer event's part
361        const innerHaps = inner_pat.query(state.setSpan(outerHap.part));
362        // A function to map over the inner events, to combine them with the
363        // outer event
364        function munge(outer, inner) {
365          let whole = undefined;
366          if (inner.whole && outer.whole) {
367            whole = inner.whole.intersection(outer.whole);
368            if (!whole) {
369              // The wholes are present, but don't intersect
370              return undefined;
371            }
372          }
373          const part = inner.part.intersection(outer.part);
374          if (!part) {
375            // The parts don't intersect
376            return undefined;
377          }
378          const context = inner.combineContext(outer);
379          return new Hap(whole, part, inner.value, context);
380        }
381        return innerHaps.map((innerHap) => munge(outerHap, innerHap));
382      }
383      const result = flatten(haps.map(flatHap));
384      // remove undefineds
385      return result.filter((x) => x);
386    }
387    return new Pattern(query);
388  }
390  squeezeBind(func) {
391    return this.fmap(func).squeezeJoin();
392  }
393
394  polyJoin = function () {
395    const pp = this;
396    return pp.fmap((p) => p.extend(pp._steps.div(p._steps))).outerJoin();
397  };
398
399  polyBind(func) {
400    return this.fmap(func).polyJoin();
401  }

//////////////////////////////////////////////////////////////////// Utility methods mainly for internal use

Query haps inside the given time span.

@tags internals @param {Fraction | number} begin from time @param {Fraction | number} end to time @returns Hap[] @example const pattern = sequence('a', ['b', 'c']) const haps = pattern.queryArc(0, 1) console.log(haps) silence @noAutocomplete

420  queryArc(begin, end, controls = {}) {
421    try {
422      return this.query(new State(new TimeSpan(begin, end), controls));
423    } catch (err) {
424      errorLogger(err, 'query');
425      return [];
426    }
427  }

Returns a new pattern, with queries split at cycle boundaries. This makes some calculations easier to express, as all haps are then constrained to happen within a cycle. @tags internals @returns Pattern @noAutocomplete

437  splitQueries() {
438    const pat = this;
439    const q = (state) => {
440      return flatten(state.span.spanCycles.map((subspan) => pat.query(state.setSpan(subspan))));
441    };
442    return new Pattern(q);
443  }

Returns a new pattern, where the given function is applied to the query timespan before passing it to the original pattern. @tags internals @param {Function} func the function to apply @returns Pattern @noAutocomplete

453  withQuerySpan(func) {
454    return new Pattern((state) => this.query(state.withSpan(func)));
455  }
457  withQuerySpanMaybe(func) {
458    const pat = this;
459    return new Pattern((state) => {
460      const newState = state.withSpan(func);
461      if (!newState.span) {
462        return [];
463      }
464      return pat.query(newState);
465    });
466  }

As with withQuerySpan, but the function is applied to both the begin and end time of the query timespan. @tags internals @param {Function} func the function to apply @returns Pattern @noAutocomplete

476  withQueryTime(func) {
477    return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func))));
478  }

Similar to withQuerySpan, but the function is applied to the timespans of all haps returned by pattern queries (both part timespans, and where present, whole timespans). @tags internals @param {Function} func @returns Pattern @noAutocomplete

489  withHapSpan(func) {
490    return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
491  }

As with withHapSpan, but the function is applied to both the begin and end time of the hap timespans. @tags internals @param {Function} func the function to apply @returns Pattern @noAutocomplete

501  withHapTime(func) {
502    return this.withHapSpan((span) => span.withTime(func));
503  }

Returns a new pattern with the given function applied to the list of haps returned by every query. @tags internals @param {Function} func @returns Pattern @noAutocomplete

512  withHaps(func) {
513    const result = new Pattern((state) => func(this.query(state), state));
514    result._steps = this._steps;
515    return result;
516  }

As with withHaps, but applies the function to every hap, rather than every list of haps. @tags internals @param {Function} func @returns Pattern @noAutocomplete

525  withHap(func) {
526    return this.withHaps((haps) => haps.map(func));
527  }

Returns a new pattern with the context field set to every hap set to the given value. @tags internals @param {*} context @returns Pattern @noAutocomplete

536  setContext(context) {
537    return this.withHap((hap) => hap.setContext(context));
538  }

Returns a new pattern with the given function applied to the context field of every hap. @tags internals @param {Function} func @returns Pattern @noAutocomplete

547  withContext(func) {
548    const result = this.withHap((hap) => hap.setContext(func(hap.context)));
549    if (this.__pure !== undefined) {
550      result.__pure = this.__pure;
551      result.__pure_loc = this.__pure_loc;
552    }
553    return result;
554  }

Returns a new pattern with the context field of every hap set to an empty object. @tags internals @returns Pattern @noAutocomplete

562  stripContext() {
563    return this.withHap((hap) => hap.setContext({}));
564  }

Returns a new pattern with the given location information added to the context of every hap. @tags internals @param {Number} start start offset @param {Number} end end offset @returns Pattern @noAutocomplete

575  withLoc(start, end) {
576    const location = {
577      start,
578      end,
579    };
580    const result = this.withContext((context) => {
581      const locations = (context.locations || []).concat([location]);
582      return { ...context, locations };
583    });
584    if (this.__pure) {
585      result.__pure = this.__pure;
586      result.__pure_loc = location;
587    }
588    return result;
589  }

Returns a new Pattern, which only returns haps that meet the given test. @tags internals @param {Function} hap_test - a function which returns false for haps to be removed from the pattern @returns Pattern @example s("bd*8").velocity(rand).filterHaps((h) => (h.whole.begin % 1) < h.value.velocity)

599  filterHaps(hap_test) {
600    return new Pattern((state) => this.query(state).filter(hap_test));
601  }

As with filterHaps, but the function is applied to values inside haps. @tags internals @param {Function} value_test @returns Pattern @example const drums = s("bd sd bd sd") kick: drums.filterValues((v) => v.s === 'bd').duck(2) snare: drums.filterValues((v) => v.s === 'sd') bass: s("saw!4").note("G#1").lpf(80).lpenv(4).orbit(2)

615  filterValues(value_test) {
616    return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))).setSteps(this._steps);
617  }

Returns a new pattern, with haps containing undefined values removed from query results. @tags internals @returns Pattern @noAutocomplete

626  removeUndefineds() {
627    return this.filterValues((val) => val != undefined);
628  }

Returns a new pattern, with all haps without onsets filtered out. A hap with an onset is one with a whole timespan that begins at the same time as its part timespan. @tags internals @returns Pattern @noAutocomplete

638  onsetsOnly() {
639    // Returns a new pattern that will only return haps where the start
640    // of the 'whole' timespan matches the start of the 'part'
641    // timespan, i.e. the haps that include their 'onset'.
642    return this.filterHaps((hap) => hap.hasOnset());
643  }

Returns a new pattern, with 'continuous' haps (those without 'whole' timespans) removed from query results. @tags internals @returns Pattern @noAutocomplete

652  discreteOnly() {
653    // removes continuous haps that don't have a 'whole' timespan
654    return this.filterHaps((hap) => hap.whole);
655  }

Combines adjacent haps with the same value and whole. Only intended for use in tests. @tags internals @noAutocomplete

663  defragmentHaps() {
664    // remove continuous haps
665    const pat = this.discreteOnly();
666
667    return pat.withHaps((haps) => {
668      const result = [];
669      for (var i = 0; i < haps.length; ++i) {
670        var searching = true;
671        var a = haps[i];
672        while (searching) {
673          const a_value = JSON.stringify(haps[i].value);
674          var found = false;
675
676          for (var j = i + 1; j < haps.length; j++) {
677            const b = haps[j];
678
679            if (a.whole.equals(b.whole)) {
680              if (a.part.begin.eq(b.part.end)) {
681                if (a_value === JSON.stringify(b.value)) {
682                  // eat the matching hap into 'a'
683                  a = new Hap(a.whole, new TimeSpan(b.part.begin, a.part.end), a.value);
684                  haps.splice(j, 1);
685                  // restart the search
686                  found = true;
687                  break;
688                }
689              } else if (b.part.begin.eq(a.part.end)) {
690                if (a_value == JSON.stringify(b.value)) {
691                  // eat the matching hap into 'a'
692                  a = new Hap(a.whole, new TimeSpan(a.part.begin, b.part.end), a.value);
693                  haps.splice(j, 1);
694                  // restart the search
695                  found = true;
696                  break;
697                }
698              }
699            }
700          }
701
702          searching = found;
703        }
704        result.push(a);
705      }
706      return result;
707    });
708  }

Queries the pattern for the first cycle, returning Haps. Mainly of use when debugging a pattern. @tags internals @param {Boolean} with_context - set to true, otherwise the context field will be stripped from the resulting haps. @returns [Hap] @noAutocomplete

719  firstCycle(with_context = false) {
720    var self = this;
721    if (!with_context) {
722      self = self.stripContext();
723    }
724    return self.query(new State(new TimeSpan(Fraction(0), Fraction(1))));
725  }

Accessor for a list of values returned by querying the first cycle. @tags internals @noAutocomplete

732  get firstCycleValues() {
733    return this.firstCycle().map((hap) => hap.value);
734  }

More human-readable version of the firstCycleValues accessor. @tags internals @noAutocomplete

741  get showFirstCycle() {
742    return this.firstCycle().map(
743      (hap) => `${hap.value}: ${hap.whole.begin.toFraction()} - ${hap.whole.end.toFraction()}`,
744    );
745  }

Returns a new pattern, which returns haps sorted in temporal order. Mainly of use when comparing two patterns for equality, in tests. @tags internals @returns Pattern @noAutocomplete

754  sortHapsByPart() {
755    return this.withHaps((haps) =>
756      haps.sort((a, b) =>
757        a.part.begin
758          .sub(b.part.begin)
759          .or(a.part.end.sub(b.part.end))
760          .or(a.whole.begin.sub(b.whole.begin).or(a.whole.end.sub(b.whole.end))),
761      ),
762    );
763  }

Returns a new pattern with all values parsed as numerals. @tags internals

769  asNumber() {
770    return this.fmap(parseNumeral);
771  }

//////////////////////////////////////////////////////////////////// Operators - see 'make composers' later..

776  _opIn(other, func) {
777    return this.fmap(func).appLeft(reify(other));
778  }
779  _opOut(other, func) {
780    return this.fmap(func).appRight(reify(other));
781  }
782  _opMix(other, func) {
783    return this.fmap(func).appBoth(reify(other));
784  }
785  _opSqueeze(other, func) {
786    const otherPat = reify(other);
787    return this.fmap((a) => otherPat.fmap((b) => func(a)(b))).squeezeJoin();
788  }
789  _opSqueezeOut(other, func) {
790    const thisPat = this;
791    const otherPat = reify(other);
792    return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a))).squeezeJoin();
793  }
794  _opReset(other, func) {
795    const otherPat = reify(other);
796    return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).resetJoin();
797  }
798  _opRestart(other, func) {
799    const otherPat = reify(other);
800    return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).restartJoin();
801  }
802  _opPoly(other, func) {
803    const otherPat = reify(other);
804    return this.fmap((b) => otherPat.fmap((a) => func(a)(b))).polyJoin();
805  }

//////////////////////////////////////////////////////////////////// End-user methods. Those beginning with an underscore (_) are 'patternified', i.e. versions are created without the underscore, that are magically transformed to accept patterns for all their arguments.

//////////////////////////////////////////////////////////////////// Methods without corresponding toplevel functions

Layers the result of the given function(s). Like superimpose, but without the original pattern: @name layer @tags combiners @memberof Pattern @returns Pattern @example "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8" .layer(x=>x.add("0,2")) .scale('C minor').note()

827  layer(...funcs) {
828    return stack(...funcs.map((func) => func(this)));
829  }

Superimposes the result of the given function(s) on top of the original pattern: @name superimpose @tags combiners @memberof Pattern @returns Pattern @example "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8" .superimpose(x=>x.add(2)) .scale('C minor').note()

842  superimpose(...funcs) {
843    return this.stack(...funcs.map((func) => func(this)));
844  }

//////////////////////////////////////////////////////////////////// Multi-pattern functions

849  stack(...pats) {
850    return stack(this, ...pats);
851  }
853  sequence(...pats) {
854    return sequence(this, ...pats);
855  }
856
857  seq(...pats) {
858    return sequence(this, ...pats);
859  }
860  cat(...pats) {
861    return cat(this, ...pats);
862  }
863
864  fastcat(...pats) {
865    return fastcat(this, ...pats);
866  }
867
868  slowcat(...pats) {
869    return slowcat(this, ...pats);
870  }

//////////////////////////////////////////////////////////////////// Context methods - ones that deal with metadata

875  onTrigger(onTrigger, dominant = true) {
876    return this.withHap((hap) =>
877      hap.setContext({
878        ...hap.context,
879        onTrigger: (...args) => {
880          // run previously set trigger, if it exists
881          hap.context.onTrigger?.(...args);
882          onTrigger(...args);
883        },
884        // if dominantTrigger is set to true, the default output (webaudio) will be disabled
885        // when using multiple triggers, you cannot flip this flag to false again!
886        // example: x.csound('CooLSynth').log() as well as x.log().csound('CooLSynth') should work the same
887        dominantTrigger: hap.context.dominantTrigger || dominant,
888      }),
889    );
890  }

Writes the content of the current event to the console (visible in the side menu). @tags visualization @name log @memberof Pattern @example s("bd sd").log()

900  log(func = (hap) => `[hap] ${hap.showWhole(true)}`, getData = (hap) => ({ hap })) {
901    return this.onTrigger((...args) => {
902      logger(func(...args), undefined, getData(...args));
903    }, false);
904  }

A simplified version of log which writes all "values" (various configurable parameters) within the event to the console (visible in the side menu). @tags visualization @name logValues @memberof Pattern @example s("bd sd").gain("0.25 0.5 1").n("2 1 0").logValues()

915  logValues(func = (value) => `[hap] ${stringifyValues(value, true)}`) {
916    return this.log((hap) => func(hap.value));
917  }

//////////////////////////////////////////////////////////////////// Visualisation

922  drawLine() {
923    console.log(drawLine(this));
924    return this;
925  }

//////////////////////////////////////////////////////////////////// methods relating to breaking patterns into subcycles

Breaks a pattern into a pattern of patterns, according to the structure of the given binary pattern.

931  unjoin(pieces, func = id) {
932    return pieces.withHap((hap) =>
933      hap.withValue((v) => (v ? func(this.ribbon(hap.whole.begin, hap.whole.duration)) : this)),
934    );
935  }

Breaks a pattern into pieces according to the structure of a given pattern. True values in the given pattern cause the corresponding subcycle of the source pattern to be looped, and for an (optional) given function to be applied. False values result in the corresponding part of the source pattern to be played unchanged. @tags temporal @name into @memberof Pattern @example sound("bd sd ht lt").into("1 0", hurry(2))

949  into(pieces, func) {
950    return this.unjoin(pieces, func).innerJoin();
951  }
952}

//////////////////////////////////////////////////////////////////// functions relating to chords/patterns of lists/lists of patterns

returns Array<Hap[]> where each list of haps satisfies eq

958function groupHapsBy(eq, haps) {
959  let groups = [];
960  haps.forEach((hap) => {
961    const match = groups.findIndex(([other]) => eq(hap, other));
962    if (match === -1) {
963      groups.push([hap]);
964    } else {
965      groups[match].push(hap);
966    }
967  });
968  return groups;
969}

congruent haps = haps with equal spans

972const congruent = (a, b) => a.spanEquals(b);

Pattern<Hap<T>> -> Pattern<Hap<T[]>> returned pattern contains arrays of congruent haps

975Pattern.prototype.collect = function () {
976  return this.withHaps((haps) =>
977    groupHapsBy(congruent, haps).map((_haps) => new Hap(_haps[0].whole, _haps[0].part, _haps, {})),
978  );
979};

Selects indices in in stacked notes. @tags temporal @example note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") .arpWith(haps => haps[2])

988export const arpWith = register('arpWith', (func, pat) => {
989  return pat
990    .collect()
991    .fmap((v) => reify(func(v)))
992    .innerJoin()
993    .withHap((h) => new Hap(h.whole, h.part, h.value.value, h.combineContext(h.value)));
994});

Selects indices in in stacked notes. @tags temporal @example note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") .arp("0 [0,2] 1 [0,2]")

1003export const arp = register(
1004  'arp',
1005  (indices, pat) => pat.arpWith((haps) => reify(indices).fmap((i) => haps[_mod(i, haps.length)])),
1006  false,
1007);

Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are distributed equally over the given time duration. They are then combined with the pattern 'weave' is called on, after it has been stretched out (i.e. slowed down by) the time duration. @name weave @memberof Pattern @example pan(saw).weave(4, s("bd(3,8)"), s("~ sd")) @example n("0 1 2 3 4 5 6 7").weave(8, s("bd(3,8)"), s("~ sd"))

addToPrototype('weave', function (t, ...pats) { return this.weaveWith(t, ...pats.map((x) => set.out(x))); });

Like 'weave', but accepts functions rather than patterns, which are applied to the pattern. @name weaveWith @memberof Pattern

addToPrototype('weaveWith', function (t, ...funcs) { const pat = this; const l = funcs.length; t = Fraction(t); if (l == 0) { return silence; } return stack(...funcs.map((func, i) => pat.inside(t, func).early(Fraction(i).div(l))))._slow(t); });

//////////////////////////////////////////////////////////////////// compose matrix functions

1041function _nonArrayObject(x) {
1042  return !Array.isArray(x) && typeof x === 'object' && !isFraction(x);
1043}
1044function _composeOp(a, b, func) {
1045  if (_nonArrayObject(a) || _nonArrayObject(b)) {
1046    if (!_nonArrayObject(a)) {
1047      a = { value: a };
1048    }
1049    if (!_nonArrayObject(b)) {
1050      b = { value: b };
1051    }
1052    return unionWithObj(a, b, func);
1053  }
1054  return func(a, b);
1055}

pattern composers

1058const COMPOSERS = {
1059  /**
1060   * When called on a pattern `a`, with a input pattern `b` (`a.set(b)`),
1061   * combines `a` and `b` such that anything defined in `b`
1062   * and anything defined in `a` that is *not* defined in `b`
1063   * will be in the resulting pattern.
1064   *
1065   * The structure is maintained from `a`,
1066   * because the default pattern alignment is `in`,
1067   * see the section on `Pattern Alignment`
1068   * in the technical manual in the docs
1069   *
1070   * This is the inverse of `keep`
1071   *
1072   * See examples below
1073   * @name set
1074   * @param {Pattern} pat
1075   * @returns {Pattern}
1076   * @memberof Pattern
1077   * @tags internal, combiners
1078   * @example
1079   * // because input pattern has `s` set,
1080   * // it overrides the "sine" declared earlier
1081   * note("c a f e").s("sine").set(s("triangle"))
1082   */
1083  set: [(a, b) => b],
1084  /**
1085   * When called on a pattern `a`, with a input pattern `b` (`a.keep(b)`),
1086   * combines `a` and `b` such that anything defined in `a`,
1087   * and anything defined in `b` that is *not* defined in `a`
1088   * will be in the resulting pattern
1089   *
1090   * The structure is maintained from `a`,
1091   * because the default pattern alignment is `in`,
1092   * see the section on `Pattern Alignment`
1093   * in the technical manual in the docs
1094   *
1095   * This is the inverse of `set`
1096   *
1097   * See examples below
1098   * @name keep
1099   * @param {Pattern} pat
1100   * @memberof Pattern
1101   * @returns {Pattern}
1102   * @tags internal, combiners
1103   * @example
1104   * // notes, already defined, will stay "c a f e",
1105   * // while "s", not defined, will be set to "piano"
1106   * note("c a f e").keep(note("e f a c").s("piano"))
1107   */
1108  keep: [(a) => a],
1109  keepif: [(a, b) => (b ? a : undefined)],

numerical functions

Assumes a pattern of numbers. Adds the given number to each item in the pattern. @name add @memberof Pattern @tags math @example // Here, the triad 0, 2, 4 is shifted by different amounts n("0 2 4".add("<0 3 4 0>")).scale("C:major") // Without add, the equivalent would be: // n("<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>").scale("C:major") @example // You can also use add with notes: note("c3 e3 g3".add("<0 5 7 0>")) // Behind the scenes, the notes are converted to midi numbers: // note("48 52 55".add("<0 5 7 0>"))

1129  add: [numeralArgs((a, b) => a + b)], // support string concatenation
1130  /**
1131   *
1132   * Like add, but the given numbers are subtracted.
1133   * @name sub
1134   * @memberof Pattern
1135   * @tags math
1136   * @example
1137   * n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor")
1138   * // See add for more information.
1139   */
1140  sub: [numeralArgs((a, b) => a - b)],
1141  /**
1142   *
1143   * Multiplies each number by the given factor.
1144   * @name mul
1145   * @memberof Pattern
1146   * @tags math
1147   * @example
1148   * "<1 1.5 [1.66, <2 2.33>]>*4".mul(150).freq()
1149   */
1150  mul: [numeralArgs((a, b) => a * b)],
1151  /**
1152   *
1153   * Divides each number by the given factor.
1154   * @name div
1155   * @memberof Pattern
1156   * @tags math
1157   */
1158  div: [numeralArgs((a, b) => a / b)],
1159  mod: [numeralArgs(_mod)],
1160  pow: [numeralArgs(Math.pow)],
1161  band: [numeralArgs((a, b) => a & b)],
1162  bor: [numeralArgs((a, b) => a | b)],
1163  bxor: [numeralArgs((a, b) => a ^ b)],
1164  blshift: [numeralArgs((a, b) => a << b)],
1165  brshift: [numeralArgs((a, b) => a >> b)],

TODO - force numerical comparison if both look like numbers?

1168  lt: [(a, b) => a < b],
1169  gt: [(a, b) => a > b],
1170  lte: [(a, b) => a <= b],
1171  gte: [(a, b) => a >= b],
1172  eq: [(a, b) => a == b],
1173  eqt: [(a, b) => a === b],
1174  ne: [(a, b) => a != b],
1175  net: [(a, b) => a !== b],
1176  and: [(a, b) => a && b],
1177  or: [(a, b) => a || b],

bitwise ops

1180  func: [(a, b) => b(a)],
1181};
1183const _setupAlignments = () => {
1184  // generate methods to do what and how
1185  for (const [what, [op, preprocess]] of Object.entries(COMPOSERS)) {
1186    // make plain version, e.g. pat._add(value) adds that plain value
1187    // to all the values in pat
1188    Pattern.prototype['_' + what] = function (value) {
1189      return this.fmap((x) => op(x, value));
1190    };

make patternified monster version

1193    Object.defineProperty(Pattern.prototype, what, {
1194      // Set to configurable so we can update if the default alignment changes
1195      configurable: true,
1196      // a getter that returns a function, so 'pat' can be
1197      // accessed by closures that are methods of that function..
1198      get: function () {
1199        const pat = this;

wrap the 'in' function as default behaviour

1202        const wrapper = (...other) => pat[what][DEFAULT_ALIGNMENT](...other);

add methods to that function for each behaviour

1205        for (const how of ALIGNMENTS) {
1206          wrapper[how.toLowerCase()] = function (...other) {
1207            var howpat = pat;
1208            other = sequence(other);
1209            if (preprocess) {
1210              howpat = preprocess(howpat);
1211              other = preprocess(other);
1212            }
1213            var result;
1214            // hack to remove undefs when doing 'keepif'
1215            if (what === 'keepif') {
1216              // avoid union, as we want to throw away the value of 'b' completely
1217              result = howpat['_op' + how](other, (a) => (b) => op(a, b));
1218              result = result.removeUndefineds();
1219            } else {
1220              result = howpat['_op' + how](other, (a) => (b) => _composeOp(a, b, op));
1221            }
1222            return result;
1223          };
1224        }
1225        wrapper.squeezein = wrapper.squeeze;
1227        return wrapper;
1228      },
1229    });
1230  }
1231};
1232
1233let DEFAULT_ALIGNMENT = 'in';
1234const ALIGNMENTS = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Reset', 'Restart', 'Poly'];
1235const ALIGNMENT_KEYS = ALIGNMENTS.map((how) => how.toLowerCase());

Make composers

1238(function () {
1239  _setupAlignments();

Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)

1242  for (const how of ALIGNMENTS) {
1243    Pattern.prototype[how.toLowerCase()] = function (...args) {
1244      return this.set[how.toLowerCase()](args);
1245    };
1246  }
1247  // binary composers
1248  /**
1249   * Applies the given structure to the pattern:
1250   *
1251   * @tags temporal
1252   * @example
1253   * note("c,eb,g")
1254   *   .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
1255   *   .slow(2)
1256   */
1257  Pattern.prototype.struct = function (...args) {
1258    return this.keepif.out(...args);
1259  };
1260  Pattern.prototype.structAll = function (...args) {
1261    return this.keep.out(...args);
1262  };
1263  /**
1264   * Returns silence when mask is 0 or "~"
1265   *
1266   * @tags temporal
1267   * @example
1268   * note("c [eb,g] d [eb,g]").mask("<1 [0 1]>")
1269   */
1270  Pattern.prototype.mask = function (...args) {
1271    return this.keepif.in(...args);
1272  };
1273  Pattern.prototype.maskAll = function (...args) {
1274    return this.keep.in(...args);
1275  };
1276  /**
1277   * Resets the pattern to the start of the cycle for each onset of the reset pattern.
1278   *
1279   * @tags temporal
1280   * @example
1281   * s("[<bd lt> sd]*2, hh*8").reset("<x@3 x(5,8)>")
1282   */
1283  Pattern.prototype.reset = function (...args) {
1284    return this.keepif.reset(...args);
1285  };
1286  Pattern.prototype.resetAll = function (...args) {
1287    return this.keep.reset(...args);
1288  };
1289  /**
1290   * Restarts the pattern for each onset of the restart pattern.
1291   * While reset will only reset the current cycle, restart will start from cycle 0.
1292   *
1293   * @tags temporal
1294   * @example
1295   * s("[<bd lt> sd]*2, hh*8").restart("<x@3 x(5,8)>")
1296   */
1297  Pattern.prototype.restart = function (...args) {
1298    return this.keepif.restart(...args);
1299  };
1300  Pattern.prototype.restartAll = function (...args) {
1301    return this.keep.restart(...args);
1302  };
1303})();

Sets the default method of combining events from two patterns (aka alignment) in Strudel. The default method is 'in', meaning that patterns to the left will (typically) dictate the event timings when combined with patterns to the right. By changing alignment to 'out', the opposite will happen. With 'mix', they will combine their event timings.

Note that we say the default method, because alignments can also be set explicitly with calls like 'add.mix', 'set.squeeze', etc.

@param {string} method Default join method to use. Options: 'in', 'out', 'mix', 'squeeze', 'squeezeout', 'reset', 'restart', 'poly' @tags combiners @example setDefaultJoin('mix') // also try 'in', 'out', 'squeeze', etc. s("saw").vel("1 0.5").note("F A C E").delay("0 0.2 0.3")

1319export const setDefaultJoin = (alignment) => {
1320  alignment = alignment?.toLowerCase();
1321  if (DEFAULT_ALIGNMENT !== alignment && ALIGNMENT_KEYS.includes(alignment)) {
1322    DEFAULT_ALIGNMENT = alignment;
1323    _setupAlignments();
1324  }
1325};

aliases

1328export const polyrhythm = stack;
1329export const pr = stack;
1331export const pm = polymeter;

methods that create patterns, which are added to patternified Pattern methods TODO: remove? this is only used in old transpiler (shapeshifter) Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timecat, sequence, seq, polymeter, pm, polyrhythm, pr, }; the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)

Elemental patterns

Does absolutely nothing, but with a given metrical 'steps' @name gap @tags generators @param {number} steps @example gap(3) // "~@3"

1361export const gap = (steps) => new Pattern(() => [], steps);

Does absolutely nothing.. @name silence @tags generators @example silence // "~"

1370export const silence = gap(1);

Like silence, but with a 'steps' (relative duration) of 0

1373export const nothing = gap(0);

A discrete value that repeats once per cycle.

@tags generators @returns {Pattern} @example pure('e4') // "e4" @noAutocomplete

1384export function pure(value) {
1385  function query(state) {
1386    return state.span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
1387  }
1388  const result = new Pattern(query, 1);
1389  result.__pure = value;
1390  return result;
1391}
1393export function isPattern(thing) {
1394  // thing?.constructor?.name !== 'Pattern' // <- this will fail when code is mangled
1395  const is = thing instanceof Pattern || thing?._Pattern;
1396  // TODO: find out how to check wrong core dependency. below will never work !thing === 'undefined'
1397  // wrapping it in (..) will result other checks to log that warning (e.g. isPattern('kalimba'))
1398  /* if (!thing instanceof Pattern) {
1399    console.warn(
1400      `Found Pattern that fails "instanceof Pattern" check.
1401      This may happen if you are using multiple versions of @strudel/core.
1402      Please check by running "npm ls @strudel/core".`,
1403    );
1404    console.log(thing);
1405  } */
1406  return is;
1407}
1408
1409export function reify(thing) {
1410  // Turns something into a pattern, unless it's already a pattern
1411  if (isPattern(thing)) {
1412    return thing;
1413  }
1414  if (stringParser && typeof thing === 'string') {
1415    return stringParser(thing);
1416  }
1417  return pure(thing);
1418}

Takes a list of patterns, and returns a pattern of lists.

@tags temporal

1425export function sequenceP(pats) {
1426  let result = pure([]);
1427  for (const pat of pats) {
1428    result = result.bind((list) => pat.fmap((v) => list.concat([v])));
1429  }
1430  return result;
1431}

The given items are played at the same time at the same length.

@tags temporal @return {Pattern} @synonyms polyrhythm, pr @example stack("g3", "b3", ["e4", "d4"]).note() // "g3,b3,[e4 d4]".note()

@example // As a chained function: s("hh*4").stack( note("c4(5,8)") )

1449export function stack(...pats) {
1450  // Array test here is to avoid infinite recursions..
1451  pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
1452  const query = (state) => flatten(pats.map((pat) => pat.query(state)));
1453  const result = new Pattern(query);
1454  if (__steps) {
1455    result._steps = lcm(...pats.map((pat) => pat._steps));
1456  }
1457  return result;
1458}
1460function _stackWith(func, pats) {
1461  pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
1462  if (pats.length === 0) {
1463    return silence;
1464  }
1465  if (pats.length === 1) {
1466    return pats[0];
1467  }
1468  const [left, ...right] = pats.map((pat) => pat._steps);
1469  const steps = __steps ? left.maximum(...right) : undefined;
1470  return stack(...func(steps, pats));
1471}
1472
1473export function stackLeft(...pats) {
1474  return _stackWith(
1475    (steps, pats) => pats.map((pat) => (pat._steps.eq(steps) ? pat : stepcat(pat, gap(steps.sub(pat._steps))))),
1476    pats,
1477  );
1478}
1479
1480export function stackRight(...pats) {
1481  return _stackWith(
1482    (steps, pats) => pats.map((pat) => (pat._steps.eq(steps) ? pat : stepcat(gap(steps.sub(pat._steps)), pat))),
1483    pats,
1484  );
1485}
1486
1487export function stackCentre(...pats) {
1488  return _stackWith(
1489    (steps, pats) =>
1490      pats.map((pat) => {
1491        if (pat._steps.eq(steps)) {
1492          return pat;
1493        }
1494        const g = gap(steps.sub(pat._steps).div(2));
1495        return stepcat(g, pat, g);
1496      }),
1497    pats,
1498  );
1499}
1500
1501export function stackBy(by, ...pats) {
1502  const [left, ...right] = pats.map((pat) => pat._steps);
1503  const steps = left.maximum(...right);
1504  const lookup = {
1505    centre: stackCentre,
1506    left: stackLeft,
1507    right: stackRight,
1508    expand: stack,
1509    repeat: (...args) => polymeter(...args).steps(steps),
1510  };
1511  return by
1512    .inhabit(lookup)
1513    .fmap((func) => func(...pats))
1514    .innerJoin()
1515    .setSteps(steps);
1516}

Concatenation: combines a list of patterns, switching between them successively, one per cycle.

@tags combiners @return {Pattern} @synonyms cat @example slowcat("e5", "b4", ["d5", "c5"])

1528export function slowcat(...pats) {
1529  // Array test here is to avoid infinite recursions..
1530  pats = pats.map((pat) => (Array.isArray(pat) ? fastcat(...pat) : reify(pat)));
1531
1532  if (!pats.length) {
1533    return silence;
1534  } else if (pats.length == 1) {
1535    return pats[0];
1536  }
1537
1538  const query = function (state) {
1539    const span = state.span;
1540    const pat_n = _mod(span.begin.sam(), pats.length);
1541    const pat = pats[pat_n];
1542    // A bit of maths to make sure that cycles from constituent patterns aren't skipped.
1543    // For example if three patterns are slowcat-ed, the fourth cycle of the result should
1544    // be the second (rather than fourth) cycle from the first pattern.
1545    const offset = span.begin.floor().sub(span.begin.div(pats.length).floor());
1546    return pat.withHapTime((t) => t.add(offset)).query(state.setSpan(span.withTime((t) => t.sub(offset))));
1547  };
1548  const steps = __steps ? lcm(...pats.map((x) => x._steps)) : undefined;
1549  return new Pattern(query).splitQueries().setSteps(steps);
1550}

Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles. @tags combiners @param {...any} items - The items to concatenate @return {Pattern}

1557export function slowcatPrime(...pats) {
1558  if (!pats.length) {
1559    return silence;
1560  }
1561  pats = pats.map(reify);
1562  const query = function (state) {
1563    const pat_n = _mod(Math.floor(state.span.begin), pats.length);
1564    const pat = pats[pat_n];
1565    return pat.query(state);
1566  };
1567  return new Pattern(query).splitQueries();
1568}

The given items are concatenated, where each one takes one cycle.

@tags combiners @param {...any} items - The items to concatenate @synonyms slowcat @return {Pattern} @example cat("e5", "b4", ["d5", "c5"]).note() // "<e5 b4 [d5 c5]>".note()

@example // As a chained function: s("hh*4").cat( note("c4(5,8)") )

1586export function cat(...pats) {
1587  return slowcat(...pats);
1588}

Allows to arrange multiple patterns together over multiple cycles. Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use.

@tags combiners @return {Pattern} @example arrange( [4, "<c a f e>(3,8)"], [2, "<g a>(5,8)"] ).note()

1602export function arrange(...sections) {
1603  const total = sections.reduce((sum, [cycles]) => sum + cycles, 0);
1604  sections = sections.map(([cycles, section]) => [cycles, section.fast(cycles)]);
1605  return stepcat(...sections).slow(total);
1606}

Similarly to arrange, allows you to arrange multiple patterns together over multiple cycles. Unlike arrange, you specify a start and stop time for each pattern rather than duration, which means that patterns can overlap. @tags combiners @return {Pattern} @example seqPLoop( [0, 2, "bd(3,8)"], [1, 3, "cp(3,8)"] ).sound()

1620export function seqPLoop(...parts) {
1621  let total = Fraction(0);
1622  const pats = [];
1623  for (let part of parts) {
1624    if (part.length == 2) {
1625      part.unshift(total);
1626    }
1627    total = part[1];
1628  }
1629
1630  return stack(
1631    ...parts.map(([start, stop, pat]) =>
1632      pure(reify(pat)).compress(Fraction(start).div(total), Fraction(stop).div(total)),
1633    ),
1634  )
1635    .slow(total)
1636    .innerJoin(); // or resetJoin or restartJoin ??
1637}
1639export function fastcat(...pats) {
1640  let result = slowcat(...pats);
1641  if (pats.length > 1) {
1642    result = result._fast(pats.length);
1643    result._steps = pats.length;
1644  }
1645  if (pats.length == 1 && pats[0].__steps_source) {
1646    pats._steps = pats[0]._steps;
1647  }
1648  return result;
1649}

See fastcat @name sequence @tags combiners

1655export function sequence(...pats) {
1656  return fastcat(...pats);
1657}

Like cat, but the items are crammed into one cycle. @tags combiners @synonyms fastcat @example seq("e5", "b4", ["d5", "c5"]).note() // "e5 b4 [d5 c5]".note()

@example // As a chained function: s("hh*4").seq( note("c4(5,8)") )

1673export function seq(...pats) {
1674  return fastcat(...pats);
1675}
1677function _sequenceCount(x) {
1678  if (Array.isArray(x)) {
1679    if (x.length == 0) {
1680      return [silence, 0];
1681    }
1682    if (x.length == 1) {
1683      return _sequenceCount(x[0]);
1684    }
1685    return [fastcat(...x.map((a) => _sequenceCount(a)[0])), x.length];
1686  }
1687  return [reify(x), 1];
1688}
1689
1690export const mask = curry((a, b) => reify(b).mask(a));
1691export const struct = curry((a, b) => reify(b).struct(a));
1692export const superimpose = curry((a, b) => reify(b).superimpose(...a));
1693export const withValue = curry((a, b) => reify(b).withValue(a));
1694
1695export const bind = curry((a, b) => reify(b).bind(a));
1696export const innerBind = curry((a, b) => reify(b).innerBind(a));
1697export const outerBind = curry((a, b) => reify(b).outerBind(a));
1698export const squeezeBind = curry((a, b) => reify(b).squeezeBind(a));
1699export const stepBind = curry((a, b) => reify(b).stepBind(a));
1700export const polyBind = curry((a, b) => reify(b).polyBind(a));

operators

1703export const set = curry((a, b) => reify(b).set(a));
1704export const keep = curry((a, b) => reify(b).keep(a));
1705export const keepif = curry((a, b) => reify(b).keepif(a));
1706export const add = curry((a, b) => reify(b).add(a));
1707export const sub = curry((a, b) => reify(b).sub(a));
1708export const mul = curry((a, b) => reify(b).mul(a));
1709export const div = curry((a, b) => reify(b).div(a));
1710export const mod = curry((a, b) => reify(b).mod(a));
1711export const pow = curry((a, b) => reify(b).pow(a));
1712export const band = curry((a, b) => reify(b).band(a));
1713export const bor = curry((a, b) => reify(b).bor(a));
1714export const bxor = curry((a, b) => reify(b).bxor(a));
1715export const blshift = curry((a, b) => reify(b).blshift(a));
1716export const brshift = curry((a, b) => reify(b).brshift(a));
1717export const lt = curry((a, b) => reify(b).lt(a));
1718export const gt = curry((a, b) => reify(b).gt(a));
1719export const lte = curry((a, b) => reify(b).lte(a));
1720export const gte = curry((a, b) => reify(b).gte(a));
1721export const eq = curry((a, b) => reify(b).eq(a));
1722export const eqt = curry((a, b) => reify(b).eqt(a));
1723export const ne = curry((a, b) => reify(b).ne(a));
1724export const net = curry((a, b) => reify(b).net(a));
1725export const and = curry((a, b) => reify(b).and(a));
1726export const or = curry((a, b) => reify(b).or(a));
1727export const func = curry((a, b) => reify(b).func(a));

Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.

@tags functional @param {string | string[]} name name of the function, or an array of names to be used as synonyms @param {function} func function with 1 or more params, where last is the current pattern @param {bool} patternify defaults to true; if set to false, you will have more control over the arguments to func as they will be in their raw form and it will be up to you to patternify them and/or query them for values @example const vlpf = register('vlpf', (freq, pat) => { return pat.fmap((v) => ({...v, cutoff: freq * (v.velocity ?? 1) })); }) s("saw").seg(8).velocity(rand).vlpf(800)

1744export function register(name, func, patternify = true, preserveSteps = false, join = (x) => x.innerJoin()) {
1745  if (isPattern(name)) {
1746    throw new Error(
1747      'Name argument for register is a pattern, try using single quotes (\'name\') instead of double quotes ("name")',
1748    );
1749  }
1750
1751  if (Array.isArray(name)) {
1752    const result = {};
1753    for (const name_item of name) {
1754      result[name_item] = register(name_item, func, patternify, preserveSteps, join);
1755    }
1756    return result;
1757  }
1758  const arity = func.length;
1759  var pfunc; // the patternified function
1760
1761  if (patternify) {
1762    pfunc = function (...args) {
1763      args = args.map(reify);
1764      const pat = args[args.length - 1];
1765      let result;
1766
1767      if (arity === 1) {
1768        result = func(pat);
1769      } else {
1770        const firstArgs = args.slice(0, -1);
1771
1772        if (firstArgs.every((arg) => arg.__pure != undefined)) {
1773          const pureArgs = firstArgs.map((arg) => arg.__pure);
1774          const pureLocs = firstArgs.filter((arg) => arg.__pure_loc).map((arg) => arg.__pure_loc);
1775          result = func(...pureArgs, pat);
1776          result = result.withContext((context) => {
1777            const locations = (context.locations || []).concat(pureLocs);
1778            return { ...context, locations };
1779          });
1780        } else {
1781          const [left, ...right] = firstArgs;
1782
1783          let mapFn = (...args) => {
1784            return func(...args, pat);
1785          };
1786          mapFn = curry(mapFn, null, arity - 1);
1787          result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)));
1788        }
1789      }
1790      if (preserveSteps) {
1791        result._steps = pat._steps;
1792      }
1793      return result;
1794    };
1795  } else {
1796    pfunc = function (...args) {
1797      args = args.map(reify);
1798      const result = func(...args);
1799      if (preserveSteps) {
1800        result._steps = args[args.length - 1]._steps;
1801      }
1802      return result;
1803    };
1804  }
1805
1806  Pattern.prototype[name] = function (...args) {
1807    // For methods that take a single argument (plus 'this'), allow
1808    // multiple arguments but sequence them
1809    if (arity === 2 && args.length !== 1) {
1810      args = [sequence(...args)];
1811    } else if (arity !== args.length + 1) {
1812      throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`);
1813    }
1814    args = args.map(reify);
1815    return pfunc(...args, this);
1816  };
1817
1818  if (arity > 1) {
1819    // There are patternified args, so lets make an unpatternified
1820    // version, prefixed by '_'
1821    Pattern.prototype['_' + name] = function (...args) {
1822      const result = func(...args, this);
1823      if (preserveSteps) {
1824        result.setSteps(this._steps);
1825      }
1826      return result;
1827    };
1828  }

toplevel functions get curried as well as patternified because pfunc uses spread args, we need to state the arity explicitly!

1832  const curried = curry(pfunc, null, arity);
1833  strudelScope[name] = curried;
1834  return curried;
1835}

Like register, but defaults to stepJoin

1838function stepRegister(name, func, patternify = true, preserveSteps = false, join = (x) => x.stepJoin()) {
1839  return register(name, func, patternify, preserveSteps, join);
1840}

//////////////////////////////////////////////////////////////////// Numerical transformations

Assumes a numerical pattern. Returns a new pattern with all values rounded to the nearest integer. @name round @tags math @memberof Pattern @returns Pattern @example n("0.5 1.5 2.5".round()).scale("C:major")

1855export const round = register('round', function (pat) {
1856  return pat.asNumber().fmap((v) => Math.round(v));
1857});

Assumes a numerical pattern. Returns a new pattern with all values set to their mathematical floor. E.g. 3.7 replaced with to 3, and -4.2 replaced with -5. @name floor @memberof Pattern @tags math @returns Pattern @example note("42 42.1 42.5 43".floor())

1869export const floor = register('floor', function (pat) {
1870  return pat.asNumber().fmap((v) => Math.floor(v));
1871});
1873export const log2 = register('log2', (pat) => pat.asNumber().fmap((v) => Math.log2(v)));

Assumes a numerical pattern. Returns a new pattern with all values set to their mathematical ceiling. E.g. 3.2 replaced with 4, and -4.2 replaced with -4. @name ceil @memberof Pattern @tags math @returns Pattern @example note("42 42.1 42.5 43".ceil())

1886export const ceil = register('ceil', function (pat) {
1887  return pat.asNumber().fmap((v) => Math.ceil(v));
1888});

Assumes a numerical pattern, containing unipolar values in the range 0 ..

  1. Returns a new pattern with values scaled to the bipolar range -1 .. 1 @tags math @returns Pattern @noAutocomplete
1896export const toBipolar = register('toBipolar', function (pat) {
1897  return pat.fmap((x) => x * 2 - 1);
1898});

Assumes a numerical pattern, containing bipolar values in the range -1 .. 1 Returns a new pattern with values scaled to the unipolar range 0 .. 1 @tags math @returns Pattern @noAutocomplete

1907export const fromBipolar = register('fromBipolar', function (pat) {
1908  return pat.fmap((x) => (x + 1) / 2);
1909});

Assumes a numerical pattern, containing unipolar values in the range 0 .. 1. Returns a new pattern with values scaled to the given min/max range. Most useful in combination with continuous patterns. @name range @memberof Pattern @tags math @returns Pattern @example s("[bd sd]2,hh8") .cutoff(sine.range(500,4000))

1923export const range = register('range', function (min, max, pat) {
1924  return pat.mul(max - min).add(min);
1925});

Assumes a numerical pattern, containing unipolar values in the range 0 .. 1 Returns a new pattern with values scaled to the given min/max range, following an exponential curve. @name rangex @memberof Pattern @tags math @returns Pattern @example s("[bd sd]2,hh8") .cutoff(sine.rangex(500,4000))

1939export const rangex = register('rangex', function (min, max, pat) {
1940  return pat._range(Math.log(min), Math.log(max)).fmap(Math.exp);
1941});

Assumes a numerical pattern, containing bipolar values in the range -1 .. 1 Returns a new pattern with values scaled to the given min/max range. @name range2 @memberof Pattern @tags math @returns Pattern @example s("[bd sd]2,hh8") .cutoff(sine2.range2(500,4000))

1954export const range2 = register('range2', function (min, max, pat) {
1955  return pat.fromBipolar()._range(min, max);
1956});

Allows dividing numbers via list notation using ":". Returns a new pattern with just numbers. @name ratio @memberof Pattern @tags math @returns Pattern @example ratio("1, 5:4, 3:2").mul(110) .freq().s("piano")

1969export const ratio = register('ratio', (pat) =>
1970  pat.fmap((v) => {
1971    if (!Array.isArray(v)) {
1972      return v;
1973    }
1974    return v.slice(1).reduce((acc, n) => acc / n, v[0]);
1975  }),
1976);

//////////////////////////////////////////////////////////////////// Structural and temporal transformations

Compress each cycle into the given timespan, leaving a gap @tags temporal @example cat( s("bd sd").compress(.25,.75), s("~ bd sd ~") )

1989export const compress = register('compress', function (b, e, pat) {
1990  b = Fraction(b);
1991  e = Fraction(e);
1992  if (b.gt(e) || b.gt(1) || e.gt(1) || b.lt(0) || e.lt(0)) {
1993    return silence;
1994  }
1995  return pat._fastGap(Fraction(1).div(e.sub(b)))._late(b);
1996});
1998export const { compressSpan, compressspan } = register(['compressSpan', 'compressspan'], function (span, pat) {
1999  return pat._compress(span.begin, span.end);
2000});

speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast. @tags temporal @name fastGap @synonyms fastgap @example s("bd sd").fastGap(2)

2010export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (factor, pat) {
2011  // A bit fiddly, to drop zero-width queries at the start of the next cycle
2012  const qf = function (span) {
2013    const cycle = span.begin.sam();
2014    const bpos = span.begin.sub(cycle).mul(factor).min(1);
2015    const epos = span.end.sub(cycle).mul(factor).min(1);
2016    if (bpos >= 1) {
2017      return undefined;
2018    }
2019    return new TimeSpan(cycle.add(bpos), cycle.add(epos));
2020  };
2021  // Also fiddly, to maintain the right 'whole' relative to the part
2022  const ef = function (hap) {
2023    const begin = hap.part.begin;
2024    const end = hap.part.end;
2025    const cycle = begin.sam();
2026    const beginPos = begin.sub(cycle).div(factor).min(1);
2027    const endPos = end.sub(cycle).div(factor).min(1);
2028    const newPart = new TimeSpan(cycle.add(beginPos), cycle.add(endPos));
2029    const newWhole = !hap.whole
2030      ? undefined
2031      : new TimeSpan(
2032          newPart.begin.sub(begin.sub(hap.whole.begin).div(factor)),
2033          newPart.end.add(hap.whole.end.sub(end).div(factor)),
2034        );
2035    return new Hap(newWhole, newPart, hap.value, hap.context);
2036  };
2037  return pat.withQuerySpanMaybe(qf).withHap(ef).splitQueries();
2038});

Similar to compress, but doesn't leave gaps, and the 'focus' can be bigger than a cycle @tags temporal @example s("bd hh sd hh").focus(1/4, 3/4)

2046export const focus = register('focus', function (b, e, pat) {
2047  b = Fraction(b);
2048  e = Fraction(e);
2049  return pat
2050    ._early(b.sam())
2051    ._fast(Fraction(1).div(e.sub(b)))
2052    ._late(b);
2053});
2055export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], function (span, pat) {
2056  return pat._focus(span.begin, span.end);
2057});

The ply function repeats each event the given number of times. @tags temporal @example s("bd ~ sd cp").ply("<1 2 3>")

2064export const ply = register('ply', function (factor, pat) {
2065  const result = pat.fmap((x) => pure(x)._fast(factor)).squeezeJoin();
2066  if (__steps) {
2067    result._steps = Fraction(factor).mulmaybe(pat._steps);
2068  }
2069  return result;
2070});

Speed up a pattern by the given factor. Used by "*" in mini notation.

@tags temporal @name fast @synonyms density @memberof Pattern @param {number | Pattern} factor speed up factor @returns Pattern @example s("bd hh sd hh").fast(2) // s("[bd hh sd hh]*2")

2084export const { fast, density } = register(
2085  ['fast', 'density'],
2086  function (factor, pat) {
2087    if (factor === 0) {
2088      return silence;
2089    }
2090    factor = Fraction(factor);
2091    const fastQuery = pat.withQueryTime((t) => t.mul(factor));
2092    return fastQuery.withHapTime((t) => t.div(factor)).setSteps(pat._steps);
2093  },
2094  true,
2095  true,
2096);

Both speeds up the pattern (like 'fast') and the sample playback (like 'speed'). @tags temporal @example s("bd sd:2").hurry("<1 2 4 3>").slow(1.5)

2104export const hurry = register('hurry', function (r, pat) {
2105  return pat._fast(r).mul(pure({ speed: r }));
2106});

Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation.

@tags temporal @name slow @synonyms sparsity @memberof Pattern @param {number | Pattern} factor slow down factor @returns Pattern @example s("bd hh sd hh").slow(2) // s("[bd hh sd hh]/2")

2120export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) {
2121  if (factor === 0) {
2122    return silence;
2123  }
2124  return pat._fast(Fraction(1).div(factor));
2125});

Carries out an operation 'inside' a cycle. @tags temporal @example "0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note() // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note()

2134export const inside = register('inside', function (factor, f, pat) {
2135  return f(pat._slow(factor))._fast(factor);
2136});

Carries out an operation 'outside' a cycle. @tags temporal @example "<[0 1] 2 [3 4] 5>".outside(4, rev).scale('C major').note() // "<[0 1] 2 [3 4] 5>".fast(4).rev().slow(4).scale('C major').note()

2145export const outside = register('outside', function (factor, f, pat) {
2146  return f(pat._fast(factor))._slow(factor);
2147});

Applies the given function every n cycles, starting from the last cycle. @tags temporal @name lastOf @memberof Pattern @param {number} n how many cycles @param {function} func function to apply @returns Pattern @example note("c3 d3 e3 g3").lastOf(4, x=>x.rev())

2160export const lastOf = register('lastOf', function (n, func, pat) {
2161  const pats = Array(n - 1).fill(pat);
2162  pats.push(func(pat));
2163  return slowcatPrime(...pats);
2164});

Applies the given function every n cycles, starting from the first cycle. @tags temporal @name firstOf @memberof Pattern @param {number} n how many cycles @param {function} func function to apply @returns Pattern @example note("c3 d3 e3 g3").firstOf(4, x=>x.rev())

An alias for firstOf @tags temporal @name every @memberof Pattern @param {number} n how many cycles @param {function} func function to apply @returns Pattern @example note("c3 d3 e3 g3").every(4, x=>x.rev())

2189export const { firstOf, every } = register(['firstOf', 'every'], function (n, func, pat) {
2190  const pats = Array(n - 1).fill(pat);
2191  pats.unshift(func(pat));
2192  return slowcatPrime(...pats);
2193});

Applies the given function to the pattern. Like layer, but with a single function: @tags combiners @name apply @example "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()

2202export const apply = register('apply', function (func, pat) {
2203  return func(pat);
2204});

Plays the pattern at the given cycles per minute. @tags temporal @deprecated @example s("<bd sd>,hh*2").cpm(90) // = 90 bpm

this is redefined in repl.mjs, using the current cps as divisor

2214export const cpm = register('cpm', function (cpm, pat) {
2215  return pat._fast(cpm / 60 / 1);
2216});

Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator

@tags temporal @name early @memberof Pattern @param {number | Pattern} cycles number of cycles to nudge left @returns Pattern @example "bd ~".stack("hh ~".early(.1)).s()

2229export const early = register(
2230  'early',
2231  function (offset, pat) {
2232    offset = Fraction(offset);
2233    return pat.withQueryTime((t) => t.add(offset)).withHapTime((t) => t.sub(offset));
2234  },
2235  true,
2236  true,
2237);

Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator

@tags temporal @name late @memberof Pattern @param {number | Pattern} cycles number of cycles to nudge right @returns Pattern @example "bd ~".stack("hh ~".late(.1)).s()

2250export const late = register(
2251  'late',
2252  function (offset, pat) {
2253    offset = Fraction(offset);
2254    return pat._early(Fraction(0).sub(offset));
2255  },
2256  true,
2257  true,
2258);

Plays a portion of a pattern, specified by the beginning and end of a time span. The new resulting pattern is played over the time period of the original pattern:

@tags temporal @example s("bd2 hh3 [sd bd]2 perc").zoom(0.25, 0.75) // s("hh3 [sd bd]*2") // equivalent

2268export const zoom = register('zoom', function (s, e, pat) {
2269  e = Fraction(e);
2270  s = Fraction(s);
2271  if (s.gte(e)) {
2272    return nothing;
2273  }
2274  const d = e.sub(s);
2275  const steps = __steps ? pat._steps?.mulmaybe(d) : undefined;
2276  return pat
2277    .withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s)))
2278    .withHapSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
2279    .splitQueries()
2280    .setSteps(steps);
2281});
2283export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a, pat) {
2284  return pat.zoom(a.begin, a.end);
2285});

Splits a pattern into the given number of slices, and plays them according to a pattern of slice numbers. Similar to slice, but slices up patterns rather than sound samples. @tags temporal @param {number} number of slices @param {number} slices to play @example note("0 1 2 3 4 5 6 7".scale('c:mixolydian')) .bite(4, "3 2 1 0") @example sound("bd - bd bd*2, - sd:6 - sd:5 sd:1 - [- sd:2] -, hh [- cp:7]") .bank("RolandTR909").speed(1.2) .bite(4, "0 0 [1 2] <3 2> 0 0 [2 1] 3")

2301export const bite = register(
2302  'bite',
2303  (npat, ipat, pat) => {
2304    return ipat
2305      .fmap((i) => (n) => {
2306        const a = Fraction(i).div(n).mod(1);
2307        const b = a.add(Fraction(1).div(n));
2308        return pat.zoom(a, b);
2309      })
2310      .appLeft(npat)
2311      .squeezeJoin();
2312  },
2313  false,
2314);

Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle. @tags temporal @param {number} fraction fraction to select @example s("lt ht mt cp, [hh oh]*2").linger("<1 .5 .25 .125>")

2323export const linger = register(
2324  'linger',
2325  function (t, pat) {
2326    if (t == 0) {
2327      return silence;
2328    } else if (t < 0) {
2329      return pat._zoom(t.add(1), 1)._slow(t);
2330    }
2331    return pat._zoom(0, t)._slow(t);
2332  },
2333  true,
2334  true,
2335);

Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one. @tags temporal @name segment @synonyms seg @param {number} segments number of segments per cycle @example note(saw.range(40,52).segment(24))

2346export const { segment, seg } = register(['segment', 'seg'], function (rate, pat) {
2347  return pat.struct(pure(true)._fast(rate)).setSteps(rate);
2348});

The function swingBy x n breaks each cycle into n slices, and then delays events in the second half of each slice by the amount x, which is relative to the size of the (half) slice. So if x is 0 it does nothing, 0.5 delays for half the note duration, and 1 will wrap around to doing nothing again. The end result is a shuffle or swing-like rhythm @tags temporal @param {number} subdivision @param {number} offset @example s("hh*8").swingBy(1/3, 4)

2358export const swingBy = register('swingBy', (swing, n, pat) => pat.inside(n, late(seq(0, swing / 2))));

Shorthand for swingBy with 1/3: @tags temporal @param {number} subdivision @example s("hh8").swing(4) // s("hh8").swingBy(1/3, 4)

2368export const swing = register('swing', (n, pat) => pat.swingBy(1 / 3, n));

Swaps 1s and 0s in a binary pattern. @tags temporal @name invert @synonyms inv @example s("bd").struct("1 0 0 1 0 0 1 0".lastOf(4, invert))

2378export const { invert, inv } = register(
2379  ['invert', 'inv'],
2380  function (pat) {
2381    // Swap true/false in a binary pattern
2382    return pat.fmap((x) => !x);
2383  },
2384  true,
2385  true,
2386);

Applies the given function whenever the given pattern is in a true state. @tags temporal @name when @memberof Pattern @param {Pattern} binary_pat @param {function} func @returns Pattern @example "c3 eb3 g3".when("<0 1>/2", x=>x.sub("5")).note()

2399export const when = register('when', function (on, func, pat) {
2400  return on ? func(pat) : pat;
2401});

Superimposes the function result on top of the original pattern, delayed by the given time. @tags temporal @name off @memberof Pattern @param {Pattern | number} time offset time @param {function} func function to apply @returns Pattern @example "c3 eb3 g3".off(1/8, x=>x.add(7)).note()

2414export const off = register('off', function (time_pat, func, pat) {
2415  return stack(pat, func(pat.late(time_pat)));
2416});

Returns a new pattern where every other cycle is played once, twice as fast, and offset in time by one quarter of a cycle. Creates a kind of breakbeat feel. @tags temporal @returns Pattern

2425export const brak = register('brak', function (pat) {
2426  return pat.when(slowcat(false, true), (x) => fastcat(x, silence)._late(0.25));
2427});

Reverse all cycles in a pattern. See also revv for reversing a whole pattern.

@tags temporal @name rev @memberof Pattern @returns Pattern @example note("c d e g").rev()

2439export const rev = register(
2440  'rev',
2441  function (pat) {
2442    const query = function (state) {
2443      const span = state.span;
2444      const cycle = span.begin.sam();
2445      const next_cycle = span.begin.nextSam();
2446      const reflect = function (to_reflect) {
2447        const reflected = to_reflect.withTime((time) => cycle.add(next_cycle.sub(time)));
2448        // [reflected.begin, reflected.end] = [reflected.end, reflected.begin] -- didn't work
2449        const tmp = reflected.begin;
2450        reflected.begin = reflected.end;
2451        reflected.end = tmp;
2452        return reflected;
2453      };
2454      const haps = pat.query(state.setSpan(reflect(span)));
2455      return haps.map((hap) => hap.withSpan(reflect));
2456    };
2457    return new Pattern(query).splitQueries();
2458  },
2459  false,
2460  true,
2461);

Reverse a whole pattern. See also rev for reversing each cycle.

@name revv @tags temporal @memberof Pattern @returns Pattern @example // This is the same as <[g e] [d c]>. If rev() is used, you get // the same as <[d c] [g e]>, where each cycle reverses, but the order of // cycles stays the same. note("<[c d] [e g]>").revv()

2476export const revv = register('revv', function (pat) {
2477  const negateSpan = (span) => new TimeSpan(Fraction(0).sub(span.end), Fraction(0).sub(span.begin));
2478  return pat.withQuerySpan(negateSpan).withHapSpan(negateSpan);
2479});

Like press, but allows you to specify the amount by which each event is shifted. pressBy(0.5) is the same as press, while pressBy(1/3) shifts each event by a third of its timespan. @tags temporal @example stack(s("hh*4"), s("bd mt sd ht").pressBy("<0 0.5 0.25>") ).slow(2)

2490export const pressBy = register('pressBy', function (r, pat) {
2491  return pat.fmap((x) => pure(x).compress(r, 1)).squeezeJoin();
2492});

Syncopates a rhythm, by shifting each event halfway into its timespan. @tags temporal @example stack(s("hh*4"), s("bd mt sd ht").every(4, press) ).slow(2)

2502export const press = register('press', function (pat) {
2503  return pat._pressBy(0.5);
2504});

Silences a pattern. @tags temporal @example stack( s("bd").hush(), s("hh*3") )

2515Pattern.prototype.hush = function () {
2516  return silence;
2517};

Applies rev to a pattern every other cycle, so that the pattern alternates between forwards and backwards. @tags temporal @example note("c d e g").palindrome()

2525export const palindrome = register(
2526  'palindrome',
2527  function (pat) {
2528    return pat.lastOf(2, rev);
2529  },
2530  true,
2531  true,
2532);

Jux with adjustable stereo width. 0 = mono, 1 = full stereo. @tags temporal @name juxBy @synonyms juxby @example s("bd lt [~ ht] mt cp ~ bd hh").juxBy("<0 .5 1>/2", rev)

2542export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, pat) {
2543  by /= 2;
2544  const elem_or = function (dict, key, dflt) {
2545    if (key in dict) {
2546      return dict[key];
2547    }
2548    return dflt;
2549  };
2550  const left = pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) - by }));
2551  const right = func(pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) + by })));
2552
2553  return stack(left, right).setSteps(__steps ? lcm(left._steps, right._steps) : undefined);
2554});

Like juxBy, except it flips the ears each cycle. @name juxFlipBy @synonyms juxflipby, fluxBy, fluxby @example s("bd lt [~ ht] mt cp ~ bd hh").juxFlipBy(".8", rev)

2563export const { juxFlipBy, juxflipby, fluxBy, fluxby } = register(
2564  ['juxFlipBy', 'juxflipby', 'fluxBy', 'fluxby'],
2565  function (by, func, pat) {
2566    return pat.juxBy(slowcat(by, -by), func);
2567  },
2568);

The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. @tags temporal, superdough @example s("bd lt [~ ht] mt cp ~ bd hh").jux(rev) @example s("bd lt [~ ht] mt cp ~ bd hh").jux(press) @example s("bd lt [~ ht] mt cp ~ bd hh").jux(iter(4))

2580export const jux = register('jux', function (func, pat) {
2581  return pat._juxBy(1, func, pat);
2582});

Like jux, but flips the ears each cycle. @name juxFlip @synonyms juxflip, flux @example s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(rev) @example s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(press) @example s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(iter(4))

2595export const { juxFlip, flux } = register(['juxFlip', 'juxflip', 'flux'], function (func, pat) {
2596  return pat._juxFlipBy(1, func, pat);
2597});

Superimpose and offset multiple times, applying the given function each time. @tags temporal, functional @name echoWith @synonyms echowith, stutWith, stutwith @param {number} times how many times to repeat @param {number} time cycle offset between iterations @param {function} func function to apply, given the pattern and the iteration index @example "<0 [2 4]>" .echoWith(4, 1/8, (p,n) => p.add(n*2)) .scale("C:minor").note()

2612export const { echoWith, echowith, stutWith, stutwith } = register(
2613  ['echoWith', 'echowith', 'stutWith', 'stutwith'],
2614  function (times, time, func, pat) {
2615    return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i)));
2616  },
2617);

Superimpose and offset multiple times, gradually decreasing the velocity @tags temporal @name echo @memberof Pattern @returns Pattern @param {number} times how many times to repeat @param {number} time cycle offset between iterations @param {number} feedback velocity multiplicator for each iteration @example s("bd sd").echo(3, 1/6, .8)

2631export const echo = register('echo', function (times, time, feedback, pat) {
2632  return pat._echoWith(times, time, (pat, i) => pat.gain(Math.pow(feedback, i)));
2633});

Deprecated. Like echo, but the last 2 parameters are flipped. @tags temporal @name stut @param {number} times how many times to repeat @param {number} feedback velocity multiplicator for each iteration @param {number} time cycle offset between iterations @example s("bd sd").stut(3, .8, 1/6)

2645export const stut = register('stut', function (times, feedback, time, pat) {
2646  return pat._echoWith(times, time, (pat, i) => pat.gain(Math.pow(feedback, i)));
2647});
2649export const applyN = register('applyN', function (n, func, p) {
2650  let result = p;
2651  for (let i = 0; i < n; i++) {
2652    result = func(result);
2653  }
2654  return result;
2655});

The plyWith function repeats each event the given number of times, applying the given function to each event.\n @tags temporal @name plyWith @synonyms plywith @param {number} factor how many times to repeat @param {function} func function to apply, given the pattern @example "<0 [2 4]>" .plyWith(4, (p) => p.add(2)) .scale("C:minor").note()

2669export const plyWith = register(['plyWith', 'plywith'], function (factor, func, pat) {
2670  const result = pat
2671    .fmap((x) => cat(...listRange(0, factor - 1).map((i) => applyN(i, func, x)))._fast(factor))
2672    .squeezeJoin();
2673  if (__steps) {
2674    result._steps = Fraction(factor).mulmaybe(pat._steps);
2675  }
2676  return result;
2677});

The plyForEach function repeats each event the given number of times, applying the given function to each event. This version of ply uses the iteration index as an argument to the function, similar to echoWith. @tags temporal @name plyForEach @synonyms plyforeach @param {number} factor how many times to repeat @param {function} func function to apply, given the pattern and the iteration index @example "<0 [2 4]>" .plyForEach(4, (p,n) => p.add(n*2)) .scale("C:minor").note()

2692export const plyForEach = register(['plyForEach', 'plyforeach'], function (factor, func, pat) {
2693  const result = pat
2694    .fmap((x) => cat(cat(pure(x), ...listRange(1, factor - 1).map((i) => func(pure(x), i))))._fast(factor))
2695    .squeezeJoin();
2696  if (__steps) {
2697    result._steps = Fraction(factor).mulmaybe(pat._steps);
2698  }
2699  return result;
2700});

Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played. @tags temporal @name iter @memberof Pattern @returns Pattern @example note("0 1 2 3".scale('A minor')).iter(4)

2712const _iter = function (times, pat, back = false) {
2713  times = Fraction(times);
2714  return slowcat(
2715    ...listRange(0, times.sub(1)).map((i) =>
2716      back ? pat.late(Fraction(i).div(times)) : pat.early(Fraction(i).div(times)),
2717    ),
2718  );
2719};
2721export const iter = register(
2722  'iter',
2723  function (times, pat) {
2724    return _iter(times, pat, false);
2725  },
2726  true,
2727  true,
2728);

Like iter, but plays the subdivisions in reverse order. Known as iter' in tidalcycles @tags temporal @name iterBack @synonyms iterback @memberof Pattern @returns Pattern @example note("0 1 2 3".scale('A minor')).iterBack(4)

2740export const { iterBack, iterback } = register(
2741  ['iterBack', 'iterback'],
2742  function (times, pat) {
2743    return _iter(times, pat, true);
2744  },
2745  true,
2746  true,
2747);

Repeats each cycle the given number of times. @tags temporal @name repeatCycles @memberof Pattern @returns Pattern @example note(irand(12).add(34)).segment(4).repeatCycles(2).s("gm_acoustic_guitar_nylon")

2758export const { repeatCycles } = register(
2759  'repeatCycles',
2760  function (n, pat) {
2761    return new Pattern(function (state) {
2762      const cycle = state.span.begin.sam();
2763      const source_cycle = cycle.div(n).sam();
2764      const delta = cycle.sub(source_cycle);
2765      state = state.withSpan((span) => span.withTime((spant) => spant.sub(delta)));
2766      return pat.query(state).map((hap) => hap.withSpan((span) => span.withTime((spant) => spant.add(delta))));
2767    }).splitQueries();
2768  },
2769  true,
2770  true,
2771);

Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle). @tags temporal, functional @name chunk @synonyms slowChunk, slowchunk @memberof Pattern @returns Pattern @example "0 1 2 3".chunk(4, x=>x.add(7)) .scale("A:minor").note()

2784const _chunk = function (n, func, pat, back = false, fast = false) {
2785  const binary = Array(n - 1).fill(false);
2786  binary.unshift(true);
2787  // Invert the 'back' because we want to shift the pattern forwards,
2788  // and so time backwards
2789  const binary_pat = _iter(n, sequence(...binary), !back);
2790  if (!fast) {
2791    pat = pat.repeatCycles(n);
2792  }
2793  return pat.when(binary_pat, func);
2794};
2796export const { chunk, slowchunk, slowChunk } = register(
2797  ['chunk', 'slowchunk', 'slowChunk'],
2798  function (n, func, pat) {
2799    return _chunk(n, func, pat, false, false);
2800  },
2801  true,
2802  true,
2803);

Like chunk, but cycles through the parts in reverse order. Known as chunk' in tidalcycles @tags temporal @name chunkBack @synonyms chunkback @memberof Pattern @returns Pattern @example "0 1 2 3".chunkBack(4, x=>x.add(7)) .scale("A:minor").note()

2816export const { chunkBack, chunkback } = register(
2817  ['chunkBack', 'chunkback'],
2818  function (n, func, pat) {
2819    return _chunk(n, func, pat, true);
2820  },
2821  true,
2822  true,
2823);

Like chunk, but the cycles of the source pattern aren't repeated for each set of chunks. @tags temporal @name fastChunk @synonyms fastchunk @memberof Pattern @returns Pattern @example "<0 8> 1 2 3 4 5 6 7" .scale("C2:major").note() .fastChunk(4, x => x.color('red')).slow(2)

2838export const { fastchunk, fastChunk } = register(
2839  ['fastchunk', 'fastChunk'],
2840  function (n, func, pat) {
2841    return _chunk(n, func, pat, false, true);
2842  },
2843  true,
2844  true,
2845);

Like chunk, but the function is applied to a looped subcycle of the source pattern. @tags temporal @name chunkInto @synonyms chunkinto @memberof Pattern @example sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2)) .bank("tr909")

2857export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], function (n, func, pat) {
2858  return pat.into(fastcat(true, ...Array(n - 1).fill(false))._iterback(n), func);
2859});

Like chunkInto, but moves backwards through the chunks. @tags temporal @name chunkBackInto @synonyms chunkbackinto @memberof Pattern @example sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2)) .bank("tr909")

2871export const { chunkbackinto, chunkBackInto } = register(['chunkbackinto', 'chunkBackInto'], function (n, func, pat) {
2872  return pat.into(
2873    fastcat(true, ...Array(n - 1).fill(false))
2874      ._iter(n)
2875      ._early(1),
2876    func,
2877  );
2878});

TODO - redefine elsewhere in terms of mask

2881export const bypass = register(
2882  'bypass',
2883  function (on, pat) {
2884    on = Boolean(parseInt(on));
2885    return on ? silence : pat;
2886  },
2887  true,
2888  true,
2889);

Loops the pattern inside an offset for cycles. If you think of the entire span of time in cycles as a ribbon, you can cut a single piece and loop it. @tags temporal @name ribbon @synonyms rib @param {number} offset start point of loop in cycles @param {number} cycles loop length in cycles @example note("<c d e f>").ribbon(1, 2) @example // Looping a portion of randomness n(irand(8).segment(4)).scale("c:pentatonic").ribbon(1337, 2) @example // rhythm generator s("bd!16?").ribbon(29,.5)

2908export const { ribbon, rib } = register(['ribbon', 'rib'], (offset, cycles, pat) =>
2909  pat.early(offset).restart(pure(1).slow(cycles)),
2910);
2912export const hsla = register('hsla', (h, s, l, a, pat) => {
2913  return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`);
2914});
2915
2916export const hsl = register('hsl', (h, s, l, pat) => {
2917  return pat.color(`hsl(${h}turn,${s * 100}%,${l * 100}%)`);
2918});

Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array). @name tag @tags temporal @param {string} tag anything unique @example s("saw!16").note("F1") .lpf(tri.range(40, 80).slow(4)).lpenv(5).lpq(4).lpd(0.15) .when(rand.late(0.1).gte(0.5), x => x.transpose("12").tag('altered')) .when(rand.late(0.2).gte(0.5), x => x.s("square").tag('altered')) .when("<0 1>", x => x.filter((hap) => hap.hasTag('altered')))

2932Pattern.prototype.tag = function (tag) {
2933  return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
2934};

Filters haps using the given function @name filter @tags temporal, functional @param {Function} test function to test Hap @example s("hh!7 oh").filter(hap => hap.value.s === 'hh')

2944export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test)));

Filters haps by their begin time @name filterWhen @tags temporal, functional @param {Function} test function to test Hap.whole.begin @example oneCycle: s("bd*4").filterWhen((t) => t < 1)

2954export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin)));

Use within to apply a function to only a part of a pattern. @name within @tags temporal, functional @param {number} start start within cycle (0 - 1) @param {number} end end within cycle (0 - 1). Must be > start @param {Function} func function to be applied to the sub-pattern

2964export const within = register('within', (a, b, fn, pat) =>
2965  stack(
2966    fn(pat.filterWhen((t) => t.cyclePos() >= a && t.cyclePos() <= b)),
2967    pat.filterWhen((t) => t.cyclePos() < a || t.cyclePos() > b),
2968  ),
2969);

//////////////////////////////////////////////////////////////////// Stepwise functions

2974Pattern.prototype.stepJoin = function () {
2975  const pp = this;
2976  const first_t = stepcat(..._retime(_slices(pp.queryArc(0, 1))))._steps;
2977  const q = function (state) {
2978    const shifted = pp.early(state.span.begin.sam());
2979    const haps = shifted.query(state.setSpan(new TimeSpan(Fraction(0), Fraction(1))));
2980    const pat = stepcat(..._retime(_slices(haps)));
2981    return pat.query(state);
2982  };
2983  return new Pattern(q, first_t);
2984};
2986Pattern.prototype.stepBind = function (func) {
2987  return this.fmap(func).stepJoin();
2988};
2989
2990export function _retime(timedHaps) {
2991  const occupied_perc = timedHaps.filter((t, pat) => pat.hasSteps).reduce((a, b) => a.add(b), Fraction(0));
2992  const occupied_steps = removeUndefineds(timedHaps.map((t, pat) => pat._steps)).reduce(
2993    (a, b) => a.add(b),
2994    Fraction(0),
2995  );
2996  const total_steps = occupied_perc.eq(0) ? undefined : occupied_steps.div(occupied_perc);
2997  function adjust(dur, pat) {
2998    if (pat._steps === undefined) {
2999      return [dur.mulmaybe(total_steps), pat];
3000    }
3001    return [pat._steps, pat];
3002  }
3003  return timedHaps.map((x) => adjust(...x));
3004}
3005
3006export function _slices(haps) {
3007  // slices evs = map (\s -> ((snd s - fst s), stack $ map value $ fit s evs))
3008  // $ pairs $ sort $ nubOrd $ 0:1:concatMap (\ev -> start (part ev):stop (part ev):[]) evs
3009  const breakpoints = flatten(haps.map((hap) => [hap.part.begin, hap.part.end]));
3010  const unique = uniqsortr([Fraction(0), Fraction(1), ...breakpoints]);
3011  const slicespans = pairs(unique);
3012  return slicespans.map((s) => [
3013    s[1].sub(s[0]),
3014    stack(..._fitslice(new TimeSpan(...s), haps).map((x) => x.value.withHap((h) => h.setContext(h.combineContext(x))))),
3015  ]);
3016}
3017
3018export function _fitslice(span, haps) {
3019  return removeUndefineds(haps.map((hap) => _match(span, hap)));
3020}
3021
3022export function _match(span, hap_p) {
3023  const subspan = span.intersection(hap_p.part);
3024  if (subspan == undefined) {
3025    return undefined;
3026  }
3027  return new Hap(hap_p.whole, subspan, hap_p.value, hap_p.context);
3028}

Experimental

Speeds a pattern up or down, to fit to the given number of steps per cycle. @tags stepwise @example sound("bd sd cp").pace(4) // The same as sound("{bd sd cp}%4") or sound("<bd sd cp>*4")

3039export const pace = register('pace', function (targetSteps, pat) {
3040  if (pat._steps === undefined) {
3041    return pat;
3042  }
3043  if (pat._steps.eq(Fraction(0))) {
3044    // avoid divide by zero..
3045    return nothing;
3046  }
3047  return pat._fast(Fraction(targetSteps).div(pat._steps)).setSteps(targetSteps);
3048});
3050export function _polymeterListSteps(steps, ...args) {
3051  const seqs = args.map((a) => _sequenceCount(a));
3052  if (seqs.length == 0) {
3053    return silence;
3054  }
3055  if (steps == 0) {
3056    steps = seqs[0][1];
3057  }
3058  const pats = [];
3059  for (const seq of seqs) {
3060    if (seq[1] == 0) {
3061      continue;
3062    }
3063    if (steps == seq[1]) {
3064      pats.push(seq[0]);
3065    } else {
3066      pats.push(seq[0]._fast(Fraction(steps).div(Fraction(seq[1]))));
3067    }
3068  }
3069  return stack(...pats);
3070}

Experimental

Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps. @tags stepwise @synonyms pm @example // The same as note("{c eb g, c2 g2}%6") polymeter("c eb g", "c2 g2").note()

3083export function polymeter(...args) {
3084  if (Array.isArray(args[0])) {
3085    // Support old behaviour
3086    return _polymeterListSteps(0, ...args);
3087  }

TODO currently ignoring arguments without steps...

3090  args = args.filter((arg) => arg.hasSteps);
3092  if (args.length == 0) {
3093    return silence;
3094  }
3095  const steps = lcm(...args.map((x) => x._steps));
3096  if (steps.eq(Fraction(0))) {
3097    return nothing;
3098  }
3099
3100  const result = stack(...args.map((x) => x.pace(steps)));
3101  result._steps = steps;
3102  return result;
3103}

'Concatenates' patterns like fastcat, but proportional to a number of steps per cycle. The steps can either be inferred from the pattern, or provided as a [length, pattern] pair. Has the alias timecat. @name stepcat @tags stepwise @synonyms timeCat, timecat @return {Pattern} @example stepcat([3,"e3"],[1, "g3"]).note() // the same as "e3@3 g3".note() @example stepcat("bd sd cp","hh hh").sound() // the same as "bd sd cp hh hh".sound()

3119export function stepcat(...timepats) {
3120  if (timepats.length === 0) {
3121    return nothing;
3122  }
3123  const findsteps = (x) => (Array.isArray(x) ? x : [x._steps ?? 1, x]);
3124  timepats = timepats.map(findsteps);
3125  if (timepats.find((x) => x[0] === undefined)) {
3126    const times = timepats.map((a) => a[0]).filter((x) => x !== undefined);
3127    if (times.length === 0) {
3128      return fastcat(...timepats.map((x) => x[1]));
3129    }
3130    if (times.length === timepats.length) {
3131      return nothing;
3132    }
3133    const avg = times.reduce((a, b) => a.add(b), Fraction(0)).div(times.length);
3134    for (let timepat of timepats) {
3135      if (timepat[0] === undefined) {
3136        timepat[0] = avg;
3137      }
3138    }
3139  }
3140  if (timepats.length == 1) {
3141    const result = reify(timepats[0][1]);
3142    return result.withSteps((_) => timepats[0][0]);
3143  }
3144
3145  const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
3146  let begin = Fraction(0);
3147  const pats = [];
3148  for (const [time, pat] of timepats) {
3149    if (Fraction(time).eq(0)) {
3150      continue;
3151    }
3152    const end = begin.add(time);
3153    pats.push(reify(pat)._compress(begin.div(total), end.div(total)));
3154    begin = end;
3155  }
3156  const result = stack(...pats);
3157  result._steps = total;
3158  return result;
3159}

Experimental

Concatenates patterns stepwise, according to an inferred 'steps per cycle'. Similar to stepcat, but if an argument is a list, the whole pattern will alternate between the elements in the list.

@tags stepwise @return {Pattern} @example stepalt(["bd cp", "mt"], "bd").sound() // The same as "bd cp bd mt bd".sound()

3173export function stepalt(...groups) {
3174  groups = groups.map((a) => (Array.isArray(a) ? a.map(reify) : [reify(a)]));
3175
3176  const cycles = lcm(...groups.map((x) => Fraction(x.length)));
3177
3178  let result = [];
3179  for (let cycle = 0; cycle < cycles; ++cycle) {
3180    result.push(...groups.map((x) => (x.length == 0 ? silence : x[cycle % x.length])));
3181  }
3182  result = result.filter((x) => x.hasSteps && x._steps > 0);
3183  const steps = result.reduce((a, b) => a.add(b._steps), Fraction(0));
3184  result = stepcat(...result);
3185  result._steps = steps;
3186  return result;
3187}

Experimental

Takes the given number of steps from a pattern (dropping the rest). A positive number will take steps from the start of a pattern, and a negative number from the end. @tags stepwise @return {Pattern} @example "bd cp ht mt".take("2").sound() // The same as "bd cp".sound() @example "bd cp ht mt".take("1 2 3").sound() // The same as "bd bd cp bd cp ht".sound() @example "bd cp ht mt".take("-1 -2 -3").sound() // The same as "mt ht mt cp ht mt".sound()

3206export const take = stepRegister('take', function (i, pat) {
3207  if (!pat.hasSteps) {
3208    return nothing;
3209  }
3210  if (pat._steps.lte(0)) {
3211    return nothing;
3212  }
3213  i = Fraction(i);
3214  if (i.eq(0)) {
3215    return nothing;
3216  }
3217  const flip = i < 0;
3218  if (flip) {
3219    i = i.abs();
3220  }
3221  const frac = i.div(pat._steps);
3222  if (frac.lte(0)) {
3223    return nothing;
3224  }
3225  if (frac.gte(1)) {
3226    return pat;
3227  }
3228  if (flip) {
3229    return pat.zoom(Fraction(1).sub(frac), 1);
3230  }
3231  return pat.zoom(0, frac);
3232});

Experimental

Drops the given number of steps from a pattern. A positive number will drop steps from the start of a pattern, and a negative number from the end. @tags stepwise @return {Pattern} @example "tha dhi thom nam".drop("1").sound().bank("mridangam") @example "tha dhi thom nam".drop("-1").sound().bank("mridangam") @example "tha dhi thom nam".drop("0 1 2 3").sound().bank("mridangam") @example "tha dhi thom nam".drop("0 -1 -2 -3").sound().bank("mridangam")

3250export const drop = stepRegister('drop', function (i, pat) {
3251  if (!pat.hasSteps) {
3252    return nothing;
3253  }
3254
3255  i = Fraction(i);
3256  if (i.lt(0)) {
3257    return pat.take(pat._steps.add(i));
3258  }
3259  return pat.take(Fraction(0).sub(pat._steps.sub(i)));
3260});

Experimental

extend is similar to fast in that it increases its density, but it also increases the step count accordingly. So stepcat("a b".extend(2), "c d") would be the same as "a b a b c d", whereas stepcat("a b".fast(2), "c d") would be the same as "[a b] [a b] c d". @tags stepwise @example stepcat( sound("bd bd - cp").extend(2), sound("bd - sd -") ).pace(8)

3275export const extend = stepRegister('extend', function (factor, pat) {
3276  return pat.fast(factor).expand(factor);
3277});

Experimental

replicate is similar to fast in that it increases its density, but it also increases the step count accordingly. So stepcat("a b".replicate(2), "c d") would be the same as "a b a b c d", whereas stepcat("a b".fast(2), "c d") would be the same as "[a b] [a b] c d".

TODO: find out how this function differs from extend @tags stepwise @example stepcat( sound("bd bd - cp").replicate(2), sound("bd - sd -") ).pace(8)

3294export const replicate = stepRegister('replicate', function (factor, pat) {
3295  return pat.repeatCycles(factor).fast(factor).expand(factor);
3296});

Experimental

Expands the step size of the pattern by the given factor. @tags stepwise @example sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8)

3306export const expand = stepRegister('expand', function (factor, pat) {
3307  return pat.withSteps((t) => t.mul(Fraction(factor)));
3308});

Experimental

Contracts the step size of the pattern by the given factor. See also expand. @tags stepwise @example sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8)

3318export const contract = stepRegister('contract', function (factor, pat) {
3319  return pat.withSteps((t) => t.div(Fraction(factor)));
3320});
3322Pattern.prototype.shrinklist = function (amount) {
3323  const pat = this;
3324
3325  if (!pat.hasSteps) {
3326    return [pat];
3327  }
3328
3329  let [amountv, times] = Array.isArray(amount) ? amount : [amount, pat._steps];
3330  amountv = Fraction(amountv);
3331
3332  if (times === 0 || amountv === 0) {
3333    return [pat];
3334  }
3335
3336  const fromstart = amountv > 0;
3337  const ranges = [];
3338  if (fromstart) {
3339    const seg = Fraction(1).div(pat._steps).mul(amountv);
3340    for (let i = 0; i < times; ++i) {
3341      const s = seg.mul(i);
3342      if (s.gt(1)) {
3343        break;
3344      }
3345      ranges.push([s, 1]);
3346    }
3347  } else {
3348    amountv = Fraction(0).sub(amountv);
3349    const seg = Fraction(1).div(pat._steps).mul(amountv);
3350    for (let i = 0; i < times; ++i) {
3351      const e = Fraction(1).sub(seg.mul(i));
3352      if (e.lt(0)) {
3353        break;
3354      }
3355      ranges.push([Fraction(0), e]);
3356    }
3357  }
3358  return ranges.map((x) => pat.zoom(...x));
3359};
3360
3361export const shrinklist = (amount, pat) => pat.shrinklist(amount);
3362
3363Pattern.prototype.growlist = function (amount) {
3364  return this.shrinklist(amount).reverse();
3365};
3366export const growlist = (amount, pat) => pat.growlist(amount);

Experimental

Progressively shrinks the pattern by 'n' steps until there's nothing left, or if a second value is given (using mininotation list syntax with :), that number of times. A positive number will progressively drop steps from the start of a pattern, and a negative number from the end. @tags stepwise @return {Pattern} @example "tha dhi thom nam".shrink("1").sound() .bank("mridangam") @example "tha dhi thom nam".shrink("-1").sound() .bank("mridangam") @example "tha dhi thom nam".shrink("1 -1").sound().bank("mridangam").pace(4) @example note("0 1 2 3 4 5 6 7".scale("C:ritusen")).sound("folkharp") .shrink("1 -1").pace(8)

3390export const shrink = register(
3391  'shrink',
3392  function (amount, pat) {
3393    if (!pat.hasSteps) {
3394      return nothing;
3395    }
3396
3397    const list = pat.shrinklist(amount);
3398    const result = stepcat(...list);
3399    // TODO is this calculation needed?
3400    result._steps = list.reduce((a, b) => a.add(b._steps), Fraction(0));
3401    return result;
3402  },
3403  true,
3404  false,
3405  (x) => x.stepJoin(),
3406);

Experimental

Progressively grows the pattern by 'n' steps until the full pattern is played, or if a second value is given (using mininotation list syntax with :), that number of times. A positive number will progressively grow steps from the start of a pattern, and a negative number from the end. @tags stepwise @return {Pattern} @example "tha dhi thom nam".grow("1").sound() .bank("mridangam") @example "tha dhi thom nam".grow("-1").sound() .bank("mridangam") @example "tha dhi thom nam".grow("1 -1").sound().bank("mridangam").pace(4) @example note("0 1 2 3 4 5 6 7".scale("C:ritusen")).sound("folkharp") .grow("1 -1").pace(8)

3429export const grow = register(
3430  'grow',
3431  function (amount, pat) {
3432    if (!pat.hasSteps) {
3433      return nothing;
3434    }
3435
3436    const list = pat.shrinklist(Fraction(0).sub(amount));
3437    list.reverse();
3438    const result = stepcat(...list);
3439    // TODO is this calculation needed?
3440    result._steps = list.reduce((a, b) => a.add(b._steps), Fraction(0));
3441    return result;
3442  },
3443  true,
3444  false,
3445  (x) => x.stepJoin(),
3446);

Experimental

Inserts a pattern into a list of patterns. On the first repetition it will be inserted at the end of the list, then moved backwards through the list on successive repetitions. The patterns are added together stepwise, with all repetitions taking place over a single cycle. Using pace to set the number of steps per cycle is therefore usually recommended.

@tags stepwise @return {Pattern} @example "[c g]".tour("e f", "e f g", "g f e c").note() .sound("folkharp") .pace(8)

3462export const tour = function (pat, ...many) {
3463  return pat.tour(...many);
3464};
3466Pattern.prototype.tour = function (...many) {
3467  return stepcat(
3468    ...[].concat(
3469      ...many.map((x, i) => [...many.slice(0, many.length - i), this, ...many.slice(many.length - i)]),
3470      this,
3471      ...many,
3472    ),
3473  );
3474};

Experimental

'zips' together the steps of the provided patterns. This can create a long repetition, taking place over a single, dense cycle. Using pace to set the number of steps per cycle is therefore usually recommended.

@tags stepwise @returns {Pattern} @example zip("e f", "e f g", "g [f e] a f4 c").note() .sound("folkharp") .pace(8)

3489export const zip = function (...pats) {
3490  pats = pats.filter((pat) => pat.hasSteps);
3491  const zipped = slowcat(...pats.map((pat) => pat._slow(pat._steps)));
3492  const steps = lcm(...pats.map((x) => x._steps));
3493  return zipped._fast(steps).setSteps(steps);
3494};

Aliases for stepcat

3497export const timecat = stepcat;
3498export const timeCat = stepcat;

Deprecated stepwise aliases

3501export const s_cat = stepcat;
3502export const s_alt = stepalt;
3503export const s_polymeter = polymeter;
3504Pattern.prototype.s_polymeter = Pattern.prototype.polymeter;
3505export const s_taper = shrink;
3506Pattern.prototype.s_taper = Pattern.prototype.shrink;
3507export const s_taperlist = shrinklist;
3508Pattern.prototype.s_taperlist = Pattern.prototype.shrinklist;
3509export const s_add = take;
3510Pattern.prototype.s_add = Pattern.prototype.take;
3511export const s_sub = drop;
3512Pattern.prototype.s_sub = Pattern.prototype.drop;
3513export const s_expand = expand;
3514Pattern.prototype.s_expand = Pattern.prototype.expand;
3515export const s_extend = extend;
3516Pattern.prototype.s_extend = Pattern.prototype.extend;
3517export const s_contract = contract;
3518Pattern.prototype.s_contract = Pattern.prototype.contract;
3519export const s_tour = tour;
3520Pattern.prototype.s_tour = Pattern.prototype.tour;
3521export const s_zip = zip;
3522Pattern.prototype.s_zip = Pattern.prototype.zip;
3523export const steps = pace;
3524Pattern.prototype.steps = Pattern.prototype.pace;

//////////////////////////////////////////////////////////////////// Control-related functions, i.e. ones that manipulate patterns of objects

Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'. It turns a pattern of samples into a pattern of parts of samples. @name chop @tags samples @memberof Pattern @returns Pattern @example samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) s("rhodes") .chop(4) .rev() // reverse order of chops .loopAt(2) // fit sample into 2 cycles

3545export const chop = register('chop', function (n, pat) {
3546  const slices = Array.from({ length: n }, (x, i) => i);
3547  const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
3548  const merge = function (a, b) {
3549    if ('begin' in a && 'end' in a && a.begin !== undefined && a.end !== undefined) {
3550      const d = a.end - a.begin;
3551      b = { begin: a.begin + b.begin * d, end: a.begin + b.end * d };
3552    }
3553    // return a;
3554    return Object.assign({}, a, b);
3555  };
3556  const func = function (o) {
3557    return sequence(slice_objects.map((slice_o) => merge(o, slice_o)));
3558  };
3559  return pat.squeezeBind(func).setSteps(__steps ? Fraction(n).mulmaybe(pat._steps) : undefined);
3560});

Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop. @name striate @tags samples @memberof Pattern @returns Pattern @example s("numbers:0 numbers:1 numbers:2").striate(6).slow(3)

3571export const striate = register('striate', function (n, pat) {
3572  const slices = Array.from({ length: n }, (x, i) => i);
3573  const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
3574  const slicePat = slowcat(...slice_objects);
3575  return pat
3576    .set(slicePat)
3577    ._fast(n)
3578    .setSteps(__steps ? Fraction(n).mulmaybe(pat._steps) : undefined);
3579});

Makes the sample fit the given number of cycles by changing the speed. @name loopAt @tags samples, pitch @memberof Pattern @returns Pattern @example samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) s("rhodes").loopAt(2)

3591const _loopAt = function (factor, pat, cps = 0.5) {
3592  return pat
3593    .speed((1 / factor) * cps)
3594    .unit('c')
3595    .slow(factor);
3596};
3598export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
3599  const steps = pat._steps ? pat._steps.div(factor) : undefined;
3600  return new Pattern((state) => _loopAt(factor, pat, state.controls._cps).query(state), steps);
3601});

Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers. Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points. @name slice @tags samples @memberof Pattern @returns Pattern @example samples('github:tidalcycles/dirt-samples') s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(0.75) @example samples('github:tidalcycles/dirt-samples') s("breaks125").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")

3618export const slice = register(
3619  'slice',
3620  function (npat, ipat, opat) {
3621    return npat
3622      .innerBind((n) =>
3623        ipat.outerBind((i) =>
3624          opat.outerBind((o) => {
3625            // If it's not an object, assume it's a string and make it a 's' control parameter
3626            o = o instanceof Object ? o : { s: o };
3627            const begin = Array.isArray(n) ? n[i] : i / n;
3628            const end = Array.isArray(n) ? n[i + 1] : (i + 1) / n;
3629            return pure({ begin, end, _slices: n, ...o });
3630          }),
3631        ),
3632      )
3633      .setSteps(ipat._steps);
3634  },
3635  false, // turns off auto-patternification
3636);

make something happen on event time uses browser timeout which is innacurate for audio tasks @name onTriggerTime @tags external_io @memberof Pattern @returns Pattern @example s("bd!8").onTriggerTime((hap) => {console.log(hap)})

3649Pattern.prototype.onTriggerTime = function (func) {
3650  return this.onTrigger((hap, currentTime, _cps, targetTime) => {
3651    const diff = targetTime - currentTime;
3652    window.setTimeout(() => {
3653      func(hap);
3654    }, diff * 1000);
3655  }, false);
3656};

Works the same as slice, but changes the playback speed of each slice to match the duration of its step. @name splice @tags samples, pitch @example samples('github:tidalcycles/dirt-samples') s("breaks165") .splice(8, "0 1 [2 3 0]@2 3 0@2 7")

3668export const splice = register(
3669  'splice',
3670  function (npat, ipat, opat) {
3671    const sliced = slice(npat, ipat, opat);
3672    return new Pattern((state) => {
3673      // TODO - default cps to 0.5
3674      const cps = state.controls._cps || 1;
3675      const haps = sliced.query(state);
3676      return haps.map((hap) =>
3677        hap.withValue((v) => ({
3678          ...{
3679            speed: (cps / v._slices / hap.whole.duration) * (v.speed || 1),
3680            unit: 'c',
3681          },
3682          ...v,
3683        })),
3684      );
3685    }).setSteps(ipat._steps);
3686  },
3687  false, // turns off auto-patternification
3688);

Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. Similar to loopAt. @name fit @tags samples, pitch @example samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) s("rhodes/2").fit()

3699export const fit = register('fit', (pat) =>
3700  pat.withHaps((haps, state) =>
3701    haps.map((hap) =>
3702      hap.withValue((v) => {
3703        const slicedur = ('end' in v ? v.end : 1) - ('begin' in v ? v.begin : 0);
3704        return {
3705          ...v,
3706          speed: ((state.controls._cps || 1) / hap.whole.duration) * slicedur,
3707          unit: 'c',
3708        };
3709      }),
3710    ),
3711  ),
3712);

Makes the sample fit the given number of cycles and cps value, by changing the speed. deprecated: use loopAt or fit instead, together with setCps / setCpm. @name loopAtCps @tags samples, pitch @memberof Pattern @deprecated @returns Pattern @example samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) s("rhodes").loopAtCps(4,1.5).cps(1.5)

3726export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
3727  return _loopAt(factor, pat, cps);
3728});

exposes a custom value at query time. basically allows mutating state without evaluation @tags internals

3733export const ref = (accessor) =>
3734  pure(1)
3735    .withValue(() => reify(accessor()))
3736    .innerJoin();
3738let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5);

Cross-fades between left and right from 0 to 1:

  • 0 = (full left, no right)
  • .5 = (both equal)
  • 1 = (no left, full right)

@name xfade @tags amplitude @example xfade(s("bd2"), "<0 .25 .5 .75 1>", s("hh8"))

3751export let xfade = (a, pos, b) => {
3752  pos = reify(pos);
3753  a = reify(a);
3754  b = reify(b);
3755  let gaina = pos.fmap((v) => ({ gain: fadeGain(v) }));
3756  let gainb = pos.fmap((v) => ({ gain: fadeGain(1 - v) }));
3757  return stack(a.mul(gaina), b.mul(gainb));
3758};

the prototype version is actually flipped so left/right makes sense

3761Pattern.prototype.xfade = function (pos, b) {
3762  return xfade(this, pos, b);
3763};

creates a structure pattern from divisions of a cycle especially useful for creating rhythms @name beat @tags temporal @example s("bd").beat("0,7,10", 16) @example s("sd").beat("4,12", 16)

3775const __beat = (join) => (t, div, pat) => {
3776  t = Fraction(t).mod(div);
3777  div = Fraction(div);
3778  const b = t.div(div);
3779  const e = t.add(1).div(div);
3780  return join(pat.fmap((x) => pure(x)._compress(b, e)));
3781};
3783export const { beat } = register(
3784  ['beat'],
3785  __beat((x) => x.innerJoin()),
3786);
3787
3788export const _morph = (from, to, by) => {
3789  by = Fraction(by);
3790  const dur = Fraction(1).div(from.length);
3791  const positions = (list) => {
3792    const result = [];
3793    for (const [pos, value] of list.entries()) {
3794      if (value) {
3795        result.push([Fraction(pos).div(list.length), value]);
3796      }
3797    }
3798    return result;
3799  };
3800  const arcs = zipWith(
3801    ([posa, valuea], [posb, valueb]) => {
3802      const b = by.mul(posb - posa).add(posa);
3803      const e = b.add(dur);
3804      return new TimeSpan(b, e);
3805    },
3806    positions(from),
3807    positions(to),
3808  );
3809  function query(state) {
3810    const cycle = state.span.begin.sam();
3811    const cycleArc = state.span.cycleArc();
3812    const result = [];
3813    for (const whole of arcs) {
3814      const part = whole.intersection(cycleArc);
3815      if (part !== undefined) {
3816        result.push(
3817          new Hap(
3818            whole.withTime((x) => x.add(cycle)),
3819            part.withTime((x) => x.add(cycle)),
3820            true,
3821          ),
3822        );
3823      }
3824    }
3825    return result;
3826  }
3827  return new Pattern(query).splitQueries();
3828};

Takes two binary rhythms represented as lists of 1s and 0s, and a number between 0 and 1 that morphs between them. The two lists should contain the same number of true values. @example sound("hh").struct(morph([1,0,1,0,1,0,1,0], // straight rhythm [1,1,0,1,0,1,0], // wonky rhythm 0.25 // creates a slightly wonky rhythm ) ) @example sound("hh").struct(morph("1:0:1:0:1:0:1:0", // straight rhythm "1:1:0:1:0:1:0", // wonky rhythm sine.slow(8) // slowly morph between the rhythms ) ) @tags temporal

3848export const morph = (frompat, topat, bypat) => {
3849  frompat = reify(frompat);
3850  topat = reify(topat);
3851  bypat = reify(bypat);
3852  return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))));
3853};
3855const _distortWithAlg = function (name) {
3856  const func = function (args, pat) {
3857    const argsPat = reify(args).fmap((v) => (Array.isArray(v) ? [...v, name] : [v, 1, name]));
3858    if (!pat) {
3859      return pure({}).distort(argsPat);
3860    }
3861    return pat.distort(argsPat);
3862  };
3863  Pattern.prototype[name] = function (args) {
3864    return func(args, this);
3865  };
3866  return func;
3867};

Soft-clipping distortion

@name soft @tags distortion, superdough @param {number | Pattern} distortion amount of distortion to apply @param {number | Pattern} volume linear postgain of the distortion

3878export const soft = _distortWithAlg('soft');

Hard-clipping distortion

@name hard @tags distortion, superdough @param {number | Pattern} distortion amount of distortion to apply @param {number | Pattern} volume linear postgain of the distortion

3889export const hard = _distortWithAlg('hard');

Cubic polynomial distortion

@name cubic @tags distortion, superdough @param {number | Pattern} distortion amount of distortion to apply @param {number | Pattern} volume linear postgain of the distortion

3900export const cubic = _distortWithAlg('cubic');

Diode-emulating distortion

@name diode @tags distortion, superdough @param {number | Pattern} distortion amount of distortion to apply @param {number | Pattern} volume linear postgain of the distortion

3911export const diode = _distortWithAlg('diode');

Asymmetrical diode distortion

@name asym @tags distortion, superdough @param {number | Pattern} distortion amount of distortion to apply @param {number | Pattern} volume linear postgain of the distortion

3922export const asym = _distortWithAlg('asym');

Wavefolding distortion

@name fold @tags distortion, superdough @param {number | Pattern} distortion amount of distortion to apply @param {number | Pattern} volume linear postgain of the distortion

3933export const fold = _distortWithAlg('fold');

Wavefolding distortion composed with sinusoid

@name sinefold @tags distortion, superdough @param {number | Pattern} distortion amount of distortion to apply @param {number | Pattern} volume linear postgain of the distortion

3944export const sinefold = _distortWithAlg('sinefold');

Distortion via Chebyshev polynomials

@name chebyshev @tags distortion, superdough @param {number | Pattern} distortion amount of distortion to apply @param {number | Pattern} volume linear postgain of the distortion

3955export const chebyshev = _distortWithAlg('chebyshev');

Turns a list of patterns into a single pattern which outputs list-values

@name parray @tags combiners @returns Pattern

3964export const parray = (pats) => {
3965  const pack = (...xs) => xs;
3966  let acc = pure(curry(pack, null, pats.length));
3967  for (const p of pats) acc = acc.appBoth(reify(p));
3968  return acc;
3969};
3971const _ensureListPattern = (list) => {
3972  if (Array.isArray(list)) {
3973    return parray(list);
3974  }
3975  return reify(list);
3976};

Scale the magnitude of the harmonics of one of the core synths ('sine', 'tri', 'saw', ..)

Can also be used to create a new synth via s('user').partials(...)

@name partials @tags superdough @param {number[] | Pattern} magnitudes List of [0, 1] magnitudes for partials. 0th entry is the fundamental harmonic (i.e. DC offset is skipped) @example s("user").seg(16).n(irand(8)).scale("A:major") .partials([1, 0, 1, 0, 0, 1]) @example s("saw").seg(8).n(irand(12)).scale("G#:minor") .partials(binaryL(irand(256).add("1")))

3993Pattern.prototype.partials = function (list) {
3994  return this.withValue((v) => (l) => ({ ...v, partials: l })).appLeft(_ensureListPattern(list));
3995};

Also create a top-level function

3998export const partials = (list) => {
3999  return _ensureListPattern(list).as('partials');
4000};

Rotates the harmonics of one of the core synths ('sine', 'tri', 'saw', 'user', ..) by a list of phases

@name phases @tags superdough @param {number[] | Pattern} phases List of [0, 1) phases for partials. 0th entry is the fundamental phase (i.e. DC offset is skipped) @example // Phase cancellation s("saw").seg(8).n(irand(12)).scale("G#1:minor") .partials(partials([1, 1, 1])) .superimpose(x => x.phases([0.5, 0.5, 0.5]))

4014Pattern.prototype.phases = function (list) {
4015  return this.withValue((v) => (l) => ({ ...v, phases: l })).appLeft(_ensureListPattern(list));
4016};

Also create a top-level function

4019export const phases = (list) => {
4020  return _ensureListPattern(list).as('phases');
4021};

Establishes an FX chain. Can be called by chaining .FX(fx1).FX(fx2).. calls and/or in a single .FX(fx1, fx2, ..) call. The fx1, .. are patterns which establish the controls of the given effect. See examples. @name FX @tags superdough @memberof Pattern @returns Pattern @example $: s("[sbd <hh [bd | lt | oh]>]*4").dec(.4) .FX( phaser(0.5).gain(2), bpf(800), distort(1.3), room(0.2), delay(0.5).gain(1.25), distort(0.3), ).fxr(1.7) // sets release time of effects (like delay) @example $: s("saw").fm(0.5) .delay(0.3) // outer effects are applied last .FX(coarse(4)) // first coarse .FX(lpf(500).lpe(4).lpa(1).lpd(2)) // then lpf .FX(distort(1)) // then distort

4048Pattern.prototype.FX = function (...effects) {
4049  effects = effects.map(reify);
4050  return this.withValue((v) => (vEff) => {
4051    const currFX = v.FX ?? [];
4052    return { ...v, FX: currFX.concat(vEff) };
4053  }).appLeft(parray(effects));
4054};
4056const _asArrayPattern = (pats) => {
4057  const pack = (...xs) => xs;
4058  let acc = pure(curry(pack, null, pats.length));
4059  for (const p of pats) acc = acc.appLeft(p);
4060  return acc;
4061};

Produces a Kabelsalat modular sound engine. This can be used as either an effect (by including audioin() at the beginning of your kabel expression) or as a sound source (via any expression which doesn't start with audioin()).

Some helpers you have available to you:

  • Strudel mini notation works fine in K(..) via "" or ``
  • More complex Strudel expressions (like "0 1 2".fast(4) or irand(24)) can be written by wrapping them in S(..) inside your Kabel code
  • We expose Strudel's note frequency under sFreq and Strudel's gate information under sGate
  • You can use more complex multi-line expressions (like let x = a; let y = b; x.lpf(y);) by wrapping them inside a function in K (see example).

@name K @tags generators, superdough @param {KabelsalatExpression | Function} expr Kabelsalat graph definition @memberof Pattern @returns Pattern

@example note("A c e".fast(4)).transpose("<0 2 4 6 8>") .scale("F:minor").transpose("12") .s("saw") .K( // audioin().mul(sGate.adsr(0.001, 0.3, 0, 0.2)) // as effect saw(saw(sFreq / "2!3 16").mul(8).add(sFreq).lag("0!3 0.1")).mul(0.3) // as source .mul(sGate.adsr(0, 0.15, 0.5, "0.1!3 1")) .lpf(sGate.adsr(0, 0.2, 0.3, 0.2).mul(1).add(0)) .add(x => x.delay(S("0.3 0.2".fast(2))).mul(0.7)) .add(x => x.delay("0.03 [0.08 0.01] 0.01 0.013").mul(0.77)).mul(0.7) .add(x => x.delay(.13).mul(0.7)) .out() )

@example n("<0 1 <2 3 2 4>>*16") .scale("G#2:minor").sometimes(x => x.transpose("12 | 24")) .K(() => { const att = S(rand.range(0, 0.05)) const dec = S(rand.range(0.05, 0.2)) let f = n(sFreq); const mod = sine(f).mul("0.1 | 0.2 | 0.3") .add("[[1.5 1] | 1 | 2 | 4 | [6 4@3]]*2") saw(f.mul(mod)) .mul(sGate.ad(att, dec)) .add(x => x.delay(0.4).mul(0.3)) .out() }).fxr(1).room(0.3)

Creates a worklet effect. Typically derived by writing K(...) in the REPL which will parse Kabelsalat code.

@name worklet @param {string} src Source code of the worklet update function @param {...number | ...Pattern} inputs Worklet inputs @memberof Pattern @returns Pattern @noAutocomplete

4125Pattern.prototype.worklet = function (src, ...inputs) {
4126  inputs = inputs.map(reify);
4127  return this.outerBind((v) => {
4128    return _asArrayPattern(inputs).withValue((vInput) => {
4129      const currInputs = v.workletInputs ?? [];
4130      return { ...v, workletSrc: src, workletInputs: currInputs.concat(vInput) };
4131    });
4132  });
4133};
4135export const worklet = (...args) => pure({}).worklet(...args);

Creates a pattern of numbers in base b from a number or pattern of numbers limited to d digits long from the right

@name base @tags generators @param {number} n - number to convert (can be a pattern or array) @param {number} b - base to convert to (defaults to 10) (can be a pattern) @param {number} d - max number of digits to produce for each n (defaults to 0 for all) (can be a pattern) @example $: note(base("7175 543", 10, 3)).scale("c:major").s("saw") // $: note("1 7 5 5 4 3").scale("c:major").s("saw")

4150export const base = (n, b = 10, d = 0) => {
4151  if (Array.isArray(n)) {
4152    n = sequence(n);
4153  }
4154  n = reify(n);
4155  b = reify(b);
4156  d = reify(d);
4157
4158  return d
4159    .withValue((e) => {
4160      return b
4161        .withValue((c) => {
4162          return n
4163            .withValue((v) => {
4164              let digits = [];
4165              let value = v;
4166              while (value > 0) {
4167                digits.unshift(value % c);
4168                value = Math.floor(value / c);
4169              }
4170              if (e) {
4171                const l = digits.length;
4172                if (l > e) {
4173                  digits = digits.slice(-1 * e);
4174                }
4175                /* 
4176          if (l < e){
4177            for (let i = l; i < e; i++) {
4178              digits.unshift("~");//0); //Would like to be padding this but ~- doesn't work
4179            }
4180            console.log("digits", digits);
4181          }
4182          */
4183              }
4184              return sequence(digits);
4185            })
4186            .squeezeJoin();
4187        })
4188        .squeezeJoin();
4189    })
4190    .squeezeJoin();
4191};