From 0925ea56ddc2f189001814d1d121f488eb928809 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Sun, 23 Feb 2025 09:52:27 +0000 Subject: [PATCH] Allow wchooseCycles probabilities to be patterned (#1292) * allow wchooseCycles probabilities to be patterned --- package.json | 1 + packages/core/pattern.mjs | 10 +++ packages/core/signal.mjs | 30 +++++-- pnpm-lock.yaml | 95 +++++++++++++++++++++++ test/__snapshots__/examples.test.mjs.snap | 53 +++++++++++++ 5 files changed, 181 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 9068b2ca..df50f1f4 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", "@tauri-apps/cli": "^2.2.7", + "@vitest/coverage-v8": "3.0.4", "@vitest/ui": "^3.0.4", "acorn": "^8.14.0", "dependency-tree": "^11.0.1", diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 4f0d3576..4e5eb15d 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1244,6 +1244,16 @@ export function reify(thing) { return pure(thing); } +/** Takes a list of patterns, and returns a pattern of lists. + */ +export function sequenceP(pats) { + let result = pure([]); + for (const pat of pats) { + result = result.bind((list) => pat.fmap((v) => list.concat([v]))); + } + return result; +} + /** The given items are played at the same time at the same length. * * @return {Pattern} diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 3ab35f23..5348173d 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ import { Hap } from './hap.mjs'; -import { Pattern, fastcat, pure, register, reify, silence, stack } from './pattern.mjs'; +import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs'; import Fraction from './fraction.mjs'; import { id, keyAlias, getCurrentKeyboardState } from './util.mjs'; @@ -433,19 +433,30 @@ export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs); export const randcat = chooseCycles; const _wchooseWith = function (pat, ...pairs) { + // A list of patterns of values const values = pairs.map((pair) => reify(pair[0])); + + // A list of weight patterns const weights = []; - let accum = 0; + + let total = pure(0); for (const pair of pairs) { - accum += pair[1]; - weights.push(accum); + // 'add' accepts either values or patterns of values here, so no need + // to explicitly reify + total = total.add(pair[1]); + // accumulate our list of weight patterns + weights.push(total); } - const total = accum; + // a pattern of lists of weights + const weightspat = sequenceP(weights); + + // Takes a number from 0-1, returns a pattern of patterns of values const match = function (r) { - const find = r * total; - return values[weights.findIndex((x) => x > find, weights)]; + const findpat = total.mul(r); + return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat); }; - return pat.fmap(match); + // This returns a pattern of patterns.. The innerJoin is in wchooseCycles + return pat.bind(match); }; const wchooseWith = (...args) => _wchooseWith(...args).outerJoin(); @@ -467,6 +478,9 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); * wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8) * @example * wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s() + * @example + * // The probability can itself be a pattern + * wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s() */ export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 494d04f0..2cdd501c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: '@tauri-apps/cli': specifier: ^2.2.7 version: 2.2.7 + '@vitest/coverage-v8': + specifier: 3.0.4 + version: 3.0.4(vitest@3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)) '@vitest/ui': specifier: ^3.0.4 version: 3.0.4(vitest@3.0.4) @@ -1412,6 +1415,10 @@ packages: resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@codemirror/autocomplete@6.18.4': resolution: {integrity: sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==} @@ -1834,6 +1841,10 @@ packages: '@isaacs/string-locale-compare@1.1.0': resolution: {integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2818,6 +2829,15 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + '@vitest/coverage-v8@3.0.4': + resolution: {integrity: sha512-f0twgRCHgbs24Dp8cLWagzcObXMcuKtAwgxjJV/nnysPAJJk1JiKu/W0gIehZLmkljhJXU/E0/dmuQzsA/4jhA==} + peerDependencies: + '@vitest/browser': 3.0.4 + vitest: 3.0.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@3.0.4': resolution: {integrity: sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==} @@ -4487,6 +4507,9 @@ packages: hs2js@0.1.0: resolution: {integrity: sha512-THlUIMX8tZf6gtbz5RUZ8xQUyKJEItsx7bxEBcouFIEWjeo90376WMocj3JEz6qTv5nM+tjo3vNvLf89XruMvg==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -4837,6 +4860,22 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -6887,6 +6926,10 @@ packages: engines: {node: '>=10'} hasBin: true + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-encoding-shim@1.0.5: resolution: {integrity: sha512-H7yYW+jRn4yhu60ygZ2f/eMhXPITRt4QSUTKzLm+eCaDsdX8avmgWpmtmHAzesjBVUTAypz9odu5RKUjX5HNYA==} @@ -7505,6 +7548,7 @@ packages: workbox-google-analytics@7.0.0: resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained workbox-navigation-preload@7.0.0: resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==} @@ -8568,6 +8612,8 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@bcoe/v8-coverage@1.0.2': {} + '@codemirror/autocomplete@6.18.4': dependencies: '@codemirror/language': 6.10.8 @@ -8934,6 +8980,8 @@ snapshots: '@isaacs/string-locale-compare@1.1.0': {} + '@istanbuljs/schema@0.1.3': {} + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -10187,6 +10235,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@3.0.4(vitest@3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.8.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.0.4': dependencies: '@vitest/spy': 3.0.4 @@ -12202,6 +12268,8 @@ snapshots: dependencies: web-tree-sitter: 0.20.8 + html-escaper@2.0.2: {} + html-escaper@3.0.3: {} html-void-elements@3.0.0: {} @@ -12540,6 +12608,27 @@ snapshots: isobject@3.0.1: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -15262,6 +15351,12 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-encoding-shim@1.0.5: {} text-extensions@1.9.0: {} diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 64d7cc1f..c100d566 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -9687,6 +9687,59 @@ exports[`runs examples > example "wchooseCycles" example index 1 1`] = ` ] `; +exports[`runs examples > example "wchooseCycles" example index 2 1`] = ` +[ + "[ 0/1 → 1/32 | s:bd ]", + "[ 3/32 → 1/8 | s:bd ]", + "[ 3/16 → 7/32 | s:bd ]", + "[ 1/4 → 1/3 | s:hh ]", + "[ 1/3 → 5/12 | s:hh ]", + "[ 5/12 → 1/2 | s:hh ]", + "[ 1/2 → 7/12 | s:hh ]", + "[ 7/12 → 2/3 | s:hh ]", + "[ 2/3 → 3/4 | s:hh ]", + "[ 3/4 → 5/6 | s:hh ]", + "[ 5/6 → 11/12 | s:hh ]", + "[ 11/12 → 1/1 | s:hh ]", + "[ 1/1 → 33/32 | s:bd ]", + "[ 35/32 → 9/8 | s:bd ]", + "[ 19/16 → 39/32 | s:bd ]", + "[ 5/4 → 4/3 | s:hh ]", + "[ 4/3 → 17/12 | s:hh ]", + "[ 17/12 → 3/2 | s:hh ]", + "[ 3/2 → 49/32 | s:bd ]", + "[ 51/32 → 13/8 | s:bd ]", + "[ 27/16 → 55/32 | s:bd ]", + "[ 7/4 → 11/6 | s:hh ]", + "[ 11/6 → 23/12 | s:hh ]", + "[ 23/12 → 2/1 | s:hh ]", + "[ 2/1 → 25/12 | s:hh ]", + "[ 25/12 → 13/6 | s:hh ]", + "[ 13/6 → 9/4 | s:hh ]", + "[ 9/4 → 7/3 | s:hh ]", + "[ 7/3 → 29/12 | s:hh ]", + "[ 29/12 → 5/2 | s:hh ]", + "[ 5/2 → 81/32 | s:bd ]", + "[ 83/32 → 21/8 | s:bd ]", + "[ 43/16 → 87/32 | s:bd ]", + "[ 11/4 → 17/6 | s:hh ]", + "[ 17/6 → 35/12 | s:hh ]", + "[ 35/12 → 3/1 | s:hh ]", + "[ 3/1 → 97/32 | s:bd ]", + "[ 99/32 → 25/8 | s:bd ]", + "[ 51/16 → 103/32 | s:bd ]", + "[ 13/4 → 10/3 | s:hh ]", + "[ 10/3 → 41/12 | s:hh ]", + "[ 41/12 → 7/2 | s:hh ]", + "[ 7/2 → 43/12 | s:hh ]", + "[ 43/12 → 11/3 | s:hh ]", + "[ 11/3 → 15/4 | s:hh ]", + "[ 15/4 → 23/6 | s:hh ]", + "[ 23/6 → 47/12 | s:hh ]", + "[ 47/12 → 4/1 | s:hh ]", +] +`; + exports[`runs examples > example "when" example index 0 1`] = ` [ "[ 0/1 → 1/3 | note:c3 ]",