Compare commits

..

10 Commits

Author SHA1 Message Date
alex 97a38a6f59 more fiddling with making module level curried functions 2022-12-05 20:45:25 +00:00
alex 986de6a73b add more toplevel functions 2022-12-05 14:51:41 +00:00
alex 1750db2f1c more strudel import fixes 2022-12-05 13:34:37 +00:00
Alex McLean eadcf25775 Merge branch 'main' into separate-alignment-methods 2022-12-05 13:22:43 +00:00
alex 1d2c4fd771 untabify 2022-12-05 13:14:19 +00:00
alex 92bf43e3a6 fix up imports from core, and remove circular deps that were breaking everything 2022-12-05 13:07:56 +00:00
alex 9dd4385bbf failing attempt at bundling everything into a single core object 2022-12-05 12:23:51 +00:00
alex 274378df42 export pattern.mjs as single object 2022-12-05 11:33:40 +00:00
alex 5ec217ea21 default alignment op to set 2022-12-05 10:10:51 +00:00
alex d2375f89ee experiment simplifying how/what matrix into separate methods 2022-11-30 08:45:34 +00:00
97 changed files with 15071 additions and 15222 deletions
+7 -9
View File
@@ -10,12 +10,10 @@ jobs:
node-version: [18] node-version: [18]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: "npm" cache: 'npm'
- run: npm install - run: npm install
- run: npm run format-check - run: npm test
- run: npm run lint
- run: npm test
-8
View File
@@ -1,8 +0,0 @@
*.md
*.json
*.yml
*.
**/out
**/dist
packages/mini/krill-parser.js
packages/xen/tunejs.js
+15 -14
View File
@@ -1,15 +1,16 @@
{ {
"printWidth": 120, "printWidth": 120,
"useTabs": false, "useTabs": false,
"tabWidth": 2, "tabWidth": 2,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"jsxSingleQuote": false, "jsxSingleQuote": false,
"trailingComma": "all", "trailingComma": "all",
"bracketSpacing": true, "bracketSpacing": true,
"jsxBracketSameLine": false, "jsxBracketSameLine": false,
"arrowParens": "always", "arrowParens": "always",
"proseWrap": "preserve", "proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css", "htmlWhitespaceSensitivity": "css",
"endOfLine": "lf" "endOfLine": "lf"
} }
+24 -150
View File
@@ -12,7 +12,7 @@
"packages/*" "packages/*"
], ],
"devDependencies": { "devDependencies": {
"@vitest/ui": "^0.25.7", "@vitest/ui": "^0.21.1",
"c8": "^7.12.0", "c8": "^7.12.0",
"eslint": "^8.28.0", "eslint": "^8.28.0",
"events": "^3.3.0", "events": "^3.3.0",
@@ -21,9 +21,8 @@
"jsdoc-json": "^2.0.2", "jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^7.1.1", "jsdoc-to-markdown": "^7.1.1",
"lerna": "^4.0.0", "lerna": "^4.0.0",
"prettier": "^2.8.1",
"rollup-plugin-visualizer": "^5.8.1", "rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.25.7" "vitest": "^0.21.1"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@@ -2423,10 +2422,9 @@
} }
}, },
"node_modules/@vitest/ui": { "node_modules/@vitest/ui": {
"version": "0.25.7", "version": "0.21.1",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.25.7.tgz",
"integrity": "sha512-mL/7SbXE11jMaSq/ynQuQvmR63amQ9/u60ZXgg9zFGJ3glTkfGfKIKSsGB64Z4vNEh8hf6MeF3vDfErlbJo0xA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"sirv": "^2.0.2" "sirv": "^2.0.2"
} }
@@ -9172,21 +9170,6 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz",
"integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"license": "MIT" "license": "MIT"
@@ -10607,30 +10590,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/strip-literal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz",
"integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==",
"dev": true,
"dependencies": {
"acorn": "^8.8.1"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/strip-literal/node_modules/acorn": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/strip-outer": { "node_modules/strip-outer": {
"version": "1.0.1", "version": "1.0.1",
"dev": true, "dev": true,
@@ -11017,26 +10976,18 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/tinybench": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz",
"integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==",
"dev": true
},
"node_modules/tinypool": { "node_modules/tinypool": {
"version": "0.3.0", "version": "0.2.4",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.0.tgz",
"integrity": "sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/tinyspy": { "node_modules/tinyspy": {
"version": "1.0.2", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz",
"integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@@ -11514,25 +11465,19 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "0.25.7", "version": "0.21.1",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.7.tgz",
"integrity": "sha512-lJ+Ue+v8kHl2JzjaKHJ9u5Yo/loU7zrWK2/Whn8OKQjtq5G7nkeWfXuq3elZaC8xKdkdIuWiiIicaNBG1F5yzg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@types/chai-subset": "^1.3.3", "@types/chai-subset": "^1.3.3",
"@types/node": "*", "@types/node": "*",
"acorn": "^8.8.0",
"acorn-walk": "^8.2.0",
"chai": "^4.3.6", "chai": "^4.3.6",
"debug": "^4.3.4", "debug": "^4.3.4",
"local-pkg": "^0.4.2", "local-pkg": "^0.4.2",
"source-map": "^0.6.1", "tinypool": "^0.2.4",
"strip-literal": "^1.0.0", "tinyspy": "^1.0.0",
"tinybench": "^2.3.1", "vite": "^2.9.12 || ^3.0.0-0"
"tinypool": "^0.3.0",
"tinyspy": "^1.0.2",
"vite": "^3.0.0 || ^4.0.0"
}, },
"bin": { "bin": {
"vitest": "vitest.mjs" "vitest": "vitest.mjs"
@@ -11547,6 +11492,7 @@
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@vitest/browser": "*", "@vitest/browser": "*",
"@vitest/ui": "*", "@vitest/ui": "*",
"c8": "*",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
@@ -11560,6 +11506,9 @@
"@vitest/ui": { "@vitest/ui": {
"optional": true "optional": true
}, },
"c8": {
"optional": true
},
"happy-dom": { "happy-dom": {
"optional": true "optional": true
}, },
@@ -11568,27 +11517,6 @@
} }
} }
}, },
"node_modules/vitest/node_modules/acorn": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/vitest/node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/vitest/node_modules/debug": { "node_modules/vitest/node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"dev": true, "dev": true,
@@ -14213,9 +14141,7 @@
} }
}, },
"@vitest/ui": { "@vitest/ui": {
"version": "0.25.7", "version": "0.21.1",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.25.7.tgz",
"integrity": "sha512-mL/7SbXE11jMaSq/ynQuQvmR63amQ9/u60ZXgg9zFGJ3glTkfGfKIKSsGB64Z4vNEh8hf6MeF3vDfErlbJo0xA==",
"dev": true, "dev": true,
"requires": { "requires": {
"sirv": "^2.0.2" "sirv": "^2.0.2"
@@ -18564,12 +18490,6 @@
"prelude-ls": { "prelude-ls": {
"version": "1.1.2" "version": "1.1.2"
}, },
"prettier": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz",
"integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==",
"dev": true
},
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.1" "version": "2.0.1"
}, },
@@ -19546,23 +19466,6 @@
"strip-json-comments": { "strip-json-comments": {
"version": "3.1.1" "version": "3.1.1"
}, },
"strip-literal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz",
"integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==",
"dev": true,
"requires": {
"acorn": "^8.8.1"
},
"dependencies": {
"acorn": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"dev": true
}
}
},
"strip-outer": { "strip-outer": {
"version": "1.0.1", "version": "1.0.1",
"dev": true, "dev": true,
@@ -19853,22 +19756,12 @@
} }
} }
}, },
"tinybench": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz",
"integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==",
"dev": true
},
"tinypool": { "tinypool": {
"version": "0.3.0", "version": "0.2.4",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.0.tgz",
"integrity": "sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==",
"dev": true "dev": true
}, },
"tinyspy": { "tinyspy": {
"version": "1.0.2", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz",
"integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==",
"dev": true "dev": true
}, },
"tmp": { "tmp": {
@@ -20151,39 +20044,20 @@
} }
}, },
"vitest": { "vitest": {
"version": "0.25.7", "version": "0.21.1",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.7.tgz",
"integrity": "sha512-lJ+Ue+v8kHl2JzjaKHJ9u5Yo/loU7zrWK2/Whn8OKQjtq5G7nkeWfXuq3elZaC8xKdkdIuWiiIicaNBG1F5yzg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@types/chai-subset": "^1.3.3", "@types/chai-subset": "^1.3.3",
"@types/node": "*", "@types/node": "*",
"acorn": "^8.8.0",
"acorn-walk": "^8.2.0",
"chai": "^4.3.6", "chai": "^4.3.6",
"debug": "^4.3.4", "debug": "^4.3.4",
"local-pkg": "^0.4.2", "local-pkg": "^0.4.2",
"source-map": "^0.6.1", "tinypool": "^0.2.4",
"strip-literal": "^1.0.0", "tinyspy": "^1.0.0",
"tinybench": "^2.3.1", "vite": "^2.9.12 || ^3.0.0-0"
"tinypool": "^0.3.0",
"tinyspy": "^1.0.2",
"vite": "^3.0.0 || ^4.0.0"
}, },
"dependencies": { "dependencies": {
"acorn": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"dev": true
},
"acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true
},
"debug": { "debug": {
"version": "4.3.4", "version": "4.3.4",
"dev": true, "dev": true,
+4 -8
View File
@@ -5,7 +5,7 @@
"description": "Port of tidalcycles to javascript", "description": "Port of tidalcycles to javascript",
"scripts": { "scripts": {
"pretest": "cd tutorial && npm run jsdoc-json", "pretest": "cd tutorial && npm run jsdoc-json",
"test": "vitest run --version", "test": "npm run lint && vitest run --version",
"test-ui": "vitest --ui", "test-ui": "vitest --ui",
"test-coverage": "vitest --coverage", "test-coverage": "vitest --coverage",
"bootstrap": "lerna bootstrap", "bootstrap": "lerna bootstrap",
@@ -18,10 +18,7 @@
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out", "deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -c jsdoc.config.json", "jsdoc": "jsdoc packages/ -c jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json", "jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
"lint": "eslint . --ext mjs,js --quiet", "lint": "npx eslint . --ext mjs,js --quiet"
"codeformat": "prettier --write .",
"format-check": "prettier --check .",
"check": "npm run format-check && npm run lint && npm run test"
}, },
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@@ -44,7 +41,7 @@
}, },
"homepage": "https://strudel.tidalcycles.org", "homepage": "https://strudel.tidalcycles.org",
"devDependencies": { "devDependencies": {
"@vitest/ui": "^0.25.7", "@vitest/ui": "^0.21.1",
"c8": "^7.12.0", "c8": "^7.12.0",
"eslint": "^8.28.0", "eslint": "^8.28.0",
"events": "^3.3.0", "events": "^3.3.0",
@@ -53,8 +50,7 @@
"jsdoc-json": "^2.0.2", "jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^7.1.1", "jsdoc-to-markdown": "^7.1.1",
"lerna": "^4.0.0", "lerna": "^4.0.0",
"prettier": "^2.8.1",
"rollup-plugin-visualizer": "^5.8.1", "rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.25.7" "vitest": "^0.21.1"
} }
} }
+4 -3
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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, sequence } from './pattern.mjs'; import pattern from './pattern.mjs';
const { Pattern, sequence } = pattern;
const controls = {}; const controls = {};
const generic_params = [ const generic_params = [
@@ -204,7 +205,7 @@ const generic_params = [
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>") * "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
* *
*/ */
// ['f', 'legato', 'controls the amount of overlap between two adjacent sounds'], ['f', 'legato', 'controls the amount of overlap between two adjacent sounds'],
// ['f', 'clhatdecay', ''], // ['f', 'clhatdecay', ''],
/** /**
* bit crusher effect. * bit crusher effect.
@@ -562,7 +563,7 @@ const generic_params = [
// TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare // TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
['f', 'semitone', ''], ['f', 'semitone', ''],
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano // TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
// ['f', 'velocity', ''], ['f', 'velocity', ''],
['f', 'voice', ''], // TODO: synth param ['f', 'voice', ''], // TODO: synth param
/** /**
* Sets the level of reverb. * Sets the level of reverb.
+5 -1
View File
@@ -4,7 +4,11 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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, getTime, State, TimeSpan } from './index.mjs'; import pattern from './pattern.mjs';
const Pattern = pattern.Pattern;
import { getTime } from './time.mjs';
import { State } from './state.mjs';
import { TimeSpan } from './timespan.mjs';
export const getDrawContext = (id = 'test-canvas') => { export const getDrawContext = (id = 'test-canvas') => {
let canvas = document.querySelector('#' + id); let canvas = document.querySelector('#' + id);
+2 -1
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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, timeCat } from './pattern.mjs'; import pattern from './pattern.mjs';
const { Pattern, timeCat } = pattern;
import bjork from 'bjork'; import bjork from 'bjork';
import { rotate } from './util.mjs'; import { rotate } from './util.mjs';
import Fraction from './fraction.mjs'; import Fraction from './fraction.mjs';
+3 -2
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { isPattern } from './index.mjs'; import pattern from './pattern.mjs';
const { isPattern, Pattern } = pattern;
let scoped = false; let scoped = false;
export const evalScope = async (...args) => { export const evalScope = async (...args) => {
@@ -19,7 +20,7 @@ export const evalScope = async (...args) => {
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason); console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
} }
}); });
Object.assign(globalThis, ...modules); Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap());
}; };
function safeEval(str, options = {}) { function safeEval(str, options = {}) {
@@ -16,7 +16,7 @@ evalScope(
import('@strudel.cycles/tonal'), import('@strudel.cycles/tonal'),
); );
setStringParser(mini); setStringParser(mini)
const { evaluate } = repl({ const { evaluate } = repl({
defaultOutput: webaudioOutput, defaultOutput: webaudioOutput,
+4 -27
View File
@@ -79,33 +79,10 @@ export class Hap {
); );
} }
show(compact = false) { show() {
const value = return (
typeof this.value === 'object' '(' + (this.whole == undefined ? '~' : this.whole.show()) + ', ' + this.part.show() + ', ' + this.value + ')'
? compact );
? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
: JSON.stringify(this.value)
: this.value;
var spans = '';
if (this.whole == undefined) {
spans = '~' + this.part.show;
} else {
var is_whole = this.whole.begin.equals(this.part.begin) && this.whole.end.equals(this.part.end);
if (!this.whole.begin.equals(this.part.begin)) {
spans = this.whole.begin.show() + ' ⇜ ';
}
if (!is_whole) {
spans += '(';
}
spans += this.part.show();
if (!is_whole) {
spans += ')';
}
if (!this.whole.end.equals(this.part.end)) {
spans += ' ⇝ ' + this.whole.end.show();
}
}
return '[ ' + spans + ' | ' + value + ' ]';
} }
showWhole(compact = false) { showWhole(compact = false) {
+28 -19
View File
@@ -4,27 +4,36 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 controls from './controls.mjs';
export * from './euclid.mjs';
import Fraction from './fraction.mjs'; import Fraction from './fraction.mjs';
import drawLine from './drawLine.mjs';
import gist from './gist.js';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
export { Fraction, controls }; // Fraction: Fraction, drawLine: drawline, gist: gist, logger: logger,
export * from './hap.mjs';
export * from './pattern.mjs'; import controls from './controls.mjs';
export * from './signal.mjs'; import pattern from './pattern.mjs';
export * from './state.mjs';
export * from './timespan.mjs'; import * as draw from './draw.mjs';
export * from './util.mjs'; import * as euclid from './euclid.mjs';
export * from './speak.mjs'; import * as evaluate from './evaluate.mjs';
export * from './evaluate.mjs'; import * as hap from './hap.mjs';
export * from './repl.mjs'; import * as repl from './repl.mjs';
export * from './logger.mjs'; import * as pianoroll from './pianoroll.mjs';
export * from './time.mjs'; import * as signal from './signal.mjs';
export * from './draw.mjs'; import * as speak from './speak.mjs';
export * from './pianoroll.mjs'; import * as state from './state.mjs';
export * from './ui.mjs'; import * as time from './time.mjs';
export { default as drawLine } from './drawLine.mjs'; import * as timespan from './timespan.mjs';
export { default as gist } from './gist.js'; import * as ui from './ui.mjs';
import * as util from './util.mjs';
const core = { drawLine, gist, logger, Fraction, ...controls, ...pattern, ...draw, ...euclid,
...evaluate, ...hap, ...repl, ...pianoroll, ...signal, ...speak,
...state, ...time, ...timespan, ...ui, ...util
};
export default core;
// below won't work with runtime.mjs (json import fails) // below won't work with runtime.mjs (json import fails)
/* import * as p from './package.json'; /* import * as p from './package.json';
export const version = p.version; */ export const version = p.version; */
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/core", "name": "@strudel.cycles/core",
"version": "0.5.0", "version": "0.4.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/core", "name": "@strudel.cycles/core",
"version": "0.5.0", "version": "0.4.1",
"description": "Port of Tidal Cycles to JavaScript", "description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+919 -827
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -4,7 +4,11 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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, toMidi, getDrawContext } from './index.mjs'; import pattern from './pattern.mjs';
const { Pattern } = pattern;
import { toMidi } from './util.mjs';
import { getDrawContext } from './draw.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min; const scale = (normalized, min, max) => normalized * (max - min) + min;
const getValue = (e) => { const getValue = (e) => {
+84 -49
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Hap } from './hap.mjs'; import { Hap } from './hap.mjs';
import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs'; import pattern from './pattern.mjs';
const { Pattern, fastcat, reify, silence, stack, isPattern } = pattern;
import Fraction from './fraction.mjs'; import Fraction from './fraction.mjs';
import { id } from './util.mjs'; import { id } from './util.mjs';
@@ -258,9 +259,9 @@ export const perlinWith = (pat) => {
*/ */
export const perlin = perlinWith(time.fmap((v) => Number(v))); export const perlin = perlinWith(time.fmap((v) => Number(v)));
export const degradeByWith = register('degradeByWith', (withPat, x, pat) => Pattern.prototype._degradeByWith = function (withPat, x) {
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)), return this.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x));
); };
/** /**
* Randomly removes events from the pattern by a given amount. * Randomly removes events from the pattern by a given amount.
@@ -276,9 +277,9 @@ export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
* @example * @example
* s("[hh?0.2]*8") * s("[hh?0.2]*8")
*/ */
export const degradeBy = register('degradeBy', function (x, pat) { Pattern.prototype._degradeBy = function (x) {
return pat._degradeByWith(rand, x); return this._degradeByWith(rand, x);
}); };
/** /**
* *
@@ -292,7 +293,9 @@ export const degradeBy = register('degradeBy', function (x, pat) {
* @example * @example
* s("[hh?]*8") * s("[hh?]*8")
*/ */
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5)); Pattern.prototype.degrade = function () {
return this._degradeBy(0.5);
};
/** /**
* Inverse of {@link Pattern#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.
@@ -307,14 +310,26 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
* @example * @example
* s("hh*8").undegradeBy(0.2) * s("hh*8").undegradeBy(0.2)
*/ */
export const undegradeBy = register('undegradeBy', function (x, pat) { Pattern.prototype._undegradeBy = function (x) {
return pat._degradeByWith( return this._degradeByWith(
rand.fmap((r) => 1 - r), rand.fmap((r) => 1 - r),
x, x,
); );
}); };
export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5)); Pattern.prototype.undegrade = function () {
return this._undegradeBy(0.5);
};
Pattern.prototype._sometimesBy = function (x, func) {
return stack(this._degradeBy(x), func(this._undegradeBy(1 - x)));
};
// https://github.com/tidalcycles/strudel/discussions/198
/* Pattern.prototype._sometimesBy = function (x, other) {
other = typeof other === 'function' ? other(this._undegradeBy(1 - x)) : reify(other)._undegradeBy(1 - x);
return stack(this._degradeBy(x), other);
}; */
/** /**
* *
@@ -329,12 +344,24 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
* @example * @example
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5")) * s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
*/ */
Pattern.prototype.sometimesBy = function (patx, func) {
export const sometimesBy = register('sometimesBy', function (patx, func, pat) { const pat = this;
return reify(patx) return reify(patx)
.fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x)))) .fmap((x) => pat._sometimesBy(x, func))
.innerJoin(); .innerJoin();
}); };
// why does this exist? it is identical to sometimesBy
Pattern.prototype._sometimesByPre = function (x, func) {
return stack(this._degradeBy(x), func(this).undegradeBy(1 - x));
};
Pattern.prototype.sometimesByPre = function (patx, func) {
const pat = this;
return reify(patx)
.fmap((x) => pat._sometimesByPre(x, func))
.innerJoin();
};
/** /**
* *
@@ -347,9 +374,20 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
* @example * @example
* s("hh*4").sometimes(x=>x.speed("0.5")) * s("hh*4").sometimes(x=>x.speed("0.5"))
*/ */
export const sometimes = register('sometimes', function (func, pat) { Pattern.prototype.sometimes = function (func) {
return pat._sometimesBy(0.5, func); return this._sometimesBy(0.5, func);
}); };
Pattern.prototype.sometimesPre = function (func) {
return this._sometimesByPre(0.5, func);
};
Pattern.prototype._someCyclesBy = function (x, func) {
return stack(
this._degradeByWith(rand._segment(1), x),
func(this._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
);
};
/** /**
* *
@@ -364,17 +402,12 @@ export const sometimes = register('sometimes', function (func, pat) {
* @example * @example
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5")) * s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
*/ */
Pattern.prototype.someCyclesBy = function (patx, func) {
export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) { const pat = this;
return reify(patx) return reify(patx)
.fmap((x) => .fmap((x) => pat._someCyclesBy(x, func))
stack(
pat._degradeByWith(rand._segment(1), x),
func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
),
)
.innerJoin(); .innerJoin();
}); };
/** /**
* *
@@ -386,9 +419,9 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat)
* @example * @example
* s("hh(3,8)").someCycles(x=>x.speed("0.5")) * s("hh(3,8)").someCycles(x=>x.speed("0.5"))
*/ */
export const someCycles = register('someCycles', function (func, pat) { Pattern.prototype.someCycles = function (func) {
return pat._someCyclesBy(0.5, func); return this._someCyclesBy(0.5, func);
}); };
/** /**
* *
@@ -400,9 +433,9 @@ export const someCycles = register('someCycles', function (func, pat) {
* @example * @example
* s("hh*8").often(x=>x.speed("0.5")) * s("hh*8").often(x=>x.speed("0.5"))
*/ */
export const often = register('often', function (func, pat) { Pattern.prototype.often = function (func) {
return pat.sometimesBy(0.75, func); return this.sometimesBy(0.75, func);
}); };
/** /**
* *
@@ -414,9 +447,9 @@ export const often = register('often', function (func, pat) {
* @example * @example
* s("hh*8").rarely(x=>x.speed("0.5")) * s("hh*8").rarely(x=>x.speed("0.5"))
*/ */
export const rarely = register('rarely', function (func, pat) { Pattern.prototype.rarely = function (func) {
return pat.sometimesBy(0.25, func); return this.sometimesBy(0.25, func);
}); };
/** /**
* *
@@ -428,9 +461,9 @@ export const rarely = register('rarely', function (func, pat) {
* @example * @example
* s("hh*8").almostNever(x=>x.speed("0.5")) * s("hh*8").almostNever(x=>x.speed("0.5"))
*/ */
export const almostNever = register('almostNever', function (func, pat) { Pattern.prototype.almostNever = function (func) {
return pat.sometimesBy(0.1, func); return this.sometimesBy(0.1, func);
}); };
/** /**
* *
@@ -442,9 +475,9 @@ export const almostNever = register('almostNever', function (func, pat) {
* @example * @example
* s("hh*8").almostAlways(x=>x.speed("0.5")) * s("hh*8").almostAlways(x=>x.speed("0.5"))
*/ */
export const almostAlways = register('almostAlways', function (func, pat) { Pattern.prototype.almostAlways = function (func) {
return pat.sometimesBy(0.9, func); return this.sometimesBy(0.9, func);
}); };
/** /**
* *
@@ -456,9 +489,9 @@ export const almostAlways = register('almostAlways', function (func, pat) {
* @example * @example
* s("hh*8").never(x=>x.speed("0.5")) * s("hh*8").never(x=>x.speed("0.5"))
*/ */
export const never = register('never', function (_, pat) { Pattern.prototype.never = function (func) {
return pat; return this;
}); };
/** /**
* *
@@ -470,6 +503,8 @@ export const never = register('never', function (_, pat) {
* @example * @example
* s("hh*8").always(x=>x.speed("0.5")) * s("hh*8").always(x=>x.speed("0.5"))
*/ */
export const always = register('always', function (func, pat) { Pattern.prototype.always = function (func) {
return func(pat); return func(this);
}); };
Pattern.prototype.patternified.push('degradeBy', 'undegradeBy');
+11 -6
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { register } from './index.mjs'; import pattern from './pattern.mjs';
const {Pattern, patternify2, reify} = pattern;
let synth; let synth;
try { try {
@@ -16,7 +17,7 @@ try {
let allVoices = synth?.getVoices(); let allVoices = synth?.getVoices();
// console.log('voices', allVoices); // console.log('voices', allVoices);
function triggerSpeech(words, lang, voice) { function speak(words, lang, voice) {
synth.cancel(); synth.cancel();
const utterance = new SpeechSynthesisUtterance(words); const utterance = new SpeechSynthesisUtterance(words);
utterance.lang = lang; utterance.lang = lang;
@@ -31,8 +32,12 @@ function triggerSpeech(words, lang, voice) {
speechSynthesis.speak(utterance); speechSynthesis.speak(utterance);
} }
export const speak = register('speak', function (lang, voice, pat) { Pattern.prototype._speak = function (lang, voice) {
return pat.onTrigger((_, hap) => { return this.onTrigger((_, hap) => {
triggerSpeech(hap.value, lang, voice); speak(hap.value, lang, voice);
}); });
}); };
Pattern.prototype.speak = function (lang, voice) {
return patternify2(Pattern.prototype._speak)(reify(lang), reify(voice), this);
};
+2 -1
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { fastcat, stack, slowcat, silence, pure } from '../pattern.mjs'; import pattern from '../pattern.mjs';
const { fastcat, stack, slowcat, silence, pure } = pattern;
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import drawLine from '../drawLine.mjs'; import drawLine from '../drawLine.mjs';
+17 -47
View File
@@ -8,15 +8,13 @@ import Fraction from 'fraction.js';
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { import core from '../index.mjs';
TimeSpan,
Hap, const {
State,
Pattern, Pattern,
pure, pure,
stack, stack,
fastcat, fastcat,
firstOf,
slowcat, slowcat,
cat, cat,
sequence, sequence,
@@ -30,6 +28,12 @@ import {
sub, sub,
mul, mul,
div, div,
id,
ply,
rev,
TimeSpan,
Hap,
State,
saw, saw,
saw2, saw2,
isaw, isaw,
@@ -40,11 +44,7 @@ import {
square2, square2,
tri, tri,
tri2, tri2,
id, time} = core;
ply,
rev,
time,
} from '../index.mjs';
import { steady } from '../signal.mjs'; import { steady } from '../signal.mjs';
@@ -155,12 +155,6 @@ describe('Pattern', () => {
}); });
}); });
describe('add()', () => { describe('add()', () => {
it('works as toplevel function', () => {
expect(add(pure(4), pure(5)).query(st(0, 1))[0].value).toBe(9);
});
it('works as toplevel function, with bare values for arguments', () => {
expect(add(4, 5).query(st(0, 1))[0].value).toBe(9);
});
it('can structure In()', () => { it('can structure In()', () => {
expect(pure(3).add(pure(4)).query(st(0, 1))[0].value).toBe(7); expect(pure(3).add(pure(4)).query(st(0, 1))[0].value).toBe(7);
expect(pure(3).add.in(pure(4)).query(st(0, 1))[0].value).toBe(7); expect(pure(3).add.in(pure(4)).query(st(0, 1))[0].value).toBe(7);
@@ -246,7 +240,7 @@ describe('Pattern', () => {
), ),
); );
}); });
it('can SqueezeOut() structure', () => { it('can squeezeOut() structure', () => {
sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]])); sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]]));
}); });
}); });
@@ -327,12 +321,12 @@ describe('Pattern', () => {
it('Can set things with plain values', () => { it('Can set things with plain values', () => {
sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3)); sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3));
}); });
describe('setOut()', () => { describe('set.out()', () => {
it('Can set things with structure from second pattern', () => { it('Can set things with structure from second pattern', () => {
sameFirst(sequence(1, 2).set.out(4), pure(4).mask(true, true)); sameFirst(sequence(1, 2).set.out(4), pure(4).mask(true, true));
}); });
}); });
describe('setSqueeze()', () => { describe('set.squeeze()', () => {
it('Can squeeze one pattern inside the haps of another', () => { it('Can squeeze one pattern inside the haps of another', () => {
sameFirst( sameFirst(
sequence(1, [2, 3]).set.squeeze(sequence('a', 'b', 'c')), sequence(1, [2, 3]).set.squeeze(sequence('a', 'b', 'c')),
@@ -413,8 +407,8 @@ describe('Pattern', () => {
); );
}); });
it('defaults to accepting sequences', () => { it('defaults to accepting sequences', () => {
expect(sequence('a', 'b', 'c').fast(sequence(1.5, 2)).sortHapsByPart().firstCycle()).toStrictEqual( expect(sequence(1, 2, 3).fast(sequence(1.5, 2)).firstCycle()).toStrictEqual(
sequence('a', 'b', 'c').fast(1.5, 2).sortHapsByPart().firstCycle(), sequence(1, 2, 3).fast(1.5, 2).firstCycle(),
); );
}); });
it('works as a static function', () => { it('works as a static function', () => {
@@ -593,16 +587,6 @@ describe('Pattern', () => {
.firstCycle(), .firstCycle(),
).toStrictEqual(sequence(sequence('a', 'a'), 'a', 'a').firstCycle()); ).toStrictEqual(sequence(sequence('a', 'a'), 'a', 'a').firstCycle());
}); });
it('Works as a toplevel function', () => {
expect(firstOf(3, fast(2), pure('a'))._fast(3).firstCycle()).toStrictEqual(
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
);
});
it('Works as a toplevel function, with a patterned first argument', () => {
expect(firstOf(pure(3), fast(2), pure('a'))._fast(3).firstCycle()).toStrictEqual(
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
);
});
it('works with currying', () => { it('works with currying', () => {
expect(pure('a').firstOf(3, fast(2))._fast(3).firstCycle()).toStrictEqual( expect(pure('a').firstOf(3, fast(2))._fast(3).firstCycle()).toStrictEqual(
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(), sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
@@ -912,23 +896,9 @@ describe('Pattern', () => {
}); });
describe('alignments', () => { describe('alignments', () => {
it('Can squeeze arguments', () => { it('Can squeeze arguments', () => {
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(sequence(5, 6, 6, 7).firstCycle()); expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(
}); sequence(5, 6, 6, 7).firstCycle()
});
describe('defragmentHaps', () => {
it('Can merge two touching haps with same whole and value', () => {
expect(stack(pure('a').mask(1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(1);
});
it('Doesnt merge two overlapping haps', () => {
expect(stack(pure('a').mask(1, 1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(
2,
); );
}); });
it('Doesnt merge two touching haps with different values', () => {
expect(stack(pure('a').mask(1, 0), pure('b').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
});
it('Doesnt merge two touching haps with different wholes', () => {
expect(stack(sequence('a', silence), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
});
}); });
}); });
+16 -22
View File
@@ -4,14 +4,14 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { pure } from '../pattern.mjs'; import pattern from '../pattern.mjs';
const { pure } = pattern;
import { import {
isNote, isNote,
tokenizeNote, tokenizeNote,
toMidi, toMidi,
fromMidi, fromMidi,
freqToMidi, mod,
_mod,
compose, compose,
getFrequency, getFrequency,
getPlayableNoteValue, getPlayableNoteValue,
@@ -95,12 +95,6 @@ describe('fromMidi', () => {
expect(fromMidi(57)).toEqual(220); expect(fromMidi(57)).toEqual(220);
}); });
}); });
describe('freqToMidi', () => {
it('should turn frequency into midi', () => {
expect(freqToMidi(440)).toEqual(69);
expect(freqToMidi(220)).toEqual(57);
});
});
describe('getFrequency', () => { describe('getFrequency', () => {
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context); const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
it('should turn note into frequency', () => { it('should turn note into frequency', () => {
@@ -125,22 +119,22 @@ describe('getFrequency', () => {
}); });
}); });
describe('_mod', () => { describe('mod', () => {
it('should work like regular modulo with positive numbers', () => { it('should work like regular modulo with positive numbers', () => {
expect(_mod(0, 3)).toEqual(0); expect(mod(0, 3)).toEqual(0);
expect(_mod(1, 3)).toEqual(1); expect(mod(1, 3)).toEqual(1);
expect(_mod(2, 3)).toEqual(2); expect(mod(2, 3)).toEqual(2);
expect(_mod(3, 3)).toEqual(0); expect(mod(3, 3)).toEqual(0);
expect(_mod(4, 3)).toEqual(1); expect(mod(4, 3)).toEqual(1);
expect(_mod(4, 2)).toEqual(0); expect(mod(4, 2)).toEqual(0);
}); });
it('should work with negative numbers', () => { it('should work with negative numbers', () => {
expect(_mod(-1, 3)).toEqual(2); expect(mod(-1, 3)).toEqual(2);
expect(_mod(-2, 3)).toEqual(1); expect(mod(-2, 3)).toEqual(1);
expect(_mod(-3, 3)).toEqual(0); expect(mod(-3, 3)).toEqual(0);
expect(_mod(-4, 3)).toEqual(2); expect(mod(-4, 3)).toEqual(2);
expect(_mod(-5, 3)).toEqual(1); expect(mod(-5, 3)).toEqual(1);
expect(_mod(-3, 2)).toEqual(1); expect(mod(-3, 2)).toEqual(1);
}); });
}); });
-6
View File
@@ -6,8 +6,6 @@ This program is free software: you can redistribute it and/or modify it under th
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { map, valued, mul } from '../value.mjs'; import { map, valued, mul } from '../value.mjs';
import controls from '../controls.mjs';
const { n } = controls;
describe('Value', () => { describe('Value', () => {
it('unionWith', () => { it('unionWith', () => {
@@ -23,8 +21,4 @@ describe('Value', () => {
expect(valued(mul).ap(3).ap(3).value).toEqual(9); expect(valued(mul).ap(3).ap(3).value).toEqual(9);
expect(valued(3).mul(3).value).toEqual(9); expect(valued(3).mul(3).value).toEqual(9);
}); });
it('union bare numbers for numeral props', () => {
expect(n(3).cutoff(500).add(10).firstCycleValues).toEqual([{ n: 13, cutoff: 510 }]);
expect(n(3).cutoff(500).mul(2).firstCycleValues).toEqual([{ n: 6, cutoff: 1000 }]);
});
}); });
+3 -3
View File
@@ -20,9 +20,9 @@ export class TimeSpan {
// Support zero-width timespans // Support zero-width timespans
if (begin.equals(end)) { if (begin.equals(end)) {
return [new TimeSpan(begin, end)]; return([new TimeSpan(begin, end)]);
} }
while (end.gt(begin)) { while (end.gt(begin)) {
// If begin and end are in the same cycle, we're done. // If begin and end are in the same cycle, we're done.
if (begin.sam().equals(end_sam)) { if (begin.sam().equals(end_sam)) {
@@ -110,7 +110,7 @@ export class TimeSpan {
} }
show() { show() {
return this.begin.show() + ' ' + this.end.show(); return this.begin.show() + ' -> ' + this.end.show();
} }
} }
+5 -28
View File
@@ -31,30 +31,6 @@ export const fromMidi = (n) => {
return Math.pow(2, (n - 69) / 12) * 440; return Math.pow(2, (n - 69) / 12) * 440;
}; };
export const freqToMidi = (freq) => {
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
};
export const valueToMidi = (value, fallbackValue) => {
if (typeof value !== 'object') {
throw new Error('valueToMidi: expected object value');
}
let { freq, note } = value;
if (typeof freq === 'number') {
return freqToMidi(freq);
}
if (typeof note === 'string') {
return toMidi(note);
}
if (typeof note === 'number') {
return note;
}
if (!fallbackValue) {
throw new Error('valueToMidi: expected freq or note to be set');
}
return fallbackValue;
};
/** /**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase * @deprecated does not appear to be referenced or invoked anywhere in the codebase
*/ */
@@ -74,8 +50,9 @@ export const midi2note = (n) => {
return pc + oct; return pc + oct;
}; };
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does) // modulo that works with negative numbers e.g. mod(-1, 3) = 2
export const _mod = (n, m) => ((n % m) + m) % m; // const mod = (n: number, m: number): number => (n < 0 ? mod(n + m, m) : n % m);
export const mod = (n, m) => ((n % m) + m) % m;
export const getPlayableNoteValue = (hap) => { export const getPlayableNoteValue = (hap) => {
let { value, context } = hap; let { value, context } = hap;
@@ -140,9 +117,9 @@ export const constant = (a, b) => a;
export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min); export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min);
export function curry(func, overload, arity = func.length) { export function curry(func, overload) {
const fn = function curried(...args) { const fn = function curried(...args) {
if (args.length >= arity) { if (args.length >= func.length) {
return func.apply(this, args); return func.apply(this, args);
} else { } else {
const partial = function (...args2) { const partial = function (...args2) {
-7
View File
@@ -7,13 +7,6 @@ This program is free software: you can redistribute it and/or modify it under th
import { curry } from './util.mjs'; import { curry } from './util.mjs';
export function unionWithObj(a, b, func) { export function unionWithObj(a, b, func) {
if (typeof b?.value === 'number') {
// https://github.com/tidalcycles/strudel/issues/262
const numKeys = Object.keys(a).filter((k) => typeof a[k] === 'number');
const numerals = Object.fromEntries(numKeys.map((k) => [k, b.value]));
b = Object.assign(b, numerals);
delete b.value;
}
const common = Object.keys(a).filter((k) => Object.keys(b).includes(k)); const common = Object.keys(a).filter((k) => Object.keys(b).includes(k));
return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])]))); return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])])));
} }
+29 -77
View File
@@ -1,4 +1,5 @@
import { getFrequency, logger, register } from '@strudel.cycles/core'; import strudel from '@strudel.cycles/core';
const { getFrequency, logger, Pattern } = strudel;
import { getAudioContext } from '@strudel.cycles/webaudio'; import { getAudioContext } from '@strudel.cycles/webaudio';
import csd from './project.csd?raw'; import csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw'; // import livecodeOrc from './livecode.orc?raw';
@@ -6,24 +7,12 @@ import presetsOrc from './presets.orc?raw';
let csoundLoader, _csound; let csoundLoader, _csound;
// initializes csound + can be used to reevaluate given instrument code // triggers given instrument name using csound.
export async function loadCSound(code = '') { Pattern.prototype._csound = function (instrument) {
await init();
if (code) {
code = `${code}`;
// ^ ^
// wrapping in backticks makes sure it works when calling as templated function
await _csound?.evalCode(code);
}
}
export const loadcsound = loadCSound;
export const loadCsound = loadCSound;
export const csound = register('csound', (instrument, pat) => {
instrument = instrument || 'triangle'; instrument = instrument || 'triangle';
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
// TODO: find a alternative way to wait for csound to load (to wait with first time playback) // TODO: find a alternative way to wait for csound to load (to wait with first time playback)
return pat.onTrigger((time, hap) => { return this.onTrigger((time, hap) => {
if (!_csound) { if (!_csound) {
logger('[csound] not loaded yet', 'warning'); logger('[csound] not loaded yet', 'warning');
return; return;
@@ -52,7 +41,20 @@ export const csound = register('csound', (instrument, pat) => {
const msg = `i ${params.join(' ')}`; const msg = `i ${params.join(' ')}`;
_csound.inputMessage(msg); _csound.inputMessage(msg);
}); });
}); };
// initializes csound + can be used to reevaluate given instrument code
export async function csound(code = '') {
await init();
if (code) {
code = `${code}`;
// ^ ^
// wrapping in backticks makes sure it works when calling as templated function
await _csound?.evalCode(code);
}
}
Pattern.prototype.define('csound', (a, pat) => pat.csound(a), { composable: false, patternified: true });
function eventLogger(type, args) { function eventLogger(type, args) {
const [msg] = args; const [msg] = args;
@@ -79,25 +81,21 @@ function eventLogger(type, args) {
async function load() { async function load() {
if (window.__csound__) { if (window.__csound__) {
// Allows using some other csound instance. // allows using some other csound instance
// In that case, the external Csound is responsible
// for compiling an orchestra and starting to perform.
logger('[load] Using external Csound', 'warning');
_csound = window.__csound__; _csound = window.__csound__;
return _csound;
} else { } else {
const { Csound } = await import('@csound/browser'); const { Csound } = await import('@csound/browser');
_csound = await Csound({ audioContext: getAudioContext() }); _csound = await Csound({ audioContext: getAudioContext() });
_csound.removeAllListeners('message');
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args)));
await _csound.setOption('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
await _csound.setOption('--sample-accurate');
await _csound.compileCsdText(csd);
// await _csound.compileOrc(livecodeOrc);
await _csound.compileOrc(presetsOrc);
await _csound.start();
return _csound;
} }
_csound.removeAllListeners('message');
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args)));
await _csound.setOption('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
await _csound.setOption('--sample-accurate');
await _csound.compileCsdText(csd);
// await _csound.compileOrc(livecodeOrc);
await _csound.compileOrc(presetsOrc);
await _csound.start();
return _csound;
} }
async function init() { async function init() {
@@ -122,49 +120,3 @@ export async function loadOrc(url) {
} }
await orcCache[url]; await orcCache[url];
} }
/**
* Sends notes to Csound for rendering with MIDI semantics. The hap value is
* translated to these Csound pfields:
*
* p1 -- Csound instrument either as a number (1-based, can be a fraction),
* or as a string name.
* p2 -- time in beats (usually seconds) from start of performance.
* p3 -- duration in beats (usually seconds).
* p4 -- MIDI key number (as a real number, not an integer but in [0, 127].
* p5 -- MIDI velocity (as a real number, not an integer but in [0, 127].
* p6 -- Strudel controls, as a string.
*/
export const csoundm = register('csoundm', (instrument, pat) => {
let p1 = instrument;
if (typeof instrument === 'string') {
p1 = `"{instrument}"`;
}
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
return pat.onTrigger((tidal_time, hap) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
}
if (typeof hap.value !== 'object') {
throw new Error('csound only support objects as hap values');
}
// Time in seconds counting from now.
const p2 = tidal_time - getAudioContext().currentTime;
const p3 = hap.duration.valueOf() + 0;
const frequency = getFrequency(hap);
// Translate frequency to MIDI key number _without_ rounding.
const C4 = 261.62558;
let octave = Math.log(frequency / C4) / Math.log(2.0) + 8.0;
const p4 = octave * 12.0 - 36.0;
// We prefer floating point precision, but over the MIDI range [0, 127].
const p5 = 127 * (hap.context?.velocity ?? 0.9);
// The Strudel controls as a string.
const p6 = Object.entries({ ...hap.value, frequency })
.flat()
.join('/');
const i_statement = `i ${p1} ${p2} ${p3} ${p4} ${p5} "${p6}"`;
console.log('[csoundm]:', i_statement);
_csound.inputMessage(i_statement);
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/csound", "name": "@strudel.cycles/csound",
"version": "0.5.1", "version": "0.3.0",
"description": "csound bindings for strudel", "description": "csound bindings for strudel",
"main": "csound.mjs", "main": "csound.mjs",
"scripts": { "scripts": {
+2 -2
View File
@@ -4,9 +4,9 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { evaluate as _evaluate } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
import shapeshifter from './shapeshifter.mjs'; import shapeshifter from './shapeshifter.mjs';
export const evaluate = async (code) => { export const evaluate = async (code) => {
return _evaluate(code, shapeshifter); return core.evaluate(code, shapeshifter);
}; };
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/eval", "name": "@strudel.cycles/eval",
"version": "0.5.0", "version": "0.4.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/eval", "name": "@strudel.cycles/eval",
"version": "0.5.0", "version": "0.4.1",
"description": "Code evaluator for strudel", "description": "Code evaluator for strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+4 -3
View File
@@ -21,7 +21,7 @@ import {
import shiftCodegen from 'shift-codegen'; import shiftCodegen from 'shift-codegen';
const codegen = shiftCodegen.default || shiftCodegen; // parcel module resolution fuckup const codegen = shiftCodegen.default || shiftCodegen; // parcel module resolution fuckup
import * as strudel from '@strudel.cycles/core'; import strudel from '@strudel.cycles/core';
const { Pattern } = strudel; const { Pattern } = strudel;
@@ -225,8 +225,9 @@ function isPatternArg(parents) {
function hasModifierCall(parent) { function hasModifierCall(parent) {
// TODO: modifiers are more than composables, for example every is not composable but should be seen as modifier.. // TODO: modifiers are more than composables, for example every is not composable but should be seen as modifier..
// need all prototypes of Pattern // need all prototypes of Pattern
return parent?.type === 'StaticMemberExpression'; return (
// && Object.keys(Pattern.prototype.composable).includes(parent.property) parent?.type === 'StaticMemberExpression' && Object.keys(Pattern.prototype.composable).includes(parent.property)
);
} }
const factories = Object.keys(Pattern.prototype.factories).concat(['mini']); const factories = Object.keys(Pattern.prototype.factories).concat(['mini']);
+1 -1
View File
@@ -8,7 +8,7 @@ import { expect, describe, it } from 'vitest';
import { evaluate } from '../evaluate.mjs'; import { evaluate } from '../evaluate.mjs';
import { mini } from '@strudel.cycles/mini'; import { mini } from '@strudel.cycles/mini';
import * as strudel from '@strudel.cycles/core'; import strudel from '@strudel.cycles/core';
const { fastcat, evalScope } = strudel; const { fastcat, evalScope } = strudel;
describe('evaluate', async () => { describe('evaluate', async () => {
+2 -1
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as _WebMidi from 'webmidi'; import * as _WebMidi from 'webmidi';
import { Pattern, isPattern, isNote, getPlayableNoteValue, logger } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern, isPattern, isNote, getPlayableNoteValue, logger } = core;
import { getAudioContext } from '@strudel.cycles/webaudio'; import { getAudioContext } from '@strudel.cycles/webaudio';
// if you use WebMidi from outside of this package, make sure to import that instance: // if you use WebMidi from outside of this package, make sure to import that instance:
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/midi", "name": "@strudel.cycles/midi",
"version": "0.5.0", "version": "0.4.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/midi", "name": "@strudel.cycles/midi",
"version": "0.5.0", "version": "0.4.1",
"description": "Midi API for strudel", "description": "Midi API for strudel",
"main": "index.mjs", "main": "index.mjs",
"repository": { "repository": {
@@ -21,7 +21,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/tone": "^0.5.0", "@strudel.cycles/tone": "^0.4.1",
"tone": "^14.7.77", "tone": "^14.7.77",
"webmidi": "^3.0.21" "webmidi": "^3.0.21"
} }
+25 -45
View File
@@ -5,16 +5,17 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as krill from './krill-parser.js'; import * as krill from './krill-parser.js';
import * as strudel from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
// import { addMiniLocations } from '@strudel.cycles/eval/shapeshifter.mjs';
const { pure, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel; const { pure, Pattern, stack, slowcat, sequence, timeCat, silence, reify, rand, chooseInWith, Fraction } = core;
/* var _seedState = 0; var _seedState = 0;
const randOffset = 0.0002; const randOffset = 0.0002;
function _nextSeed() { function _nextSeed() {
return _seedState++; return _seedState++;
} */ }
const applyOptions = (parent) => (pat, i) => { const applyOptions = (parent) => (pat, i) => {
const ast = parent.source_[i]; const ast = parent.source_[i];
@@ -29,28 +30,10 @@ const applyOptions = (parent) => (pat, i) => {
case 'bjorklund': case 'bjorklund':
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation); return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
case 'degradeBy': case 'degradeBy':
// TODO: find out what is right here
// example:
/*
stack(
s("hh*8").degrade(),
s("[ht*8]?")
)
*/
// above example will only be in sync when _degradeBy is used...
// it also seems that the nextSeed will create undeterministic behaviour
// as it uses a global _seedState. This is probably the reason for
// https://github.com/tidalcycles/strudel/issues/245
// this is how it was:
/*
return reify(pat)._degradeByWith( return reify(pat)._degradeByWith(
strudel.rand.early(randOffset * _nextSeed()).segment(1), rand.early(randOffset * _nextSeed()).segment(1),
operator.arguments_.amount ?? 0.5, operator.arguments_.amount,
); );
*/
return reify(pat)._degradeBy(operator.arguments_.amount ?? 0.5);
// TODO: case 'fixed-step': "%" // TODO: case 'fixed-step': "%"
} }
console.warn(`operator "${operator.type_}" not implemented`); console.warn(`operator "${operator.type_}" not implemented`);
@@ -103,19 +86,17 @@ function resolveReplications(ast) {
}); });
} }
export function patternifyAST(ast, code) { export function patternifyAST(ast) {
switch (ast.type_) { switch (ast.type_) {
case 'pattern': { case 'pattern': {
resolveReplications(ast); resolveReplications(ast);
const children = ast.source_.map((child) => patternifyAST(child, code)).map(applyOptions(ast)); const children = ast.source_.map(patternifyAST).map(applyOptions(ast));
const alignment = ast.arguments_.alignment; const alignment = ast.arguments_.alignment;
if (alignment === 'v') { if (alignment === 'v') {
return stack(...children); return stack(...children);
} }
if (alignment === 'r') { if (alignment === 'r') {
// https://github.com/tidalcycles/strudel/issues/245#issuecomment-1345406422 return chooseInWith(rand.early(randOffset * _nextSeed()).segment(1), children);
// return strudel.chooseInWith(strudel.rand.early(randOffset * _nextSeed()).segment(1), children);
return strudel.chooseCycles(...children);
} }
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight); const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 't') { if (!weightedChildren && alignment === 't') {
@@ -136,6 +117,9 @@ export function patternifyAST(ast, code) {
return silence; return silence;
} }
if (typeof ast.source_ !== 'object') { if (typeof ast.source_ !== 'object') {
/* if (!addMiniLocations) {
return ast.source_;
} */
if (!ast.location_) { if (!ast.location_) {
console.warn('no location for', ast); console.warn('no location for', ast);
return ast.source_; return ast.source_;
@@ -144,20 +128,12 @@ export function patternifyAST(ast, code) {
const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_; const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
// the following line expects the shapeshifter append .withMiniLocation // the following line expects the shapeshifter append .withMiniLocation
// because location_ is only relative to the mini string, but we need it relative to whole code // because location_ is only relative to the mini string, but we need it relative to whole code
// make sure whitespaces are not part of the highlight: return pure(value).withLocation([start.line, start.column, start.offset], [end.line, end.column, end.offset]);
const actual = code?.split('').slice(start.offset, end.offset).join('');
const [offsetStart = 0, offsetEnd = 0] = actual
? actual.split(ast.source_).map((p) => p.split('').filter((c) => c === ' ').length)
: [];
return pure(value).withLocation(
[start.line, start.column + offsetStart, start.offset + offsetStart],
[start.line, end.column - offsetEnd, end.offset - offsetEnd],
);
} }
return patternifyAST(ast.source_, code); return patternifyAST(ast.source_);
} }
case 'stretch': case 'stretch':
return patternifyAST(ast.source_, code).slow(ast.arguments_.amount); return patternifyAST(ast.source_).slow(ast.arguments_.amount);
/* case 'scale': /* case 'scale':
let [tonic, scale] = Scale.tokenize(ast.arguments_.scale); let [tonic, scale] = Scale.tokenize(ast.arguments_.scale);
const intervals = Scale.get(scale).intervals; const intervals = Scale.get(scale).intervals;
@@ -189,9 +165,8 @@ export function patternifyAST(ast, code) {
// mini notation only (wraps in "") // mini notation only (wraps in "")
export const mini = (...strings) => { export const mini = (...strings) => {
const pats = strings.map((str) => { const pats = strings.map((str) => {
const code = `"${str}"`; const ast = krill.parse(`"${str}"`);
const ast = krill.parse(code); return patternifyAST(ast);
return patternifyAST(ast, code);
}); });
return sequence(...pats); return sequence(...pats);
}; };
@@ -200,9 +175,14 @@ export const mini = (...strings) => {
export const h = (string) => { export const h = (string) => {
const ast = krill.parse(string); const ast = krill.parse(string);
// console.log('ast', ast); // console.log('ast', ast);
return patternifyAST(ast, string); return patternifyAST(ast);
}; };
// shorthand for mini
Pattern.prototype.define('mini', mini, { composable: true });
Pattern.prototype.define('m', mini, { composable: true });
Pattern.prototype.define('h', h, { composable: true });
export function minify(thing) { export function minify(thing) {
if (typeof thing === 'string') { if (typeof thing === 'string') {
return mini(thing); return mini(thing);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/mini", "name": "@strudel.cycles/mini",
"version": "0.5.0", "version": "0.4.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/mini", "name": "@strudel.cycles/mini",
"version": "0.5.0", "version": "0.4.1",
"description": "Mini notation for strudel", "description": "Mini notation for strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
@@ -27,8 +27,8 @@
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "^0.4.1", "@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/eval": "^0.5.0", "@strudel.cycles/eval": "^0.4.1",
"@strudel.cycles/tone": "^0.5.0" "@strudel.cycles/tone": "^0.4.1"
}, },
"devDependencies": { "devDependencies": {
"peggy": "^2.0.1" "peggy": "^2.0.1"
+2 -2
View File
@@ -77,7 +77,7 @@ describe('mini', () => {
expect(haps.length < 230).toBe(true); expect(haps.length < 230).toBe(true);
// 'Had too many cycles remaining after degradeBy 0.8'); // 'Had too many cycles remaining after degradeBy 0.8');
}); });
/*it('supports the random choice operator ("|") with nesting', () => { it('supports the random choice operator ("|") with nesting', () => {
const numCycles = 900; const numCycles = 900;
const haps = mini('a | [b | c] | [d | e | f]').queryArc(0, numCycles); const haps = mini('a | [b | c] | [d | e | f]').queryArc(0, numCycles);
// Should have about 1/3 a, 1/6 each of b | c, and 1/9 each of d | e | f. // Should have about 1/3 a, 1/6 each of b | c, and 1/9 each of d | e | f.
@@ -103,5 +103,5 @@ describe('mini', () => {
// PRNG, this test should succeed // PRNG, this test should succeed
expect(chisq <= 15.086).toBe(true); expect(chisq <= 15.086).toBe(true);
// assert(chisq <= 15.086, chisq + ' was expected to be less than 15.086 under chi-squared test'); // assert(chisq <= 15.086, chisq + ' was expected to be less than 15.086 under chi-squared test');
});*/ });
}); });
+2 -1
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import OSC from 'osc-js'; import OSC from 'osc-js';
import { logger, parseNumeral, Pattern } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern, logger, parseNumeral } = core;
let connection; // Promise<OSC> let connection; // Promise<OSC>
function connect() { function connect() {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/osc", "name": "@strudel.cycles/osc",
"version": "0.4.0", "version": "0.3.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/osc", "name": "@strudel.cycles/osc",
"version": "0.4.0", "version": "0.3.1",
"description": "OSC messaging for strudel", "description": "OSC messaging for strudel",
"main": "osc.mjs", "main": "osc.mjs",
"scripts": { "scripts": {
+2
View File
@@ -25,6 +25,8 @@ const config = {
}, },
}; };
const osc = new OSC({ plugin: new OSC.BridgePlugin(config) }); const osc = new OSC({ plugin: new OSC.BridgePlugin(config) });
osc.open(); // start a WebSocket server on port 8080 osc.open(); // start a WebSocket server on port 8080
+3 -2
View File
@@ -13,12 +13,13 @@ npm i @strudel.cycles/react
Here is a minimal example of how to set up a MiniRepl: Here is a minimal example of how to set up a MiniRepl:
```jsx ```jsx
import { evalScope, controls } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const {evalScope} = core;
import { MiniRepl } from '@strudel.cycles/react'; import { MiniRepl } from '@strudel.cycles/react';
import { prebake } from '../repl/src/prebake.mjs'; import { prebake } from '../repl/src/prebake.mjs';
evalScope( evalScope(
controls, core,
import('@strudel.cycles/core'), import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'), import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'), import('@strudel.cycles/mini'),
+1 -1
View File
File diff suppressed because one or more lines are too long
+14 -13
View File
@@ -7,7 +7,7 @@ import { tags as r } from "@lezer/highlight";
import { createTheme as Z } from "@uiw/codemirror-themes"; import { createTheme as Z } from "@uiw/codemirror-themes";
import { useInView as ee } from "react-hook-inview"; import { useInView as ee } from "react-hook-inview";
import { webaudioOutput as te, getAudioContext as re } from "@strudel.cycles/webaudio"; import { webaudioOutput as te, getAudioContext as re } from "@strudel.cycles/webaudio";
import { repl as oe } from "@strudel.cycles/core"; import oe from "@strudel.cycles/core";
import { transpiler as ne } from "@strudel.cycles/transpiler"; import { transpiler as ne } from "@strudel.cycles/transpiler";
const ae = Z({ const ae = Z({
theme: "dark", theme: "dark",
@@ -177,7 +177,8 @@ function O({ type: e }) {
function Ee(e) { function Ee(e) {
return L(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), _((t) => window.postMessage(t, "*"), []); return L(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), _((t) => window.postMessage(t, "*"), []);
} }
function we({ const we = oe.repl;
function ye({
defaultOutput: e, defaultOutput: e,
interval: t, interval: t,
getTime: o, getTime: o,
@@ -189,8 +190,8 @@ function we({
onEvalError: c, onEvalError: c,
onToggle: i onToggle: i
}) { }) {
const m = V(() => ye(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V( const m = V(() => ke(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V(
() => oe({ () => we({
interval: t, interval: t,
defaultOutput: e, defaultOutput: e,
onSchedulerError: g, onSchedulerError: g,
@@ -243,11 +244,11 @@ function we({
} }
}; };
} }
function ye() { function ke() {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
} }
const ke = () => re().currentTime; const _e = () => re().currentTime;
function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) { function Te({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
const { const {
code: s, code: s,
setCode: u, setCode: u,
@@ -261,10 +262,10 @@ function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
scheduler: C, scheduler: C,
togglePlay: N, togglePlay: N,
stop: p stop: p
} = we({ } = ye({
initialCode: e, initialCode: e,
defaultOutput: te, defaultOutput: te,
getTime: ke getTime: _e
}), [y, M] = w(), [S, k] = ee({ }), [y, M] = w(), [S, k] = ee({
threshold: 0.01 threshold: 0.01
}), D = H(), F = V(() => ((k || !t) && (D.current = !0), k || D.current), [k, t]); }), D = H(), F = V(() => ((k || !t) && (D.current = !0), k || D.current), [k, t]);
@@ -307,14 +308,14 @@ function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
onViewChanged: M onViewChanged: M
}))); })));
} }
const Te = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); const Ve = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
export { export {
de as CodeMirror, de as CodeMirror,
Se as MiniRepl, Te as MiniRepl,
K as cx, K as cx,
ce as flash, ce as flash,
ue as useHighlighting, ue as useHighlighting,
Te as useKeydown, Ve as useKeydown,
Ee as usePostMessage, Ee as usePostMessage,
we as useStrudel ye as useStrudel
}; };
@@ -1,4 +1,5 @@
import { evalScope, controls } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { evalScope } = core;
import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio'; import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import CodeMirror, { flash } from '../../../src/components/CodeMirror6'; import CodeMirror, { flash } from '../../../src/components/CodeMirror6';
@@ -10,8 +11,8 @@ import './style.css';
// TODO: only import stuff when play is pressed? // TODO: only import stuff when play is pressed?
evalScope( evalScope(
controls, core,
import('@strudel.cycles/core'), // import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'), // import('@strudel.cycles/tone'),
// import('@strudel.cycles/midi'), // TODO: find out why midi loads tone.js // import('@strudel.cycles/midi'), // TODO: find out why midi loads tone.js
import('@strudel.cycles/tonal'), import('@strudel.cycles/tonal'),
@@ -1,7 +1,7 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()]
}); })
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /> --> <!-- <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Strudel React Components</title> <title>Strudel React Components</title>
</head> </head>
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/react", "name": "@strudel.cycles/react",
"version": "0.5.0", "version": "0.4.2",
"description": "React components for strudel", "description": "React components for strudel",
"main": "dist/index.cjs.js", "main": "dist/index.cjs.js",
"module": "dist/index.es.js", "module": "dist/index.es.js",
@@ -40,9 +40,9 @@
"dependencies": { "dependencies": {
"@codemirror/lang-javascript": "^6.1.1", "@codemirror/lang-javascript": "^6.1.1",
"@strudel.cycles/core": "^0.4.1", "@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/tone": "^0.5.0", "@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/transpiler": "^0.5.0", "@strudel.cycles/transpiler": "^0.4.1",
"@strudel.cycles/webaudio": "^0.5.0", "@strudel.cycles/webaudio": "^0.4.2",
"@uiw/codemirror-themes": "^4.12.4", "@uiw/codemirror-themes": "^4.12.4",
"@uiw/react-codemirror": "^4.12.4", "@uiw/react-codemirror": "^4.12.4",
"react-hook-inview": "^4.5.0" "react-hook-inview": "^4.5.0"
+1 -1
View File
@@ -9,4 +9,4 @@ module.exports = {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; }
+2 -1
View File
@@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { MiniRepl } from './components/MiniRepl'; import { MiniRepl } from './components/MiniRepl';
import 'tailwindcss/tailwind.css'; import 'tailwindcss/tailwind.css';
import { controls, evalScope } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const {controls, evalScope} = core;
evalScope( evalScope(
controls, controls,
+2 -1
View File
@@ -1,5 +1,6 @@
import { useRef, useCallback, useEffect, useMemo, useState } from 'react'; import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
import { repl } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const repl = core.repl
import { transpiler } from '@strudel.cycles/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import usePostMessage from './usePostMessage.mjs'; import usePostMessage from './usePostMessage.mjs';
+4 -9
View File
@@ -1,10 +1,5 @@
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom'
import App from './App'; import App from './App'
ReactDOM.render( ReactDOM.render(<React.StrictMode><App /></React.StrictMode>,document.getElementById('root'))
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/serial", "name": "@strudel.cycles/serial",
"version": "0.3.0", "version": "0.2.0",
"description": "Webserial API for strudel", "description": "Webserial API for strudel",
"main": "serial.mjs", "main": "serial.mjs",
"repository": { "repository": {
+13 -13
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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, isPattern } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern, isPattern } = core;
var writeMessage; var writeMessage;
var choosing = false; var choosing = false;
@@ -23,16 +24,17 @@ export async function getWriter(br = 38400) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const writer = port.writable.getWriter(); const writer = port.writable.getWriter();
writeMessage = function (message, chk) { writeMessage = function (message, chk) {
const encoded = encoder.encode(message); const encoded = encoder.encode(message)
if (!chk) { if (!chk) {
writer.write(encoded); writer.write(encoded);
} else { }
else {
const bytes = new Uint8Array(4); const bytes = new Uint8Array(4);
bytes[0] = 124; // | symbol bytes[0] = 124; // | symbol
bytes[1] = (chk >> 8) & 0xff; bytes[1] = (chk >> 8) & 0xFF;
bytes[2] = chk & 0xff; bytes[2] = chk & 0xFF;
bytes[3] = 59; // semicolon bytes[3] = 59; // semicolon
const withchk = new Uint8Array(encoded.length + 4); const withchk = new Uint8Array(encoded.length+4)
withchk.set(encoded); withchk.set(encoded);
withchk.set(bytes, encoded.length); withchk.set(bytes, encoded.length);
writer.write(withchk); writer.write(withchk);
@@ -47,12 +49,12 @@ const latency = 0.1;
// crc16 (CCITT-FALSE) https://gist.github.com/tijnkooijmans/10981093 // crc16 (CCITT-FALSE) https://gist.github.com/tijnkooijmans/10981093
function crc16(data) { function crc16(data) {
const length = data.length; const length = data.length
if (length == 0) { if (length == 0) {
return 0; return 0;
} }
var crc = 0xffff; var crc = 0xFFFF;
for (var i = 0; i < length; ++i) { for (var i = 0; i < length; ++i) {
crc ^= data.charCodeAt(i) << 8; crc ^= data.charCodeAt(i) << 8;
for (var j = 0; j < 8; ++j) { for (var j = 0; j < 8; ++j) {
@@ -63,7 +65,7 @@ function crc16(data) {
return crc & 0xffff; return crc & 0xffff;
} }
Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids = false) { Pattern.prototype.serial = function (br=38400,sendcrc=false,singlecharids=false) {
return this.withHap((hap) => { return this.withHap((hap) => {
if (!writeMessage) { if (!writeMessage) {
getWriter(br); getWriter(br);
@@ -95,7 +97,7 @@ Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids
} }
message += ')'; message += ')';
if (sendcrc) { if (sendcrc) {
chk = crc16(message); chk = crc16(message)
} }
} else { } else {
for (const [key, val] of Object.entries(hap.value)) { for (const [key, val] of Object.entries(hap.value)) {
@@ -107,9 +109,7 @@ Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids
} }
const offset = (time - currentTime + latency) * 1000; const offset = (time - currentTime + latency) * 1000;
window.setTimeout(function () { window.setTimeout(function () {writeMessage(message, chk)}, offset);
writeMessage(message, chk);
}, offset);
}; };
return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true }); return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true });
}); });
+2 -1
View File
@@ -1,4 +1,5 @@
import { toMidi } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const toMidi = core.toMidi;
let loadCache = {}; let loadCache = {};
async function loadFont(name) { async function loadFont(name) {
+3 -3
View File
@@ -42,7 +42,7 @@ export const instruments = [
'0031_FluidR3_GM_sf2_file', '0031_FluidR3_GM_sf2_file',
'0031_GeneralUserGS_sf2_file', '0031_GeneralUserGS_sf2_file',
'0031_SoundBlasterOld_sf2', // pianos until here '0031_SoundBlasterOld_sf2', // pianos until here
'0040_Aspirin_sf2_file', '0040_Aspirin_sf2_file',
'0040_Chaos_sf2_file', '0040_Chaos_sf2_file',
'0040_FluidR3_GM_sf2_file', // rhodes '0040_FluidR3_GM_sf2_file', // rhodes
'0040_GeneralUserGS_sf2_file', // staccato rhodes '0040_GeneralUserGS_sf2_file', // staccato rhodes
@@ -59,7 +59,7 @@ export const instruments = [
'0046_GeneralUserGS_sf2_file', // ? '0046_GeneralUserGS_sf2_file', // ?
'0050_Aspirin_sf2_file', // glass organ '0050_Aspirin_sf2_file', // glass organ
'0050_Chaos_sf2_file', // short glass organ '0050_Chaos_sf2_file', // short glass organ
'0050_FluidR3_GM_sf2_file', // long glass organ ! '0050_FluidR3_GM_sf2_file',// long glass organ !
'0050_GeneralUserGS_sf2_file', // short glass organ '0050_GeneralUserGS_sf2_file', // short glass organ
'0050_JCLive_sf2_file', // glass organ '0050_JCLive_sf2_file', // glass organ
'0050_SBLive_sf2', // ? '0050_SBLive_sf2', // ?
@@ -1635,7 +1635,7 @@ export const drums = [
'81_4_Chaos_sf2_file', '81_4_Chaos_sf2_file',
]; ];
// see https://www.midi.org/specifications-old/item/gm-level-1-sound-set // see https://www.midi.org/specifications-old/item/gm-level-1-sound-set
export const instrumentNames = []; export const instrumentNames = [];
instrumentNames[0] = 'Acoustic Grand Piano: Piano'; instrumentNames[0] = 'Acoustic Grand Piano: Piano';
instrumentNames[1] = 'Bright Acoustic Piano: Piano'; instrumentNames[1] = 'Bright Acoustic Piano: Piano';
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/soundfonts", "name": "@strudel.cycles/soundfonts",
"version": "0.5.0", "version": "0.4.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/soundfonts", "name": "@strudel.cycles/soundfonts",
"version": "0.5.0", "version": "0.4.2",
"description": "Soundsfont support for strudel", "description": "Soundsfont support for strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
@@ -23,7 +23,7 @@
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "^0.4.1", "@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/webaudio": "^0.5.0", "@strudel.cycles/webaudio": "^0.4.2",
"sfumato": "^0.1.2", "sfumato": "^0.1.2",
"soundfont2": "^0.4.0" "soundfont2": "^0.4.0"
}, },
+2 -1
View File
@@ -1,4 +1,5 @@
import { Pattern, getPlayableNoteValue, toMidi } from '@strudel.cycles/core'; import strudel from '@strudel.cycles/core';
const { Pattern, getPlayableNoteValue, toMidi } = strudel;
import { getAudioContext } from '@strudel.cycles/webaudio'; import { getAudioContext } from '@strudel.cycles/webaudio';
import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato'; import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato';
-3
View File
@@ -1,5 +1,2 @@
import './tonal.mjs'; import './tonal.mjs';
import './voicings.mjs'; import './voicings.mjs';
export * from './tonal.mjs';
export * from './voicings.mjs';
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/tonal", "name": "@strudel.cycles/tonal",
"version": "0.5.0", "version": "0.4.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/tonal", "name": "@strudel.cycles/tonal",
"version": "0.5.0", "version": "0.4.1",
"description": "Tonal functions for strudel", "description": "Tonal functions for strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+2 -1
View File
@@ -7,7 +7,8 @@ This program is free software: you can redistribute it and/or modify it under th
// import { strict as assert } from 'assert'; // import { strict as assert } from 'assert';
import '../tonal.mjs'; // need to import this to add prototypes import '../tonal.mjs'; // need to import this to add prototypes
import { pure } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { pure } = core;
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
describe('tonal', () => { describe('tonal', () => {
+18 -11
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Note, Interval, Scale } from '@tonaljs/tonal'; import { Note, Interval, Scale } from '@tonaljs/tonal';
import { register, _mod } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern, pattern, mod } = core;
// transpose note inside scale by offset steps // transpose note inside scale by offset steps
// function scaleOffset(scale: string, offset: number, note: string) { // function scaleOffset(scale: string, offset: number, note: string) {
@@ -29,7 +30,7 @@ function scaleOffset(scale, offset, note) {
// TODO: find way to do this smarter // TODO: find way to do this smarter
while (Math.abs(i - noteIndex) < Math.abs(offset)) { while (Math.abs(i - noteIndex) < Math.abs(offset)) {
i += direction; i += direction;
const index = _mod(i, notes.length); const index = mod(i, notes.length);
if (direction < 0 && n[0] === 'C') { if (direction < 0 && n[0] === 'C') {
o += direction; o += direction;
} }
@@ -74,8 +75,8 @@ function scaleOffset(scale, offset, note) {
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note() * "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
*/ */
export const transpose = register('transpose', function (intervalOrSemitones, pat) { Pattern.prototype._transpose = function (intervalOrSemitones) {
return pat.withHap((hap) => { return this.withHap((hap) => {
const interval = !isNaN(Number(intervalOrSemitones)) const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones /* as number */) ? Interval.fromSemitones(intervalOrSemitones /* as number */)
: String(intervalOrSemitones); : String(intervalOrSemitones);
@@ -87,9 +88,11 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3 // tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval))); return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval)));
}); });
}); };
// example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2)) // example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2))
// TODO: add Pattern.define(name, function, options) that handles all the meta programming stuff
// TODO: find out how to patternify this function when it's standalone
// e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or // e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or // or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
@@ -108,8 +111,8 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
* .note() * .note()
*/ */
export const scaleTranspose = register('scaleTranspose', function (offset /* : number | string */, pat) { Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
return pat.withHap((hap) => { return this.withHap((hap) => {
if (!hap.context.scale) { if (!hap.context.scale) {
throw new Error('can only use scaleTranspose after .scale'); throw new Error('can only use scaleTranspose after .scale');
} }
@@ -118,7 +121,7 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n
} }
return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value)); return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value));
}); });
}); };
/** /**
* Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}. * Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
@@ -139,8 +142,8 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n
* .note() * .note()
*/ */
export const scale = register('scale', function (scale /* : string */, pat) { Pattern.prototype._scale = function (scale /* : string */) {
return pat.withHap((hap) => { return this.withHap((hap) => {
let note = hap.value; let note = hap.value;
const asNumber = Number(note); const asNumber = Number(note);
if (!isNaN(asNumber)) { if (!isNaN(asNumber)) {
@@ -150,4 +153,8 @@ export const scale = register('scale', function (scale /* : string */, pat) {
} }
return hap.withValue(() => note).setContext({ ...hap.context, scale }); return hap.withValue(() => note).setContext({ ...hap.context, scale });
}); });
}); };
Pattern.prototype.define('transpose', (a, pat) => pat.transpose(a), { composable: true, patternified: true });
Pattern.prototype.define('scale', (a, pat) => pat.scale(a), { composable: true, patternified: true });
Pattern.prototype.define('scaleTranspose', (a, pat) => pat.scaleTranspose(a), { composable: true, patternified: true });
+40 -62
View File
@@ -4,49 +4,33 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { stack, register } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern, stack, reify, Hap } = core
import _voicings from 'chord-voicings'; import _voicings from 'chord-voicings';
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup
export const voicingRegistry = { const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) =>
lefthand: { dictionary: lefthand, range: ['F3', 'A4'] }, dictionaryVoicing({
};
export const setVoicingRange = (name, range) => addVoicings(name, voicingRegistry[name].dictionary, range);
/**
* Adds a new custom voicing dictionary.
*
* @name addVoicings
* @memberof Pattern
* @param {string} name identifier for the voicing dictionary
* @param {Object} dictionary maps chord symbol to possible voicings
* @param {Array} range min, max note
* @returns Pattern
* @example
* addVoicings('cookie', {
* 7: ['3M 7m 9M 12P 15P', '7m 10M 13M 16M 19P'],
* '^7': ['3M 6M 9M 12P 14M', '7M 10M 13M 16M 19P'],
* m7: ['8P 11P 14m 17m 19P', '5P 8P 11P 14m 17m'],
* m7b5: ['3m 5d 8P 11P 14m', '5d 8P 11P 14m 17m'],
* o7: ['3m 6M 9M 11A 15P'],
* '7alt': ['3M 7m 10m 13m 15P'],
* '7#11': ['7m 10m 13m 15P 17m'],
* }, ['C3', 'C6'])
* "<C^7 A7 Dm7 G7>".voicings('cookie').note()
*/
export const addVoicings = (name, dictionary, range = ['F3', 'A4']) => {
Object.assign(voicingRegistry, { [name]: { dictionary, range } });
};
const getVoicing = (chord, dictionaryName, lastVoicing) => {
const { dictionary, range } = voicingRegistry[dictionaryName];
return dictionaryVoicing({
chord, chord,
dictionary, dictionary: lefthand,
range, range,
picker: minTopNoteDiff, picker: minTopNoteDiff,
lastVoicing, lastVoicing,
}); });
// const Pattern = _Pattern;
Pattern.prototype.fmapNested = function (func) {
return new Pattern((span) =>
this.query(span)
.map((event) =>
reify(func(event))
.query(span)
.map((hap) => new Hap(event.whole, event.part, hap.value, hap.context)),
)
.flat(),
);
}; };
/** /**
@@ -55,38 +39,32 @@ const getVoicing = (chord, dictionaryName, lastVoicing) => {
* *
* @name voicings * @name voicings
* @memberof Pattern * @memberof Pattern
* @param {string} dictionary which voicing dictionary to use. * @param {range} range note range for possible voicings (optional, defaults to `['F3', 'A4']`)
* @returns Pattern * @returns Pattern
* @example * @example
* stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note() * stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note()
*/ */
let lastVoicing; // this now has to be global until another solution is found :-/ Pattern.prototype.voicings = function (range) {
// it used to be local to the voicings function at evaluation time let lastVoicing;
// but since register will patternify by default, means that if (!range?.length) {
// the function is called over and over again, resetting the lastVoicing variables // allows to pass empty array, if too lazy to specify range
export const voicings = register('voicings', function (dictionary, pat) { range = ['F3', 'A4'];
return pat }
.fmap((value) => { return this.fmapNested((event) => {
lastVoicing = getVoicing(value, dictionary, lastVoicing); lastVoicing = getVoicing(event.value, lastVoicing, range);
return stack(...lastVoicing); return stack(...lastVoicing).withContext(() => ({
}) locations: event.context.locations || [],
.outerJoin(); }));
}); });
};
/** Pattern.prototype._rootNotes = function (octave = 2) {
* Maps the chords of the incoming pattern to root notes in the given octave. return this.fmap((value) => {
*
* @name rootNotes
* @memberof Pattern
* @param {octave} octave octave to use
* @returns Pattern
* @example
* "<C^7 A7 Dm7 G7>".rootNotes(2).note()
*/
export const rootNotes = register('rootNotes', function (octave, pat) {
return pat.fmap((value) => {
const root = value.match(/^([a-gA-G][b#]?).*$/)[1]; const root = value.match(/^([a-gA-G][b#]?).*$/)[1];
return root + octave; return root + octave;
}); });
}); };
Pattern.prototype.define('voicings', (range, pat) => pat.voicings(range), { composable: true });
Pattern.prototype.define('rootNotes', (oct, pat) => pat.rootNotes(oct), { composable: true, patternified: true });
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/tone", "name": "@strudel.cycles/tone",
"version": "0.5.0", "version": "0.4.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/tone", "name": "@strudel.cycles/tone",
"version": "0.5.0", "version": "0.4.1",
"description": "Tone.js API for strudel", "description": "Tone.js API for strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+2 -1
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import '../tone.mjs'; import '../tone.mjs';
import { pure } from '@strudel.cycles/core'; import strudel from '@strudel.cycles/core';
const { pure } = strudel;
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
describe('tone', () => { describe('tone', () => {
+11 -4
View File
@@ -4,15 +4,20 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { register } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern } = core;
import * as _Tone from 'tone'; import * as _Tone from 'tone';
// import Tone from here, to make sure to get the same AudioContext // import Tone from here, to make sure to get the same AudioContext
export const Tone = _Tone; export const Tone = _Tone;
const { const {
AutoFilter,
Destination,
Filter, Filter,
Gain, Gain,
isNote,
Synth, Synth,
PolySynth, PolySynth,
MembraneSynth, MembraneSynth,
@@ -46,8 +51,8 @@ export const getDefaultSynth = () => {
// https://www.charlie-roberts.com/gibberish/playground/ // https://www.charlie-roberts.com/gibberish/playground/
// with this function, you can play the pattern with any tone synth // with this function, you can play the pattern with any tone synth
export const tone = register('tone', function (instrument, pat) { Pattern.prototype.tone = function (instrument) {
return pat.onTrigger((time, hap) => { return this.onTrigger((time, hap) => {
let note; let note;
let velocity = hap.context?.velocity ?? 0.75; let velocity = hap.context?.velocity ?? 0.75;
if (instrument instanceof PluckSynth) { if (instrument instanceof PluckSynth) {
@@ -71,7 +76,9 @@ export const tone = register('tone', function (instrument, pat) {
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity); instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
} }
}); });
}); };
Pattern.prototype.define('tone', (type, pat) => pat.tone(type), { composable: true, patternified: false });
// synth helpers // synth helpers
export const amsynth = (options) => new AMSynth(options); export const amsynth = (options) => new AMSynth(options);
+2 -2
View File
@@ -1,5 +1,5 @@
import { evaluate as _evaluate } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
import { transpiler } from './transpiler.mjs'; import { transpiler } from './transpiler.mjs';
export * from './transpiler.mjs'; export * from './transpiler.mjs';
export const evaluate = (code) => _evaluate(code, transpiler); export const evaluate = (code) => core.evaluate(code, transpiler);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/transpiler", "name": "@strudel.cycles/transpiler",
"version": "0.5.0", "version": "0.4.1",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.", "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs", "main": "index.mjs",
"scripts": { "scripts": {
+2 -1
View File
@@ -1,7 +1,8 @@
import escodegen from 'escodegen'; import escodegen from 'escodegen';
import { parse } from 'acorn'; import { parse } from 'acorn';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { isNote } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { isNote } = core;
export function transpiler(input, options = {}) { export function transpiler(input, options = {}) {
const { wrapAsync = false, addReturn = true, simpleLocs = false } = options; const { wrapAsync = false, addReturn = true, simpleLocs = false } = options;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/webaudio", "name": "@strudel.cycles/webaudio",
"version": "0.5.0", "version": "0.4.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/webaudio", "name": "@strudel.cycles/webaudio",
"version": "0.5.0", "version": "0.4.2",
"description": "Web Audio helpers for Strudel", "description": "Web Audio helpers for Strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+4 -6
View File
@@ -1,4 +1,5 @@
import { logger, toMidi, valueToMidi } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { logger, toMidi } = core;
import { getAudioContext } from './index.mjs'; import { getAudioContext } from './index.mjs';
const bufferCache = {}; // string: Promise<ArrayBuffer> const bufferCache = {}; // string: Promise<ArrayBuffer>
@@ -20,12 +21,9 @@ function humanFileSize(bytes, si) {
return bytes.toFixed(1) + ' ' + units[u]; return bytes.toFixed(1) + ' ' + units[u];
} }
export const getSampleBufferSource = async (s, n, note, speed, freq) => { export const getSampleBufferSource = async (s, n, note, speed) => {
let transpose = 0; let transpose = 0;
if (freq !== undefined && note !== undefined) { let midi = typeof note === 'string' ? toMidi(note) : note || 36;
logger('[sampler] hap has note and freq. ignoring note', 'warning');
}
let midi = valueToMidi({ freq, note }, 36);
transpose = midi - 36; // C3 is middle C transpose = midi - 36; // C3 is middle C
const ac = getAudioContext(); const ac = getAudioContext();
+4 -4
View File
@@ -4,8 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 * as strudel from '@strudel.cycles/core'; import strudel from '@strudel.cycles/core';
import { fromMidi, logger, toMidi } from '@strudel.cycles/core'; const { fromMidi, logger, toMidi } = strudel;
import './feedbackdelay.mjs'; import './feedbackdelay.mjs';
import './reverb.mjs'; import './reverb.mjs';
import { getSampleBufferSource } from './sampler.mjs'; import { getSampleBufferSource } from './sampler.mjs';
@@ -288,10 +288,10 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
if (soundfont) { if (soundfont) {
// is soundfont // is soundfont
bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac, freq); bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac);
} else { } else {
// is sample from loaded samples(..) // is sample from loaded samples(..)
bufferSource = await getSampleBufferSource(s, n, note, speed, freq); bufferSource = await getSampleBufferSource(s, n, note, speed);
} }
// asny stuff above took too long? // asny stuff above took too long?
if (ac.currentTime > t) { if (ac.currentTime > t) {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/webdirt", "name": "@strudel.cycles/webdirt",
"version": "0.5.0", "version": "0.4.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/webdirt", "name": "@strudel.cycles/webdirt",
"version": "0.5.0", "version": "0.4.1",
"description": "WebDirt integration for Strudel", "description": "WebDirt integration for Strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
-2
View File
@@ -1,4 +1,2 @@
import './xen.mjs'; import './xen.mjs';
import './tune.mjs'; import './tune.mjs';
export * from './xen.mjs';
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/xen", "name": "@strudel.cycles/xen",
"version": "0.5.0", "version": "0.4.1",
"description": "Xenharmonic API for strudel", "description": "Xenharmonic API for strudel",
"main": "index.mjs", "main": "index.mjs",
"scripts": { "scripts": {
+9 -6
View File
@@ -5,16 +5,19 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import Tune from './tunejs.js'; import Tune from './tunejs.js';
import { register } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern } = core;
export const tune = register('tune', (scale, pat) => { Pattern.prototype._tune = function (scale, tonic = 220) {
const tune = new Tune(); const tune = new Tune();
if (!tune.isValidScale(scale)) { if (!tune.isValidScale(scale)) {
throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html'); throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
} }
tune.loadScale(scale); tune.loadScale(scale);
tune.tonicize(1); tune.tonicize(tonic);
return pat.withHap((hap) => { return this._asNumber()._withHap((hap) => {
return hap.withValue(() => tune.note(hap.value)); return hap.withValue(() => tune.note(hap.value)).setContext({ ...hap.context, type: 'frequency' });
}); });
}); };
Pattern.prototype.define('tune', (scale, pat) => pat.tune(scale), { composable: true, patternified: true });
+16 -12
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { register, _mod, parseNumeral } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern, mod } = core;
export function edo(name) { export function edo(name) {
if (!/^[1-9]+[0-9]*edo$/.test(name)) { if (!/^[1-9]+[0-9]*edo$/.test(name)) {
@@ -42,23 +43,26 @@ function getXenScale(scale, indices) {
} }
function xenOffset(xenScale, offset, index = 0) { function xenOffset(xenScale, offset, index = 0) {
const i = _mod(index + offset, xenScale.length); const i = mod(index + offset, xenScale.length);
const oct = Math.floor(offset / xenScale.length); const oct = Math.floor(offset / xenScale.length);
return xenScale[i] * Math.pow(2, oct); return xenScale[i] * Math.pow(2, oct);
} }
// scaleNameOrRatios: string || number[], steps?: number // scaleNameOrRatios: string || number[], steps?: number
export const xen = register('xen', function (scaleNameOrRatios, pat) { Pattern.prototype._xen = function (scaleNameOrRatios, steps) {
return pat.withHap((hap) => { return this._asNumber()._withHap((hap) => {
const scale = getXenScale(scaleNameOrRatios); const scale = getXenScale(scaleNameOrRatios);
const frequency = xenOffset(scale, parseNumeral(hap.value)); steps = steps || scale.length;
return hap.withValue(() => frequency); const frequency = xenOffset(scale, hap.value);
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
}); });
}); };
export const tuning = register('tuning', function (ratios, pat) { Pattern.prototype.tuning = function (steps) {
return pat.withHap((hap) => { return this._asNumber()._withHap((hap) => {
const frequency = xenOffset(ratios, parseNumeral(hap.value)); const frequency = xenOffset(steps, hap.value);
return hap.withValue(() => frequency); return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
}); });
}); };
Pattern.prototype.define('xen', (scale, pat) => pat.xen(scale), { composable: true, patternified: true });
// Pattern.prototype.define('tuning', (scale, pat) => pat.xen(scale), { composable: true, patternified: false });
+1 -1
View File
@@ -9,4 +9,4 @@ module.exports = {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; }
+5 -2
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { cleanupDraw, cleanupUi, controls, evalScope, logger } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { cleanupDraw, cleanupUi, evalScope, logger } = core;
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react'; import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
import { import {
getAudioContext, getAudioContext,
@@ -33,6 +34,7 @@ const supabase = createClient(
const modules = [ const modules = [
import('@strudel.cycles/core'), import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'), import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'), import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'), import('@strudel.cycles/midi'),
@@ -45,7 +47,8 @@ const modules = [
]; ];
evalScope( evalScope(
controls, // sadly, this cannot be exported from core direclty // Tone,
core,
{ WebDirt }, { WebDirt },
...modules, ...modules,
); );
+2 -1
View File
@@ -1,5 +1,6 @@
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon'; import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
import { logger } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { logger } = core;
import { cx } from '@strudel.cycles/react'; import { cx } from '@strudel.cycles/react';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import React, { useContext, useCallback, useLayoutEffect, useRef, useState } from 'react'; import React, { useContext, useCallback, useLayoutEffect, useRef, useState } from 'react';
+5 -3
View File
@@ -1,11 +1,13 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Droid Sans', 'Helvetica Neue', sans-serif; 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
} }
+4 -3
View File
@@ -1,4 +1,5 @@
import { Pattern, toMidi, valueToMidi } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
const { Pattern, toMidi } = core;
import { samples } from '@strudel.cycles/webaudio'; import { samples } from '@strudel.cycles/webaudio';
export async function prebake({ isMock = false, baseDir = '.' } = {}) { export async function prebake({ isMock = false, baseDir = '.' } = {}) {
@@ -25,9 +26,9 @@ Pattern.prototype.piano = function () {
.s('piano') .s('piano')
.release(0.1) .release(0.1)
.fmap((value) => { .fmap((value) => {
const midi = valueToMidi(value); const midi = typeof value.note === 'string' ? toMidi(value.note) : value.note;
// pan by pitch // pan by pitch
const pan = panwidth(Math.min(Math.round(midi) / maxPan, 1), 0.5); const pan = panwidth(Math.min(midi / maxPan, 1), 0.5);
return { ...value, pan: (value.pan || 1) * pan }; return { ...value, pan: (value.pan || 1) * pan };
}); });
}; };
+19 -24
View File
@@ -5,8 +5,8 @@
// import * as tunes from './tunes.mjs'; // import * as tunes from './tunes.mjs';
// import { evaluate } from '@strudel.cycles/eval'; // import { evaluate } from '@strudel.cycles/eval';
import { evaluate } from '@strudel.cycles/transpiler'; import { evaluate } from '@strudel.cycles/transpiler';
import { evalScope } from '@strudel.cycles/core'; import strudel from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core'; const { Pattern, evalScope } = strudel;
import * as webaudio from '@strudel.cycles/webaudio'; import * as webaudio from '@strudel.cycles/webaudio';
import controls from '@strudel.cycles/core/controls.mjs'; import controls from '@strudel.cycles/core/controls.mjs';
// import gist from '@strudel.cycles/core/gist.js'; // import gist from '@strudel.cycles/core/gist.js';
@@ -19,7 +19,8 @@ import { mini } from '@strudel.cycles/mini/mini.mjs';
// import euclid from '@strudel.cycles/core/euclid.mjs'; // import euclid from '@strudel.cycles/core/euclid.mjs';
// import '@strudel.cycles/tone/tone.mjs'; // import '@strudel.cycles/tone/tone.mjs';
// import '@strudel.cycles/midi/midi.mjs'; // import '@strudel.cycles/midi/midi.mjs';
import * as tonalHelpers from '@strudel.cycles/tonal'; import '@strudel.cycles/tonal/voicings.mjs';
import '@strudel.cycles/tonal/tonal.mjs';
import '@strudel.cycles/xen/xen.mjs'; import '@strudel.cycles/xen/xen.mjs';
// import '@strudel.cycles/xen/tune.mjs'; // import '@strudel.cycles/xen/tune.mjs';
// import '@strudel.cycles/core/euclid.mjs'; // import '@strudel.cycles/core/euclid.mjs';
@@ -84,51 +85,51 @@ const toneHelpersMocked = {
highpass: mockNode, highpass: mockNode,
}; };
strudel.Pattern.prototype.osc = function () { Pattern.prototype.osc = function () {
return this; return this;
}; };
strudel.Pattern.prototype.csound = function () { Pattern.prototype.csound = function () {
return this; return this;
}; };
strudel.Pattern.prototype.tone = function () { Pattern.prototype.tone = function () {
return this; return this;
}; };
strudel.Pattern.prototype.webdirt = function () { Pattern.prototype.webdirt = function () {
return this; return this;
}; };
// draw mock // draw mock
strudel.Pattern.prototype.pianoroll = function () { Pattern.prototype.pianoroll = function () {
return this; return this;
}; };
// speak mock // speak mock
strudel.Pattern.prototype.speak = function () { Pattern.prototype.speak = function () {
return this; return this;
}; };
// webaudio mock // webaudio mock
strudel.Pattern.prototype.wave = function () { Pattern.prototype.wave = function () {
return this; return this;
}; };
strudel.Pattern.prototype.filter = function () { Pattern.prototype.filter = function () {
return this; return this;
}; };
strudel.Pattern.prototype.adsr = function () { Pattern.prototype.adsr = function () {
return this; return this;
}; };
strudel.Pattern.prototype.out = function () { Pattern.prototype.out = function () {
return this; return this;
}; };
strudel.Pattern.prototype.soundfont = function () { Pattern.prototype.soundfont = function () {
return this; return this;
}; };
// tune mock // tune mock
strudel.Pattern.prototype.tune = function () { Pattern.prototype.tune = function () {
return this; return this;
}; };
strudel.Pattern.prototype.midi = function () { Pattern.prototype.midi = function () {
return this; return this;
}; };
@@ -153,19 +154,16 @@ const audioCtx = {
const getDrawContext = () => canvasCtx; const getDrawContext = () => canvasCtx;
const getAudioContext = () => audioCtx; const getAudioContext = () => audioCtx;
const loadSoundfont = () => {}; const loadSoundfont = () => {};
const loadCsound = () => {};
const loadCSound = () => {};
const loadcsound = () => {};
// TODO: refactor to evalScope // TODO: refactor to evalScope
evalScope( evalScope(
// Tone, // Tone,
strudel, strudel,
Pattern.prototype.bootstrap(),
toneHelpersMocked, toneHelpersMocked,
uiHelpersMocked, uiHelpersMocked,
controls, controls,
webaudio, webaudio,
tonalHelpers,
/* controls, /* controls,
toneHelpers, toneHelpers,
voicingHelpers, voicingHelpers,
@@ -181,9 +179,6 @@ evalScope(
getDrawContext, getDrawContext,
getAudioContext, getAudioContext,
loadSoundfont, loadSoundfont,
loadCSound,
loadCsound,
loadcsound,
Clock: {}, // whatever Clock: {}, // whatever
// Tone, // Tone,
}, },
@@ -192,7 +187,7 @@ evalScope(
export const queryCode = async (code, cycles = 1) => { export const queryCode = async (code, cycles = 1) => {
const { pattern } = await evaluate(code); const { pattern } = await evaluate(code);
const haps = pattern.queryArc(0, cycles); const haps = pattern.queryArc(0, cycles);
return haps.map((h) => h.show(true)); return haps.map((h) => h.showWhole());
}; };
export const testCycles = { export const testCycles = {
File diff suppressed because it is too large Load Diff
+7 -13
View File
@@ -89,11 +89,10 @@ export const whirlyStrudel = `seq(e4, [b2, b3], c4)
export const transposedChordsHacked = `stack( export const transposedChordsHacked = `stack(
"c2 eb2 g2", "c2 eb2 g2",
"Cm7".voicings('lefthand').slow(2) "Cm7".voicings(['g2','c4']).slow(2)
).transpose( ).transpose(
"<1 2 3 2>".slow(2) "<1 2 3 2>".slow(2)
).transpose(5)`; ).transpose(5)`;
// range ['g2','c4']
export const scaleTranspose = `"f2,f3,c4,ab4" export const scaleTranspose = `"f2,f3,c4,ab4"
.scale(seq('F minor', 'F harmonic minor').slow(4)) .scale(seq('F minor', 'F harmonic minor').slow(4))
@@ -103,14 +102,13 @@ export const scaleTranspose = `"f2,f3,c4,ab4"
export const struct = `stack( export const struct = `stack(
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]", "c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
"[C^7 A7] [Dm7 G7]".struct("[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2") "[C^7 A7] [Dm7 G7]".struct("[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2")
.voicings('lefthand') .voicings(['G3','A4'])
).slow(4)`; ).slow(4)`;
// range ['G3','A4']
export const magicSofa = `stack( export const magicSofa = `stack(
"<C^7 F^7 ~> <Dm7 G7 A7 ~>" "<C^7 F^7 ~> <Dm7 G7 A7 ~>"
.every(2, fast(2)) .every(2, fast(2))
.voicings('lefthand'), .voicings(),
"<c2 f2 g2> <d2 g2 a2 e2>" "<c2 f2 g2> <d2 g2 a2 e2>"
).transpose("<0 2 3 4>")`; ).transpose("<0 2 3 4>")`;
// below doesn't work anymore due to constructor cleanup // below doesn't work anymore due to constructor cleanup
@@ -158,7 +156,7 @@ const synths = stack(
scaleTranspose(8).early(3/8) scaleTranspose(8).early(3/8)
).apply(thru).tone(keys).mask("<~ x>/16"), ).apply(thru).tone(keys).mask("<~ x>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).apply(thru).tone(bass), "<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).apply(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings('lefthand').apply(thru).every(2, early(1/8)).tone(keys).mask("<x@7 ~>/8".early(1/4)) "<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().apply(thru).every(2, early(1/8)).tone(keys).mask("<x@7 ~>/8".early(1/4))
) )
stack( stack(
drums.fast(2), drums.fast(2),
@@ -199,7 +197,7 @@ export const giantStepsReggae = `stack(
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]" "B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
) )
.struct("~ [x ~]".fast(4*8)) .struct("~ [x ~]".fast(4*8))
.voicings('lefthand'), .voicings(['E3', 'G4']),
// bass // bass
seq( seq(
"[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]", "[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]",
@@ -210,8 +208,6 @@ export const giantStepsReggae = `stack(
.struct("x ~".fast(4*8)) .struct("x ~".fast(4*8))
).slow(25).note()`; ).slow(25).note()`;
// range ['E3', 'G4']
// TODO: // TODO:
/* /*
export const xylophoneCalling = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ export const xylophoneCalling = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@@ -355,7 +351,7 @@ stack(
export const bossa = `const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C locrian','F phrygian']).slow(4) export const bossa = `const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C locrian','F phrygian']).slow(4)
stack( stack(
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings('lefthand'), "<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25) "[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
).note().piano().slow(2)`; ).note().piano().slow(2)`;
@@ -447,11 +443,9 @@ export const bossaRandom = `const chords = "<Am7 Am7 Dm7 E7>"
const roots = chords.rootNotes(2) const roots = chords.rootNotes(2)
stack( stack(
chords.voicings('lefthand').struct( chords.voicings(['F4', 'A5']).struct(
\` x@2 ~ x ~ ~ ~ x | \` x@2 ~ x ~ ~ ~ x |
x? ~ ~ x@3 ~ x | x? ~ ~ x@3 ~ x |
x? ~ ~ x ~ x@3\`), x? ~ ~ x ~ x@3\`),
roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7") roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7")
).slow(2).pianoroll().note().piano()`; ).slow(2).pianoroll().note().piano()`;
// range ['F4', 'A5']
+16 -19
View File
@@ -67,8 +67,6 @@ stack(
`; `;
export const giantSteps = `// John Coltrane - Giant Steps export const giantSteps = `// John Coltrane - Giant Steps
setVoicingRange('lefthand', ['E3', 'G4']);
stack( stack(
// melody // melody
seq( seq(
@@ -83,7 +81,7 @@ stack(
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]", "[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]", "Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]" "B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).voicings('lefthand'), ).voicings(['E3', 'G4']),
// bass // bass
seq( seq(
"[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]", "[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]",
@@ -132,15 +130,15 @@ const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']) "<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor'])
.slow(8)).struct("[~ x]*2") .slow(8)).struct("[~ x]*2")
.layer( .layer(
x=>x.scaleTranspose(0).early(0), scaleTranspose(0).early(0),
x=>x.scaleTranspose(2).early(1/8), scaleTranspose(2).early(1/8),
x=>x.scaleTranspose(7).early(1/4), scaleTranspose(7).early(1/4),
x=>x.scaleTranspose(8).early(3/8) scaleTranspose(8).early(3/8)
).apply(thru).note().apply(keys).mask("<~ x>/16"), ).apply(thru).note().apply(keys).mask("<~ x>/16"),
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru)) note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru))
.struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)) .struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2))
.s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500), .s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.2 ~]".fast(2)).voicings('lefthand') "<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.2 ~]".fast(2)).voicings()
.apply(thru).every(2, early(1/8)).note().apply(keys).sustain(0) .apply(thru).every(2, early(1/8)).note().apply(keys).sustain(0)
.delay(.4).delaytime(.12) .delay(.4).delaytime(.12)
.mask("<x@7 ~>/8".early(1/4)) .mask("<x@7 ~>/8".early(1/4))
@@ -247,7 +245,7 @@ export const festivalOfFingers = `// licensed with CC BY-NC-SA 4.0 https://creat
// by Felix Roos // by Felix Roos
const chords = "<Cm7 Fm7 G7 F#7>"; const chords = "<Cm7 Fm7 G7 F#7>";
stack( stack(
chords.voicings('lefthand').struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
chords.rootNotes(2).struct("x(4,8,-2)"), chords.rootNotes(2).struct("x(4,8,-2)"),
chords.rootNotes(4) chords.rootNotes(4)
.scale(cat('C minor','F dorian','G dorian','F# mixolydian')) .scale(cat('C minor','F dorian','G dorian','F# mixolydian'))
@@ -503,7 +501,7 @@ stack(
.gain(.4) // turn down .gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff .cutoff(sine.slow(7).range(300,5000)) // automate cutoff
//.hush() //.hush()
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords ,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice .superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation .add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n" .n() // wrap in "n"
@@ -533,7 +531,7 @@ samples({
perc: ['perc/002_perc2.wav'], perc: ['perc/002_perc2.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/'); }, 'github:tidalcycles/Dirt-Samples/master/');
"C^7 Am7 Dm7 G7".slow(2).voicings('lefthand') "C^7 Am7 Dm7 G7".slow(2).voicings()
.stack("0@6 [<1 2> <2 0> 1]@2".scale('C5 major')) .stack("0@6 [<1 2> <2 0> 1]@2".scale('C5 major'))
.n().slow(4) .n().slow(4)
.s('0040_FluidR3_GM_sf2_file') .s('0040_FluidR3_GM_sf2_file')
@@ -584,7 +582,7 @@ export const chop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.or
samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' }) samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
s("p") s("p")
.loopAt(32) .loopAt(32,1)
.chop(128) .chop(128)
.jux(rev) .jux(rev)
.shape(.4) .shape(.4)
@@ -628,7 +626,7 @@ stack(
s("mt lt ht").struct("x(3,8)").fast(2).gain(.5).room(.5).sometimes(x=>x.speed(".5")), s("mt lt ht").struct("x(3,8)").fast(2).gain(.5).room(.5).sometimes(x=>x.speed(".5")),
s("misc:2").speed(1).delay(.5).delaytime(1/3).gain(.4), s("misc:2").speed(1).delay(.5).delaytime(1/3).gain(.4),
// chords // chords
note("[~ Gm7] ~ [~ Dm7] ~".voicings('lefthand').superimpose(x=>x.add(.1))) note("[~ Gm7] ~ [~ Dm7] ~".voicings().superimpose(x=>x.add(.1)))
.s('sawtooth').gain(.5) .s('sawtooth').gain(.5)
.cutoff(perlin.range(400,3000).slow(8)) .cutoff(perlin.range(400,3000).slow(8))
.decay(perlin.range(0.05,.2)).sustain(0) .decay(perlin.range(0.05,.2)).sustain(0)
@@ -652,12 +650,11 @@ export const dinofunk = `// licensed with CC BY-NC-SA 4.0 https://creativecommon
// by Felix Roos // by Felix Roos
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3', samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}}) dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
setVoicingRange('lefthand', ['c3','a4'])
stack( stack(
s('bass').loopAt(8).clip(1), s('bass').loopAt(8,1).clip(1),
s("bd*2, ~ sd,hh*4"), s("bd*2, ~ sd,hh*4"),
note("Abm7".voicings('lefthand').struct("x(3,8,1)".slow(2))), note("Abm7".voicings(['c3','a4']).struct("x(3,8,1)".slow(2))),
"0 1 2 3".scale('ab4 minor pentatonic') "0 1 2 3".scale('ab4 minor pentatonic')
.superimpose(x=>x.add(.1)) .superimpose(x=>x.add(.1))
.sometimes(x=>x.add(12)) .sometimes(x=>x.add(12))
@@ -770,7 +767,7 @@ note("c3 eb3 g3 bb3").palindrome()
export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos // by Felix Roos
await loadCsound\` await csound\`
instr CoolSynth instr CoolSynth
iduration = p3 iduration = p3
ifreq = p4 ifreq = p4
@@ -809,7 +806,7 @@ export const loungeSponge = `
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc') await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
stack( stack(
note("<C^7 A7 Dm7 Fm7>/2".voicings('lefthand')) note("<C^7 A7 Dm7 Fm7>/2".voicings())
.cutoff(sine.range(500,2000).round().slow(16)) .cutoff(sine.range(500,2000).round().slow(16))
.euclidLegato(3,8).csound('FM1') .euclidLegato(3,8).csound('FM1')
, ,
@@ -826,7 +823,7 @@ export const arpoon = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.
// "Arpoon" by Felix Roos // "Arpoon" by Felix Roos
await samples('github:tidalcycles/Dirt-Samples/master') await samples('github:tidalcycles/Dirt-Samples/master')
"<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand') "<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings()
.arp("[0,3] 2 [1,3] 2".fast(3)).legato(2) .arp("[0,3] 2 [1,3] 2".fast(3)).legato(2)
.add(perlin.range(0,0.2)).sub("<0 -12>/8") .add(perlin.range(0,0.2)).sub("<0 -12>/8")
.note().cutoff(perlin.range(500,4000)).resonance(12) .note().cutoff(perlin.range(500,4000)).resonance(12)
+4 -4
View File
@@ -1,4 +1,4 @@
import { evalScope, controls } from '@strudel.cycles/core'; import core from '@strudel.cycles/core';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react'; import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import { samples } from '@strudel.cycles/webaudio'; import { samples } from '@strudel.cycles/webaudio';
import { prebake } from '../repl/src/prebake.mjs'; import { prebake } from '../repl/src/prebake.mjs';
@@ -7,9 +7,9 @@ fetch('https://strudel.tidalcycles.org/EmuSP12.json')
.then((res) => res.json()) .then((res) => res.json())
.then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/')); .then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/'));
evalScope( core.evalScope(
controls, core,
import('@strudel.cycles/core'), // import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'), // import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'), import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'), import('@strudel.cycles/mini'),
+1 -1
View File
@@ -9,4 +9,4 @@ module.exports = {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; }
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -35,7 +35,7 @@ s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
.s('sawtooth') // waveform .s('sawtooth') // waveform
.gain(.4) // turn down .gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff .cutoff(sine.slow(7).range(300,5000)) // automate cutoff
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords ,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice .superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation .add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n" .n() // wrap in "n"
@@ -755,7 +755,9 @@ Transposes notes inside the scale by the number of steps:
Turns chord symbols into voicings, using the smoothest voice leading possible: Turns chord symbols into voicings, using the smoothest voice leading possible:
<MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()`} /> <MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note()`} />
<!-- TODO: use voicing collection as first param + patternify. -->
### rootNotes(octave = 2) ### rootNotes(octave = 2)
@@ -767,7 +769,7 @@ Together with layer, struct and voicings, this can be used to create a basic bac
<MiniRepl <MiniRepl
tune={`"<C^7 A7b13 Dm7 G7>".layer( tune={`"<C^7 A7b13 Dm7 G7>".layer(
x => x.voicings('lefthand').struct("~ x").note(), x => x.voicings(['d3','g4']).struct("~ x").note(),
x => x.rootNotes(2).note().s('sawtooth').cutoff(800) x => x.rootNotes(2).note().s('sawtooth').cutoff(800)
)`} )`}
/> />
@@ -788,7 +790,7 @@ Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (L
If no outputName is given, it uses the first midi output it finds. If no outputName is given, it uses the first midi output it finds.
<MiniRepl <MiniRepl
tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>") tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")
.midi()`} .midi()`}
/> />