1/* 2signal.mjs - continuous patterns 3Copyright (C) 2024 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/signal.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 { Hap } from './hap.mjs'; 8import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs'; 9import { _mod } from './util.mjs'; 10import Fraction from './fraction.mjs'; 11 12import { id, keyAlias, getCurrentKeyboardState } from './util.mjs'; 13 14export function steady(value) { 15 // A continuous value 16 return new Pattern((state) => [new Hap(undefined, state.span, value)]); 17} 18 19export const signal = (func) => { 20 const query = (state) => [new Hap(undefined, state.span, func(state.span.begin, state.controls))]; 21 return new Pattern(query); 22}; 23 24/** 25 * A sawtooth signal between 0 and 1. 26 * 27 * @return {Pattern} 28 * @tags generators 29 * @example 30 * note("<c3 [eb3,g3] g2 [g3,bb3]>*8") 31 * .clip(saw.slow(2)) 32 * @example 33 * n(saw.range(0,8).segment(8)) 34 * .scale('C major') 35 * 36 */ 37export const saw = signal((t) => _mod(t, 1)); 38 39/** 40 * A sawtooth signal between -1 and 1 (like `saw`, but bipolar). 41 * 42 * @return {Pattern} 43 * @tags generators 44 */ 45export const saw2 = saw.toBipolar(); 46 47/** 48 * A sawtooth signal between 1 and 0 (like `saw`, but flipped). 49 * 50 * @return {Pattern} 51 * @tags generators 52 * @example 53 * note("<c3 [eb3,g3] g2 [g3,bb3]>*8") 54 * .clip(isaw.slow(2)) 55 * @example 56 * n(isaw.range(0,8).segment(8)) 57 * .scale('C major') 58 * 59 */ 60export const isaw = signal((t) => 1 - _mod(t, 1)); 61 62/** 63 * A sawtooth signal between 1 and -1 (like `saw2`, but flipped). 64 * 65 * @return {Pattern} 66 * @tags generators 67 */ 68export const isaw2 = isaw.toBipolar(); 69 70/** 71 * A sine signal between -1 and 1 (like `sine`, but bipolar). 72 * 73 * @return {Pattern} 74 * @tags generators 75 */ 76export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t)); 77 78/** 79 * A sine signal between 0 and 1. 80 * @return {Pattern} 81 * @tags generators 82 * @example 83 * n(sine.segment(16).range(0,15)) 84 * .scale("C:minor") 85 * 86 */ 87export const sine = sine2.fromBipolar(); 88 89/** 90 * A cosine signal between 0 and 1. 91 * 92 * @return {Pattern} 93 * @tags generators 94 * @example 95 * n(stack(sine,cosine).segment(16).range(0,15)) 96 * .scale("C:minor") 97 * 98 */ 99export const cosine = sine._early(Fraction(1).div(4)); 100 101/** 102 * A cosine signal between -1 and 1 (like `cosine`, but bipolar). 103 * 104 * @return {Pattern} 105 * @tags generators 106 */ 107export const cosine2 = sine2._early(Fraction(1).div(4)); 108 109/** 110 * A square signal between 0 and 1. 111 * @return {Pattern} 112 * @tags generators 113 * @example 114 * n(square.segment(4).range(0,7)).scale("C:minor") 115 * 116 */ 117export const square = signal((t) => Math.floor(_mod(t * 2, 2))); 118 119/** 120 * A square signal between -1 and 1 (like `square`, but bipolar). 121 * 122 * @return {Pattern} 123 * @tags generators 124 */ 125export const square2 = square.toBipolar(); 126 127/** 128 * A square signal between 1 and 0 (like `square` but flipped). 129 * 130 * @return {Pattern} 131 * @tags generators 132 */ 133export const isquare = signal((t) => 1 - Math.floor(_mod(t * 2, 2))); 134 135/** 136 * A square signal between 1 and -1 (like `isquare`, but bipolar). 137 * 138 * @return {Pattern} 139 * @tags generators 140 */ 141export const isquare2 = isquare.toBipolar(); 142 143/** 144 * A triangle signal between 0 and 1. 145 * 146 * @return {Pattern} 147 * @tags generators 148 * @example 149 * n(tri.segment(8).range(0,7)).scale("C:minor") 150 * 151 */ 152export const tri = fastcat(saw, isaw); 153 154/** 155 * A triangle signal between -1 and 1 (like `tri`, but bipolar). 156 * 157 * @return {Pattern} 158 * @tags generators 159 */ 160export const tri2 = fastcat(saw2, isaw2); 161 162/** 163 * An inverted triangle signal between 1 and 0 (like `tri`, but flipped). 164 * 165 * @return {Pattern} 166 * @tags generators 167 * @example 168 * n(itri.segment(8).range(0,7)).scale("C:minor") 169 * 170 */ 171export const itri = fastcat(isaw, saw); 172 173/** 174 * An inverted triangle signal between -1 and 1 (like `itri`, but bipolar). 175 * 176 * @return {Pattern} 177 * @tags generators 178 */ 179export const itri2 = fastcat(isaw2, saw2); 180 181/** 182 * A signal representing the cycle time. 183 * 184 * @return {Pattern} 185 * @tags generators 186 */ 187export const time = signal(id); 188 189/** 190 * The mouse's x position value ranges from 0 to 1. 191 * @name mousex 192 * @return {Pattern} 193 * @tags external_io 194 * @example 195 * n(mousex.segment(4).range(0,7)).scale("C:minor") 196 * 197 */ 198 199/** 200 * The mouse's y position value ranges from 0 to 1. 201 * @name mousey 202 * @return {Pattern} 203 * @tags external_io 204 * @example 205 * n(mousey.segment(4).range(0,7)).scale("C:minor") 206 * 207 */ 208let _mouseY = 0, 209 _mouseX = 0; 210if (typeof window !== 'undefined') { 211 //document.onmousemove = (e) => { 212 document.addEventListener('mousemove', (e) => { 213 _mouseY = e.clientY / document.body.clientHeight; 214 _mouseX = e.clientX / document.body.clientWidth; 215 }); 216} 217 218export const mousey = signal(() => _mouseY); 219export const mouseY = signal(() => _mouseY); 220export const mousex = signal(() => _mouseX); 221export const mouseX = signal(() => _mouseX); 222 223// Random number generators 224 225// Produce "Avalanche effect" where flipping a single bit of x 226// results in all output bits flipping with probability 0.5 227// See e.g. https://github.com/aappleby/smhasher/blob/0ff96f7835817a27d0487325b6c16033e2992eb5/src/MurmurHash3.cpp#L68-L77 228const _murmurHashFinalizer = (x) => { 229 x |= 0; 230 x ^= x >>> 16; 231 x = Math.imul(x, 0x85ebca6b); 232 x ^= x >>> 13; 233 x = Math.imul(x, 0xc2b2ae35); 234 x ^= x >>> 16; 235 return x >>> 0; // unsigned 236}; 237 238// Convert t to a 32 bit integer, preserving temporal resolution down to 1/2^29 239const _tToT = (t) => { 240 return Math.floor(t * 536870912); 241}; 242 243// Used to decorrelate nearby T, i, and seed prior to hashing 244const _decorrelate = (T, i = 0, seed = 0) => { 245 const lowBits = (T >>> 0) >>> 0; 246 const highBits = Math.floor(T / 4294967296) >>> 0; // 2^32 247 let key = lowBits ^ Math.imul(highBits ^ 0x85ebca6b, 0xc2b2ae35); 248 key ^= Math.imul(i ^ 0x7f4a7c15, 0x9e3779b9); 249 key ^= Math.imul(seed ^ 0x165667b1, 0x27d4eb2d); 250 return key >>> 0; 251}; 252 253const randAt = (T, i = 0, seed = 0) => { 254 return _murmurHashFinalizer(_decorrelate(T, i, seed)) / 4294967296; // 2^32 255}; 256 257// n samples at time t 258const timeToRands = (t, n, seed = 0) => { 259 const T = _tToT(t); 260 if (n === 1) { 261 return randAt(T, 0, seed); 262 } 263 const out = new Array(n); 264 for (let i = 0; i < n; i++) out[i] = randAt(T, i, seed); 265 return out; 266}; 267 268// Old random signals. Currently the default, but can also be chosen via 269// `useRNG('legacy')` 270 271// stretch 300 cycles over the range of [0,2**29 == 536870912) then apply the xorshift algorithm 272const __xorwise = (x) => { 273 const a = (x << 13) ^ x; 274 const b = (a >> 17) ^ a; 275 return (b << 5) ^ b; 276}; 277const __frac = (x) => x - Math.trunc(x); 278const __timeToIntSeed = (x) => __xorwise(Math.trunc(__frac(x / 300) * 536870912)); 279const __intSeedToRand = (x) => (x % 536870912) / 536870912; 280const __timeToRandsPrime = (seed, n) => { 281 if (n === 1) { 282 return Math.abs(__intSeedToRand(seed)); 283 } 284 const result = []; 285 for (let i = 0; i < n; i++) { 286 result.push(__intSeedToRand(seed)); 287 seed = __xorwise(seed); 288 } 289 return result; 290}; 291const __timeToRands = (t, n) => __timeToRandsPrime(__timeToIntSeed(t), n); 292 293// End old random 294 295let RNG_MODE = 'legacy'; 296export const getRandsAtTime = (t, n = 1, seed = 0) => { 297 return RNG_MODE === 'legacy' ? __timeToRands(t + seed, n) : timeToRands(t, n, seed); 298}; 299 300/** 301 * Sets which random number generator to use. Historically Strudel would 302 * use `useRNG('legacy')`, which remains the default. To use a new more statistically 303 * precise RNG, try `useRNG('precise')`. 304 * 305 * @name useRNG 306 * @tags generators, math 307 * @param {string} mod - Mode. One of 'legacy', 'precise' 308 * @example 309 * useRNG('legacy') 310 * // Repeats every 300 cycles 311 * $: n(irand(50)).seg(16).scale("C:minor").ribbon(88, 32) 312 * $: n(irand(50)).seg(16).scale("C:minor").ribbon(388, 32) 313 */ 314export const useRNG = (mode = 'legacy') => (RNG_MODE = mode); 315 316/** 317 * A discrete pattern of numbers from 0 to n-1 318 * @tags generators 319 * @example 320 * n(run(4)).scale("C4:pentatonic") 321 * // n("0 1 2 3").scale("C4:pentatonic") 322 */ 323export const run = (n) => saw.range(0, n).round().segment(n); 324 325/** 326 * Creates a binary pattern from a number. 327 * 328 * @name binary 329 * @tags generators 330 * @param {number} n - input number to convert to binary 331 * @example 332 * "hh".s().struct(binary(5)) 333 * // "hh".s().struct("1 0 1") 334 */ 335export const binary = (n) => { 336 const nBits = reify(n).log2().floor().add(1); 337 return binaryN(n, nBits); 338}; 339 340/** 341 * Creates a binary pattern from a number, padded to n bits long. 342 * 343 * @name binaryN 344 * @tags generators 345 * @param {number} n - input number to convert to binary 346 * @param {number} nBits - pattern length, defaults to 16 347 * @example 348 * "hh".s().struct(binaryN(55532, 16)) 349 * // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0") 350 */ 351export const binaryN = (n, nBits = 16) => { 352 nBits = reify(nBits); 353 // Shift and mask, putting msb on the right-side 354 const bitPos = run(nBits).mul(-1).add(nBits.sub(1)); 355 return reify(n).segment(nBits).brshift(bitPos).band(pure(1)); 356}; 357 358/** 359 * Creates a binary list pattern from a number. 360 * 361 * @name binaryL 362 * @tags generators 363 * @param {number} n - input number to convert to binary 364 * s("saw").seg(8) 365 * .partials(binaryL(irand(4096).add(1))) 366 */ 367export const binaryL = (n) => { 368 const nBits = reify(n).log2().floor().add(1); 369 return binaryNL(n, nBits); 370}; 371 372/** 373 * Creates a binary list pattern from a number, padded to n bits long. 374 * 375 * @name binaryNL 376 * @tags generators 377 * @param {number} n - input number to convert to binary 378 * @param {number} nBits - pattern length, defaults to 16 379 */ 380export const binaryNL = (n, nBits = 16) => { 381 return reify(n) 382 .withValue((v) => (bits) => { 383 const bList = []; 384 for (let i = bits - 1; i >= 0; i--) { 385 bList.push((v >> i) & 1); 386 } 387 return bList; 388 }) 389 .appLeft(reify(nBits)); 390}; 391 392/** 393 * Creates a list of random numbers of the given length 394 * 395 * @name randL 396 * @tags generators 397 * @param {number} n Number of random numbers to sample 398 * @example 399 * s("saw").seg(16).n(irand(12)).scale("F1:minor") 400 * .partials(randL(8)) 401 */ 402export const randL = (n) => { 403 return signal((t) => (nVal) => getRandsAtTime(t, nVal).map(Math.abs)).appLeft(reify(n)); 404}; 405 406export const randrun = (n) => { 407 return signal((t, controls) => { 408 // Without adding 0.5, the first cycle is always 0,1,2,3,... 409 let rands = getRandsAtTime(t.floor().add(0.5), n, controls.randSeed); 410 // Support n = 1 411 if (!Array.isArray(rands)) rands = [rands]; 412 const nums = rands 413 .map((n, i) => [n, i]) 414 .sort((a, b) => (a[0] > b[0]) - (a[0] < b[0])) 415 .map((x) => x[1]); 416 const i = _mod(t.cyclePos().mul(n).floor(), n); 417 return nums[i]; 418 })._segment(n); 419}; 420 421const _rearrangeWith = (ipat, n, pat) => { 422 const pats = [...Array(n).keys()].map((i) => pat.zoom(Fraction(i).div(n), Fraction(i + 1).div(n))); 423 return ipat.fmap((i) => pats[i].repeatCycles(n)._fast(n)).innerJoin(); 424}; 425 426/** 427 * Slices a pattern into the given number of parts, then plays those parts in random order. 428 * Each part will be played exactly once per cycle. 429 * @name shuffle 430 * @tags temporal 431 * @example 432 * note("c d e f").sound("piano").shuffle(4) 433 * @example 434 * seq("c d e f".shuffle(4), "g").note().sound("piano") 435 */ 436export const shuffle = register('shuffle', (n, pat) => { 437 return _rearrangeWith(randrun(n), n, pat); 438}); 439 440/** 441 * Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`, 442 * but parts might be played more than once, or not at all, per cycle. 443 * @name scramble 444 * @tags temporal 445 * @example 446 * note("c d e f").sound("piano").scramble(4) 447 * @example 448 * seq("c d e f".scramble(4), "g").note().sound("piano") 449 */ 450export const scramble = register('scramble', (n, pat) => { 451 return _rearrangeWith(_irand(n)._segment(n), n, pat); 452}); 453 454/** 455 * Modify a pattern by applying a function to the `randomSeed` control if present 456 * 457 * @tags math 458 * @param {Function} func Function from seed (or undefined) to seed (or undefined) 459 * @param {Pattern} pat Pattern to update 460 * @returns Pattern 461 */ 462export const withSeed = (func, pat) => { 463 return new Pattern((state) => { 464 let { randSeed, ...controls } = state.controls; 465 randSeed = func(randSeed); 466 return pat.query(state.setControls({ ...controls, randSeed })); 467 }, pat._steps); 468}; 469 470/** 471 * Change the seed for random signals. Normally, random signals depend on time, 472 * so two patterns at the same time will have the same random values. Specifying 473 * a new seed changes the signal output by `rand`. This also affects other functions 474 * that use randomness, like `shuffle` and `sometimes`. 475 * 476 * @name seed 477 * @tags math 478 * @param {number} n A new seed. Can be any number. 479 * @example 480 * $: s("hh*4").degrade(); 481 * $: s("bd*4").degrade().seed(1); // Will degrade different events from the hi-hat 482 */ 483export const seed = register('seed', (n, pat) => { 484 return withSeed(() => n, pat); 485}); 486 487/** 488 * A continuous pattern of random numbers, between 0 and 1. 489 * 490 * @name rand 491 * @tags generators 492 * @example 493 * // randomly change the cutoff 494 * s("bd*4,hh*8").cutoff(rand.range(500,8000)) 495 * 496 */ 497export const rand = signal((t, controls) => getRandsAtTime(t, 1, controls.randSeed)); 498/** 499 * A continuous pattern of random numbers, between -1 and 1 500 * @tags generators 501 */ 502export const rand2 = rand.toBipolar(); 503 504export const _brandBy = (p) => rand.fmap((x) => x < p); 505 506/** 507 * A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1 508 * 509 * @name brandBy 510 * @tags generators 511 * @param {number} probability - a number between 0 and 1 512 * @example 513 * s("hh*10").pan(brandBy(0.2)) 514 */ 515export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin(); 516 517/** 518 * A continuous pattern of 0 or 1 (binary random) 519 * 520 * @name brand 521 * @tags generators 522 * @example 523 * s("hh*10").pan(brand) 524 */ 525export const brand = _brandBy(0.5); 526 527export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); 528 529/** 530 * A continuous pattern of random integers, between 0 and n-1. 531 * 532 * @name irand 533 * @tags generators 534 * @param {number} n max value (exclusive) 535 * @example 536 * // randomly select scale notes from 0 - 7 (= C to C) 537 * n(irand(8)).struct("x x*2 x x*3").scale("C:minor") 538 * 539 */ 540export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); 541 542export const __chooseWith = (pat, xs) => { 543 xs = xs.map(reify); 544 if (xs.length == 0) { 545 return silence; 546 } 547 548 return pat.range(0, xs.length).fmap((i) => { 549 const key = Math.min(Math.max(Math.floor(i), 0), xs.length - 1); 550 return xs[key]; 551 }); 552}; 553/** 554 * Choose from the list of values (or patterns of values) using the given 555 * pattern of numbers, which should be in the range of 0..1 556 * @tags temporal 557 * @param {Pattern} pat 558 * @param {*} xs 559 * @returns {Pattern} 560 * @example 561 * note("c2 g2!2 d2 f1").s(chooseWith(sine.fast(2), ["sawtooth", "triangle", "bd:6"])) 562 */ 563export const chooseWith = (pat, xs) => { 564 return __chooseWith(pat, xs).outerJoin(); 565}; 566 567/** 568 * As with {chooseWith}, but the structure comes from the chosen values, rather 569 * than the pattern you're using to choose with. 570 * @tags temporal 571 * @param {Pattern} pat 572 * @param {*} xs 573 * @returns {Pattern} 574 */ 575export const chooseInWith = (pat, xs) => { 576 return __chooseWith(pat, xs).innerJoin(); 577}; 578 579/** 580 * Chooses randomly from the given list of elements. 581 * @tags temporal 582 * @param {...any} xs values / patterns to choose from. 583 * @returns {Pattern} - a continuous pattern. 584 * @example 585 * note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6")) 586 */ 587export const choose = (...xs) => chooseWith(rand, xs); 588 589// todo: doc 590export const chooseIn = (...xs) => chooseInWith(rand, xs); 591export const chooseOut = choose; 592 593/** 594 * Chooses from the given list of values (or patterns of values), according 595 * to the pattern that the method is called on. The pattern should be in 596 * the range 0 .. 1. 597 * @tags temporal 598 * @param {...any} xs 599 * @returns {Pattern} 600 */ 601Pattern.prototype.choose = function (...xs) { 602 return chooseWith(this, xs); 603}; 604 605/** 606 * As with choose, but the pattern that this method is called on should be 607 * in the range -1 .. 1 608 * @tags temporal 609 * @param {...any} xs 610 * @returns {Pattern} 611 */ 612Pattern.prototype.choose2 = function (...xs) { 613 return chooseWith(this.fromBipolar(), xs); 614}; 615 616/** 617 * Picks one of the elements at random each cycle. 618 * @tags temporal 619 * @synonyms randcat 620 * @returns {Pattern} 621 * @example 622 * chooseCycles("bd", "hh", "sd").s().fast(8) 623 * @example 624 * s("bd | hh | sd").fast(8) 625 */ 626export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs); 627 628export const randcat = chooseCycles; 629 630const _wchooseWith = function (pat, ...pairs) { 631 // A list of patterns of values 632 const values = pairs.map((pair) => reify(pair[0])); 633 634 // A list of weight patterns 635 const weights = []; 636 637 let total = pure(0); 638 for (const pair of pairs) { 639 // 'add' accepts either values or patterns of values here, so no need 640 // to explicitly reify 641 total = total.add(pair[1]); 642 // accumulate our list of weight patterns 643 weights.push(total); 644 } 645 // a pattern of lists of weights 646 const weightspat = sequenceP(weights); 647 648 // Takes a number from 0-1, returns a pattern of patterns of values 649 const match = function (r) { 650 const findpat = total.mul(r); 651 return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat); 652 }; 653 // This returns a pattern of patterns.. The innerJoin is in wchooseCycles 654 return pat.bind(match); 655}; 656 657const wchooseWith = (...args) => _wchooseWith(...args).outerJoin(); 658 659/** 660 * Chooses randomly from the given list of elements by giving a probability to each element 661 * @tags temporal 662 * @param {...any} pairs arrays of value and weight 663 * @returns {Pattern} - a continuous pattern. 664 * @example 665 * note("c2 g2!2 d2 f1").s(wchoose(["sine",10], ["triangle",1], ["bd:6",1])) 666 */ 667export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); 668 669/** 670 * Picks one of the elements at random each cycle by giving a probability to each element 671 * @tags temporal 672 * @synonyms wrandcat 673 * @returns {Pattern} 674 * @example 675 * wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8) 676 * @example 677 * wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note() 678 * @example 679 * // The probability can itself be a pattern 680 * wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s() 681 */ 682export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin(); 683 684export const wrandcat = wchooseCycles; 685 686function _perlin(t, seed = 0) { 687 let ta = Math.floor(t); 688 let tb = ta + 1; 689 const smootherStep = (x) => 6.0 * x ** 5 - 15.0 * x ** 4 + 10.0 * x ** 3; 690 const interp = (x) => (a) => (b) => a + smootherStep(x) * (b - a); 691 const ra = getRandsAtTime(ta, 1, seed); 692 const rb = getRandsAtTime(tb, 1, seed); 693 const v = interp(t - ta)(ra)(rb); 694 return v; 695} 696 697function _berlin(t, seed = 0) { 698 const prevRidgeStartIndex = Math.floor(t); 699 const nextRidgeStartIndex = prevRidgeStartIndex + 1; 700 701 const prevRidgeBottomPoint = getRandsAtTime(prevRidgeStartIndex, 1, seed); 702 const height = getRandsAtTime(nextRidgeStartIndex, 1, seed); 703 const nextRidgeTopPoint = prevRidgeBottomPoint + height; 704 705 const currentPercent = (t - prevRidgeStartIndex) / (nextRidgeStartIndex - prevRidgeStartIndex); 706 const interp = (a, b, t) => { 707 return a + t * (b - a); 708 }; 709 return interp(prevRidgeBottomPoint, nextRidgeTopPoint, currentPercent) / 2; 710} 711 712/** 713 * Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1. 714 * 715 * @tags generators 716 * @name perlin 717 * @example 718 * // randomly change the cutoff 719 * s("bd*4,hh*8").cutoff(perlin.range(500,8000)) 720 * 721 */ 722export const perlin = signal((t, controls) => _perlin(t, controls.randSeed)); 723 724/** 725 * Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful, 726 * like perlin noise but with sawtooth waves), in the range 0..1. 727 * 728 * @tags generators 729 * @name berlin 730 * @example 731 * // ascending arpeggios 732 * n("0!16".add(berlin.fast(4).mul(14))).scale("d:minor") 733 * 734 */ 735export const berlin = signal((t, controls) => _berlin(t, controls.randSeed)); 736 737export const degradeByWith = register( 738 'degradeByWith', 739 (withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)), 740 true, 741 true, 742); 743 744/** 745 * Randomly removes events from the pattern by a given amount. 746 * 0 = 0% chance of removal 747 * 1 = 100% chance of removal 748 * 749 * @tags temporal 750 * @name degradeBy 751 * @memberof Pattern 752 * @param {number} amount - a number between 0 and 1 753 * @returns Pattern 754 * @example 755 * s("hh*8").degradeBy(0.2) 756 * @example 757 * s("[hh?0.2]*8") 758 * @example 759 * //beat generator 760 * s("bd").segment(16).degradeBy(.5).ribbon(16,1) 761 */ 762export const degradeBy = register( 763 'degradeBy', 764 function (x, pat) { 765 return pat._degradeByWith(rand, x); 766 }, 767 true, 768 true, 769); 770 771/** 772 * 773 * Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)` 774 * 775 * @tags temporal 776 * @name degrade 777 * @memberof Pattern 778 * @returns Pattern 779 * @example 780 * s("hh*8").degrade() 781 * @example 782 * s("[hh?]*8") 783 */ 784export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, true); 785 786/** 787 * Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount. 788 * 0 = 100% chance of removal 789 * 1 = 0% chance of removal 790 * Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example). 791 * 792 * @tags temporal 793 * @name undegradeBy 794 * @memberof Pattern 795 * @param {number} amount - a number between 0 and 1 796 * @returns Pattern 797 * @example 798 * s("hh*8").undegradeBy(0.2) 799 * @example 800 * s("hh*10").layer( 801 * x => x.degradeBy(0.2).pan(0), 802 * x => x.undegradeBy(0.8).pan(1) 803 * ) 804 */ 805export const undegradeBy = register( 806 'undegradeBy', 807 function (x, pat) { 808 return pat._degradeByWith( 809 rand.fmap((r) => 1 - r), 810 x, 811 ); 812 }, 813 true, 814 true, 815); 816 817/** 818 * Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)` 819 * Events that would be removed by degrade are let through by undegrade and vice versa (see second example). 820 * 821 * @tags temporal 822 * @name undegrade 823 * @memberof Pattern 824 * @returns Pattern 825 * @example 826 * s("hh*8").undegrade() 827 * @example 828 * s("hh*10").layer( 829 * x => x.degrade().pan(0), 830 * x => x.undegrade().pan(1) 831 * ) 832 */ 833export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), true, true); 834 835/** 836 * 837 * Randomly applies the given function by the given probability. 838 * Similar to `someCyclesBy` 839 * 840 * @tags temporal 841 * @name sometimesBy 842 * @memberof Pattern 843 * @param {number | Pattern} probability - a number between 0 and 1 844 * @param {function} function - the transformation to apply 845 * @returns Pattern 846 * @example 847 * s("hh*8").sometimesBy(.4, x=>x.speed("0.5")) 848 */ 849 850export const sometimesBy = register('sometimesBy', function (patx, func, pat) { 851 return reify(patx) 852 .fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x)))) 853 .innerJoin(); 854}); 855 856/** 857 * 858 * Applies the given function with a 50% chance 859 * 860 * @tags temporal 861 * @name sometimes 862 * @memberof Pattern 863 * @param {function} function - the transformation to apply 864 * @returns Pattern 865 * @example 866 * s("hh*8").sometimes(x=>x.speed("0.5")) 867 */ 868export const sometimes = register('sometimes', function (func, pat) { 869 return pat._sometimesBy(0.5, func); 870}); 871 872/** 873 * 874 * Randomly applies the given function by the given probability on a cycle by cycle basis. 875 * Similar to `sometimesBy` 876 * 877 * @name someCyclesBy 878 * @memberof Pattern 879 * @param {number | Pattern} probability - a number between 0 and 1 880 * @param {function} function - the transformation to apply 881 * @returns Pattern 882 * @tags temporal 883 * @example 884 * s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5")) 885 */ 886 887export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) { 888 return reify(patx) 889 .fmap((x) => 890 stack( 891 pat._degradeByWith(rand._segment(1), x), 892 func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)), 893 ), 894 ) 895 .innerJoin(); 896}); 897 898/** 899 * 900 * Shorthand for `.someCyclesBy(0.5, fn)` 901 * 902 * @name someCycles 903 * @memberof Pattern 904 * @returns Pattern 905 * @tags temporal 906 * @example 907 * s("bd,hh*8").someCycles(x=>x.speed("0.5")) 908 */ 909export const someCycles = register('someCycles', function (func, pat) { 910 return pat._someCyclesBy(0.5, func); 911}); 912 913/** 914 * 915 * Shorthand for `.sometimesBy(0.75, fn)` 916 * 917 * @name often 918 * @memberof Pattern 919 * @returns Pattern 920 * @tags temporal 921 * @example 922 * s("hh*8").often(x=>x.speed("0.5")) 923 */ 924export const often = register('often', function (func, pat) { 925 return pat.sometimesBy(0.75, func); 926}); 927 928/** 929 * 930 * Shorthand for `.sometimesBy(0.25, fn)` 931 * 932 * @name rarely 933 * @memberof Pattern 934 * @returns Pattern 935 * @tags temporal 936 * @example 937 * s("hh*8").rarely(x=>x.speed("0.5")) 938 */ 939export const rarely = register('rarely', function (func, pat) { 940 return pat.sometimesBy(0.25, func); 941}); 942 943/** 944 * 945 * Shorthand for `.sometimesBy(0.1, fn)` 946 * 947 * @tags temporal 948 * @name almostNever 949 * @memberof Pattern 950 * @returns Pattern 951 * @example 952 * s("hh*8").almostNever(x=>x.speed("0.5")) 953 */ 954export const almostNever = register('almostNever', function (func, pat) { 955 return pat.sometimesBy(0.1, func); 956}); 957 958/** 959 * 960 * Shorthand for `.sometimesBy(0.9, fn)` 961 * 962 * @tags temporal 963 * @name almostAlways 964 * @memberof Pattern 965 * @returns Pattern 966 * @example 967 * s("hh*8").almostAlways(x=>x.speed("0.5")) 968 */ 969export const almostAlways = register('almostAlways', function (func, pat) { 970 return pat.sometimesBy(0.9, func); 971}); 972 973/** 974 * 975 * Shorthand for `.sometimesBy(0, fn)` (never calls fn) 976 * 977 * @tags temporal 978 * @name never 979 * @memberof Pattern 980 * @returns Pattern 981 * @example 982 * s("hh*8").never(x=>x.speed("0.5")) 983 */ 984export const never = register('never', function (_, pat) { 985 return pat; 986}); 987 988/** 989 * 990 * Shorthand for `.sometimesBy(1, fn)` (always calls fn) 991 * 992 * @tags temporal 993 * @name always 994 * @memberof Pattern 995 * @returns Pattern 996 * @example 997 * s("hh*8").always(x=>x.speed("0.5")) 998 */ 999export const always = register('always', function (func, pat) { 1000 return func(pat); 1001}); 1002 1003//keyname: string | Array<string> 1004//keyname reference: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values 1005export function _keyDown(keyname) { 1006 if (Array.isArray(keyname) === false) { 1007 keyname = [keyname]; 1008 } 1009 const keyState = getCurrentKeyboardState(); 1010 return keyname.every((x) => { 1011 const keyName = keyAlias.get(x) ?? x; 1012 return keyState[keyName]; 1013 }); 1014} 1015 1016/** 1017 * 1018 * Do something on a keypress, or array of keypresses 1019 * [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) 1020 * 1021 * @tags external_io 1022 * @name whenKey 1023 * @memberof Pattern 1024 * @returns Pattern 1025 * @example 1026 * s("bd(5,8)").whenKey("Control:j", x => x.segment(16).color("red")).whenKey("Control:i", x => x.fast(2).color("blue")) 1027 */ 1028 1029export const whenKey = register('whenKey', function (input, func, pat) { 1030 return pat.when(_keyDown(input), func); 1031}); 1032 1033/** 1034 * 1035 * returns true when a key or array of keys is held 1036 * [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) 1037 * 1038 * @tags external_io 1039 * @name keyDown 1040 * @memberof Pattern 1041 * @returns Pattern 1042 * @example 1043 * keyDown("Control:j").pick([s("bd(5,8)"), s("cp(3,8)")]) 1044 */ 1045 1046export const keyDown = register('keyDown', function (pat) { 1047 return pat.fmap(_keyDown); 1048}); 1049 1050/** 1051 * A pattern measuring the duration of events, 1052 * in cycles per event. `cyclesPer` doesn't have structure itself, but takes structure, and therefore 1053 * event durations, from the pattern that it is combined with. 1054 * For example `cyclesPer.struct("1 1 [1 1] 1")` would give the same as `"0.25 0.25 [0.125 0.125] 0.25"`. 1055 * See also its reciprocal, `per`, also known as `perCycle`. 1056 * 1057 * @tags temporal 1058 * @example 1059 * // Shorter events are lower in pitch 1060 * sound("saw saw [saw saw] saw") 1061 * .note(cyclesPer.range(50, 100)) 1062 * @example 1063 * sound("bd sd [bd bd] sd*4 [- sd] [bd [bd bd]]") 1064 * .note(cyclesPer.add(20)) 1065 */ 1066export const cyclesPer = new Pattern(function (state) { 1067 return [new Hap(undefined, state.span, state.span.duration)]; 1068}); 1069 1070/** 1071 * A pattern measuring the 'shortness' of events, or in other words, the duration of pattern events, 1072 * in events per cycle. `per` doesn't have structure itself, but takes structure, and therefore 1073 * event durations, from the pattern that it is combined with. 1074 * For example `per.struct("1 1 [1 1] 1")` would give the same as `"4 4 [8 8] 4"`. 1075 * See also its reciprocal, `cyclesPer`. 1076 * @tags temporal 1077 * @synonyms perCycle 1078 * @example 1079 * // Shorter events are more distorted 1080 * n("0 0*2 0 0*2 0 [0 0 0]@2").sound("bd") 1081 * .distort(per.div(2)) 1082 */ 1083export const per = new Pattern(function (state) { 1084 return [new Hap(undefined, state.span, Fraction(1).div(state.span.duration))]; 1085}); 1086 1087export const perCycle = per; 1088 1089/** 1090 * Like `per` but measures the shortness of events according to an exponential curve. In 1091 * particular, where the event duration halves, the 1092 * returned value increases by one. `perx.struct("1 1 [1 [1 1]] 1")` would therefore be 1093 * the same as `"3 3 [4 [5 5]] 3"`. 1094 * @tags temporal 1095 */ 1096export const perx = new Pattern(function (state) { 1097 const n = Fraction(1).div(state.span.duration); 1098 return [new Hap(undefined, state.span, Math.log(n) / Math.log(2) + 1)]; 1099});