pick.mjsannotatedpick.mjssource231 lines · 7.8 KB · raw

pick.mjs - methods that use one pattern to pick events from other patterns. Copyright (C) 2024 Strudel contributors - see https://codeberg.org/uzu/strudel/src/branch/main/packages/core/signal.mjs This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.

7import { Pattern, reify, silence, register } from './pattern.mjs';
9import { _mod, clamp, objectMap } from './util.mjs';
10
11const _pick = function (lookup, pat, modulo = true) {
12  const array = Array.isArray(lookup);
13  const len = Object.keys(lookup).length;
14
15  lookup = objectMap(lookup, reify);
16
17  if (len === 0) {
18    return silence;
19  }
20  return pat.fmap((i) => {
21    let key = i;
22    if (array) {
23      key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
24    }
25    return lookup[key];
26  });
27};
  • Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). Similar to inhabit, but maintains the structure of the original patterns. @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern} @example note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g" , "g c d"])) @example sound("<0 1 [2,0]>".pick(["bd sd", "cp cp", "hh hh"])) @example sound("<0!2 [0,1] 1>".pick(["bd(3,8)", "sd sd"])) @example s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
45export const pick = function (lookup, pat) {
46  // backward compatibility - the args used to be flipped
47  if (Array.isArray(pat)) {
48    [pat, lookup] = [lookup, pat];
49  }
50  return __pick(lookup, pat);
51};
53const __pick = register('pick', function (lookup, pat) {
54  return _pick(lookup, pat, false).innerJoin();
55});
  • The same as pick, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. For example, if you pick the fifth pattern of a list of three, you'll get the second one. @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern}
67export const pickmod = register('pickmod', function (lookup, pat) {
68  return _pick(lookup, pat, true).innerJoin();
69});
  • pickF lets you use a pattern of numbers to pick which function to apply to another pattern. @tags combiners, functional @param {Pattern} pat @param {Pattern} lookup a pattern of indices or names @param {function[] | object} lookup the array or lookup object of functions from which to pull @returns {Pattern} @example s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)]) @example note("<c2 d2>(3,8)").s("square") .pickF("<0 2> 1", [jux(rev), fast(2), x=>x.lpf(800)]) @example note("<c2 d2>(3,8)").s("square") .pickF("<jr l> f", { jr:jux(rev), f:fast(2), l:x=>x.lpf(800) })
86export const pickF = register('pickF', function (pickPattern, lookup, pat) {
87  return pat.apply(pick(lookup, pickPattern));
88});
  • The same as pickF, but if you pick a number greater than the size of the functions list, it wraps around, rather than sticking at the maximum value. @tags combiners @param {Pattern} pat @param {Pattern} lookup a pattern of indices or names @param {function[] | object} lookup the array or lookup object of functions from which to pull @returns {Pattern}
98export const pickmodF = register('pickmodF', function (pickPattern, lookup, pat) {
99  return pat.apply(pickmod(lookup, pickPattern));
100});
  • Similar to pick, but it applies an outerJoin instead of an innerJoin. @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern}
108export const pickOut = register('pickOut', function (lookup, pat) {
109  return _pick(lookup, pat, false).outerJoin();
110});
  • The same as pickOut, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern}
119export const pickmodOut = register('pickmodOut', function (lookup, pat) {
120  return _pick(lookup, pat, true).outerJoin();
121});
  • Similar to pick, but the choosen pattern is restarted when its index is triggered. @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern}
129export const pickRestart = register('pickRestart', function (lookup, pat) {
130  return _pick(lookup, pat, false).restartJoin();
131});
  • The same as pickRestart, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern} @example "<a@2 b@2 c@2 d@2>".pickRestart({ a: n("0 1 2 0"), b: n("2 3 4 ~"), c: n("[4 5] [4 3] 2 0"), d: n("0 -3 0 ~") }).scale("C:major").s("piano")
147export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
148  return _pick(lookup, pat, true).restartJoin();
149});
  • Similar to pick, but the choosen pattern is reset when its index is triggered. @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern}
157export const pickReset = register('pickReset', function (lookup, pat) {
158  return _pick(lookup, pat, false).resetJoin();
159});
  • The same as pickReset, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern}
168export const pickmodReset = register('pickmodReset', function (lookup, pat) {
169  return _pick(lookup, pat, true).resetJoin();
170});

Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). Similar to pick, but cycles are squeezed into the target ('inhabited') pattern. @name inhabit @tags combiners @synonyms pickSqueeze @param {Pattern} pat @param {*} xs @returns {Pattern} @example let a = s("bd(3,8)") let b = s("cp sd") "<a b [a,b]>".inhabit({ a, b }) @example s("a@2 [a b] a" .inhabit({a: "bd(3,8)", b: "sd sd"})) .slow(4)

189export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
190  return _pick(lookup, pat, false).squeezeJoin();
191});
  • The same as inhabit, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. For example, if you pick the fifth pattern of a list of three, you'll get the second one. @name inhabitmod @synonyms pickmodSqueeze @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern}
205export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
206  return _pick(lookup, pat, true).squeezeJoin();
207});

Pick from the list of values (or patterns of values) via the index using the given pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event @tags combiners @param {Pattern} pat @param {*} xs @returns {Pattern} @example note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g" , "g a c d"]))

220export const squeeze = (pat, xs) => {
221  xs = xs.map(reify);
222  if (xs.length == 0) {
223    return silence;
224  }
225  return pat
226    .fmap((i) => {
227      const key = _mod(Math.round(i), xs.length);
228      return xs[key];
229    })
230    .squeezeJoin();
231};