diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..580f7338 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,46 @@ +name: Build and Deploy + +on: [workflow_dispatch] + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + deployments: write + +# Allow one concurrent deployment +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: "npm" + - name: Install Dependencies + run: npm ci && cd repl && npm ci && cd ../tutorial && npm ci + + - name: Build + run: npm run build + + - name: Setup Pages + uses: actions/configure-pages@v2 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + # Upload entire repository + path: "./out" + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.vscode/settings.json b/.vscode/settings.json index e8635e12..e005426f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "cSpell.words": [ "subspan", "vals" - ] + ], + "yaml.schemas": { + "https://json.schemastore.org/github-workflow.json": "file:///home/felix/projects/strudel/.github/workflows/deploy.yml" + } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 04b7cca7..494751aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "jsdoc-json": "^2.0.2", "jsdoc-to-markdown": "^7.1.1", "lerna": "^4.0.0", + "rollup-plugin-visualizer": "^5.8.1", "vitest": "^0.21.1" } }, @@ -4256,6 +4257,15 @@ "clone": "^1.0.2" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -6506,6 +6516,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6722,6 +6747,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -7998,6 +8035,18 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -8575,6 +8624,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -9178,18 +9244,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -9941,6 +9995,91 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-visualizer": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.8.1.tgz", + "integrity": "sha512-NBT/xN/LWCwDM2/j5vYmjzpEAKHyclo/8Cv8AfTCwgADAG+tLJDy1vzxMw6NO0dSDjmTeRELD9UU3FwknLv0GQ==", + "dev": true, + "dependencies": { + "nanoid": "^3.3.4", + "open": "^8.4.0", + "source-map": "^0.7.3", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "rollup": "^2.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -15950,6 +16089,12 @@ "clone": "^1.0.2" } }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -17575,6 +17720,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -17725,6 +17876,15 @@ "call-bind": "^1.0.2" } }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -18733,6 +18893,12 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -19176,6 +19342,17 @@ "mimic-fn": "^2.1.0" } }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -19551,14 +19728,6 @@ "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" - }, - "dependencies": { - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true - } } }, "postcss-js": { @@ -20263,6 +20432,64 @@ "fsevents": "~2.3.2" } }, + "rollup-plugin-visualizer": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.8.1.tgz", + "integrity": "sha512-NBT/xN/LWCwDM2/j5vYmjzpEAKHyclo/8Cv8AfTCwgADAG+tLJDy1vzxMw6NO0dSDjmTeRELD9UU3FwknLv0GQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "open": "^8.4.0", + "source-map": "^0.7.3", + "yargs": "^17.5.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", diff --git a/package.json b/package.json index bf045288..97f26247 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "osc": "cd packages/osc && npm run server", "build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build", "preview": "npx serve ./out", - "deploy": "gh-pages -d out", + "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" }, @@ -46,6 +46,7 @@ "jsdoc-json": "^2.0.2", "jsdoc-to-markdown": "^7.1.1", "lerna": "^4.0.0", + "rollup-plugin-visualizer": "^5.8.1", "vitest": "^0.21.1" } } diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index b9d8a257..c5c19644 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -11,7 +11,7 @@ const generic_params = [ /** * Select a sound / sample by name. * - *
+ *
* show all sounds * * 808 (6) 808bd (25) 808cy (25) 808hc (5) 808ht (5) 808lc (5) 808lt (5) 808mc (5) 808mt (5) 808oh (5) 808sd (25) 909 (1) ab (12) ade (10) ades2 (9) ades3 (7) ades4 (6) alex (2) alphabet (26) amencutup (32) armora (7) arp (2) arpy (11) auto (11) baa (7) baa2 (7) bass (4) bass0 (3) bass1 (30) bass2 (5) bass3 (11) bassdm (24) bassfoo (3) battles (2) bd (24) bend (4) bev (2) bin (2) birds (10) birds3 (19) bleep (13) blip (2) blue (2) bottle (13) breaks125 (2) breaks152 (1) breaks157 (1) breaks165 (1) breath (1) bubble (8) can (14) casio (3) cb (1) cc (6) chin (4) circus (3) clak (2) click (4) clubkick (5) co (4) coins (1) control (2) cosmicg (15) cp (2) cr (6) crow (4) d (4) db (13) diphone (38) diphone2 (12) dist (16) dork2 (4) dorkbot (2) dr (42) dr2 (6) dr55 (4) dr_few (8) drum (6) drumtraks (13) e (8) east (9) electro1 (13) em2 (6) erk (1) f (1) feel (7) feelfx (8) fest (1) fire (1) flick (17) fm (17) foo (27) future (17) gab (10) gabba (4) gabbaloud (4) gabbalouder (4) glasstap (3) glitch (8) glitch2 (8) gretsch (24) gtr (3) h (7) hand (17) hardcore (12) hardkick (6) haw (6) hc (6) hh (13) hh27 (13) hit (6) hmm (1) ho (6) hoover (6) house (8) ht (16) if (5) ifdrums (3) incoming (8) industrial (32) insect (3) invaders (18) jazz (8) jungbass (20) jungle (13) juno (12) jvbass (13) kicklinn (1) koy (2) kurt (7) latibro (8) led (1) less (4) lighter (33) linnhats (6) lt (16) made (7) made2 (1) mash (2) mash2 (4) metal (10) miniyeah (4) monsterb (6) moog (7) mouth (15) mp3 (4) msg (9) mt (16) mute (28) newnotes (15) noise (1) noise2 (8) notes (15) numbers (9) oc (4) odx (15) off (1) outdoor (6) pad (3) padlong (1) pebbles (1) perc (6) peri (15) pluck (17) popkick (10) print (11) proc (2) procshort (8) psr (30) rave (8) rave2 (4) ravemono (2) realclaps (4) reverbkick (1) rm (2) rs (1) sax (22) sd (2) seawolf (3) sequential (8) sf (18) sheffield (1) short (5) sid (12) sine (6) sitar (8) sn (52) space (18) speakspell (12) speech (7) speechless (10) speedupdown (9) stab (23) stomp (10) subroc3d (11) sugar (2) sundance (6) tabla (26) tabla2 (46) tablex (3) tacscan (22) tech (13) techno (7) tink (5) tok (4) toys (13) trump (11) ul (10) ulgab (5) uxay (3) v (6) voodoo (5) wind (10) wobble (1) world (3) xmas (1) yeah (31) @@ -23,7 +23,7 @@ const generic_params = [ * @name s * @param {string | Pattern} sound The sound / pattern of sounds to pick * @example - * s("bd hh").osc() + * s("bd hh").out() * */ ['s', 's', 'sound'], @@ -129,7 +129,7 @@ const generic_params = [ * @name bandf * @param {number | Pattern} frequency center frequency * @example - * s("bd sd").bandf("<1000 2000 4000 8000>").osc() + * s("bd sd,hh*3").bandf("<1000 2000 4000 8000>").out() * */ ['f', 'bandf', 'A pattern of numbers from 0 to 1. Sets the center frequency of the band-pass filter.'], @@ -140,7 +140,7 @@ const generic_params = [ * @name bandq * @param {number | Pattern} q q factor * @example - * s("bd sd").bandf(2000).bandq("<.2 .9>").osc() + * s("bd sd").bandf(500).bandq("<0 1 2 3>").out() * */ ['f', 'bandq', 'a pattern of anumbers from 0 to 1. Sets the q-factor of the band-pass filter.'], @@ -202,7 +202,7 @@ const generic_params = [ * @name crush * @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction). * @example - * s(",hh*3,jvbass*2").fast(2).crush("<16 8 7 6 5 4 3 2>").osc() + * s(",hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>").out() * */ [ @@ -216,7 +216,7 @@ const generic_params = [ * @name coarse * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @example - * s("xmas").coarse("<1 4 8 16 32>").osc() + * s("bd sd,hh*4").coarse("<1 4 8 16 32>").out() * */ [ @@ -253,7 +253,7 @@ const generic_params = [ * @name cutoff * @param {number | Pattern} frequency audible between 0 and 20000 * @example - * s("bd,hh*2,<~ sd>").fast(2).cutoff("<4000 2000 1000 500 200 100>").osc() + * s("bd sd,hh*3").cutoff("<4000 2000 1000 500 200 100>").out() * */ // TODO: add lpf synonym @@ -264,7 +264,7 @@ const generic_params = [ * @name hcutoff * @param {number | Pattern} frequency audible between 0 and 20000 * @example - * s("bd,hh*2,<~ sd>").fast(2).hcutoff("<4000 2000 1000 500 200 100>").osc() + * s("bd sd,hh*4").hcutoff("<4000 2000 1000 500 200 100>").out() * */ // TODO: add hpf synonym @@ -274,12 +274,12 @@ const generic_params = [ 'a pattern of numbers from 0 to 1. Applies the cutoff frequency of the high-pass filter. Also has alias @hpf@', ], /** - * Applies the cutoff frequency of the high-pass filter. + * Applies the resonance of the high-pass filter. * * @name hresonance * @param {number | Pattern} q resonance factor between 0 and 1 * @example - * s("bd,hh*2,<~ sd>").fast(2).hcutoff(2000).hresonance("<0 .2 .4 .6>").osc() + * s("bd sd,hh*4").hcutoff(2000).hresonance("<0 10 20 30>").out() * */ [ @@ -294,13 +294,13 @@ const generic_params = [ * @name resonance * @param {number | Pattern} q resonance factor between 0 and 1 * @example - * s("bd,hh*2,<~ sd>").fast(2).cutoff(2000).resonance("<0 .2 .4 .6>").osc() + * s("bd sd,hh*4").cutoff(2000).resonance("<0 10 20 30>").out() * */ ['f', 'resonance', 'a pattern of numbers from 0 to 1. Specifies the resonance of the low-pass filter.'], // TODO: add lpq synonym? /** - * Set detune of oscillators. Works only with some synths, see tidal doc + * DJ filter, below 0.5 is low pass filter, above is high pass filter. * * @name djf * @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter @@ -493,7 +493,7 @@ const generic_params = [ * @name pan * @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel) * @example - * s("[bd hh]*2").pan("<.5 1 .5 0>").osc() + * s("[bd hh]*2").pan("<.5 1 .5 0>").out() * */ [ @@ -591,7 +591,7 @@ const generic_params = [ * @name shape * @param {number | Pattern} distortion between 0 and 1 * @example - * s("bd sd").shape("<0 .2 .4 .6 .8 1>").osc() + * s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>").out() * */ [ @@ -648,16 +648,16 @@ const generic_params = [ // ['f', 'tomdecay', ''], // ['f', 'vcfegint', ''], // ['f', 'vcoegint', ''], + // TODO: Use a rest (~) to override the effect <- vowel /** * * Formant filter to make things sound like vowels. * * @name vowel - * @param {string | Pattern} vowel You can use a e i o u. Use a rest (~) to override the effect + * @param {string | Pattern} vowel You can use a e i o u. * @example - * vowel("a e i [o u]").slow(2) - * .n("<[0,7]!4 [2,7]!4>") - * .s('supersquare').osc() + * note("c2 >").s('sawtooth') + * .vowel(">").out() * */ [ diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 48e52643..2950d015 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -513,11 +513,14 @@ export class Pattern { } /** - * Assumes a numerical pattern, containing unipolar values in the range 0 .. - * 1. Returns a new pattern with values scaled to the given min/max range. - * @param {Number} min - * @param {Number} max + * Assumes a numerical pattern, containing unipolar values in the range 0 .. 1. + * Returns a new pattern with values scaled to the given min/max range. + * Most useful in combination with continuous patterns. + * @name range + * @memberof Pattern * @returns Pattern + * @example + * s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4)).out() */ range(min, max) { return this.mul(max - min).add(min); @@ -683,6 +686,16 @@ export class Pattern { return func(this); } + /** + * Layers the result of the given function(s). Like {@link superimpose}, but without the original pattern: + * @name layer + * @memberof Pattern + * @returns Pattern + * @example + * "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4" + * .layer(x=>x.add("0,2")) + * .scale('C minor').note().out() + */ layer(...funcs) { return stack(...funcs.map((func) => func(this))); } @@ -734,14 +747,14 @@ export class Pattern { } /** - * Speed up a pattern by the given factor. + * Speed up a pattern by the given factor. Used by "*" in mini notation. * * @name fast * @memberof Pattern * @param {number | Pattern} factor speed up factor * @returns Pattern * @example - * seq(e5, b4, d5, c5).fast(2) + * s(" hh").fast(2).out() // s("[ hh]*2").out() */ _fast(factor) { const fastQuery = this.withQueryTime((t) => t.mul(factor)); @@ -749,14 +762,14 @@ export class Pattern { } /** - * Slow down a pattern over the given number of cycles. + * Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation. * * @name slow * @memberof Pattern * @param {number | Pattern} factor slow down factor * @returns Pattern * @example - * seq(e5, b4, d5, c5).slow(2) + * s(" hh").slow(2).out() // s("[ hh]/2").out() */ _slow(factor) { return this._fast(Fraction(1).div(factor)); @@ -795,14 +808,32 @@ export class Pattern { return this._fast(cpm / 60); } + /** + * Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator + * + * @name early + * @memberof Pattern + * @param {number | Pattern} cycles number of cycles to nudge left + * @returns Pattern + * @example + * "bd ~".stack("hh ~".early(.1)).s().out() + */ _early(offset) { - // Equivalent of Tidal's <~ operator offset = Fraction(offset); return this.withQueryTime((t) => t.add(offset)).withHapTime((t) => t.sub(offset)); } + /** + * Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator + * + * @name late + * @memberof Pattern + * @param {number | Pattern} cycles number of cycles to nudge right + * @returns Pattern + * @example + * "bd ~".stack("hh ~".late(.1)).s().out() + */ _late(offset) { - // Equivalent of Tidal's ~> operator offset = Fraction(offset); return this._early(Fraction(0).sub(offset)); } @@ -829,6 +860,17 @@ export class Pattern { return this._zoom(0, t)._slow(t); } + /** + * Applies the given structure to the pattern: + * + * @name struct + * @memberof Pattern + * @returns Pattern + * @example + * "c3,eb3,g3" + * .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~") + * .slow(4).note().out() + */ // struct(...binary_pats) { // // Re structure the pattern according to a binary pattern (false values are dropped) // const binary_pat = sequence(binary_pats); @@ -876,6 +918,16 @@ export class Pattern { return this.invert(); } + /** + * Applies the given function whenever the given pattern is in a true state. + * @name when + * @memberof Pattern + * @param {Pattern} binary_pat + * @param {function} func + * @returns Pattern + * @example + * "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)) + */ when(binary_pat, func) { //binary_pat = sequence(binary_pat) const true_pat = binary_pat._filterValues(id); @@ -885,10 +937,47 @@ export class Pattern { return stack(with_pat, without_pat); } + /** + * Superimposes the function result on top of the original pattern, delayed by the given time. + * @name off + * @memberof Pattern + * @param {Pattern | number} time offset time + * @param {function} func function to apply + * @returns Pattern + * @example + * "c3 eb3 g3".off(1/8, x=>x.add(7)) + */ off(time_pat, func) { return stack(this, func(this.late(time_pat))); } + /** + * Applies the given function every n cycles. + * @name every + * @memberof Pattern + * @param {number} n how many cycles + * @param {function} func function to apply + * @returns Pattern + * @example + * note("c3 d3 e3 g3").every(4, x=>x.rev()).out() + */ + every(n, func) { + const pat = this; + const pats = Array(n - 1).fill(pat); + // pats.unshift(func(pat)); + pats.push(func(pat)); + return slowcatPrime(...pats); + } + /** + * Applies the given function every n cycles, starting from the first cycle. + * @name every + * @memberof Pattern + * @param {number} n how many cycles + * @param {function} func function to apply + * @returns Pattern + * @example + * note("c3 d3 e3 g3").every(4, x=>x.rev()).out() + */ every(n, func) { const pat = this; const pats = Array(n - 1).fill(pat); @@ -896,6 +985,23 @@ export class Pattern { return slowcatPrime(...pats); } + /** + * Applies the given function every n cycles, starting from the last cycle. + * @name each + * @memberof Pattern + * @param {number} n how many cycles + * @param {function} func function to apply + * @returns Pattern + * @example + * note("c3 d3 e3 g3").every(4, x=>x.rev()).out() + */ + each(n, func) { + const pat = this; + const pats = Array(n - 1).fill(pat); + pats.push(func(pat)); + return slowcatPrime(...pats); + } + /** * Returns a new pattern where every other cycle is played once, twice as * fast, and offset in time by one quarter of a cycle. Creates a kind of @@ -906,6 +1012,15 @@ export class Pattern { return this.when(slowcat(false, true), (x) => fastcat(x, silence)._late(0.25)); } + /** + * Reverse all haps in a pattern + * + * @name rev + * @memberof Pattern + * @returns Pattern + * @example + * "c3 d3 e3 g3".rev() + */ rev() { const pat = this; const query = function (state) { @@ -948,6 +1063,15 @@ export class Pattern { return this.juxBy(1, func); } + /** + * Stacks the given pattern(s) to the current pattern. + * @name stack + * @memberof Pattern + * @example + * s("hh*2").stack( + * n("c2(3,8)") + * ).out() + */ stack(...pats) { return stack(this, ...pats); } @@ -956,11 +1080,28 @@ export class Pattern { return sequence(this, ...pats); } - // shorthand for sequence + /** + * Appends the given pattern(s) to the current pattern. Synonyms: .sequence .fastcat + * @name seq + * @memberof Pattern + * @example + * s("hh*2").seq( + * n("c2(3,8)") + * ).out() + */ seq(...pats) { return sequence(this, ...pats); } + /** + * Appends the given pattern(s) to the next cycle. Synonym: .slowcat + * @name cat + * @memberof Pattern + * @example + * s("hh*2").cat( + * n("c2(3,8)") + * ).out() + */ cat(...pats) { return cat(this, ...pats); } @@ -973,6 +1114,16 @@ export class Pattern { return slowcat(this, ...pats); } + /** + * Superimposes the result of the given function(s) on top of the original pattern: + * @name superimpose + * @memberof Pattern + * @returns Pattern + * @example + * "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4" + * .superimpose(x=>x.add(2)) + * .scale('C minor').note().out() + */ superimpose(...funcs) { return this.stack(...funcs.map((func) => func(this))); } @@ -985,24 +1136,67 @@ export class Pattern { return this.stutWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i))); } - // these might change with: https://github.com/tidalcycles/Tidal/issues/902 + /** + * Superimpose and offset multiple times, applying the given function each time. + * @name echoWith + * @memberof Pattern + * @returns Pattern + * @param {number} times how many times to repeat + * @param {number} time cycle offset between iterations + * @param {function} func function to apply, given the pattern and the iteration index + * @example + * "<0 [2 4]>" + * .echoWith(4, 1/8, (p,n) => p.add(n*2)) + * .scale('C minor').note().legato(.2).out() + */ _echoWith(times, time, func) { return stack(...listRange(0, times - 1).map((i) => func(this.late(Fraction(time).mul(i)), i))); } + /** + * Superimpose and offset multiple times, gradually decreasing the velocity + * @name echo + * @memberof Pattern + * @returns Pattern + * @example + * s("bd sd").echo(3, 1/6, .8).out() + */ _echo(times, time, feedback) { return this._echoWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i))); } + /** + * Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played. + * @name iter + * @memberof Pattern + * @returns Pattern + * @example + * note("0 1 2 3".scale('A minor')).iter(4).out() + */ iter(times, back = false) { return slowcat(...listRange(0, times - 1).map((i) => (back ? this.late(i / times) : this.early(i / times)))); } - // known as iter' in tidalcycles + /** + * Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles + * @name iterBack + * @memberof Pattern + * @returns Pattern + * @example + * note("0 1 2 3".scale('A minor')).iterBack(4).out() + */ iterBack(times) { return this.iter(times, true); } + /** + * Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle). + * @name chunk + * @memberof Pattern + * @returns Pattern + * @example + * "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note().out() + */ _chunk(n, func, back = false) { const binary = Array(n - 1).fill(false); binary.unshift(true); @@ -1010,6 +1204,14 @@ export class Pattern { return this.when(binary_pat, func); } + /** + * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles + * @name chunkBack + * @memberof Pattern + * @returns Pattern + * @example + * "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note().out() + */ _chunkBack(n, func) { return this._chunk(n, func, true); } @@ -1184,6 +1386,11 @@ Pattern.prototype.patternified = [ 'slow', 'velocity', ]; + +// aliases +export const polyrhythm = stack; +export const pr = stack; + // methods that create patterns, which are added to patternified Pattern methods Pattern.prototype.factories = { pure, @@ -1206,12 +1413,11 @@ Pattern.prototype.factories = { // Nothing export const silence = new Pattern((_) => []); -/** A discrete value that repeats once per cycle: +/** A discrete value that repeats once per cycle. * - * @param {any} value - The value to repeat * @returns {Pattern} * @example - * pure('e4') + * pure('e4') // "e4" */ export function pure(value) { function query(state) { @@ -1241,12 +1447,11 @@ export function reify(thing) { return pure(thing); } -/** The given items are played at the same time at the same length: +/** The given items are played at the same time at the same length. * - * @param {...any} items - The items to stack * @return {Pattern} * @example - * stack(g3, b3, [e4, d4]) + * stack(g3, b3, [e4, d4]) // "g3,b3,[e4,d4]" */ export function stack(...pats) { // Array test here is to avoid infinite recursions.. @@ -1259,7 +1464,6 @@ export function stack(...pats) { * * synonyms: {@link cat} * - * @param {...any} items - The items to concatenate * @return {Pattern} * @example * slowcat(e5, b4, [d5, c5]) @@ -1315,16 +1519,22 @@ export function fastcat(...pats) { return slowcat(...pats)._fast(pats.length); } -/** See {@link slowcat} */ +/** The given items are con**cat**enated, where each one takes one cycle. Synonym: slowcat + * + * @param {...any} items - The items to concatenate + * @return {Pattern} + * @example + * cat(e5, b4, [d5, c5]) // "" + * + */ export function cat(...pats) { return slowcat(...pats); } -/** Like {@link fastcat}, but where each step has a temporal weight: - * @param {...Array} items - The items to concatenate +/** Like {@link seq}, but each step has a length, relative to the whole. * @return {Pattern} * @example - * timeCat([3,e3],[1, g3]) + * timeCat([3,e3],[1, g3]) // "e3@3 g3" */ export function timeCat(...timepats) { const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0)); @@ -1343,7 +1553,11 @@ export function sequence(...pats) { return fastcat(...pats); } -/** See {@link fastcat} */ +/** Like **cat**, but the items are crammed into one cycle. Synonyms: fastcat, sequence + * @example + * seq(e5, b4, [d5, c5]) // "e5 b4 [d5 c5]" + * + */ export function seq(...pats) { return fastcat(...pats); } @@ -1392,20 +1606,6 @@ export function pm(...args) { polymeter(...args); } -export function polyrhythm(...xs) { - const seqs = xs.map((a) => sequence(a)); - - if (seqs.length == 0) { - return silence; - } - return stack(...seqs); -} - -// alias -export function pr(args) { - polyrhythm(args); -} - export const add = curry((a, pat) => pat.add(a)); export const chop = curry((a, pat) => pat.chop(a)); export const chunk = curry((a, pat) => pat.chunk(a)); diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 9a4b5fe5..8637c30e 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -74,7 +74,7 @@ export const square2 = square._toBipolar(); * * @return {Pattern} * @example - * triangle.segment(2).range(0,7).scale('C minor') + * tri.segment(8).range(0,7).scale('C minor') * */ export const tri = fastcat(isaw, saw); @@ -111,7 +111,17 @@ const timeToRandsPrime = (seed, n) => { const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); /** - * A continuous pattern of random numbers, between 0 and 1 + * + */ + +/** + * A continuous pattern of random numbers, between 0 and 1. + * + * @name rand + * @example + * // randomly change the cutoff + * s("bd sd,hh*4").cutoff(rand.range(500,2000)).out() + * */ export const rand = signal(timeToRand); /** @@ -124,6 +134,17 @@ export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin(); export const brand = _brandBy(0.5); export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); + +/** + * A continuous pattern of random integers, between 0 and n-1. + * + * @name irand + * @param {number} n max value (exclusive) + * @example + * // randomly select scale notes from 0 - 7 (= C to C) + * irand(8).struct("x(3,8)").scale('C minor').note().out() + * + */ export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); export const __chooseWith = (pat, xs) => { @@ -225,6 +246,15 @@ export const perlinWith = (pat) => { return pat.sub(pata).fmap(interp).appBoth(pata.fmap(timeToRand)).appBoth(patb.fmap(timeToRand)); }; +/** + * Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1. + * + * @name perlin + * @example + * // randomly change the cutoff + * s("bd sd,hh*4").cutoff(perlin.range(500,2000)).out() + * + */ export const perlin = perlinWith(time); Pattern.prototype._degradeByWith = function (withPat, x) { diff --git a/packages/webaudio/sampler.mjs b/packages/webaudio/sampler.mjs index 10c6d6be..a86d26ce 100644 --- a/packages/webaudio/sampler.mjs +++ b/packages/webaudio/sampler.mjs @@ -82,14 +82,14 @@ export const loadGithubSamples = async (path, nameFn) => { }; /** - * load the given sample map for webdirt + * Loads a collection of samples to use with `s` * * @example - * loadSamples({ - * bd: '808bd/BD0000.WAV', - * sd: ['808sd/SD0000.WAV','808sd/SD0010.WAV','808sd/SD0050.WAV'] + * samples({ + * bd: '808bd/BD0000.WAV', + * sd: '808sd/SD0010.WAV' * }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/'); - * s("bd ").n(2).webdirt() + * s("[bd ~]*2, [~ hh]*2, ~ sd").out() * */ diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 18186118..0ae4afe9 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -10,6 +10,7 @@ import { fromMidi, toMidi } from '@strudel.cycles/core'; import { loadBuffer } from './sampler.mjs'; const { Pattern } = strudel; import './vowel.mjs'; +import workletsUrl from './worklets.mjs?url'; // export const getAudioContext = () => Tone.getContext().rawContext; @@ -129,6 +130,31 @@ const splitSN = (s, n) => { return [s2, n2]; }; +let workletsLoading; +function loadWorklets() { + if (workletsLoading) { + return workletsLoading; + } + workletsLoading = getAudioContext().audioWorklet.addModule(workletsUrl); + return workletsLoading; +} + +function getWorklet(ac, processor, params) { + const node = new AudioWorkletNode(ac, processor); + Object.entries(params).forEach(([key, value]) => { + node.parameters.get(key).value = value; + }); + return node; +} + +try { + loadWorklets(); +} catch (err) { + console.warn('could not load AudioWorklet effects coarse, crush and shape', err); +} + +const cutGroups = []; + Pattern.prototype.out = function () { return this.onTrigger(async (t, hap, ct, cps) => { const hapDuration = hap.duration / cps; @@ -151,15 +177,22 @@ Pattern.prototype.out = function () { hresonance = 1, bandf, bandq = 1, + coarse, + crush, + shape, pan, attack = 0.001, - decay = 0.05, - sustain = 0.5, + decay = 0.001, + sustain = 1, release = 0.001, speed = 1, // sample playback speed begin = 0, end = 1, vowel, + unit, + nudge = 0, // TODO: is this in seconds? + cut, + loop, } = hap.value; const { velocity = 1 } = hap.context; gain *= velocity; // legacy fix for velocity @@ -173,7 +206,7 @@ Pattern.prototype.out = function () { } if (!s || ['sine', 'square', 'triangle', 'sawtooth'].includes(s)) { // with synths, n and note are the same thing - n = note || n; + n = note || n || 36; if (typeof n === 'string') { n = toMidi(n); // e.g. c3 => 48 } @@ -227,29 +260,33 @@ Pattern.prototype.out = function () { return; } bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value; - // TODO: nudge, unit, cut, loop - let duration = soundfont || clip ? hapDuration : bufferSource.buffer.duration; - // let duration = bufferSource.buffer.duration; - const offset = begin * duration; - duration = ((end - begin) * duration) / Math.abs(speed); - if (soundfont || clip) { - bufferSource.start(t, offset); // duration does not work here for some reason - } else { - bufferSource.start(t, offset, duration); + if (unit === 'c') { + // are there other units? + bufferSource.playbackRate.value = bufferSource.playbackRate.value * bufferSource.buffer.duration; + } + let duration = soundfont || clip ? hapDuration : bufferSource.buffer.duration / bufferSource.playbackRate.value; + // "The computation of the offset into the sound is performed using the sound buffer's natural sample rate, + // rather than the current playback rate, so even if the sound is playing at twice its normal speed, + // the midway point through a 10-second audio buffer is still 5." + const offset = begin * duration * bufferSource.playbackRate.value; + duration = (end - begin) * duration; + if (loop) { + bufferSource.loop = true; + bufferSource.loopStart = offset; + bufferSource.loopEnd = offset + duration; + duration = loop * duration; + } + t += nudge; + + bufferSource.start(t, offset); + if (cut !== undefined) { + cutGroups[cut]?.stop(t); // fade out? + cutGroups[cut] = bufferSource; } chain.push(bufferSource); - if (soundfont || clip) { - const env = ac.createGain(); - const releaseLength = 0.1; - env.gain.value = 0.6; - env.gain.setValueAtTime(env.gain.value, t + duration); - env.gain.linearRampToValueAtTime(0, t + duration + releaseLength); - // env.gain.linearRampToValueAtTime(0, t + duration + releaseLength); - chain.push(env); - bufferSource.stop(t + duration + releaseLength); - } else { - bufferSource.stop(t + duration); - } + bufferSource.stop(t + duration + release); + const adsr = getADSR(attack, decay, sustain, release, 1, t, t + duration); + chain.push(adsr); } // master out const master = ac.createGain(); @@ -261,7 +298,9 @@ Pattern.prototype.out = function () { hcutoff !== undefined && chain.push(getFilter('highpass', hcutoff, hresonance)); bandf !== undefined && chain.push(getFilter('bandpass', bandf, bandq)); vowel !== undefined && chain.push(ac.createVowelFilter(vowel)); - // TODO vowel + coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })); + crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); + shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape })); // TODO delay / delaytime / delayfeedback // panning if (pan !== undefined) { @@ -276,6 +315,8 @@ Pattern.prototype.out = function () { chain.push(ac.destination); // connect chain elements together chain.slice(1).reduce((last, current) => last.connect(current), chain[0]); + // disconnect all nodes when source node has ended: + chain[0].onended = () => chain.forEach((n) => n.disconnect()); } catch (e) { console.warn('.out error:', e); } diff --git a/packages/webaudio/worklets.mjs b/packages/webaudio/worklets.mjs new file mode 100644 index 00000000..c5306c45 --- /dev/null +++ b/packages/webaudio/worklets.mjs @@ -0,0 +1,96 @@ +// LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE +// all the credit goes to dktr0's webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js +// <3 + +class CoarseProcessor extends AudioWorkletProcessor { + static get parameterDescriptors() { + return [{ name: 'coarse', defaultValue: 1 }]; + } + + constructor() { + super(); + this.notStarted = true; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const coarse = parameters.coarse; + const blockSize = 128; + const hasInput = !(input[0] === undefined); + if (hasInput) { + this.notStarted = false; + output[0][0] = input[0][0]; + for (let n = 1; n < blockSize; n++) { + if (n % coarse == 0) output[0][n] = input[0][n]; + else output[0][n] = output[0][n - 1]; + } + } + return this.notStarted || hasInput; + } +} + +registerProcessor('coarse-processor', CoarseProcessor); + +class CrushProcessor extends AudioWorkletProcessor { + static get parameterDescriptors() { + return [{ name: 'crush', defaultValue: 0 }]; + } + + constructor() { + super(); + this.notStarted = true; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const crush = parameters.crush; + const blockSize = 128; + const hasInput = !(input[0] === undefined); + if (hasInput) { + this.notStarted = false; + if (crush.length === 1) { + const x = Math.pow(2, crush[0] - 1); + for (let n = 0; n < blockSize; n++) output[0][n] = Math.round(input[0][n] * x) / x; + } else { + for (let n = 0; n < blockSize; n++) { + let x = Math.pow(2, crush[n] - 1); + output[0][n] = Math.round(input[0][n] * x) / x; + } + } + } + return this.notStarted || hasInput; + } +} +registerProcessor('crush-processor', CrushProcessor); + +class ShapeProcessor extends AudioWorkletProcessor { + static get parameterDescriptors() { + return [{ name: 'shape', defaultValue: 0 }]; + } + + constructor() { + super(); + this.notStarted = true; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const shape0 = parameters.shape[0]; + const shape1 = shape0 < 1 ? shape0 : 1.0 - 4e-10; + const shape = (2.0 * shape1) / (1.0 - shape1); + const blockSize = 128; + const hasInput = !(input[0] === undefined); + if (hasInput) { + this.notStarted = false; + for (let n = 0; n < blockSize; n++) { + output[0][n] = ((1 + shape) * input[0][n]) / (1 + shape * Math.abs(input[0][n])); + } + } + return this.notStarted || hasInput; + } +} + +registerProcessor('shape-processor', ShapeProcessor); diff --git a/repl/src/App.jsx b/repl/src/App.jsx index fd464d2b..ae55fc78 100644 --- a/repl/src/App.jsx +++ b/repl/src/App.jsx @@ -214,6 +214,19 @@ function App() { <>loading... )} + {!isEmbedded && (