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]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm install
- run: npm run format-check
- run: npm run lint
- run: npm test
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm install
- 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,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf"
}
"printWidth": 120,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf"
}
+24 -150
View File
@@ -12,7 +12,7 @@
"packages/*"
],
"devDependencies": {
"@vitest/ui": "^0.25.7",
"@vitest/ui": "^0.21.1",
"c8": "^7.12.0",
"eslint": "^8.28.0",
"events": "^3.3.0",
@@ -21,9 +21,8 @@
"jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^7.1.1",
"lerna": "^4.0.0",
"prettier": "^2.8.1",
"rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.25.7"
"vitest": "^0.21.1"
}
},
"node_modules/@ampproject/remapping": {
@@ -2423,10 +2422,9 @@
}
},
"node_modules/@vitest/ui": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.25.7.tgz",
"integrity": "sha512-mL/7SbXE11jMaSq/ynQuQvmR63amQ9/u60ZXgg9zFGJ3glTkfGfKIKSsGB64Z4vNEh8hf6MeF3vDfErlbJo0xA==",
"version": "0.21.1",
"dev": true,
"license": "MIT",
"dependencies": {
"sirv": "^2.0.2"
}
@@ -9172,21 +9170,6 @@
"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": {
"version": "2.0.1",
"license": "MIT"
@@ -10607,30 +10590,6 @@
"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": {
"version": "1.0.1",
"dev": true,
@@ -11017,26 +10976,18 @@
"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": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.0.tgz",
"integrity": "sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==",
"version": "0.2.4",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tinyspy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz",
"integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==",
"version": "1.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
@@ -11514,25 +11465,19 @@
}
},
"node_modules/vitest": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.7.tgz",
"integrity": "sha512-lJ+Ue+v8kHl2JzjaKHJ9u5Yo/loU7zrWK2/Whn8OKQjtq5G7nkeWfXuq3elZaC8xKdkdIuWiiIicaNBG1F5yzg==",
"version": "0.21.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^4.3.3",
"@types/chai-subset": "^1.3.3",
"@types/node": "*",
"acorn": "^8.8.0",
"acorn-walk": "^8.2.0",
"chai": "^4.3.6",
"debug": "^4.3.4",
"local-pkg": "^0.4.2",
"source-map": "^0.6.1",
"strip-literal": "^1.0.0",
"tinybench": "^2.3.1",
"tinypool": "^0.3.0",
"tinyspy": "^1.0.2",
"vite": "^3.0.0 || ^4.0.0"
"tinypool": "^0.2.4",
"tinyspy": "^1.0.0",
"vite": "^2.9.12 || ^3.0.0-0"
},
"bin": {
"vitest": "vitest.mjs"
@@ -11547,6 +11492,7 @@
"@edge-runtime/vm": "*",
"@vitest/browser": "*",
"@vitest/ui": "*",
"c8": "*",
"happy-dom": "*",
"jsdom": "*"
},
@@ -11560,6 +11506,9 @@
"@vitest/ui": {
"optional": true
},
"c8": {
"optional": true
},
"happy-dom": {
"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": {
"version": "4.3.4",
"dev": true,
@@ -14213,9 +14141,7 @@
}
},
"@vitest/ui": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.25.7.tgz",
"integrity": "sha512-mL/7SbXE11jMaSq/ynQuQvmR63amQ9/u60ZXgg9zFGJ3glTkfGfKIKSsGB64Z4vNEh8hf6MeF3vDfErlbJo0xA==",
"version": "0.21.1",
"dev": true,
"requires": {
"sirv": "^2.0.2"
@@ -18564,12 +18490,6 @@
"prelude-ls": {
"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": {
"version": "2.0.1"
},
@@ -19546,23 +19466,6 @@
"strip-json-comments": {
"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": {
"version": "1.0.1",
"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": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.0.tgz",
"integrity": "sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==",
"version": "0.2.4",
"dev": true
},
"tinyspy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz",
"integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==",
"version": "1.0.0",
"dev": true
},
"tmp": {
@@ -20151,39 +20044,20 @@
}
},
"vitest": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.7.tgz",
"integrity": "sha512-lJ+Ue+v8kHl2JzjaKHJ9u5Yo/loU7zrWK2/Whn8OKQjtq5G7nkeWfXuq3elZaC8xKdkdIuWiiIicaNBG1F5yzg==",
"version": "0.21.1",
"dev": true,
"requires": {
"@types/chai": "^4.3.3",
"@types/chai-subset": "^1.3.3",
"@types/node": "*",
"acorn": "^8.8.0",
"acorn-walk": "^8.2.0",
"chai": "^4.3.6",
"debug": "^4.3.4",
"local-pkg": "^0.4.2",
"source-map": "^0.6.1",
"strip-literal": "^1.0.0",
"tinybench": "^2.3.1",
"tinypool": "^0.3.0",
"tinyspy": "^1.0.2",
"vite": "^3.0.0 || ^4.0.0"
"tinypool": "^0.2.4",
"tinyspy": "^1.0.0",
"vite": "^2.9.12 || ^3.0.0-0"
},
"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": {
"version": "4.3.4",
"dev": true,
+4 -8
View File
@@ -5,7 +5,7 @@
"description": "Port of tidalcycles to javascript",
"scripts": {
"pretest": "cd tutorial && npm run jsdoc-json",
"test": "vitest run --version",
"test": "npm run lint && vitest run --version",
"test-ui": "vitest --ui",
"test-coverage": "vitest --coverage",
"bootstrap": "lerna bootstrap",
@@ -18,10 +18,7 @@
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -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",
"codeformat": "prettier --write .",
"format-check": "prettier --check .",
"check": "npm run format-check && npm run lint && npm run test"
"lint": "npx eslint . --ext mjs,js --quiet"
},
"workspaces": [
"packages/*"
@@ -44,7 +41,7 @@
},
"homepage": "https://strudel.tidalcycles.org",
"devDependencies": {
"@vitest/ui": "^0.25.7",
"@vitest/ui": "^0.21.1",
"c8": "^7.12.0",
"eslint": "^8.28.0",
"events": "^3.3.0",
@@ -53,8 +50,7 @@
"jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^7.1.1",
"lerna": "^4.0.0",
"prettier": "^2.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/>.
*/
import { Pattern, sequence } from './pattern.mjs';
import pattern from './pattern.mjs';
const { Pattern, sequence } = pattern;
const controls = {};
const generic_params = [
@@ -204,7 +205,7 @@ const generic_params = [
* "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', ''],
/**
* bit crusher effect.
@@ -562,7 +563,7 @@ const generic_params = [
// TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
['f', 'semitone', ''],
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
// ['f', 'velocity', ''],
['f', 'velocity', ''],
['f', 'voice', ''], // TODO: synth param
/**
* 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/>.
*/
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') => {
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/>.
*/
import { Pattern, timeCat } from './pattern.mjs';
import pattern from './pattern.mjs';
const { Pattern, timeCat } = pattern;
import bjork from 'bjork';
import { rotate } from './util.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/>.
*/
import { isPattern } from './index.mjs';
import pattern from './pattern.mjs';
const { isPattern, Pattern } = pattern;
let scoped = false;
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);
}
});
Object.assign(globalThis, ...modules);
Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap());
};
function safeEval(str, options = {}) {
@@ -16,7 +16,7 @@ evalScope(
import('@strudel.cycles/tonal'),
);
setStringParser(mini);
setStringParser(mini)
const { evaluate } = repl({
defaultOutput: webaudioOutput,
+4 -27
View File
@@ -79,33 +79,10 @@ export class Hap {
);
}
show(compact = false) {
const value =
typeof this.value === 'object'
? 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 + ' ]';
show() {
return (
'(' + (this.whole == undefined ? '~' : this.whole.show()) + ', ' + this.part.show() + ', ' + this.value + ')'
);
}
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/>.
*/
import controls from './controls.mjs';
export * from './euclid.mjs';
import Fraction from './fraction.mjs';
import drawLine from './drawLine.mjs';
import gist from './gist.js';
import { logger } from './logger.mjs';
export { Fraction, controls };
export * from './hap.mjs';
export * from './pattern.mjs';
export * from './signal.mjs';
export * from './state.mjs';
export * from './timespan.mjs';
export * from './util.mjs';
export * from './speak.mjs';
export * from './evaluate.mjs';
export * from './repl.mjs';
export * from './logger.mjs';
export * from './time.mjs';
export * from './draw.mjs';
export * from './pianoroll.mjs';
export * from './ui.mjs';
export { default as drawLine } from './drawLine.mjs';
export { default as gist } from './gist.js';
// Fraction: Fraction, drawLine: drawline, gist: gist, logger: logger,
import controls from './controls.mjs';
import pattern from './pattern.mjs';
import * as draw from './draw.mjs';
import * as euclid from './euclid.mjs';
import * as evaluate from './evaluate.mjs';
import * as hap from './hap.mjs';
import * as repl from './repl.mjs';
import * as pianoroll from './pianoroll.mjs';
import * as signal from './signal.mjs';
import * as speak from './speak.mjs';
import * as state from './state.mjs';
import * as time from './time.mjs';
import * as timespan from './timespan.mjs';
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)
/* import * as p from './package.json';
export const version = p.version; */
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.5.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.5.0",
"version": "0.4.1",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"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/>.
*/
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 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 { 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 { id } from './util.mjs';
@@ -258,9 +259,9 @@ export const perlinWith = (pat) => {
*/
export const perlin = perlinWith(time.fmap((v) => Number(v)));
export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
);
Pattern.prototype._degradeByWith = function (withPat, x) {
return this.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x));
};
/**
* Randomly removes events from the pattern by a given amount.
@@ -276,9 +277,9 @@ export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
* @example
* s("[hh?0.2]*8")
*/
export const degradeBy = register('degradeBy', function (x, pat) {
return pat._degradeByWith(rand, x);
});
Pattern.prototype._degradeBy = function (x) {
return this._degradeByWith(rand, x);
};
/**
*
@@ -292,7 +293,9 @@ export const degradeBy = register('degradeBy', function (x, pat) {
* @example
* 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.
@@ -307,14 +310,26 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
* @example
* s("hh*8").undegradeBy(0.2)
*/
export const undegradeBy = register('undegradeBy', function (x, pat) {
return pat._degradeByWith(
Pattern.prototype._undegradeBy = function (x) {
return this._degradeByWith(
rand.fmap((r) => 1 - r),
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
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
*/
export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
Pattern.prototype.sometimesBy = function (patx, func) {
const pat = this;
return reify(patx)
.fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x))))
.fmap((x) => pat._sometimesBy(x, func))
.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
* s("hh*4").sometimes(x=>x.speed("0.5"))
*/
export const sometimes = register('sometimes', function (func, pat) {
return pat._sometimesBy(0.5, func);
});
Pattern.prototype.sometimes = function (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
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
*/
export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) {
Pattern.prototype.someCyclesBy = function (patx, func) {
const pat = this;
return reify(patx)
.fmap((x) =>
stack(
pat._degradeByWith(rand._segment(1), x),
func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
),
)
.fmap((x) => pat._someCyclesBy(x, func))
.innerJoin();
});
};
/**
*
@@ -386,9 +419,9 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat)
* @example
* s("hh(3,8)").someCycles(x=>x.speed("0.5"))
*/
export const someCycles = register('someCycles', function (func, pat) {
return pat._someCyclesBy(0.5, func);
});
Pattern.prototype.someCycles = function (func) {
return this._someCyclesBy(0.5, func);
};
/**
*
@@ -400,9 +433,9 @@ export const someCycles = register('someCycles', function (func, pat) {
* @example
* s("hh*8").often(x=>x.speed("0.5"))
*/
export const often = register('often', function (func, pat) {
return pat.sometimesBy(0.75, func);
});
Pattern.prototype.often = function (func) {
return this.sometimesBy(0.75, func);
};
/**
*
@@ -414,9 +447,9 @@ export const often = register('often', function (func, pat) {
* @example
* s("hh*8").rarely(x=>x.speed("0.5"))
*/
export const rarely = register('rarely', function (func, pat) {
return pat.sometimesBy(0.25, func);
});
Pattern.prototype.rarely = function (func) {
return this.sometimesBy(0.25, func);
};
/**
*
@@ -428,9 +461,9 @@ export const rarely = register('rarely', function (func, pat) {
* @example
* s("hh*8").almostNever(x=>x.speed("0.5"))
*/
export const almostNever = register('almostNever', function (func, pat) {
return pat.sometimesBy(0.1, func);
});
Pattern.prototype.almostNever = function (func) {
return this.sometimesBy(0.1, func);
};
/**
*
@@ -442,9 +475,9 @@ export const almostNever = register('almostNever', function (func, pat) {
* @example
* s("hh*8").almostAlways(x=>x.speed("0.5"))
*/
export const almostAlways = register('almostAlways', function (func, pat) {
return pat.sometimesBy(0.9, func);
});
Pattern.prototype.almostAlways = function (func) {
return this.sometimesBy(0.9, func);
};
/**
*
@@ -456,9 +489,9 @@ export const almostAlways = register('almostAlways', function (func, pat) {
* @example
* s("hh*8").never(x=>x.speed("0.5"))
*/
export const never = register('never', function (_, pat) {
return pat;
});
Pattern.prototype.never = function (func) {
return this;
};
/**
*
@@ -470,6 +503,8 @@ export const never = register('never', function (_, pat) {
* @example
* s("hh*8").always(x=>x.speed("0.5"))
*/
export const always = register('always', function (func, pat) {
return func(pat);
});
Pattern.prototype.always = function (func) {
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/>.
*/
import { register } from './index.mjs';
import pattern from './pattern.mjs';
const {Pattern, patternify2, reify} = pattern;
let synth;
try {
@@ -16,7 +17,7 @@ try {
let allVoices = synth?.getVoices();
// console.log('voices', allVoices);
function triggerSpeech(words, lang, voice) {
function speak(words, lang, voice) {
synth.cancel();
const utterance = new SpeechSynthesisUtterance(words);
utterance.lang = lang;
@@ -31,8 +32,12 @@ function triggerSpeech(words, lang, voice) {
speechSynthesis.speak(utterance);
}
export const speak = register('speak', function (lang, voice, pat) {
return pat.onTrigger((_, hap) => {
triggerSpeech(hap.value, lang, voice);
Pattern.prototype._speak = function (lang, voice) {
return this.onTrigger((_, hap) => {
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/>.
*/
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 drawLine from '../drawLine.mjs';
+17 -47
View File
@@ -8,15 +8,13 @@ import Fraction from 'fraction.js';
import { describe, it, expect } from 'vitest';
import {
TimeSpan,
Hap,
State,
import core from '../index.mjs';
const {
Pattern,
pure,
stack,
fastcat,
firstOf,
slowcat,
cat,
sequence,
@@ -30,6 +28,12 @@ import {
sub,
mul,
div,
id,
ply,
rev,
TimeSpan,
Hap,
State,
saw,
saw2,
isaw,
@@ -40,11 +44,7 @@ import {
square2,
tri,
tri2,
id,
ply,
rev,
time,
} from '../index.mjs';
time} = core;
import { steady } from '../signal.mjs';
@@ -155,12 +155,6 @@ describe('Pattern', () => {
});
});
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()', () => {
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);
@@ -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]]));
});
});
@@ -327,12 +321,12 @@ describe('Pattern', () => {
it('Can set things with plain values', () => {
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', () => {
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', () => {
sameFirst(
sequence(1, [2, 3]).set.squeeze(sequence('a', 'b', 'c')),
@@ -413,8 +407,8 @@ describe('Pattern', () => {
);
});
it('defaults to accepting sequences', () => {
expect(sequence('a', 'b', 'c').fast(sequence(1.5, 2)).sortHapsByPart().firstCycle()).toStrictEqual(
sequence('a', 'b', 'c').fast(1.5, 2).sortHapsByPart().firstCycle(),
expect(sequence(1, 2, 3).fast(sequence(1.5, 2)).firstCycle()).toStrictEqual(
sequence(1, 2, 3).fast(1.5, 2).firstCycle(),
);
});
it('works as a static function', () => {
@@ -593,16 +587,6 @@ describe('Pattern', () => {
.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', () => {
expect(pure('a').firstOf(3, fast(2))._fast(3).firstCycle()).toStrictEqual(
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
@@ -912,23 +896,9 @@ describe('Pattern', () => {
});
describe('alignments', () => {
it('Can squeeze arguments', () => {
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,
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(
sequence(5, 6, 6, 7).firstCycle()
);
});
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/>.
*/
import { pure } from '../pattern.mjs';
import pattern from '../pattern.mjs';
const { pure } = pattern;
import {
isNote,
tokenizeNote,
toMidi,
fromMidi,
freqToMidi,
_mod,
mod,
compose,
getFrequency,
getPlayableNoteValue,
@@ -95,12 +95,6 @@ describe('fromMidi', () => {
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', () => {
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
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', () => {
expect(_mod(0, 3)).toEqual(0);
expect(_mod(1, 3)).toEqual(1);
expect(_mod(2, 3)).toEqual(2);
expect(_mod(3, 3)).toEqual(0);
expect(_mod(4, 3)).toEqual(1);
expect(_mod(4, 2)).toEqual(0);
expect(mod(0, 3)).toEqual(0);
expect(mod(1, 3)).toEqual(1);
expect(mod(2, 3)).toEqual(2);
expect(mod(3, 3)).toEqual(0);
expect(mod(4, 3)).toEqual(1);
expect(mod(4, 2)).toEqual(0);
});
it('should work with negative numbers', () => {
expect(_mod(-1, 3)).toEqual(2);
expect(_mod(-2, 3)).toEqual(1);
expect(_mod(-3, 3)).toEqual(0);
expect(_mod(-4, 3)).toEqual(2);
expect(_mod(-5, 3)).toEqual(1);
expect(_mod(-3, 2)).toEqual(1);
expect(mod(-1, 3)).toEqual(2);
expect(mod(-2, 3)).toEqual(1);
expect(mod(-3, 3)).toEqual(0);
expect(mod(-4, 3)).toEqual(2);
expect(mod(-5, 3)).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 { map, valued, mul } from '../value.mjs';
import controls from '../controls.mjs';
const { n } = controls;
describe('Value', () => {
it('unionWith', () => {
@@ -23,8 +21,4 @@ describe('Value', () => {
expect(valued(mul).ap(3).ap(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
if (begin.equals(end)) {
return [new TimeSpan(begin, end)];
return([new TimeSpan(begin, end)]);
}
while (end.gt(begin)) {
// If begin and end are in the same cycle, we're done.
if (begin.sam().equals(end_sam)) {
@@ -110,7 +110,7 @@ export class TimeSpan {
}
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;
};
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
*/
@@ -74,8 +50,9 @@ export const midi2note = (n) => {
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)
export const _mod = (n, m) => ((n % m) + m) % m;
// modulo that works with negative numbers e.g. mod(-1, 3) = 2
// 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) => {
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 function curry(func, overload, arity = func.length) {
export function curry(func, overload) {
const fn = function curried(...args) {
if (args.length >= arity) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
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';
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));
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 csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw';
@@ -6,24 +7,12 @@ import presetsOrc from './presets.orc?raw';
let csoundLoader, _csound;
// initializes csound + can be used to reevaluate given instrument code
export async function loadCSound(code = '') {
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) => {
// triggers given instrument name using csound.
Pattern.prototype._csound = function (instrument) {
instrument = instrument || 'triangle';
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)
return pat.onTrigger((time, hap) => {
return this.onTrigger((time, hap) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
@@ -52,7 +41,20 @@ export const csound = register('csound', (instrument, pat) => {
const msg = `i ${params.join(' ')}`;
_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) {
const [msg] = args;
@@ -79,25 +81,21 @@ function eventLogger(type, args) {
async function load() {
if (window.__csound__) {
// 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');
// allows using some other csound instance
_csound = window.__csound__;
return _csound;
} else {
const { Csound } = await import('@csound/browser');
_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() {
@@ -122,49 +120,3 @@ export async function loadOrc(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",
"version": "0.5.1",
"version": "0.3.0",
"description": "csound bindings for strudel",
"main": "csound.mjs",
"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/>.
*/
import { evaluate as _evaluate } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
import shapeshifter from './shapeshifter.mjs';
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",
"version": "0.5.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/eval",
"version": "0.5.0",
"version": "0.4.1",
"description": "Code evaluator for strudel",
"main": "index.mjs",
"type": "module",
+4 -3
View File
@@ -21,7 +21,7 @@ import {
import shiftCodegen from 'shift-codegen';
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;
@@ -225,8 +225,9 @@ function isPatternArg(parents) {
function hasModifierCall(parent) {
// TODO: modifiers are more than composables, for example every is not composable but should be seen as modifier..
// need all prototypes of Pattern
return parent?.type === 'StaticMemberExpression';
// && Object.keys(Pattern.prototype.composable).includes(parent.property)
return (
parent?.type === 'StaticMemberExpression' && Object.keys(Pattern.prototype.composable).includes(parent.property)
);
}
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 { mini } from '@strudel.cycles/mini';
import * as strudel from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { fastcat, evalScope } = strudel;
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 { 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';
// 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",
"version": "0.5.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/midi",
"version": "0.5.0",
"version": "0.4.1",
"description": "Midi API for strudel",
"main": "index.mjs",
"repository": {
@@ -21,7 +21,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/tone": "^0.5.0",
"@strudel.cycles/tone": "^0.4.1",
"tone": "^14.7.77",
"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 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;
function _nextSeed() {
return _seedState++;
} */
}
const applyOptions = (parent) => (pat, i) => {
const ast = parent.source_[i];
@@ -29,28 +30,10 @@ const applyOptions = (parent) => (pat, i) => {
case 'bjorklund':
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
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(
strudel.rand.early(randOffset * _nextSeed()).segment(1),
operator.arguments_.amount ?? 0.5,
);
*/
return reify(pat)._degradeBy(operator.arguments_.amount ?? 0.5);
rand.early(randOffset * _nextSeed()).segment(1),
operator.arguments_.amount,
);
// TODO: case 'fixed-step': "%"
}
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_) {
case 'pattern': {
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;
if (alignment === 'v') {
return stack(...children);
}
if (alignment === 'r') {
// https://github.com/tidalcycles/strudel/issues/245#issuecomment-1345406422
// return strudel.chooseInWith(strudel.rand.early(randOffset * _nextSeed()).segment(1), children);
return strudel.chooseCycles(...children);
return chooseInWith(rand.early(randOffset * _nextSeed()).segment(1), children);
}
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 't') {
@@ -136,6 +117,9 @@ export function patternifyAST(ast, code) {
return silence;
}
if (typeof ast.source_ !== 'object') {
/* if (!addMiniLocations) {
return ast.source_;
} */
if (!ast.location_) {
console.warn('no location for', ast);
return ast.source_;
@@ -144,20 +128,12 @@ export function patternifyAST(ast, code) {
const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
// 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
// make sure whitespaces are not part of the highlight:
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 pure(value).withLocation([start.line, start.column, start.offset], [end.line, end.column, end.offset]);
}
return patternifyAST(ast.source_, code);
return patternifyAST(ast.source_);
}
case 'stretch':
return patternifyAST(ast.source_, code).slow(ast.arguments_.amount);
return patternifyAST(ast.source_).slow(ast.arguments_.amount);
/* case 'scale':
let [tonic, scale] = Scale.tokenize(ast.arguments_.scale);
const intervals = Scale.get(scale).intervals;
@@ -189,9 +165,8 @@ export function patternifyAST(ast, code) {
// mini notation only (wraps in "")
export const mini = (...strings) => {
const pats = strings.map((str) => {
const code = `"${str}"`;
const ast = krill.parse(code);
return patternifyAST(ast, code);
const ast = krill.parse(`"${str}"`);
return patternifyAST(ast);
});
return sequence(...pats);
};
@@ -200,9 +175,14 @@ export const mini = (...strings) => {
export const h = (string) => {
const ast = krill.parse(string);
// 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) {
if (typeof thing === 'string') {
return mini(thing);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.5.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.5.0",
"version": "0.4.1",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
@@ -27,8 +27,8 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/eval": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0"
"@strudel.cycles/eval": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1"
},
"devDependencies": {
"peggy": "^2.0.1"
+2 -2
View File
@@ -77,7 +77,7 @@ describe('mini', () => {
expect(haps.length < 230).toBe(true);
// '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 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.
@@ -103,5 +103,5 @@ describe('mini', () => {
// PRNG, this test should succeed
expect(chisq <= 15.086).toBe(true);
// 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 { logger, parseNumeral, Pattern } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, logger, parseNumeral } = core;
let connection; // Promise<OSC>
function connect() {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.4.0",
"version": "0.3.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.4.0",
"version": "0.3.1",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"scripts": {
+2
View File
@@ -25,6 +25,8 @@ const config = {
},
};
const osc = new OSC({ plugin: new OSC.BridgePlugin(config) });
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:
```jsx
import { evalScope, controls } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const {evalScope} = core;
import { MiniRepl } from '@strudel.cycles/react';
import { prebake } from '../repl/src/prebake.mjs';
evalScope(
controls,
core,
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
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 { useInView as ee } from "react-hook-inview";
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";
const ae = Z({
theme: "dark",
@@ -177,7 +177,8 @@ function O({ type: e }) {
function Ee(e) {
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,
interval: t,
getTime: o,
@@ -189,8 +190,8 @@ function we({
onEvalError: c,
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(
() => oe({
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(
() => we({
interval: t,
defaultOutput: e,
onSchedulerError: g,
@@ -243,11 +244,11 @@ function we({
}
};
}
function ye() {
function ke() {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
}
const ke = () => re().currentTime;
function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
const _e = () => re().currentTime;
function Te({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
const {
code: s,
setCode: u,
@@ -261,10 +262,10 @@ function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
scheduler: C,
togglePlay: N,
stop: p
} = we({
} = ye({
initialCode: e,
defaultOutput: te,
getTime: ke
getTime: _e
}), [y, M] = w(), [S, k] = ee({
threshold: 0.01
}), 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
})));
}
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 {
de as CodeMirror,
Se as MiniRepl,
Te as MiniRepl,
K as cx,
ce as flash,
ue as useHighlighting,
Te as useKeydown,
Ve as useKeydown,
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 { useCallback, useState } from 'react';
import CodeMirror, { flash } from '../../../src/components/CodeMirror6';
@@ -10,8 +11,8 @@ import './style.css';
// TODO: only import stuff when play is pressed?
evalScope(
controls,
import('@strudel.cycles/core'),
core,
// import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
// import('@strudel.cycles/midi'), // TODO: find out why midi loads tone.js
import('@strudel.cycles/tonal'),
@@ -1,7 +1,7 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});
plugins: [react()]
})
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<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" />
<title>Strudel React Components</title>
</head>
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/react",
"version": "0.5.0",
"version": "0.4.2",
"description": "React components for strudel",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
@@ -40,9 +40,9 @@
"dependencies": {
"@codemirror/lang-javascript": "^6.1.1",
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/tone": "^0.5.0",
"@strudel.cycles/transpiler": "^0.5.0",
"@strudel.cycles/webaudio": "^0.5.0",
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/transpiler": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@uiw/codemirror-themes": "^4.12.4",
"@uiw/react-codemirror": "^4.12.4",
"react-hook-inview": "^4.5.0"
+1 -1
View File
@@ -9,4 +9,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
};
}
+2 -1
View File
@@ -1,7 +1,8 @@
import React from 'react';
import { MiniRepl } from './components/MiniRepl';
import 'tailwindcss/tailwind.css';
import { controls, evalScope } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const {controls, evalScope} = core;
evalScope(
controls,
+2 -1
View File
@@ -1,5 +1,6 @@
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 usePostMessage from './usePostMessage.mjs';
+4 -9
View File
@@ -1,10 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
);
ReactDOM.render(<React.StrictMode><App /></React.StrictMode>,document.getElementById('root'))
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/serial",
"version": "0.3.0",
"version": "0.2.0",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"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/>.
*/
import { Pattern, isPattern } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, isPattern } = core;
var writeMessage;
var choosing = false;
@@ -23,16 +24,17 @@ export async function getWriter(br = 38400) {
const encoder = new TextEncoder();
const writer = port.writable.getWriter();
writeMessage = function (message, chk) {
const encoded = encoder.encode(message);
const encoded = encoder.encode(message)
if (!chk) {
writer.write(encoded);
} else {
}
else {
const bytes = new Uint8Array(4);
bytes[0] = 124; // | symbol
bytes[1] = (chk >> 8) & 0xff;
bytes[2] = chk & 0xff;
bytes[1] = (chk >> 8) & 0xFF;
bytes[2] = chk & 0xFF;
bytes[3] = 59; // semicolon
const withchk = new Uint8Array(encoded.length + 4);
const withchk = new Uint8Array(encoded.length+4)
withchk.set(encoded);
withchk.set(bytes, encoded.length);
writer.write(withchk);
@@ -47,12 +49,12 @@ const latency = 0.1;
// crc16 (CCITT-FALSE) https://gist.github.com/tijnkooijmans/10981093
function crc16(data) {
const length = data.length;
const length = data.length
if (length == 0) {
return 0;
}
var crc = 0xffff;
var crc = 0xFFFF;
for (var i = 0; i < length; ++i) {
crc ^= data.charCodeAt(i) << 8;
for (var j = 0; j < 8; ++j) {
@@ -63,7 +65,7 @@ function crc16(data) {
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) => {
if (!writeMessage) {
getWriter(br);
@@ -95,7 +97,7 @@ Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids
}
message += ')';
if (sendcrc) {
chk = crc16(message);
chk = crc16(message)
}
} else {
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;
window.setTimeout(function () {
writeMessage(message, chk);
}, offset);
window.setTimeout(function () {writeMessage(message, chk)}, offset);
};
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 = {};
async function loadFont(name) {
+3 -3
View File
@@ -42,7 +42,7 @@ export const instruments = [
'0031_FluidR3_GM_sf2_file',
'0031_GeneralUserGS_sf2_file',
'0031_SoundBlasterOld_sf2', // pianos until here
'0040_Aspirin_sf2_file',
'0040_Aspirin_sf2_file',
'0040_Chaos_sf2_file',
'0040_FluidR3_GM_sf2_file', // rhodes
'0040_GeneralUserGS_sf2_file', // staccato rhodes
@@ -59,7 +59,7 @@ export const instruments = [
'0046_GeneralUserGS_sf2_file', // ?
'0050_Aspirin_sf2_file', // 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_JCLive_sf2_file', // glass organ
'0050_SBLive_sf2', // ?
@@ -1635,7 +1635,7 @@ export const drums = [
'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 = [];
instrumentNames[0] = 'Acoustic Grand Piano: Piano';
instrumentNames[1] = 'Bright Acoustic Piano: Piano';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.5.0",
"version": "0.4.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.5.0",
"version": "0.4.2",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"type": "module",
@@ -23,7 +23,7 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/webaudio": "^0.5.0",
"@strudel.cycles/webaudio": "^0.4.2",
"sfumato": "^0.1.2",
"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 { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato';
-3
View File
@@ -1,5 +1,2 @@
import './tonal.mjs';
import './voicings.mjs';
export * from './tonal.mjs';
export * from './voicings.mjs';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.5.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.5.0",
"version": "0.4.1",
"description": "Tonal functions for strudel",
"main": "index.mjs",
"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 '../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';
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 { register, _mod } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, pattern, mod } = core;
// transpose note inside scale by offset steps
// function scaleOffset(scale: string, offset: number, note: string) {
@@ -29,7 +30,7 @@ function scaleOffset(scale, offset, note) {
// TODO: find way to do this smarter
while (Math.abs(i - noteIndex) < Math.abs(offset)) {
i += direction;
const index = _mod(i, notes.length);
const index = mod(i, notes.length);
if (direction < 0 && n[0] === 'C') {
o += direction;
}
@@ -74,8 +75,8 @@ function scaleOffset(scale, offset, note) {
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
*/
export const transpose = register('transpose', function (intervalOrSemitones, pat) {
return pat.withHap((hap) => {
Pattern.prototype._transpose = function (intervalOrSemitones) {
return this.withHap((hap) => {
const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones /* as number */)
: 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
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))
// 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
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
@@ -108,8 +111,8 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
* .note()
*/
export const scaleTranspose = register('scaleTranspose', function (offset /* : number | string */, pat) {
return pat.withHap((hap) => {
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
return this.withHap((hap) => {
if (!hap.context.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));
});
});
};
/**
* 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()
*/
export const scale = register('scale', function (scale /* : string */, pat) {
return pat.withHap((hap) => {
Pattern.prototype._scale = function (scale /* : string */) {
return this.withHap((hap) => {
let note = hap.value;
const asNumber = Number(note);
if (!isNaN(asNumber)) {
@@ -150,4 +153,8 @@ export const scale = register('scale', function (scale /* : string */, pat) {
}
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/>.
*/
import { stack, register } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, stack, reify, Hap } = core
import _voicings from 'chord-voicings';
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup
export const voicingRegistry = {
lefthand: { dictionary: lefthand, range: ['F3', 'A4'] },
};
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({
const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) =>
dictionaryVoicing({
chord,
dictionary,
dictionary: lefthand,
range,
picker: minTopNoteDiff,
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
* @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
* @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 :-/
// it used to be local to the voicings function at evaluation time
// but since register will patternify by default, means that
// the function is called over and over again, resetting the lastVoicing variables
export const voicings = register('voicings', function (dictionary, pat) {
return pat
.fmap((value) => {
lastVoicing = getVoicing(value, dictionary, lastVoicing);
return stack(...lastVoicing);
})
.outerJoin();
});
Pattern.prototype.voicings = function (range) {
let lastVoicing;
if (!range?.length) {
// allows to pass empty array, if too lazy to specify range
range = ['F3', 'A4'];
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
return stack(...lastVoicing).withContext(() => ({
locations: event.context.locations || [],
}));
});
};
/**
* Maps the chords of the incoming pattern to root notes in the given octave.
*
* @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) => {
Pattern.prototype._rootNotes = function (octave = 2) {
return this.fmap((value) => {
const root = value.match(/^([a-gA-G][b#]?).*$/)[1];
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",
"version": "0.5.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tone",
"version": "0.5.0",
"version": "0.4.1",
"description": "Tone.js API for strudel",
"main": "index.mjs",
"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 { pure } from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { pure } = strudel;
import { describe, it, expect } from 'vitest';
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/>.
*/
import { register } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern } = core;
import * as _Tone from 'tone';
// import Tone from here, to make sure to get the same AudioContext
export const Tone = _Tone;
const {
AutoFilter,
Destination,
Filter,
Gain,
isNote,
Synth,
PolySynth,
MembraneSynth,
@@ -46,8 +51,8 @@ export const getDefaultSynth = () => {
// https://www.charlie-roberts.com/gibberish/playground/
// with this function, you can play the pattern with any tone synth
export const tone = register('tone', function (instrument, pat) {
return pat.onTrigger((time, hap) => {
Pattern.prototype.tone = function (instrument) {
return this.onTrigger((time, hap) => {
let note;
let velocity = hap.context?.velocity ?? 0.75;
if (instrument instanceof PluckSynth) {
@@ -71,7 +76,9 @@ export const tone = register('tone', function (instrument, pat) {
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
}
});
});
};
Pattern.prototype.define('tone', (type, pat) => pat.tone(type), { composable: true, patternified: false });
// synth helpers
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';
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",
"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.",
"main": "index.mjs",
"scripts": {
+2 -1
View File
@@ -1,7 +1,8 @@
import escodegen from 'escodegen';
import { parse } from 'acorn';
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 = {}) {
const { wrapAsync = false, addReturn = true, simpleLocs = false } = options;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.5.0",
"version": "0.4.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.5.0",
"version": "0.4.2",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"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';
const bufferCache = {}; // string: Promise<ArrayBuffer>
@@ -20,12 +21,9 @@ function humanFileSize(bytes, si) {
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;
if (freq !== undefined && note !== undefined) {
logger('[sampler] hap has note and freq. ignoring note', 'warning');
}
let midi = valueToMidi({ freq, note }, 36);
let midi = typeof note === 'string' ? toMidi(note) : note || 36;
transpose = midi - 36; // C3 is middle C
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/>.
*/
import * as strudel from '@strudel.cycles/core';
import { fromMidi, logger, toMidi } from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { fromMidi, logger, toMidi } = strudel;
import './feedbackdelay.mjs';
import './reverb.mjs';
import { getSampleBufferSource } from './sampler.mjs';
@@ -288,10 +288,10 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
if (soundfont) {
// is soundfont
bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac, freq);
bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac);
} else {
// 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?
if (ac.currentTime > t) {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.5.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.5.0",
"version": "0.4.1",
"description": "WebDirt integration for Strudel",
"main": "index.mjs",
"type": "module",
-2
View File
@@ -1,4 +1,2 @@
import './xen.mjs';
import './tune.mjs';
export * from './xen.mjs';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/xen",
"version": "0.5.0",
"version": "0.4.1",
"description": "Xenharmonic API for strudel",
"main": "index.mjs",
"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 { 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();
if (!tune.isValidScale(scale)) {
throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
}
tune.loadScale(scale);
tune.tonicize(1);
return pat.withHap((hap) => {
return hap.withValue(() => tune.note(hap.value));
tune.tonicize(tonic);
return this._asNumber()._withHap((hap) => {
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/>.
*/
import { register, _mod, parseNumeral } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, mod } = core;
export function edo(name) {
if (!/^[1-9]+[0-9]*edo$/.test(name)) {
@@ -42,23 +43,26 @@ function getXenScale(scale, indices) {
}
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);
return xenScale[i] * Math.pow(2, oct);
}
// scaleNameOrRatios: string || number[], steps?: number
export const xen = register('xen', function (scaleNameOrRatios, pat) {
return pat.withHap((hap) => {
Pattern.prototype._xen = function (scaleNameOrRatios, steps) {
return this._asNumber()._withHap((hap) => {
const scale = getXenScale(scaleNameOrRatios);
const frequency = xenOffset(scale, parseNumeral(hap.value));
return hap.withValue(() => frequency);
steps = steps || scale.length;
const frequency = xenOffset(scale, hap.value);
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
});
});
};
export const tuning = register('tuning', function (ratios, pat) {
return pat.withHap((hap) => {
const frequency = xenOffset(ratios, parseNumeral(hap.value));
return hap.withValue(() => frequency);
Pattern.prototype.tuning = function (steps) {
return this._asNumber()._withHap((hap) => {
const frequency = xenOffset(steps, hap.value);
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: {},
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/>.
*/
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 {
getAudioContext,
@@ -33,6 +34,7 @@ const supabase = createClient(
const modules = [
import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'),
@@ -45,7 +47,8 @@ const modules = [
];
evalScope(
controls, // sadly, this cannot be exported from core direclty
// Tone,
core,
{ WebDirt },
...modules,
);
+2 -1
View File
@@ -1,5 +1,6 @@
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 { nanoid } from 'nanoid';
import React, { useContext, useCallback, useLayoutEffect, useRef, useState } from 'react';
+5 -3
View File
@@ -1,11 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue', sans-serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
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';
export async function prebake({ isMock = false, baseDir = '.' } = {}) {
@@ -25,9 +26,9 @@ Pattern.prototype.piano = function () {
.s('piano')
.release(0.1)
.fmap((value) => {
const midi = valueToMidi(value);
const midi = typeof value.note === 'string' ? toMidi(value.note) : value.note;
// 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 };
});
};
+19 -24
View File
@@ -5,8 +5,8 @@
// import * as tunes from './tunes.mjs';
// import { evaluate } from '@strudel.cycles/eval';
import { evaluate } from '@strudel.cycles/transpiler';
import { evalScope } from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { Pattern, evalScope } = strudel;
import * as webaudio from '@strudel.cycles/webaudio';
import controls from '@strudel.cycles/core/controls.mjs';
// 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 '@strudel.cycles/tone/tone.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/tune.mjs';
// import '@strudel.cycles/core/euclid.mjs';
@@ -84,51 +85,51 @@ const toneHelpersMocked = {
highpass: mockNode,
};
strudel.Pattern.prototype.osc = function () {
Pattern.prototype.osc = function () {
return this;
};
strudel.Pattern.prototype.csound = function () {
Pattern.prototype.csound = function () {
return this;
};
strudel.Pattern.prototype.tone = function () {
Pattern.prototype.tone = function () {
return this;
};
strudel.Pattern.prototype.webdirt = function () {
Pattern.prototype.webdirt = function () {
return this;
};
// draw mock
strudel.Pattern.prototype.pianoroll = function () {
Pattern.prototype.pianoroll = function () {
return this;
};
// speak mock
strudel.Pattern.prototype.speak = function () {
Pattern.prototype.speak = function () {
return this;
};
// webaudio mock
strudel.Pattern.prototype.wave = function () {
Pattern.prototype.wave = function () {
return this;
};
strudel.Pattern.prototype.filter = function () {
Pattern.prototype.filter = function () {
return this;
};
strudel.Pattern.prototype.adsr = function () {
Pattern.prototype.adsr = function () {
return this;
};
strudel.Pattern.prototype.out = function () {
Pattern.prototype.out = function () {
return this;
};
strudel.Pattern.prototype.soundfont = function () {
Pattern.prototype.soundfont = function () {
return this;
};
// tune mock
strudel.Pattern.prototype.tune = function () {
Pattern.prototype.tune = function () {
return this;
};
strudel.Pattern.prototype.midi = function () {
Pattern.prototype.midi = function () {
return this;
};
@@ -153,19 +154,16 @@ const audioCtx = {
const getDrawContext = () => canvasCtx;
const getAudioContext = () => audioCtx;
const loadSoundfont = () => {};
const loadCsound = () => {};
const loadCSound = () => {};
const loadcsound = () => {};
// TODO: refactor to evalScope
evalScope(
// Tone,
strudel,
Pattern.prototype.bootstrap(),
toneHelpersMocked,
uiHelpersMocked,
controls,
webaudio,
tonalHelpers,
/* controls,
toneHelpers,
voicingHelpers,
@@ -181,9 +179,6 @@ evalScope(
getDrawContext,
getAudioContext,
loadSoundfont,
loadCSound,
loadCsound,
loadcsound,
Clock: {}, // whatever
// Tone,
},
@@ -192,7 +187,7 @@ evalScope(
export const queryCode = async (code, cycles = 1) => {
const { pattern } = await evaluate(code);
const haps = pattern.queryArc(0, cycles);
return haps.map((h) => h.show(true));
return haps.map((h) => h.showWhole());
};
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(
"c2 eb2 g2",
"Cm7".voicings('lefthand').slow(2)
"Cm7".voicings(['g2','c4']).slow(2)
).transpose(
"<1 2 3 2>".slow(2)
).transpose(5)`;
// range ['g2','c4']
export const scaleTranspose = `"f2,f3,c4,ab4"
.scale(seq('F minor', 'F harmonic minor').slow(4))
@@ -103,14 +102,13 @@ export const scaleTranspose = `"f2,f3,c4,ab4"
export const struct = `stack(
"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")
.voicings('lefthand')
.voicings(['G3','A4'])
).slow(4)`;
// range ['G3','A4']
export const magicSofa = `stack(
"<C^7 F^7 ~> <Dm7 G7 A7 ~>"
.every(2, fast(2))
.voicings('lefthand'),
.voicings(),
"<c2 f2 g2> <d2 g2 a2 e2>"
).transpose("<0 2 3 4>")`;
// below doesn't work anymore due to constructor cleanup
@@ -158,7 +156,7 @@ const synths = stack(
scaleTranspose(8).early(3/8)
).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),
"<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(
drums.fast(2),
@@ -199,7 +197,7 @@ export const giantStepsReggae = `stack(
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
)
.struct("~ [x ~]".fast(4*8))
.voicings('lefthand'),
.voicings(['E3', 'G4']),
// bass
seq(
"[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]",
@@ -210,8 +208,6 @@ export const giantStepsReggae = `stack(
.struct("x ~".fast(4*8))
).slow(25).note()`;
// range ['E3', 'G4']
// TODO:
/*
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)
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)
).note().piano().slow(2)`;
@@ -447,11 +443,9 @@ export const bossaRandom = `const chords = "<Am7 Am7 Dm7 E7>"
const roots = chords.rootNotes(2)
stack(
chords.voicings('lefthand').struct(
chords.voicings(['F4', 'A5']).struct(
\` x@2 ~ x ~ ~ ~ x |
x? ~ ~ x@3 ~ x |
x? ~ ~ x ~ x@3\`),
roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7")
).slow(2).pianoroll().note().piano()`;
// range ['F4', 'A5']
+16 -19
View File
@@ -67,8 +67,6 @@ stack(
`;
export const giantSteps = `// John Coltrane - Giant Steps
setVoicingRange('lefthand', ['E3', 'G4']);
stack(
// melody
seq(
@@ -83,7 +81,7 @@ stack(
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).voicings('lefthand'),
).voicings(['E3', 'G4']),
// bass
seq(
"[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'])
.slow(8)).struct("[~ x]*2")
.layer(
x=>x.scaleTranspose(0).early(0),
x=>x.scaleTranspose(2).early(1/8),
x=>x.scaleTranspose(7).early(1/4),
x=>x.scaleTranspose(8).early(3/8)
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).apply(thru).note().apply(keys).mask("<~ x>/16"),
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru))
.struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2))
.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)
.delay(.4).delaytime(.12)
.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
const chords = "<Cm7 Fm7 G7 F#7>";
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(4)
.scale(cat('C minor','F dorian','G dorian','F# mixolydian'))
@@ -503,7 +501,7 @@ stack(
.gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
//.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
.add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n"
@@ -533,7 +531,7 @@ samples({
perc: ['perc/002_perc2.wav'],
}, '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'))
.n().slow(4)
.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' })
s("p")
.loopAt(32)
.loopAt(32,1)
.chop(128)
.jux(rev)
.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("misc:2").speed(1).delay(.5).delaytime(1/3).gain(.4),
// chords
note("[~ Gm7] ~ [~ Dm7] ~".voicings('lefthand').superimpose(x=>x.add(.1)))
note("[~ Gm7] ~ [~ Dm7] ~".voicings().superimpose(x=>x.add(.1)))
.s('sawtooth').gain(.5)
.cutoff(perlin.range(400,3000).slow(8))
.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
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
setVoicingRange('lefthand', ['c3','a4'])
stack(
s('bass').loopAt(8).clip(1),
s('bass').loopAt(8,1).clip(1),
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')
.superimpose(x=>x.add(.1))
.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/
// by Felix Roos
await loadCsound\`
await csound\`
instr CoolSynth
iduration = p3
ifreq = p4
@@ -809,7 +806,7 @@ export const loungeSponge = `
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
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))
.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
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)
.add(perlin.range(0,0.2)).sub("<0 -12>/8")
.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 { samples } from '@strudel.cycles/webaudio';
import { prebake } from '../repl/src/prebake.mjs';
@@ -7,9 +7,9 @@ fetch('https://strudel.tidalcycles.org/EmuSP12.json')
.then((res) => res.json())
.then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/'));
evalScope(
controls,
import('@strudel.cycles/core'),
core.evalScope(
core,
// import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
+1 -1
View File
@@ -9,4 +9,4 @@ module.exports = {
tailwindcss: {},
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
.gain(.4) // turn down
.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
.add(perlin.range(0,.5)) // random pitch variation
.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:
<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)
@@ -767,7 +769,7 @@ Together with layer, struct and voicings, this can be used to create a basic bac
<MiniRepl
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)
)`}
/>
@@ -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.
<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()`}
/>