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};