Skip to content

Commit

Permalink
mux operator, muxers.mjs with pick variants
Browse files Browse the repository at this point in the history
  • Loading branch information
eefano committed Feb 25, 2024
1 parent 74ef157 commit af062f9
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 178 deletions.
1 change: 1 addition & 0 deletions packages/core/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { Fraction, controls };
export * from './hap.mjs';
export * from './pattern.mjs';
export * from './signal.mjs';
export * from './muxers.mjs';
export * from './state.mjs';
export * from './timespan.mjs';
export * from './util.mjs';
Expand Down
162 changes: 162 additions & 0 deletions packages/core/muxers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
muxers.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/muxers.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/>.
*/

import { Pattern, register } from './pattern.mjs';


/** * 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.
* @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"}))
*/

export const pick = function (lookup, pat) {
// backward compatibility - the args used to be flipped
if (Array.isArray(pat)) {
[pat, lookup] = [lookup, pat];
}
return __pick(lookup, pat);
};

const __pick = register('pick', function (lookup, pat) {
return pat.mux.in(lookup);
});

/** * 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.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/

export const pickmod = register('pickmod', function (lookup, pat) {
return pat.muxm.in(lookup);
});

/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array 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)])
*/
export const pickF = register('pickF', function (lookup, funcs, pat) {
return pat.apply(pick(lookup, funcs));
});

/** * 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.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
*/
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
return pat.apply(pickmod(lookup, funcs));
});

/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickOuter = register('pickOuter', function (lookup, pat) {
return pat.mux.out(lookup);
});

/** * The same as `pickOuter`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodOuter = register('pickmodOuter', function (lookup, pat) {
return pat.muxm.out(lookup);
});

/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickRestart = register('pickRestart', function (lookup, pat) {
return pat.mux.trigzero(lookup);
});

/** * 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.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
return pat.muxm.trigzero(lookup);
});

/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickReset = register('pickReset', function (lookup, pat) {
return pat.mux.trig(lookup);
});

/** * 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.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
return pat.muxm.trig(lookup);
});

/**
/** * 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.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
b: s("cp sd")
})
* @example
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
*/
export const inhabit = register('inhabit', function (lookup, pat) {
return pat.mux.squeeze(lookup);
});

/** * 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.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/

export const inhabitmod = register('inhabitmod', function (lookup, pat) {
return pat.muxm.squeeze(lookup);
});

51 changes: 44 additions & 7 deletions packages/core/pattern.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Hap from './hap.mjs';
import State from './state.mjs';
import { unionWithObj } from './value.mjs';

import { compose, removeUndefineds, flatten, id, listRange, curry, _mod, numeralArgs, parseNumeral } from './util.mjs';
import { compose, removeUndefineds, flatten, id, listRange, curry, _mod, numeralArgs, parseNumeral, objectMap } from './util.mjs';
import drawLine from './drawLine.mjs';
import { logger } from './logger.mjs';

Expand Down Expand Up @@ -681,6 +681,22 @@ export class Pattern {
return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).trigzeroJoin();
}

_muxIn(other, func) {
return func(this,other).innerJoin();
}
_muxOut(other, func) {
return func(this,other).outerJoin();
}
_muxSqueeze(other, func) {
return func(this,other).squeezeJoin();
}
_muxTrig(other, func) {
return func(this,other).trigJoin();
}
_muxTrigzero(other, func) {
return func(this,other).trigzeroJoin();
}

//////////////////////////////////////////////////////////////////////
// End-user methods.
// Those beginning with an underscore (_) are 'patternified',
Expand Down Expand Up @@ -913,13 +929,34 @@ function _composeOp(a, b, func) {
return func(a, b);
}

function _muxer(lookup, pat, modulo = true) {

const array = Array.isArray(lookup);
const len = Object.keys(lookup).length;

lookup = objectMap(lookup, reify);

if (len === 0) {
return silence;
}
return pat.fmap((i) => {
let key = i;
if (array) {
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
}
return lookup[key];
});
};

// Make composers
(function () {
// pattern composers
const composers = {
set: [(a, b) => b],
keep: [(a) => a],
keepif: [(a, b) => (b ? a : undefined)],
mux: [(a, b) => _muxer(b,a,false),'mux'],
muxm: [(a, b) => _muxer(b,a,true),'mux'],

// numerical functions
/**
Expand Down Expand Up @@ -992,7 +1029,7 @@ function _composeOp(a, b, func) {
const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Trig', 'Trigzero'];

// generate methods to do what and how
for (const [what, [op, preprocess]] of Object.entries(composers)) {
for (const [what, [op, category]] of Object.entries(composers)) {
// make plain version, e.g. pat._add(value) adds that plain value
// to all the values in pat
Pattern.prototype['_' + what] = function (value) {
Expand All @@ -1013,17 +1050,15 @@ function _composeOp(a, b, func) {
for (const how of hows) {
wrapper[how.toLowerCase()] = function (...other) {
var howpat = pat;
other = sequence(other);
if (preprocess) {
howpat = preprocess(howpat);
other = preprocess(other);
}
if(!category==='mux') other = sequence(other);
var result;
// hack to remove undefs when doing 'keepif'
if (what === 'keepif') {
// avoid union, as we want to throw away the value of 'b' completely
result = howpat['_op' + how](other, (a) => (b) => op(a, b));
result = result.removeUndefineds();
} else if (category === 'mux') {
result = howpat['_mux' + how](other[0], op);
} else {
result = howpat['_op' + how](other, (a) => (b) => _composeOp(a, b, op));
}
Expand Down Expand Up @@ -1366,6 +1401,8 @@ export const superimpose = curry((a, b) => reify(b).superimpose(...a));
export const set = curry((a, b) => reify(b).set(a));
export const keep = curry((a, b) => reify(b).keep(a));
export const keepif = curry((a, b) => reify(b).keepif(a));
export const mux = curry((a, b) => reify(b).mux(a));
export const muxm = curry((a, b) => reify(b).muxm(a));
export const add = curry((a, b) => reify(b).add(a));
export const sub = curry((a, b) => reify(b).sub(a));
export const mul = curry((a, b) => reify(b).mul(a));
Expand Down
Loading

0 comments on commit af062f9

Please sign in to comment.