jevstrudel.git / packages / core / pattern.mjs
1/*
2pattern.mjs - Core pattern representation for strudel
3Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/pattern.mjs>
4This 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/>.
5*/
6
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';
12
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};
38
39// parser is expected to turn a string into a pattern
40// if set, the reify function will parse all strings with it
41// intended to use with mini to automatically interpret all strings as mini notation
42export const setStringParser = (parser) => (stringParser = parser);
43
44/** @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  }
81
82  //////////////////////////////////////////////////////////////////////
83  // Haskell-style functor, applicative and monadic operations
84
85  /**
86   * Returns a new pattern, with the function applied to the value of
87   * each hap. It has the alias `fmap`.
88   * @tags functional
89   * @synonyms fmap
90   * @param {Function} func to to apply to the value
91   * @returns Pattern
92   * @example
93   * "0 1 2".withValue(v => v + 10).log()
94   */
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  }
100
101  // runs func on query state
102  withState(func) {
103    return new Pattern((state) => this.query(func(state)));
104  }
105
106  /**
107   * see `withValue`
108   * @noAutocomplete
109   */
110  fmap(func) {
111    return this.withValue(func);
112  }
113
114  /**
115   * Assumes 'this' is a pattern of functions, and given a function to
116   * resolve wholes, applies a given pattern of values to that
117   * pattern of functions.
118   * @tags functional
119   * @param {Function} whole_func
120   * @param {Function} func
121   * @noAutocomplete
122   * @returns Pattern
123   */
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  }
147
148  /**
149   * When this method is called on a pattern of functions, it matches its haps
150   * with those in the given pattern of values.  A new pattern is returned, with
151   * each matching value applied to the corresponding function.
152   *
153   * In this `_appBoth` variant, where timespans of the function and value haps
154   * are not the same but do intersect, the resulting hap has a timespan of the
155   * intersection. This applies to both the part and the whole timespan.
156   * @tags functional
157   * @param {Pattern} pat_val
158   * @noAutocomplete
159   * @returns Pattern
160   */
161  appBoth(pat_val) {
162    const pat_func = this;
163
164    // 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  }
177
178  /**
179   * As with `appBoth`, but the `whole` timespan is not the intersection,
180   * but the timespan from the function of patterns that this method is called
181   * on. In practice, this means that the pattern structure, including onsets,
182   * are preserved from the pattern of functions (often referred to as the left
183   * hand or inner pattern).
184   * @tags functional
185   * @param {Pattern} pat_val
186   * @noAutocomplete
187   * @returns Pattern
188   */
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  }
213
214  /**
215   * As with `appLeft`, but `whole` timespans are instead taken from the
216   * pattern of values, i.e. structure is preserved from the right hand/outer
217   * pattern.
218   * @tags functional
219   * @param {Pattern} pat_val
220   * @noAutocomplete
221   * @returns Pattern
222   */
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  }
247
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  }
306
307  // 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  }
339
340  restartJoin() {
341    return this.resetJoin(true);
342  }
343
344  // Like the other joins above, joins a pattern of patterns of values, into a flatter
345  // pattern of values. In this case it takes whole cycles of the inner pattern to fit each event
346  // 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  }
389
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  }
402
403  //////////////////////////////////////////////////////////////////////
404  // Utility methods mainly for internal use
405
406  /**
407   * Query haps inside the given time span.
408   *
409   * @tags internals
410   * @param {Fraction | number} begin from time
411   * @param {Fraction | number} end to time
412   * @returns Hap[]
413   * @example
414   * const pattern = sequence('a', ['b', 'c'])
415   * const haps = pattern.queryArc(0, 1)
416   * console.log(haps)
417   * silence
418   * @noAutocomplete
419   */
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  }
428
429  /**
430   * Returns a new pattern, with queries split at cycle boundaries. This makes
431   * some calculations easier to express, as all haps are then constrained to
432   * happen within a cycle.
433   * @tags internals
434   * @returns Pattern
435   * @noAutocomplete
436   */
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  }
444
445  /**
446   * Returns a new pattern, where the given function is applied to the query
447   * timespan before passing it to the original pattern.
448   * @tags internals
449   * @param {Function} func the function to apply
450   * @returns Pattern
451   * @noAutocomplete
452   */
453  withQuerySpan(func) {
454    return new Pattern((state) => this.query(state.withSpan(func)));
455  }
456
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  }
467
468  /**
469   * As with `withQuerySpan`, but the function is applied to both the
470   * begin and end time of the query timespan.
471   * @tags internals
472   * @param {Function} func the function to apply
473   * @returns Pattern
474   * @noAutocomplete
475   */
476  withQueryTime(func) {
477    return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func))));
478  }
479
480  /**
481   * Similar to `withQuerySpan`, but the function is applied to the timespans
482   * of all haps returned by pattern queries (both `part` timespans, and where
483   * present, `whole` timespans).
484   * @tags internals
485   * @param {Function} func
486   * @returns Pattern
487   * @noAutocomplete
488   */
489  withHapSpan(func) {
490    return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
491  }
492
493  /**
494   * As with `withHapSpan`, but the function is applied to both the
495   * begin and end time of the hap timespans.
496   * @tags internals
497   * @param {Function} func the function to apply
498   * @returns Pattern
499   * @noAutocomplete
500   */
501  withHapTime(func) {
502    return this.withHapSpan((span) => span.withTime(func));
503  }
504
505  /**
506   * Returns a new pattern with the given function applied to the list of haps returned by every query.
507   * @tags internals
508   * @param {Function} func
509   * @returns Pattern
510   * @noAutocomplete
511   */
512  withHaps(func) {
513    const result = new Pattern((state) => func(this.query(state), state));
514    result._steps = this._steps;
515    return result;
516  }
517
518  /**
519   * As with `withHaps`, but applies the function to every hap, rather than every list of haps.
520   * @tags internals
521   * @param {Function} func
522   * @returns Pattern
523   * @noAutocomplete
524   */
525  withHap(func) {
526    return this.withHaps((haps) => haps.map(func));
527  }
528
529  /**
530   * Returns a new pattern with the context field set to every hap set to the given value.
531   * @tags internals
532   * @param {*} context
533   * @returns Pattern
534   * @noAutocomplete
535   */
536  setContext(context) {
537    return this.withHap((hap) => hap.setContext(context));
538  }
539
540  /**
541   * Returns a new pattern with the given function applied to the context field of every hap.
542   * @tags internals
543   * @param {Function} func
544   * @returns Pattern
545   * @noAutocomplete
546   */
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  }
555
556  /**
557   * Returns a new pattern with the context field of every hap set to an empty object.
558   * @tags internals
559   * @returns Pattern
560   * @noAutocomplete
561   */
562  stripContext() {
563    return this.withHap((hap) => hap.setContext({}));
564  }
565
566  /**
567   * Returns a new pattern with the given location information added to the
568   * context of every hap.
569   * @tags internals
570   * @param {Number} start start offset
571   * @param {Number} end end offset
572   * @returns Pattern
573   * @noAutocomplete
574   */
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  }
590
591  /**
592   * Returns a new Pattern, which only returns haps that meet the given test.
593   * @tags internals
594   * @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
595   * @returns Pattern
596   * @example
597   * s("bd*8").velocity(rand).filterHaps((h) => (h.whole.begin % 1) < h.value.velocity)
598   */
599  filterHaps(hap_test) {
600    return new Pattern((state) => this.query(state).filter(hap_test));
601  }
602
603  /**
604   * As with `filterHaps`, but the function is applied to values
605   * inside haps.
606   * @tags internals
607   * @param {Function} value_test
608   * @returns Pattern
609   * @example
610   * const drums = s("bd sd bd sd")
611   * kick: drums.filterValues((v) => v.s === 'bd').duck(2)
612   * snare: drums.filterValues((v) => v.s === 'sd')
613   * bass: s("saw!4").note("G#1").lpf(80).lpenv(4).orbit(2)
614   */
615  filterValues(value_test) {
616    return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))).setSteps(this._steps);
617  }
618
619  /**
620   * Returns a new pattern, with haps containing undefined values removed from
621   * query results.
622   * @tags internals
623   * @returns Pattern
624   * @noAutocomplete
625   */
626  removeUndefineds() {
627    return this.filterValues((val) => val != undefined);
628  }
629
630  /**
631   * Returns a new pattern, with all haps without onsets filtered out. A hap
632   * with an onset is one with a `whole` timespan that begins at the same time
633   * as its `part` timespan.
634   * @tags internals
635   * @returns Pattern
636   * @noAutocomplete
637   */
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  }
644
645  /**
646   * Returns a new pattern, with 'continuous' haps (those without 'whole'
647   * timespans) removed from query results.
648   * @tags internals
649   * @returns Pattern
650   * @noAutocomplete
651   */
652  discreteOnly() {
653    // removes continuous haps that don't have a 'whole' timespan
654    return this.filterHaps((hap) => hap.whole);
655  }
656
657  /**
658   * Combines adjacent haps with the same value and whole.  Only
659   * intended for use in tests.
660   * @tags internals
661   * @noAutocomplete
662   */
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  }
709
710  /**
711   * Queries the pattern for the first cycle, returning Haps. Mainly of use when
712   * debugging a pattern.
713   * @tags internals
714   * @param {Boolean} with_context - set to true, otherwise the context field
715   * will be stripped from the resulting haps.
716   * @returns [Hap]
717   * @noAutocomplete
718   */
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  }
726
727  /**
728   * Accessor for a list of values returned by querying the first cycle.
729   * @tags internals
730   * @noAutocomplete
731   */
732  get firstCycleValues() {
733    return this.firstCycle().map((hap) => hap.value);
734  }
735
736  /**
737   * More human-readable version of the `firstCycleValues` accessor.
738   * @tags internals
739   * @noAutocomplete
740   */
741  get showFirstCycle() {
742    return this.firstCycle().map(
743      (hap) => `${hap.value}: ${hap.whole.begin.toFraction()} - ${hap.whole.end.toFraction()}`,
744    );
745  }
746
747  /**
748   * Returns a new pattern, which returns haps sorted in temporal order. Mainly
749   * of use when comparing two patterns for equality, in tests.
750   * @tags internals
751   * @returns Pattern
752   * @noAutocomplete
753   */
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  }
764
765  /**
766   * Returns a new pattern with all values parsed as numerals.
767   * @tags internals
768   */
769  asNumber() {
770    return this.fmap(parseNumeral);
771  }
772
773  //////////////////////////////////////////////////////////////////////
774  // Operators - see 'make composers' later..
775
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  }
806
807  //////////////////////////////////////////////////////////////////////
808  // End-user methods.
809  // Those beginning with an underscore (_) are 'patternified',
810  // i.e. versions are created without the underscore, that are
811  // magically transformed to accept patterns for all their arguments.
812
813  //////////////////////////////////////////////////////////////////////
814  // Methods without corresponding toplevel functions
815
816  /**
817   * Layers the result of the given function(s). Like `superimpose`, but without the original pattern:
818   * @name layer
819   * @tags combiners
820   * @memberof Pattern
821   * @returns Pattern
822   * @example
823   * "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8"
824   *   .layer(x=>x.add("0,2"))
825   *   .scale('C minor').note()
826   */
827  layer(...funcs) {
828    return stack(...funcs.map((func) => func(this)));
829  }
830
831  /**
832   * Superimposes the result of the given function(s) on top of the original pattern:
833   * @name superimpose
834   * @tags combiners
835   * @memberof Pattern
836   * @returns Pattern
837   * @example
838   * "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8"
839   *   .superimpose(x=>x.add(2))
840   *   .scale('C minor').note()
841   */
842  superimpose(...funcs) {
843    return this.stack(...funcs.map((func) => func(this)));
844  }
845
846  //////////////////////////////////////////////////////////////////////
847  // Multi-pattern functions
848
849  stack(...pats) {
850    return stack(this, ...pats);
851  }
852
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  }
871
872  //////////////////////////////////////////////////////////////////////
873  // Context methods - ones that deal with metadata
874
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  }
891
892  /**
893   * Writes the content of the current event to the console (visible in the side menu).
894   * @tags visualization
895   * @name log
896   * @memberof Pattern
897   * @example
898   * s("bd sd").log()
899   */
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  }
905
906  /**
907   * A simplified version of `log` which writes all "values" (various configurable parameters)
908   * within the event to the console (visible in the side menu).
909   * @tags visualization
910   * @name logValues
911   * @memberof Pattern
912   * @example
913   * s("bd sd").gain("0.25 0.5 1").n("2 1 0").logValues()
914   */
915  logValues(func = (value) => `[hap] ${stringifyValues(value, true)}`) {
916    return this.log((hap) => func(hap.value));
917  }
918
919  //////////////////////////////////////////////////////////////////////
920  // Visualisation
921
922  drawLine() {
923    console.log(drawLine(this));
924    return this;
925  }
926
927  //////////////////////////////////////////////////////////////////////
928  // methods relating to breaking patterns into subcycles
929
930  // 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  }
936
937  /**
938   * Breaks a pattern into pieces according to the structure of a given pattern.
939   * True values in the given pattern cause the corresponding subcycle of the
940   * source pattern to be looped, and for an (optional) given function to be
941   * applied. False values result in the corresponding part of the source pattern
942   * to be played unchanged.
943   * @tags temporal
944   * @name into
945   * @memberof Pattern
946   * @example
947   * sound("bd sd ht lt").into("1 0", hurry(2))
948   */
949  into(pieces, func) {
950    return this.unjoin(pieces, func).innerJoin();
951  }
952}
953
954//////////////////////////////////////////////////////////////////////
955// functions relating to chords/patterns of lists/lists of patterns
956
957// 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}
970
971// congruent haps = haps with equal spans
972const congruent = (a, b) => a.spanEquals(b);
973// Pattern<Hap<T>> -> Pattern<Hap<T[]>>
974// 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};
980
981/**
982 * Selects indices in in stacked notes.
983 * @tags temporal
984 * @example
985 * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
986 * .arpWith(haps => haps[2])
987 * */
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});
995
996/**
997 * Selects indices in in stacked notes.
998 * @tags temporal
999 * @example
1000 * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
1001 * .arp("0 [0,2] 1 [0,2]")
1002 * */
1003export const arp = register(
1004  'arp',
1005  (indices, pat) => pat.arpWith((haps) => reify(indices).fmap((i) => haps[_mod(i, haps.length)])),
1006  false,
1007);
1008
1009/*
1010 * Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are
1011 * 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.
1012 * @name weave
1013 * @memberof Pattern
1014 * @example pan(saw).weave(4, s("bd(3,8)"), s("~ sd"))
1015 * @example n("0 1 2 3 4 5 6 7").weave(8, s("bd(3,8)"), s("~ sd"))
1016
1017addToPrototype('weave', function (t, ...pats) {
1018  return this.weaveWith(t, ...pats.map((x) => set.out(x)));
1019});
1020
1021*/
1022/*
1023 * Like 'weave', but accepts functions rather than patterns, which are applied to the pattern.
1024 * @name weaveWith
1025 * @memberof Pattern
1026
1027addToPrototype('weaveWith', function (t, ...funcs) {
1028  const pat = this;
1029  const l = funcs.length;
1030  t = Fraction(t);
1031  if (l == 0) {
1032    return silence;
1033  }
1034  return stack(...funcs.map((func, i) => pat.inside(t, func).early(Fraction(i).div(l))))._slow(t);
1035});
1036*/
1037
1038//////////////////////////////////////////////////////////////////////
1039// compose matrix functions
1040
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}
1056
1057// 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)],
1110
1111  // numerical functions
1112  /**
1113   *
1114   * Assumes a pattern of numbers. Adds the given number to each item in the pattern.
1115   * @name add
1116   * @memberof Pattern
1117   * @tags math
1118   * @example
1119   * // Here, the triad 0, 2, 4 is shifted by different amounts
1120   * n("0 2 4".add("<0 3 4 0>")).scale("C:major")
1121   * // Without add, the equivalent would be:
1122   * // n("<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>").scale("C:major")
1123   * @example
1124   * // You can also use add with notes:
1125   * note("c3 e3 g3".add("<0 5 7 0>"))
1126   * // Behind the scenes, the notes are converted to midi numbers:
1127   * // note("48 52 55".add("<0 5 7 0>"))
1128   */
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)],
1166
1167  // 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],
1178
1179  //  bitwise ops
1180  func: [(a, b) => b(a)],
1181};
1182
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    };
1191
1192    // 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;
1200
1201        // wrap the 'in' function as default behaviour
1202        const wrapper = (...other) => pat[what][DEFAULT_ALIGNMENT](...other);
1203
1204        // 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;
1226
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());
1236
1237// Make composers
1238(function () {
1239  _setupAlignments();
1240
1241  // 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})();
1304
1305/**
1306 * Sets the default method of combining events from two patterns (aka [alignment](https://strudel.cc/technical-manual/alignment/)) in Strudel.
1307 * The default method is 'in', meaning that patterns to the left will (typically) dictate the event timings when combined with patterns to the right.
1308 * By changing alignment to 'out', the opposite will happen. With 'mix', they will combine their event timings.
1309 *
1310 * Note that we say the _default_ method, because alignments can also be set explicitly with calls like
1311 * 'add.mix', 'set.squeeze', etc.
1312 *
1313 * @param {string} method Default join method to use. Options: 'in', 'out', 'mix', 'squeeze', 'squeezeout', 'reset', 'restart', 'poly'
1314 * @tags combiners
1315 * @example
1316 * setDefaultJoin('mix') // also try 'in', 'out', 'squeeze', etc.
1317 * s("saw").vel("1 0.5").note("F A C E").delay("0 0.2 0.3")
1318 */
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};
1326
1327// aliases
1328export const polyrhythm = stack;
1329export const pr = stack;
1330
1331export const pm = polymeter;
1332
1333// methods that create patterns, which are added to patternified Pattern methods
1334// TODO: remove? this is only used in old transpiler (shapeshifter)
1335// Pattern.prototype.factories = {
1336//   pure,
1337//   stack,
1338//   slowcat,
1339//   fastcat,
1340//   cat,
1341//   timecat,
1342//   sequence,
1343//   seq,
1344//   polymeter,
1345//   pm,
1346//   polyrhythm,
1347//   pr,
1348// };
1349// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)
1350
1351// Elemental patterns
1352
1353/**
1354 * Does absolutely nothing, but with a given metrical 'steps'
1355 * @name gap
1356 * @tags generators
1357 * @param  {number} steps
1358 * @example
1359 * gap(3) // "~@3"
1360 */
1361export const gap = (steps) => new Pattern(() => [], steps);
1362
1363/**
1364 * Does absolutely nothing..
1365 * @name silence
1366 * @tags generators
1367 * @example
1368 * silence // "~"
1369 */
1370export const silence = gap(1);
1371
1372/* Like silence, but with a 'steps' (relative duration) of 0 */
1373export const nothing = gap(0);
1374
1375/**
1376 * A discrete value that repeats once per cycle.
1377 *
1378 * @tags generators
1379 * @returns {Pattern}
1380 * @example
1381 * pure('e4') // "e4"
1382 * @noAutocomplete
1383 */
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}
1392
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}
1419
1420/**
1421 * Takes a list of patterns, and returns a pattern of lists.
1422 *
1423 * @tags temporal
1424 */
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}
1432
1433/**
1434 * The given items are played at the same time at the same length.
1435 *
1436 * @tags temporal
1437 * @return {Pattern}
1438 * @synonyms polyrhythm, pr
1439 * @example
1440 * stack("g3", "b3", ["e4", "d4"]).note()
1441 * // "g3,b3,[e4 d4]".note()
1442 *
1443 * @example
1444 * // As a chained function:
1445 * s("hh*4").stack(
1446 *   note("c4(5,8)")
1447 * )
1448 */
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}
1459
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}
1517
1518/**
1519 * Concatenation: combines a list of patterns, switching between them successively, one per cycle.
1520 *
1521 * @tags combiners
1522 * @return {Pattern}
1523 * @synonyms cat
1524 * @example
1525 * slowcat("e5", "b4", ["d5", "c5"])
1526 *
1527 */
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}
1551
1552/** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles.
1553 * @tags combiners
1554 * @param {...any} items - The items to concatenate
1555 * @return {Pattern}
1556 */
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}
1569
1570/** The given items are con**cat**enated, where each one takes one cycle.
1571 *
1572 * @tags combiners
1573 * @param {...any} items - The items to concatenate
1574 * @synonyms slowcat
1575 * @return {Pattern}
1576 * @example
1577 * cat("e5", "b4", ["d5", "c5"]).note()
1578 * // "<e5 b4 [d5 c5]>".note()
1579 *
1580 * @example
1581 * // As a chained function:
1582 * s("hh*4").cat(
1583 *    note("c4(5,8)")
1584 * )
1585 */
1586export function cat(...pats) {
1587  return slowcat(...pats);
1588}
1589
1590/**
1591 * Allows to arrange multiple patterns together over multiple cycles.
1592 * Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use.
1593 *
1594 * @tags combiners
1595 * @return {Pattern}
1596 * @example
1597 * arrange(
1598 *   [4, "<c a f e>(3,8)"],
1599 *   [2, "<g a>(5,8)"]
1600 * ).note()
1601 */
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}
1607
1608/**
1609 * Similarly to `arrange`, allows you to arrange multiple patterns together over multiple cycles.
1610 * Unlike `arrange`, you specify a start and stop time for each pattern rather than duration, which
1611 * means that patterns can overlap.
1612 * @tags combiners
1613 * @return {Pattern}
1614 * @example
1615seqPLoop(
1616  [0, 2, "bd(3,8)"],
1617  [1, 3, "cp(3,8)"]
1618).sound()
1619 */
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}
1638
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}
1650
1651/** See `fastcat`
1652 * @name sequence
1653 * @tags combiners
1654 */
1655export function sequence(...pats) {
1656  return fastcat(...pats);
1657}
1658
1659/** Like **cat**, but the items are crammed into one cycle.
1660 * @tags combiners
1661 * @synonyms fastcat
1662 * @example
1663 * seq("e5", "b4", ["d5", "c5"]).note()
1664 * // "e5 b4 [d5 c5]".note()
1665 *
1666 * @example
1667 * // As a chained function:
1668 * s("hh*4").seq(
1669 *   note("c4(5,8)")
1670 * )
1671 */
1672
1673export function seq(...pats) {
1674  return fastcat(...pats);
1675}
1676
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));
1701
1702// 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));
1728
1729/**
1730 * Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
1731 *
1732 * @tags functional
1733 * @param {string | string[]} name name of the function, or an array of names to be used as synonyms
1734 * @param {function} func function with 1 or more params, where last is the current pattern
1735 * @param {bool} patternify defaults to true; if set to false, you will have more control over the arguments to `func` as they will be
1736 *   in their raw form and it will be up to you to patternify them and/or query them for values
1737 * @example
1738 * const vlpf = register('vlpf', (freq, pat) => {
1739 *   return pat.fmap((v) => ({...v, cutoff: freq * (v.velocity ?? 1) }));
1740 * })
1741 * s("saw").seg(8).velocity(rand).vlpf(800)
1742 *
1743 */
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  }
1829
1830  // toplevel functions get curried as well as patternified
1831  // 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}
1836
1837// 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}
1841
1842//////////////////////////////////////////////////////////////////////
1843// Numerical transformations
1844
1845/**
1846 * Assumes a numerical pattern. Returns a new pattern with all values rounded
1847 * to the nearest integer.
1848 * @name round
1849 * @tags math
1850 * @memberof Pattern
1851 * @returns Pattern
1852 * @example
1853 * n("0.5 1.5 2.5".round()).scale("C:major")
1854 */
1855export const round = register('round', function (pat) {
1856  return pat.asNumber().fmap((v) => Math.round(v));
1857});
1858/**
1859 * Assumes a numerical pattern. Returns a new pattern with all values set to
1860 * their mathematical floor. E.g. `3.7` replaced with to `3`, and `-4.2`
1861 * replaced with `-5`.
1862 * @name floor
1863 * @memberof Pattern
1864 * @tags math
1865 * @returns Pattern
1866 * @example
1867 * note("42 42.1 42.5 43".floor())
1868 */
1869export const floor = register('floor', function (pat) {
1870  return pat.asNumber().fmap((v) => Math.floor(v));
1871});
1872
1873export const log2 = register('log2', (pat) => pat.asNumber().fmap((v) => Math.log2(v)));
1874
1875/**
1876 * Assumes a numerical pattern. Returns a new pattern with all values set to
1877 * their mathematical ceiling. E.g. `3.2` replaced with `4`, and `-4.2`
1878 * replaced with `-4`.
1879 * @name ceil
1880 * @memberof Pattern
1881 * @tags math
1882 * @returns Pattern
1883 * @example
1884 * note("42 42.1 42.5 43".ceil())
1885 */
1886export const ceil = register('ceil', function (pat) {
1887  return pat.asNumber().fmap((v) => Math.ceil(v));
1888});
1889/**
1890 * Assumes a numerical pattern, containing unipolar values in the range 0 ..
1891 * 1. Returns a new pattern with values scaled to the bipolar range -1 .. 1
1892 * @tags math
1893 * @returns Pattern
1894 * @noAutocomplete
1895 */
1896export const toBipolar = register('toBipolar', function (pat) {
1897  return pat.fmap((x) => x * 2 - 1);
1898});
1899
1900/**
1901 * Assumes a numerical pattern, containing bipolar values in the range -1 .. 1
1902 * Returns a new pattern with values scaled to the unipolar range 0 .. 1
1903 * @tags math
1904 * @returns Pattern
1905 * @noAutocomplete
1906 */
1907export const fromBipolar = register('fromBipolar', function (pat) {
1908  return pat.fmap((x) => (x + 1) / 2);
1909});
1910
1911/**
1912 * Assumes a numerical pattern, containing unipolar values in the range 0 .. 1.
1913 * Returns a new pattern with values scaled to the given min/max range.
1914 * Most useful in combination with continuous patterns.
1915 * @name range
1916 * @memberof Pattern
1917 * @tags math
1918 * @returns Pattern
1919 * @example
1920 * s("[bd sd]*2,hh*8")
1921 * .cutoff(sine.range(500,4000))
1922 */
1923export const range = register('range', function (min, max, pat) {
1924  return pat.mul(max - min).add(min);
1925});
1926
1927/**
1928 * Assumes a numerical pattern, containing unipolar values in the range 0 .. 1
1929 * Returns a new pattern with values scaled to the given min/max range,
1930 * following an exponential curve.
1931 * @name rangex
1932 * @memberof Pattern
1933 * @tags math
1934 * @returns Pattern
1935 * @example
1936 * s("[bd sd]*2,hh*8")
1937 * .cutoff(sine.rangex(500,4000))
1938 */
1939export const rangex = register('rangex', function (min, max, pat) {
1940  return pat._range(Math.log(min), Math.log(max)).fmap(Math.exp);
1941});
1942
1943/**
1944 * Assumes a numerical pattern, containing bipolar values in the range -1 .. 1
1945 * Returns a new pattern with values scaled to the given min/max range.
1946 * @name range2
1947 * @memberof Pattern
1948 * @tags math
1949 * @returns Pattern
1950 * @example
1951 * s("[bd sd]*2,hh*8")
1952 * .cutoff(sine2.range2(500,4000))
1953 */
1954export const range2 = register('range2', function (min, max, pat) {
1955  return pat.fromBipolar()._range(min, max);
1956});
1957
1958/**
1959 * Allows dividing numbers via list notation using ":".
1960 * Returns a new pattern with just numbers.
1961 * @name ratio
1962 * @memberof Pattern
1963 * @tags math
1964 * @returns Pattern
1965 * @example
1966 * ratio("1, 5:4, 3:2").mul(110)
1967 * .freq().s("piano")
1968 */
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);
1977
1978//////////////////////////////////////////////////////////////////////
1979// Structural and temporal transformations
1980
1981/** Compress each cycle into the given timespan, leaving a gap
1982 * @tags temporal
1983 * @example
1984 * cat(
1985 *   s("bd sd").compress(.25,.75),
1986 *   s("~ bd sd ~")
1987 * )
1988 */
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});
1997
1998export const { compressSpan, compressspan } = register(['compressSpan', 'compressspan'], function (span, pat) {
1999  return pat._compress(span.begin, span.end);
2000});
2001
2002/**
2003 * 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.
2004 * @tags temporal
2005 * @name fastGap
2006 * @synonyms fastgap
2007 * @example
2008 * s("bd sd").fastGap(2)
2009 */
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});
2039
2040/**
2041 * Similar to `compress`, but doesn't leave gaps, and the 'focus' can be bigger than a cycle
2042 * @tags temporal
2043 * @example
2044 * s("bd hh sd hh").focus(1/4, 3/4)
2045 */
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});
2054
2055export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], function (span, pat) {
2056  return pat._focus(span.begin, span.end);
2057});
2058
2059/** The ply function repeats each event the given number of times.
2060 * @tags temporal
2061 * @example
2062 * s("bd ~ sd cp").ply("<1 2 3>")
2063 */
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});
2071
2072/**
2073 * Speed up a pattern by the given factor. Used by "*" in mini notation.
2074 *
2075 * @tags temporal
2076 * @name fast
2077 * @synonyms density
2078 * @memberof Pattern
2079 * @param {number | Pattern} factor speed up factor
2080 * @returns Pattern
2081 * @example
2082 * s("bd hh sd hh").fast(2) // s("[bd hh sd hh]*2")
2083 */
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);
2097
2098/**
2099 * Both speeds up the pattern (like 'fast') and the sample playback (like 'speed').
2100 * @tags temporal
2101 * @example
2102 * s("bd sd:2").hurry("<1 2 4 3>").slow(1.5)
2103 */
2104export const hurry = register('hurry', function (r, pat) {
2105  return pat._fast(r).mul(pure({ speed: r }));
2106});
2107
2108/**
2109 * Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation.
2110 *
2111 * @tags temporal
2112 * @name slow
2113 * @synonyms sparsity
2114 * @memberof Pattern
2115 * @param {number | Pattern} factor slow down factor
2116 * @returns Pattern
2117 * @example
2118 * s("bd hh sd hh").slow(2) // s("[bd hh sd hh]/2")
2119 */
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});
2126
2127/**
2128 * Carries out an operation 'inside' a cycle.
2129 * @tags temporal
2130 * @example
2131 * "0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note()
2132 * // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note()
2133 */
2134export const inside = register('inside', function (factor, f, pat) {
2135  return f(pat._slow(factor))._fast(factor);
2136});
2137
2138/**
2139 * Carries out an operation 'outside' a cycle.
2140 * @tags temporal
2141 * @example
2142 * "<[0 1] 2 [3 4] 5>".outside(4, rev).scale('C major').note()
2143 * // "<[0 1] 2 [3 4] 5>".fast(4).rev().slow(4).scale('C major').note()
2144 */
2145export const outside = register('outside', function (factor, f, pat) {
2146  return f(pat._fast(factor))._slow(factor);
2147});
2148
2149/**
2150 * Applies the given function every n cycles, starting from the last cycle.
2151 * @tags temporal
2152 * @name lastOf
2153 * @memberof Pattern
2154 * @param {number} n how many cycles
2155 * @param {function} func function to apply
2156 * @returns Pattern
2157 * @example
2158 * note("c3 d3 e3 g3").lastOf(4, x=>x.rev())
2159 */
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});
2165
2166/**
2167 * Applies the given function every n cycles, starting from the first cycle.
2168 * @tags temporal
2169 * @name firstOf
2170 * @memberof Pattern
2171 * @param {number} n how many cycles
2172 * @param {function} func function to apply
2173 * @returns Pattern
2174 * @example
2175 * note("c3 d3 e3 g3").firstOf(4, x=>x.rev())
2176 */
2177
2178/**
2179 * An alias for `firstOf`
2180 * @tags temporal
2181 * @name every
2182 * @memberof Pattern
2183 * @param {number} n how many cycles
2184 * @param {function} func function to apply
2185 * @returns Pattern
2186 * @example
2187 * note("c3 d3 e3 g3").every(4, x=>x.rev())
2188 */
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});
2194
2195/**
2196 * Applies the given function to the pattern. Like layer, but with a single function:
2197 * @tags combiners
2198 * @name apply
2199 * @example
2200 * "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
2201 */
2202export const apply = register('apply', function (func, pat) {
2203  return func(pat);
2204});
2205
2206/**
2207 * Plays the pattern at the given cycles per minute.
2208 * @tags temporal
2209 * @deprecated
2210 * @example
2211 * s("<bd sd>,hh*2").cpm(90) // = 90 bpm
2212 */
2213// 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});
2217
2218/**
2219 * Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator
2220 *
2221 * @tags temporal
2222 * @name early
2223 * @memberof Pattern
2224 * @param {number | Pattern} cycles number of cycles to nudge left
2225 * @returns Pattern
2226 * @example
2227 * "bd ~".stack("hh ~".early(.1)).s()
2228 */
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);
2238
2239/**
2240 * Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator
2241 *
2242 * @tags temporal
2243 * @name late
2244 * @memberof Pattern
2245 * @param {number | Pattern} cycles number of cycles to nudge right
2246 * @returns Pattern
2247 * @example
2248 * "bd ~".stack("hh ~".late(.1)).s()
2249 */
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);
2259
2260/**
2261 * 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:
2262 *
2263 * @tags temporal
2264 * @example
2265 * s("bd*2 hh*3 [sd bd]*2 perc").zoom(0.25, 0.75)
2266 * // s("hh*3 [sd bd]*2") // equivalent
2267 */
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});
2282
2283export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a, pat) {
2284  return pat.zoom(a.begin, a.end);
2285});
2286
2287/**
2288 * Splits a pattern into the given number of slices, and plays them according to a pattern of slice numbers.
2289 * Similar to `slice`, but slices up patterns rather than sound samples.
2290 * @tags temporal
2291 * @param {number} number of slices
2292 * @param {number} slices to play
2293 * @example
2294 * note("0 1 2 3 4 5 6 7".scale('c:mixolydian'))
2295 *.bite(4, "3 2 1 0")
2296 * @example
2297 * sound("bd - bd bd*2, - sd:6 - sd:5 sd:1 - [- sd:2] -, hh [- cp:7]")
2298  .bank("RolandTR909").speed(1.2)
2299  .bite(4, "0 0 [1 2] <3 2> 0 0 [2 1] 3")
2300 */
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);
2315
2316/**
2317 * Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle.
2318 * @tags temporal
2319 * @param {number} fraction fraction to select
2320 * @example
2321 * s("lt ht mt cp, [hh oh]*2").linger("<1 .5 .25 .125>")
2322 */
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);
2336
2337/**
2338 * Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one.
2339 * @tags temporal
2340 * @name segment
2341 * @synonyms seg
2342 * @param {number} segments number of segments per cycle
2343 * @example
2344 * note(saw.range(40,52).segment(24))
2345 */
2346export const { segment, seg } = register(['segment', 'seg'], function (rate, pat) {
2347  return pat.struct(pure(true)._fast(rate)).setSteps(rate);
2348});
2349
2350/**
2351 * 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
2352 * @tags temporal
2353 * @param {number} subdivision
2354 * @param {number} offset
2355 * @example
2356 * s("hh*8").swingBy(1/3, 4)
2357 */
2358export const swingBy = register('swingBy', (swing, n, pat) => pat.inside(n, late(seq(0, swing / 2))));
2359
2360/**
2361 * Shorthand for swingBy with 1/3:
2362 * @tags temporal
2363 * @param {number} subdivision
2364 * @example
2365 * s("hh*8").swing(4)
2366 * // s("hh*8").swingBy(1/3, 4)
2367 */
2368export const swing = register('swing', (n, pat) => pat.swingBy(1 / 3, n));
2369
2370/**
2371 * Swaps 1s and 0s in a binary pattern.
2372 * @tags temporal
2373 * @name invert
2374 * @synonyms inv
2375 * @example
2376 * s("bd").struct("1 0 0 1 0 0 1 0".lastOf(4, invert))
2377 */
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);
2387
2388/**
2389 * Applies the given function whenever the given pattern is in a true state.
2390 * @tags temporal
2391 * @name when
2392 * @memberof Pattern
2393 * @param {Pattern} binary_pat
2394 * @param {function} func
2395 * @returns Pattern
2396 * @example
2397 * "c3 eb3 g3".when("<0 1>/2", x=>x.sub("5")).note()
2398 */
2399export const when = register('when', function (on, func, pat) {
2400  return on ? func(pat) : pat;
2401});
2402
2403/**
2404 * Superimposes the function result on top of the original pattern, delayed by the given time.
2405 * @tags temporal
2406 * @name off
2407 * @memberof Pattern
2408 * @param {Pattern | number} time offset time
2409 * @param {function} func function to apply
2410 * @returns Pattern
2411 * @example
2412 * "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
2413 */
2414export const off = register('off', function (time_pat, func, pat) {
2415  return stack(pat, func(pat.late(time_pat)));
2416});
2417
2418/**
2419 * Returns a new pattern where every other cycle is played once, twice as
2420 * fast, and offset in time by one quarter of a cycle. Creates a kind of
2421 * breakbeat feel.
2422 * @tags temporal
2423 * @returns Pattern
2424 */
2425export const brak = register('brak', function (pat) {
2426  return pat.when(slowcat(false, true), (x) => fastcat(x, silence)._late(0.25));
2427});
2428
2429/**
2430 * Reverse all cycles in a pattern. See also `revv` for reversing a whole pattern.
2431 *
2432 * @tags temporal
2433 * @name rev
2434 * @memberof Pattern
2435 * @returns Pattern
2436 * @example
2437 * note("c d e g").rev()
2438 */
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);
2462
2463/**
2464 * Reverse a whole pattern. See also `rev` for reversing each cycle.
2465 *
2466 * @name revv
2467 * @tags temporal
2468 * @memberof Pattern
2469 * @returns Pattern
2470 * @example
2471 * // This is the same as `<[g e] [d c]>`. If `rev()` is used, you get
2472 * // the same as `<[d c] [g e]>`, where each cycle reverses, but the order of
2473 * // cycles stays the same.
2474 * note("<[c d] [e g]>").revv()
2475 */
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});
2480
2481/** Like press, but allows you to specify the amount by which each
2482 * event is shifted. pressBy(0.5) is the same as press, while
2483 * pressBy(1/3) shifts each event by a third of its timespan.
2484 * @tags temporal
2485 * @example
2486 * stack(s("hh*4"),
2487 *       s("bd mt sd ht").pressBy("<0 0.5 0.25>")
2488 *      ).slow(2)
2489 */
2490export const pressBy = register('pressBy', function (r, pat) {
2491  return pat.fmap((x) => pure(x).compress(r, 1)).squeezeJoin();
2492});
2493
2494/**
2495 * Syncopates a rhythm, by shifting each event halfway into its timespan.
2496 * @tags temporal
2497 * @example
2498 * stack(s("hh*4"),
2499 *       s("bd mt sd ht").every(4, press)
2500 *      ).slow(2)
2501 */
2502export const press = register('press', function (pat) {
2503  return pat._pressBy(0.5);
2504});
2505
2506/**
2507 * Silences a pattern.
2508 * @tags temporal
2509 * @example
2510 * stack(
2511 *   s("bd").hush(),
2512 *   s("hh*3")
2513 * )
2514 */
2515Pattern.prototype.hush = function () {
2516  return silence;
2517};
2518
2519/**
2520 * Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards.
2521 * @tags temporal
2522 * @example
2523 * note("c d e g").palindrome()
2524 */
2525export const palindrome = register(
2526  'palindrome',
2527  function (pat) {
2528    return pat.lastOf(2, rev);
2529  },
2530  true,
2531  true,
2532);
2533
2534/**
2535 * Jux with adjustable stereo width. 0 = mono, 1 = full stereo.
2536 * @tags temporal
2537 * @name juxBy
2538 * @synonyms juxby
2539 * @example
2540 * s("bd lt [~ ht] mt cp ~ bd hh").juxBy("<0 .5 1>/2", rev)
2541 */
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});
2555
2556/**
2557 * Like juxBy, except it flips the ears each cycle.
2558 * @name juxFlipBy
2559 * @synonyms juxflipby, fluxBy, fluxby
2560 * @example
2561 * s("bd lt [~ ht] mt cp ~ bd hh").juxFlipBy(".8", rev)
2562 */
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);
2569
2570/**
2571 * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel.
2572 * @tags temporal, superdough
2573 * @example
2574 * s("bd lt [~ ht] mt cp ~ bd hh").jux(rev)
2575 * @example
2576 * s("bd lt [~ ht] mt cp ~ bd hh").jux(press)
2577 * @example
2578 * s("bd lt [~ ht] mt cp ~ bd hh").jux(iter(4))
2579 */
2580export const jux = register('jux', function (func, pat) {
2581  return pat._juxBy(1, func, pat);
2582});
2583
2584/**
2585 * Like jux, but flips the ears each cycle.
2586 * @name juxFlip
2587 * @synonyms juxflip, flux
2588 * @example
2589 * s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(rev)
2590 * @example
2591 * s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(press)
2592 * @example
2593 * s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(iter(4))
2594 */
2595export const { juxFlip, flux } = register(['juxFlip', 'juxflip', 'flux'], function (func, pat) {
2596  return pat._juxFlipBy(1, func, pat);
2597});
2598
2599/**
2600 * Superimpose and offset multiple times, applying the given function each time.
2601 * @tags temporal, functional
2602 * @name echoWith
2603 * @synonyms echowith, stutWith, stutwith
2604 * @param {number} times how many times to repeat
2605 * @param {number} time cycle offset between iterations
2606 * @param {function} func function to apply, given the pattern and the iteration index
2607 * @example
2608 * "<0 [2 4]>"
2609 * .echoWith(4, 1/8, (p,n) => p.add(n*2))
2610 * .scale("C:minor").note()
2611 */
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);
2618
2619/**
2620 * Superimpose and offset multiple times, gradually decreasing the velocity
2621 * @tags temporal
2622 * @name echo
2623 * @memberof Pattern
2624 * @returns Pattern
2625 * @param {number} times how many times to repeat
2626 * @param {number} time cycle offset between iterations
2627 * @param {number} feedback velocity multiplicator for each iteration
2628 * @example
2629 * s("bd sd").echo(3, 1/6, .8)
2630 */
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});
2634
2635/**
2636 * Deprecated. Like echo, but the last 2 parameters are flipped.
2637 * @tags temporal
2638 * @name stut
2639 * @param {number} times how many times to repeat
2640 * @param {number} feedback velocity multiplicator for each iteration
2641 * @param {number} time cycle offset between iterations
2642 * @example
2643 * s("bd sd").stut(3, .8, 1/6)
2644 */
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});
2648
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});
2656
2657/**
2658 * The plyWith function repeats each event the given number of times, applying the given function to each event.\n
2659 * @tags temporal
2660 * @name plyWith
2661 * @synonyms plywith
2662 * @param {number} factor how many times to repeat
2663 * @param {function} func function to apply, given the pattern
2664 * @example
2665 * "<0 [2 4]>"
2666 * .plyWith(4, (p) => p.add(2))
2667 * .scale("C:minor").note()
2668 */
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});
2678
2679/**
2680 * The plyForEach function repeats each event the given number of times, applying the given function to each event.
2681 * This version of ply uses the iteration index as an argument to the function, similar to echoWith.
2682 * @tags temporal
2683 * @name plyForEach
2684 * @synonyms plyforeach
2685 * @param {number} factor how many times to repeat
2686 * @param {function} func function to apply, given the pattern and the iteration index
2687 * @example
2688 * "<0 [2 4]>"
2689 * .plyForEach(4, (p,n) => p.add(n*2))
2690 * .scale("C:minor").note()
2691 */
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});
2701
2702/**
2703 * 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.
2704 * @tags temporal
2705 * @name iter
2706 * @memberof Pattern
2707 * @returns Pattern
2708 * @example
2709 * note("0 1 2 3".scale('A minor')).iter(4)
2710 */
2711
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};
2720
2721export const iter = register(
2722  'iter',
2723  function (times, pat) {
2724    return _iter(times, pat, false);
2725  },
2726  true,
2727  true,
2728);
2729
2730/**
2731 * Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles
2732 * @tags temporal
2733 * @name iterBack
2734 * @synonyms iterback
2735 * @memberof Pattern
2736 * @returns Pattern
2737 * @example
2738 * note("0 1 2 3".scale('A minor')).iterBack(4)
2739 */
2740export const { iterBack, iterback } = register(
2741  ['iterBack', 'iterback'],
2742  function (times, pat) {
2743    return _iter(times, pat, true);
2744  },
2745  true,
2746  true,
2747);
2748
2749/**
2750 * Repeats each cycle the given number of times.
2751 * @tags temporal
2752 * @name repeatCycles
2753 * @memberof Pattern
2754 * @returns Pattern
2755 * @example
2756 * note(irand(12).add(34)).segment(4).repeatCycles(2).s("gm_acoustic_guitar_nylon")
2757 */
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);
2772
2773/**
2774 * 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).
2775 * @tags temporal, functional
2776 * @name chunk
2777 * @synonyms slowChunk, slowchunk
2778 * @memberof Pattern
2779 * @returns Pattern
2780 * @example
2781 * "0 1 2 3".chunk(4, x=>x.add(7))
2782 * .scale("A:minor").note()
2783 */
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};
2795
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);
2804
2805/**
2806 * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
2807 * @tags temporal
2808 * @name chunkBack
2809 * @synonyms chunkback
2810 * @memberof Pattern
2811 * @returns Pattern
2812 * @example
2813 * "0 1 2 3".chunkBack(4, x=>x.add(7))
2814 * .scale("A:minor").note()
2815 */
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);
2824
2825/**
2826 * Like `chunk`, but the cycles of the source pattern aren't repeated
2827 * for each set of chunks.
2828 * @tags temporal
2829 * @name fastChunk
2830 * @synonyms fastchunk
2831 * @memberof Pattern
2832 * @returns Pattern
2833 * @example
2834 * "<0 8> 1 2 3 4 5 6 7"
2835 * .scale("C2:major").note()
2836 * .fastChunk(4, x => x.color('red')).slow(2)
2837 */
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);
2846
2847/**
2848 * Like `chunk`, but the function is applied to a looped subcycle of the source pattern.
2849 * @tags temporal
2850 * @name chunkInto
2851 * @synonyms chunkinto
2852 * @memberof Pattern
2853 * @example
2854 * sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2))
2855 *   .bank("tr909")
2856 */
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});
2860
2861/**
2862 * Like `chunkInto`, but moves backwards through the chunks.
2863 * @tags temporal
2864 * @name chunkBackInto
2865 * @synonyms chunkbackinto
2866 * @memberof Pattern
2867 * @example
2868 * sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2))
2869 *   .bank("tr909")
2870 */
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});
2879
2880// 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);
2890
2891/**
2892 * Loops the pattern inside an `offset` for `cycles`.
2893 * If you think of the entire span of time in cycles as a ribbon, you can cut a single piece and loop it.
2894 * @tags temporal
2895 * @name ribbon
2896 * @synonyms rib
2897 * @param {number} offset start point of loop in cycles
2898 * @param {number} cycles loop length in cycles
2899 * @example
2900 * note("<c d e f>").ribbon(1, 2)
2901 * @example
2902 * // Looping a portion of randomness
2903 * n(irand(8).segment(4)).scale("c:pentatonic").ribbon(1337, 2)
2904 * @example
2905 * // rhythm generator
2906 * s("bd!16?").ribbon(29,.5)
2907 */
2908export const { ribbon, rib } = register(['ribbon', 'rib'], (offset, cycles, pat) =>
2909  pat.early(offset).restart(pure(1).slow(cycles)),
2910);
2911
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});
2919
2920/**
2921 * Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array).
2922 * @name tag
2923 * @tags temporal
2924 * @param {string} tag anything unique
2925 * @example
2926 * s("saw!16").note("F1")
2927 *   .lpf(tri.range(40, 80).slow(4)).lpenv(5).lpq(4).lpd(0.15)
2928 *   .when(rand.late(0.1).gte(0.5), x => x.transpose("12").tag('altered'))
2929 *   .when(rand.late(0.2).gte(0.5), x => x.s("square").tag('altered'))
2930 *   .when("<0 1>", x => x.filter((hap) => hap.hasTag('altered')))
2931 */
2932Pattern.prototype.tag = function (tag) {
2933  return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
2934};
2935
2936/**
2937 * Filters haps using the given function
2938 * @name filter
2939 * @tags temporal, functional
2940 * @param {Function} test function to test Hap
2941 * @example
2942 * s("hh!7 oh").filter(hap => hap.value.s === 'hh')
2943 */
2944export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test)));
2945
2946/**
2947 * Filters haps by their begin time
2948 * @name filterWhen
2949 * @tags temporal, functional
2950 * @param {Function} test function to test Hap.whole.begin
2951 * @example
2952 * oneCycle: s("bd*4").filterWhen((t) => t < 1)
2953 */
2954export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin)));
2955
2956/**
2957 * Use within to apply a function to only a part of a pattern.
2958 * @name within
2959 * @tags temporal, functional
2960 * @param {number} start start within cycle (0 - 1)
2961 * @param {number} end end within cycle (0 - 1). Must be > start
2962 * @param {Function} func function to be applied to the sub-pattern
2963 */
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);
2970
2971//////////////////////////////////////////////////////////////////////
2972// Stepwise functions
2973
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};
2985
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}
3029
3030/**
3031 * *Experimental*
3032 *
3033 * Speeds a pattern up or down, to fit to the given number of steps per cycle.
3034 * @tags stepwise
3035 * @example
3036 * sound("bd sd cp").pace(4)
3037 * // The same as sound("{bd sd cp}%4") or sound("<bd sd cp>*4")
3038 */
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});
3049
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}
3071
3072/**
3073 * *Experimental*
3074 *
3075 * 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.
3076 * @tags stepwise
3077 * @synonyms pm
3078 * @example
3079 * // The same as note("{c eb g, c2 g2}%6")
3080 * polymeter("c eb g", "c2 g2").note()
3081 *
3082 */
3083export function polymeter(...args) {
3084  if (Array.isArray(args[0])) {
3085    // Support old behaviour
3086    return _polymeterListSteps(0, ...args);
3087  }
3088
3089  // TODO currently ignoring arguments without steps...
3090  args = args.filter((arg) => arg.hasSteps);
3091
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}
3104
3105/** 'Concatenates' patterns like `fastcat`, but proportional to a number of steps per cycle.
3106 * The steps can either be inferred from the pattern, or provided as a [length, pattern] pair.
3107 * Has the alias `timecat`.
3108 * @name stepcat
3109 * @tags stepwise
3110 * @synonyms timeCat, timecat
3111 * @return {Pattern}
3112 * @example
3113 * stepcat([3,"e3"],[1, "g3"]).note()
3114 * // the same as "e3@3 g3".note()
3115 * @example
3116 * stepcat("bd sd cp","hh hh").sound()
3117 * // the same as "bd sd cp hh hh".sound()
3118 */
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}
3160
3161/**
3162 * *Experimental*
3163 *
3164 * Concatenates patterns stepwise, according to an inferred 'steps per cycle'.
3165 * Similar to `stepcat`, but if an argument is a list, the whole pattern will alternate between the elements in the list.
3166 *
3167 * @tags stepwise
3168 * @return {Pattern}
3169 * @example
3170 * stepalt(["bd cp", "mt"], "bd").sound()
3171 * // The same as "bd cp bd mt bd".sound()
3172 */
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}
3188
3189/**
3190 * *Experimental*
3191 *
3192 * Takes the given number of steps from a pattern (dropping the rest).
3193 * A positive number will take steps from the start of a pattern, and a negative number from the end.
3194 * @tags stepwise
3195 * @return {Pattern}
3196 * @example
3197 * "bd cp ht mt".take("2").sound()
3198 * // The same as "bd cp".sound()
3199 * @example
3200 * "bd cp ht mt".take("1 2 3").sound()
3201 * // The same as "bd bd cp bd cp ht".sound()
3202 * @example
3203 * "bd cp ht mt".take("-1 -2 -3").sound()
3204 * // The same as "mt ht mt cp ht mt".sound()
3205 */
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});
3233
3234/**
3235 * *Experimental*
3236 *
3237 * Drops the given number of steps from a pattern.
3238 * A positive number will drop steps from the start of a pattern, and a negative number from the end.
3239 * @tags stepwise
3240 * @return {Pattern}
3241 * @example
3242 * "tha dhi thom nam".drop("1").sound().bank("mridangam")
3243 * @example
3244 * "tha dhi thom nam".drop("-1").sound().bank("mridangam")
3245 * @example
3246 * "tha dhi thom nam".drop("0 1 2 3").sound().bank("mridangam")
3247 * @example
3248 * "tha dhi thom nam".drop("0 -1 -2 -3").sound().bank("mridangam")
3249 */
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});
3261
3262/**
3263 * *Experimental*
3264 *
3265 * `extend` is similar to `fast` in that it increases its density, but it also increases the step count
3266 * accordingly. So `stepcat("a b".extend(2), "c d")` would be the same as `"a b a b c d"`, whereas
3267 * `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`.
3268 * @tags stepwise
3269 * @example
3270 * stepcat(
3271 *   sound("bd bd - cp").extend(2),
3272 *   sound("bd - sd -")
3273 * ).pace(8)
3274 */
3275export const extend = stepRegister('extend', function (factor, pat) {
3276  return pat.fast(factor).expand(factor);
3277});
3278
3279/**
3280 * *Experimental*
3281 *
3282 * `replicate` is similar to `fast` in that it increases its density, but it also increases the step count
3283 * accordingly. So `stepcat("a b".replicate(2), "c d")` would be the same as `"a b a b c d"`, whereas
3284 * `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`.
3285 *
3286 * TODO: find out how this function differs from extend
3287 * @tags stepwise
3288 * @example
3289 * stepcat(
3290 *   sound("bd bd - cp").replicate(2),
3291 *   sound("bd - sd -")
3292 * ).pace(8)
3293 */
3294export const replicate = stepRegister('replicate', function (factor, pat) {
3295  return pat.repeatCycles(factor).fast(factor).expand(factor);
3296});
3297
3298/**
3299 * *Experimental*
3300 *
3301 * Expands the step size of the pattern by the given factor.
3302 * @tags stepwise
3303 * @example
3304 * sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8)
3305 */
3306export const expand = stepRegister('expand', function (factor, pat) {
3307  return pat.withSteps((t) => t.mul(Fraction(factor)));
3308});
3309
3310/**
3311 * *Experimental*
3312 *
3313 * Contracts the step size of the pattern by the given factor. See also `expand`.
3314 * @tags stepwise
3315 * @example
3316 * sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8)
3317 */
3318export const contract = stepRegister('contract', function (factor, pat) {
3319  return pat.withSteps((t) => t.div(Fraction(factor)));
3320});
3321
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);
3367
3368/**
3369 * *Experimental*
3370 *
3371 * Progressively shrinks the pattern by 'n' steps until there's nothing left, or if a second value is given (using mininotation list syntax with `:`),
3372 * that number of times.
3373 * A positive number will progressively drop steps from the start of a pattern, and a negative number from the end.
3374 * @tags stepwise
3375 * @return {Pattern}
3376 * @example
3377 * "tha dhi thom nam".shrink("1").sound()
3378 * .bank("mridangam")
3379 * @example
3380 * "tha dhi thom nam".shrink("-1").sound()
3381 * .bank("mridangam")
3382 * @example
3383 * "tha dhi thom nam".shrink("1 -1").sound().bank("mridangam").pace(4)
3384 * @example
3385 * note("0 1 2 3 4 5 6 7".scale("C:ritusen")).sound("folkharp")
3386   .shrink("1 -1").pace(8)
3387
3388 */
3389
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);
3407
3408/**
3409 * *Experimental*
3410 *
3411 * 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 `:`),
3412 * that number of times.
3413 * A positive number will progressively grow steps from the start of a pattern, and a negative number from the end.
3414 * @tags stepwise
3415 * @return {Pattern}
3416 * @example
3417 * "tha dhi thom nam".grow("1").sound()
3418 * .bank("mridangam")
3419 * @example
3420 * "tha dhi thom nam".grow("-1").sound()
3421 * .bank("mridangam")
3422 * @example
3423 * "tha dhi thom nam".grow("1 -1").sound().bank("mridangam").pace(4)
3424 * @example
3425 * note("0 1 2 3 4 5 6 7".scale("C:ritusen")).sound("folkharp")
3426   .grow("1 -1").pace(8)
3427 */
3428
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);
3447
3448/**
3449 * *Experimental*
3450 * 
3451 * 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 
3452 * on successive repetitions. The patterns are added together stepwise, with all repetitions taking place over a single cycle. Using `pace` to set the 
3453 * number of steps per cycle is therefore usually recommended.
3454 *
3455 * @tags stepwise
3456 * @return {Pattern}
3457 * @example
3458 * "[c g]".tour("e f", "e f g", "g f e c").note()
3459   .sound("folkharp")
3460   .pace(8)
3461 */
3462export const tour = function (pat, ...many) {
3463  return pat.tour(...many);
3464};
3465
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};
3475
3476/**
3477 * *Experimental*
3478 * 
3479 * 'zips' together the steps of the provided patterns. This can create a long repetition, taking place over a single, dense cycle. 
3480 * Using `pace` to set the number of steps per cycle is therefore usually recommended.
3481 * 
3482 * @tags stepwise
3483 * @returns {Pattern}
3484 * @example
3485 * zip("e f", "e f g", "g [f e] a f4 c").note()
3486   .sound("folkharp")
3487   .pace(8)
3488 */
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};
3495
3496/** Aliases for `stepcat` */
3497export const timecat = stepcat;
3498export const timeCat = stepcat;
3499
3500// 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;
3525
3526//////////////////////////////////////////////////////////////////////
3527// Control-related functions, i.e. ones that manipulate patterns of
3528// objects
3529
3530/**
3531 * Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'.
3532 * It turns a pattern of samples into a pattern of parts of samples.
3533 * @name chop
3534 * @tags samples
3535 * @memberof Pattern
3536 * @returns Pattern
3537 * @example
3538 * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
3539 * s("rhodes")
3540 *  .chop(4)
3541 *  .rev() // reverse order of chops
3542 *  .loopAt(2) // fit sample into 2 cycles
3543 *
3544 */
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});
3561
3562/**
3563 * Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop.
3564 * @name striate
3565 * @tags samples
3566 * @memberof Pattern
3567 * @returns Pattern
3568 * @example
3569 * s("numbers:0 numbers:1 numbers:2").striate(6).slow(3)
3570 */
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});
3580
3581/**
3582 * Makes the sample fit the given number of cycles by changing the speed.
3583 * @name loopAt
3584 * @tags samples, pitch
3585 * @memberof Pattern
3586 * @returns Pattern
3587 * @example
3588 * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
3589 * s("rhodes").loopAt(2)
3590 */
3591const _loopAt = function (factor, pat, cps = 0.5) {
3592  return pat
3593    .speed((1 / factor) * cps)
3594    .unit('c')
3595    .slow(factor);
3596};
3597
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});
3602
3603/**
3604 * Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
3605 * Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points.
3606 * @name slice
3607 * @tags samples
3608 * @memberof Pattern
3609 * @returns Pattern
3610 * @example
3611 * samples('github:tidalcycles/dirt-samples')
3612 * s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(0.75)
3613 * @example
3614 * samples('github:tidalcycles/dirt-samples')
3615 * s("breaks125").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")
3616 */
3617
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);
3637
3638/**
3639 *
3640 * make something happen on event time
3641 * uses browser timeout which is innacurate for audio tasks
3642 * @name onTriggerTime
3643 * @tags external_io
3644 * @memberof Pattern
3645 *  @returns Pattern
3646 * @example
3647 * s("bd!8").onTriggerTime((hap) => {console.log(hap)})
3648 */
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};
3657
3658/**
3659 * Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
3660 * @name splice
3661 * @tags samples, pitch
3662 * @example
3663 * samples('github:tidalcycles/dirt-samples')
3664 * s("breaks165")
3665 * .splice(8,  "0 1 [2 3 0]@2 3 0@2 7")
3666 */
3667
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);
3689
3690/**
3691 * Makes the sample fit its event duration. Good for rhythmical loops like drum breaks.
3692 * Similar to `loopAt`.
3693 * @name fit
3694 * @tags samples, pitch
3695 * @example
3696 * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
3697 * s("rhodes/2").fit()
3698 */
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);
3713
3714/**
3715 * Makes the sample fit the given number of cycles and cps value, by
3716 * changing the speed. deprecated: use loopAt or fit instead, together with setCps / setCpm.
3717 * @name loopAtCps
3718 * @tags samples, pitch
3719 * @memberof Pattern
3720 * @deprecated
3721 * @returns Pattern
3722 * @example
3723 * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
3724 * s("rhodes").loopAtCps(4,1.5).cps(1.5)
3725 */
3726export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
3727  return _loopAt(factor, pat, cps);
3728});
3729
3730/** exposes a custom value at query time. basically allows mutating state without evaluation
3731 * @tags internals
3732 */
3733export const ref = (accessor) =>
3734  pure(1)
3735    .withValue(() => reify(accessor()))
3736    .innerJoin();
3737
3738let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5);
3739
3740/**
3741 * Cross-fades between left and right from 0 to 1:
3742 * - 0 = (full left, no right)
3743 * - .5 = (both equal)
3744 * - 1 = (no left, full right)
3745 *
3746 * @name xfade
3747 * @tags amplitude
3748 * @example
3749 * xfade(s("bd*2"), "<0 .25 .5 .75 1>", s("hh*8"))
3750 */
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};
3759
3760// the prototype version is actually flipped so left/right makes sense
3761Pattern.prototype.xfade = function (pos, b) {
3762  return xfade(this, pos, b);
3763};
3764
3765/**
3766 * creates a structure pattern from divisions of a cycle
3767 * especially useful for creating rhythms
3768 * @name beat
3769 * @tags temporal
3770 * @example
3771 * s("bd").beat("0,7,10", 16)
3772 * @example
3773 * s("sd").beat("4,12", 16)
3774 */
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};
3782
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};
3829
3830/**
3831 * Takes two binary rhythms represented as lists of 1s and 0s, and a number
3832 * between 0 and 1 that morphs between them. The two lists should contain the same
3833 * number of true values.
3834 * @example
3835 * sound("hh").struct(morph([1,0,1,0,1,0,1,0], // straight rhythm
3836 *                          [1,1,0,1,0,1,0], // wonky rhythm
3837 *                          0.25 // creates a slightly wonky rhythm
3838 *                         )
3839 *                   )
3840 * @example
3841 * sound("hh").struct(morph("1:0:1:0:1:0:1:0", // straight rhythm
3842 *                          "1:1:0:1:0:1:0", // wonky rhythm
3843 *                          sine.slow(8) // slowly morph between the rhythms
3844 *                         )
3845 *                   )
3846 * @tags temporal
3847 */
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};
3854
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};
3868
3869/**
3870 * Soft-clipping distortion
3871 *
3872 * @name soft
3873 * @tags distortion, superdough
3874 * @param {number | Pattern} distortion amount of distortion to apply
3875 * @param {number | Pattern} volume linear postgain of the distortion
3876 *
3877 */
3878export const soft = _distortWithAlg('soft');
3879
3880/**
3881 * Hard-clipping distortion
3882 *
3883 * @name hard
3884 * @tags distortion, superdough
3885 * @param {number | Pattern} distortion amount of distortion to apply
3886 * @param {number | Pattern} volume linear postgain of the distortion
3887 *
3888 */
3889export const hard = _distortWithAlg('hard');
3890
3891/**
3892 * Cubic polynomial distortion
3893 *
3894 * @name cubic
3895 * @tags distortion, superdough
3896 * @param {number | Pattern} distortion amount of distortion to apply
3897 * @param {number | Pattern} volume linear postgain of the distortion
3898 *
3899 */
3900export const cubic = _distortWithAlg('cubic');
3901
3902/**
3903 * Diode-emulating distortion
3904 *
3905 * @name diode
3906 * @tags distortion, superdough
3907 * @param {number | Pattern} distortion amount of distortion to apply
3908 * @param {number | Pattern} volume linear postgain of the distortion
3909 *
3910 */
3911export const diode = _distortWithAlg('diode');
3912
3913/**
3914 * Asymmetrical diode distortion
3915 *
3916 * @name asym
3917 * @tags distortion, superdough
3918 * @param {number | Pattern} distortion amount of distortion to apply
3919 * @param {number | Pattern} volume linear postgain of the distortion
3920 *
3921 */
3922export const asym = _distortWithAlg('asym');
3923
3924/**
3925 * Wavefolding distortion
3926 *
3927 * @name fold
3928 * @tags distortion, superdough
3929 * @param {number | Pattern} distortion amount of distortion to apply
3930 * @param {number | Pattern} volume linear postgain of the distortion
3931 *
3932 */
3933export const fold = _distortWithAlg('fold');
3934
3935/**
3936 * Wavefolding distortion composed with sinusoid
3937 *
3938 * @name sinefold
3939 * @tags distortion, superdough
3940 * @param {number | Pattern} distortion amount of distortion to apply
3941 * @param {number | Pattern} volume linear postgain of the distortion
3942 *
3943 */
3944export const sinefold = _distortWithAlg('sinefold');
3945
3946/**
3947 * Distortion via Chebyshev polynomials
3948 *
3949 * @name chebyshev
3950 * @tags distortion, superdough
3951 * @param {number | Pattern} distortion amount of distortion to apply
3952 * @param {number | Pattern} volume linear postgain of the distortion
3953 *
3954 */
3955export const chebyshev = _distortWithAlg('chebyshev');
3956
3957/**
3958 * Turns a list of patterns into a single pattern which outputs list-values
3959 *
3960 * @name parray
3961 * @tags combiners
3962 * @returns Pattern
3963 */
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};
3970
3971const _ensureListPattern = (list) => {
3972  if (Array.isArray(list)) {
3973    return parray(list);
3974  }
3975  return reify(list);
3976};
3977
3978/**
3979 * Scale the magnitude of the harmonics of one of the core synths ('sine', 'tri', 'saw', ..)
3980 *
3981 * Can also be used to create a new synth via `s('user').partials(...)`
3982 *
3983 * @name partials
3984 * @tags superdough
3985 * @param {number[] | Pattern} magnitudes List of [0, 1] magnitudes for partials. 0th entry is the fundamental harmonic (i.e. DC offset is skipped)
3986 * @example
3987 * s("user").seg(16).n(irand(8)).scale("A:major")
3988 *   .partials([1, 0, 1, 0, 0, 1])
3989 * @example
3990 * s("saw").seg(8).n(irand(12)).scale("G#:minor")
3991 *   .partials(binaryL(irand(256).add("1")))
3992 */
3993Pattern.prototype.partials = function (list) {
3994  return this.withValue((v) => (l) => ({ ...v, partials: l })).appLeft(_ensureListPattern(list));
3995};
3996
3997// Also create a top-level function
3998export const partials = (list) => {
3999  return _ensureListPattern(list).as('partials');
4000};
4001
4002/**
4003 * Rotates the harmonics of one of the core synths ('sine', 'tri', 'saw', 'user', ..) by a list of phases
4004 *
4005 * @name phases
4006 * @tags superdough
4007 * @param {number[] | Pattern} phases List of [0, 1) phases for partials. 0th entry is the fundamental phase (i.e. DC offset is skipped)
4008 * @example
4009 * // Phase cancellation
4010 * s("saw").seg(8).n(irand(12)).scale("G#1:minor")
4011 *   .partials(partials([1, 1, 1]))
4012 *   .superimpose(x => x.phases([0.5, 0.5, 0.5]))
4013 */
4014Pattern.prototype.phases = function (list) {
4015  return this.withValue((v) => (l) => ({ ...v, phases: l })).appLeft(_ensureListPattern(list));
4016};
4017
4018// Also create a top-level function
4019export const phases = (list) => {
4020  return _ensureListPattern(list).as('phases');
4021};
4022
4023/**
4024 * Establishes an FX chain. Can be called by chaining .FX(fx1).FX(fx2)..
4025 * calls and/or in a single .FX(fx1, fx2, ..) call. The fx1, .. are _patterns_ which
4026 * establish the controls of the given effect. See examples.
4027 * @name FX
4028 * @tags superdough
4029 * @memberof Pattern
4030 * @returns Pattern
4031 * @example
4032 * $: s("[sbd <hh [bd | lt | oh]>]*4").dec(.4)
4033 *   .FX(
4034 *     phaser(0.5).gain(2),
4035 *     bpf(800),
4036 *     distort(1.3),
4037 *     room(0.2),
4038 *     delay(0.5).gain(1.25),
4039 *     distort(0.3),
4040 *   ).fxr(1.7) // sets release time of effects (like delay)
4041 * @example
4042 * $: s("saw").fm(0.5)
4043 *   .delay(0.3) // outer effects are applied *last*
4044 *   .FX(coarse(4)) // first coarse
4045 *   .FX(lpf(500).lpe(4).lpa(1).lpd(2)) // then lpf
4046 *   .FX(distort(1)) // then distort
4047 */
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};
4055
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};
4062
4063/**
4064 * Produces a [Kabelsalat](https://kabel.salat.dev/) modular sound engine.
4065 * This can be used as either an effect (by including `audioin()` at the beginning
4066 * of your kabel expression) or as a sound source (via any expression which doesn't
4067 * start with `audioin()`).
4068 *
4069 * Some helpers you have available to you:
4070 *   * Strudel mini notation works fine in K(..) via "" or ``
4071 *   * More complex Strudel expressions (like "0 1 2".fast(4) or irand(24)) can be
4072 *     written by wrapping them in `S(..)` inside your Kabel code
4073 *   * We expose Strudel's note frequency under `sFreq` and Strudel's gate
4074 *     information under `sGate`
4075 *   * You can use more complex multi-line expressions (like `let x = a; let y = b; x.lpf(y);`)
4076 *     by wrapping them inside a function in K (see example).
4077 *
4078 * @name K
4079 * @tags generators, superdough
4080 * @param {KabelsalatExpression | Function} expr Kabelsalat graph definition
4081 * @memberof Pattern
4082 * @returns Pattern
4083 *
4084 * @example
4085 * note("A c e".fast(4)).transpose("<0 2 4 6 8>")
4086 *   .scale("F:minor").transpose("12")
4087 *   .s("saw")
4088 *   .K(
4089 *     // audioin().mul(sGate.adsr(0.001, 0.3, 0, 0.2)) // as effect
4090 *     saw(saw(sFreq / "2!3 16").mul(8).add(sFreq).lag("0!3 0.1")).mul(0.3) // as source
4091 *     .mul(sGate.adsr(0, 0.15, 0.5, "0.1!3 1"))
4092 *     .lpf(sGate.adsr(0, 0.2, 0.3, 0.2).mul(1).add(0))
4093 *     .add(x => x.delay(S("0.3 0.2".fast(2))).mul(0.7))
4094 *     .add(x => x.delay("0.03 [0.08 0.01] 0.01 0.013").mul(0.77)).mul(0.7)
4095 *     .add(x => x.delay(.13).mul(0.7))
4096 *     .out()
4097 *   )
4098 *
4099 * @example
4100 * n("<0 1 <2 3 2 4>>*16")
4101 *   .scale("G#2:minor").sometimes(x => x.transpose("12 | 24"))
4102 *   .K(() => {
4103 *     const att = S(rand.range(0, 0.05))
4104 *     const dec = S(rand.range(0.05, 0.2))
4105 *     let f = n(sFreq);
4106 *     const mod = sine(f).mul("0.1 | 0.2 | 0.3")
4107 *       .add("[[1.5 1] | 1 | 2 | 4 | [6 4@3]]*2")
4108 *     saw(f.mul(mod))
4109 *     .mul(sGate.ad(att, dec))
4110 *     .add(x => x.delay(0.4).mul(0.3))
4111 *     .out()
4112 *   }).fxr(1).room(0.3)
4113 */
4114/**
4115 * Creates a worklet effect. Typically derived by writing K(...) in the REPL which will parse
4116 * Kabelsalat code.
4117 *
4118 * @name worklet
4119 * @param {string} src Source code of the worklet update function
4120 * @param {...number | ...Pattern} inputs Worklet inputs
4121 * @memberof Pattern
4122 * @returns Pattern
4123 * @noAutocomplete
4124 */
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};
4134
4135export const worklet = (...args) => pure({}).worklet(...args);
4136
4137/**
4138 * Creates a pattern of numbers in base b from a number or pattern of numbers
4139 * limited to d digits long from the right
4140 *
4141 * @name base
4142 * @tags generators
4143 * @param {number} n - number to convert (can be a pattern or array)
4144 * @param {number} b - base to convert to (defaults to 10) (can be a pattern)
4145 * @param {number} d - max number of digits to produce for each n (defaults to 0 for all) (can be a pattern)
4146 * @example
4147 * $: note(base("7175 543", 10, 3)).scale("c:major").s("saw")
4148 * // $: note("1 7 5 5 4 3").scale("c:major").s("saw")
4149 */
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};