From a8db70744053264274fedc6663bad15d440432d3 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 18 Jan 2024 17:04:26 +0000 Subject: [PATCH] Revert "`pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function" (#920) --- packages/core/controls.mjs | 10 +-- packages/core/pattern.mjs | 36 ++++---- packages/core/signal.mjs | 103 +++++----------------- packages/core/test/pattern.test.mjs | 51 ----------- packages/core/util.mjs | 7 -- test/__snapshots__/examples.test.mjs.snap | 93 +------------------ 6 files changed, 50 insertions(+), 250 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 42212dd3..444b4ea1 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -97,7 +97,7 @@ const generic_params = [ */ ['postgain'], /** - * Like `gain`, but linear. + * Like {@link gain}, but linear. * * @name amp * @param {number | Pattern} amount gain. @@ -856,7 +856,7 @@ const generic_params = [ */ ['detune', 'det'], /** - * Set dryness of reverb. See `room` and `size` for more information about reverb. + * Set dryness of reverb. See {@link room} and {@link size} for more information about reverb. * * @name dry * @param {number | Pattern} dry 0 = wet, 1 = dry @@ -868,7 +868,7 @@ const generic_params = [ ['dry'], // TODO: does not seem to do anything /* - * Used when using `begin`/`end` or `chop`/`striate` and friends, to change the fade out time of the 'grain' envelope. + * Used when using {@link begin}/{@link end} or {@link chop}/{@link striate} and friends, to change the fade out time of the 'grain' envelope. * * @name fadeTime * @param {number | Pattern} time between 0 and 1 @@ -1191,7 +1191,7 @@ const generic_params = [ */ [['ir', 'i'], 'iresponse'], /** - * Sets the room size of the reverb, see `room`. + * Sets the room size of the reverb, see {@link room}. * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomsize @@ -1249,7 +1249,7 @@ const generic_params = [ */ ['speed'], /** - * Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. + * Used in conjunction with {@link speed}, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. * * @name unit * @param {number | string | Pattern} unit see description above diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 6a7b210b..9e804b23 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -26,7 +26,7 @@ export class Pattern { /** * Create a pattern. As an end user, you will most likely not create a Pattern directly. * - * @param {function} query - The function that maps a `State` to an array of `Hap`. + * @param {function} query - The function that maps a {@link State} to an array of {@link Hap}. * @noAutocomplete */ constructor(query) { @@ -39,7 +39,7 @@ export class Pattern { /** * Returns a new pattern, with the function applied to the value of - * each hap. It has the alias `fmap`. + * each hap. It has the alias {@link Pattern#fmap}. * @synonyms fmap * @param {Function} func to to apply to the value * @returns Pattern @@ -51,7 +51,7 @@ export class Pattern { } /** - * see `withValue` + * see {@link Pattern#withValue} * @noAutocomplete */ fmap(func) { @@ -115,7 +115,7 @@ export class Pattern { } /** - * As with `appBoth`, but the `whole` timespan is not the intersection, + * As with {@link Pattern#appBoth}, but the `whole` timespan is not the intersection, * but the timespan from the function of patterns that this method is called * on. In practice, this means that the pattern structure, including onsets, * are preserved from the pattern of functions (often referred to as the left @@ -148,7 +148,7 @@ export class Pattern { } /** - * As with `appLeft`, but `whole` timespans are instead taken from the + * As with {@link Pattern#appLeft}, but `whole` timespans are instead taken from the * pattern of values, i.e. structure is preserved from the right hand/outer * pattern. * @param {Pattern} pat_val @@ -387,7 +387,7 @@ export class Pattern { } /** - * As with `withQuerySpan`, but the function is applied to both the + * As with {@link Pattern#withQuerySpan}, but the function is applied to both the * begin and end time of the query timespan. * @param {Function} func the function to apply * @returns Pattern @@ -398,7 +398,7 @@ export class Pattern { } /** - * Similar to `withQuerySpan`, but the function is applied to the timespans + * Similar to {@link Pattern#withQuerySpan}, but the function is applied to the timespans * of all haps returned by pattern queries (both `part` timespans, and where * present, `whole` timespans). * @param {Function} func @@ -410,7 +410,7 @@ export class Pattern { } /** - * As with `withHapSpan`, but the function is applied to both the + * As with {@link Pattern#withHapSpan}, but the function is applied to both the * begin and end time of the hap timespans. * @param {Function} func the function to apply * @returns Pattern @@ -431,7 +431,7 @@ export class Pattern { } /** - * As with `withHaps`, but applies the function to every hap, rather than every list of haps. + * As with {@link Pattern#withHaps}, but applies the function to every hap, rather than every list of haps. * @param {Function} func * @returns Pattern * @noAutocomplete @@ -499,7 +499,7 @@ export class Pattern { } /** - * As with `filterHaps`, but the function is applied to values + * As with {@link Pattern#filterHaps}, but the function is applied to values * inside haps. * @param {Function} value_test * @returns Pattern @@ -621,7 +621,7 @@ export class Pattern { } /** - * More human-readable version of the `firstCycleValues` accessor. + * More human-readable version of the {@link Pattern#firstCycleValues} accessor. * @noAutocomplete */ get showFirstCycle() { @@ -691,7 +691,7 @@ export class Pattern { // Methods without corresponding toplevel functions /** - * Layers the result of the given function(s). Like `superimpose`, but without the original pattern: + * Layers the result of the given function(s). Like {@link Pattern.superimpose}, but without the original pattern: * @name layer * @memberof Pattern * @synonyms apply @@ -1189,7 +1189,7 @@ export function stack(...pats) { /** Concatenation: combines a list of patterns, switching between them successively, one per cycle: * - * synonyms: `cat` + * synonyms: {@link cat} * * @return {Pattern} * @example @@ -1244,7 +1244,7 @@ export function cat(...pats) { return slowcat(...pats); } -/** Like `seq`, but each step has a length, relative to the whole. +/** Like {@link Pattern.seq}, but each step has a length, relative to the whole. * @return {Pattern} * @example * timeCat([3,"e3"],[1, "g3"]).note() // "e3@3 g3".note() @@ -1279,7 +1279,7 @@ export function fastcat(...pats) { return slowcat(...pats)._fast(pats.length); } -/** See `fastcat` */ +/** See {@link fastcat} */ export function sequence(...pats) { return fastcat(...pats); } @@ -1636,7 +1636,7 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f }); /** - * Similar to `compress`, but doesn't leave gaps, and the 'focus' can be bigger than a cycle + * Similar to compress, but doesn't leave gaps, and the 'focus' can be bigger than a cycle * @example * s("bd hh sd hh").focus(1/4, 3/4) */ @@ -1753,7 +1753,7 @@ export const lastOf = register('lastOf', function (n, func, pat) { */ /** - * An alias for `firstOf` + * An alias for {@link firstOf} * @name every * @memberof Pattern * @param {number} n how many cycles @@ -2365,7 +2365,7 @@ export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (facto // It is still here to work in cases where repl.mjs is not used /** * Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. - * Similar to `loopAt`. + * Similar to loopAt. * @name fit * @example * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 9e8db9a0..1446beeb 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th import { Hap } from './hap.mjs'; import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs'; import Fraction from './fraction.mjs'; -import { id, _mod, clamp, objectMap } from './util.mjs'; +import { id, _mod, clamp } from './util.mjs'; export function steady(value) { // A continuous value @@ -156,88 +156,31 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); */ export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); -const _pick = function (lookup, pat, modulo = true) { - const array = Array.isArray(lookup); - const len = Object.keys(lookup).length; +/** + * pick from the list of values (or patterns of values) via the index using the given + * pattern of integers + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + * @example + * note(pick("<0 1 [2!2] 3>", ["g a", "e f", "f g f g" , "g a c d"])) + */ - lookup = objectMap(lookup, reify); - - if (len === 0) { +export const pick = (pat, xs) => { + xs = xs.map(reify); + if (xs.length == 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]; - }); + return pat + .fmap((i) => { + const key = clamp(Math.round(i), 0, xs.length - 1); + return xs[key]; + }) + .innerJoin(); }; -/** * 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("".pick({a: "bd(3,8)", b: "sd sd"})) - */ - -export const pick = register('pick', function (lookup, pat) { - return _pick(lookup, pat, false).innerJoin(); -}); - -/** * 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 _pick(lookup, pat, true).innerJoin(); -}); - /** -/** * 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 - * "".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 _pick(lookup, pat, true).squeezeJoin(); -}); - -/** * 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('inhabit', function (lookup, pat) { - return _pick(lookup, pat, false).squeezeJoin(); -}); - -/** - * Pick from the list of values (or patterns of values) via the index using the given + * 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 * @param {Pattern} pat * @param {*} xs @@ -413,7 +356,7 @@ export const degradeBy = register('degradeBy', function (x, pat) { export const degrade = register('degrade', (pat) => pat._degradeBy(0.5)); /** - * Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount. + * Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount. * 0 = 100% chance of removal * 1 = 0% chance of removal * Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example). @@ -437,7 +380,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5)); /** * * Randomly applies the given function by the given probability. - * Similar to `someCyclesBy` + * Similar to {@link Pattern#someCyclesBy} * * @name sometimesBy * @memberof Pattern @@ -472,7 +415,7 @@ export const sometimes = register('sometimes', function (func, pat) { /** * * Randomly applies the given function by the given probability on a cycle by cycle basis. - * Similar to `sometimesBy` + * Similar to {@link Pattern#sometimesBy} * * @name someCyclesBy * @memberof Pattern diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index d9da114d..928bfcef 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -1057,55 +1057,4 @@ describe('Pattern', () => { expect(slowcat(0, 1).repeatCycles(2).fast(6).firstCycleValues).toStrictEqual([0, 0, 1, 1, 0, 0]); }); }); - describe('inhabit', () => { - it('Can pattern named patterns', () => { - expect( - sameFirst( - sequence('a', 'b', stack('a', 'b')).inhabit({ a: sequence(1, 2), b: sequence(10, 20, 30) }), - sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])), - ), - ); - }); - it('Can pattern indexed patterns', () => { - expect( - sameFirst( - sequence('0', '1', stack('0', '1')).inhabit([sequence(1, 2), sequence(10, 20, 30)]), - sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])), - ), - ); - }); - }); - describe('pick', () => { - it('Can pattern named patterns', () => { - expect( - sameFirst( - sequence('a', 'b', 'a', stack('a', 'b')).pick({ a: sequence(1, 2, 3, 4), b: sequence(10, 20, 30, 40) }), - sequence(1, 20, 3, stack(4, 40)), - ), - ); - }); - it('Can pattern indexed patterns', () => { - expect( - sameFirst( - sequence(0, 1, 0, stack(0, 1)).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), - sequence(1, 20, 3, stack(4, 40)), - ), - ); - }); - it('Clamps indexes', () => { - expect( - sameFirst(sequence(0, 1, 2, 3).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), sequence(1, 20, 30, 40)), - ); - }); - }); - describe('pickmod', () => { - it('Wraps indexes', () => { - expect( - sameFirst( - sequence(0, 1, 2, 3).pickmod([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), - sequence(1, 20, 3, 40), - ), - ); - }); - }); }); diff --git a/packages/core/util.mjs b/packages/core/util.mjs index ca3cfc12..ef55de95 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -316,10 +316,3 @@ export function hash2code(hash) { return base64ToUnicode(decodeURIComponent(hash)); //return atob(decodeURIComponent(codeParam || '')); } - -export function objectMap(obj, fn) { - if (Array.isArray(obj)) { - return obj.map(fn); - } - return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)])); -} diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index b3114ed5..002ccb5f 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -2408,40 +2408,6 @@ exports[`runs examples > example "hush" example index 0 1`] = ` ] `; -exports[`runs examples > example "inhabit" example index 0 1`] = ` -[ - "[ 0/1 → 1/8 | s:bd ]", - "[ 3/8 → 1/2 | s:bd ]", - "[ 3/4 → 7/8 | s:bd ]", - "[ 1/1 → 3/2 | s:cp ]", - "[ 3/2 → 2/1 | s:sd ]", - "[ 2/1 → 17/8 | s:bd ]", - "[ 2/1 → 5/2 | s:cp ]", - "[ 19/8 → 5/2 | s:bd ]", - "[ 5/2 → 3/1 | s:sd ]", - "[ 11/4 → 23/8 | s:bd ]", - "[ 3/1 → 25/8 | s:bd ]", - "[ 27/8 → 7/2 | s:bd ]", - "[ 15/4 → 31/8 | s:bd ]", -] -`; - -exports[`runs examples > example "inhabit" example index 1 1`] = ` -[ - "[ 0/1 → 1/4 | s:bd ]", - "[ 3/4 → 1/1 | s:bd ]", - "[ 3/2 → 7/4 | s:bd ]", - "[ 2/1 → 33/16 | s:bd ]", - "[ 35/16 → 9/4 | s:bd ]", - "[ 19/8 → 39/16 | s:bd ]", - "[ 5/2 → 11/4 | s:sd ]", - "[ 11/4 → 3/1 | s:sd ]", - "[ 3/1 → 25/8 | s:bd ]", - "[ 27/8 → 7/2 | s:bd ]", - "[ 15/4 → 31/8 | s:bd ]", -] -`; - exports[`runs examples > example "inside" example index 0 1`] = ` [ "[ 0/1 → 1/8 | note:D3 ]", @@ -3635,61 +3601,10 @@ exports[`runs examples > example "pick" example index 0 1`] = ` "[ 9/4 → 5/2 | note:g ]", "[ 5/2 → 11/4 | note:f ]", "[ 11/4 → 3/1 | note:g ]", - "[ 3/1 → 13/4 | note:f ]", - "[ 13/4 → 7/2 | note:g ]", - "[ 7/2 → 15/4 | note:f ]", - "[ 15/4 → 4/1 | note:g ]", -] -`; - -exports[`runs examples > example "pick" example index 1 1`] = ` -[ - "[ 0/1 → 1/2 | s:bd ]", - "[ 1/2 → 1/1 | s:sd ]", - "[ 1/1 → 3/2 | s:cp ]", - "[ 3/2 → 2/1 | s:cp ]", - "[ 2/1 → 5/2 | s:hh ]", - "[ 2/1 → 5/2 | s:bd ]", - "[ 5/2 → 3/1 | s:hh ]", - "[ 5/2 → 3/1 | s:sd ]", - "[ 3/1 → 7/2 | s:bd ]", - "[ 7/2 → 4/1 | s:sd ]", -] -`; - -exports[`runs examples > example "pick" example index 2 1`] = ` -[ - "[ 0/1 → 1/8 | s:bd ]", - "[ 3/8 → 1/2 | s:bd ]", - "[ 3/4 → 7/8 | s:bd ]", - "[ 1/1 → 9/8 | s:bd ]", - "[ 11/8 → 3/2 | s:bd ]", - "[ 7/4 → 15/8 | s:bd ]", - "[ 2/1 → 17/8 | s:bd ]", - "[ 2/1 → 5/2 | s:sd ]", - "[ 19/8 → 5/2 | s:bd ]", - "[ 5/2 → 3/1 | s:sd ]", - "[ 11/4 → 23/8 | s:bd ]", - "[ 3/1 → 7/2 | s:sd ]", - "[ 7/2 → 4/1 | s:sd ]", -] -`; - -exports[`runs examples > example "pick" example index 3 1`] = ` -[ - "[ 0/1 → 1/8 | s:bd ]", - "[ 3/8 → 1/2 | s:bd ]", - "[ 3/4 → 7/8 | s:bd ]", - "[ 1/1 → 9/8 | s:bd ]", - "[ 11/8 → 3/2 | s:bd ]", - "[ 7/4 → 15/8 | s:bd ]", - "[ 2/1 → 17/8 | s:bd ]", - "[ 2/1 → 5/2 | s:sd ]", - "[ 19/8 → 5/2 | s:bd ]", - "[ 5/2 → 3/1 | s:sd ]", - "[ 11/4 → 23/8 | s:bd ]", - "[ 3/1 → 7/2 | s:sd ]", - "[ 7/2 → 4/1 | s:sd ]", + "[ 3/1 → 13/4 | note:g ]", + "[ 13/4 → 7/2 | note:a ]", + "[ 7/2 → 15/4 | note:c ]", + "[ 15/4 → 4/1 | note:d ]", ] `;