Compare commits

..

11 Commits

Author SHA1 Message Date
alex 185abf1fc1 abandoned experiment 2023-01-21 09:12:41 +00:00
alex 37efdd9ce1 prettier 2023-01-16 21:55:16 +00:00
alex f4039d3666 Merge branch 'main' into composable 2023-01-16 21:47:46 +00:00
Alex McLean c0d6e36113 Merge branch 'main' into composable 2023-01-12 16:47:47 +00:00
Alex McLean 8261802078 more hackery, composing seems to work now 2023-01-12 16:36:42 +00:00
alex 6dda6bac66 more composable magic 2023-01-11 22:27:00 +00:00
alex d5ab1a3471 actually compose functions together 2023-01-11 21:59:10 +00:00
alex ded90733f6 mark up more function args 2023-01-11 20:30:07 +00:00
alex c64485db99 hack register to support function parameters that can be patterns which compose 2023-01-10 23:28:22 +00:00
alex afa5d6e704 prettier 2023-01-10 11:16:43 +00:00
alex bde7e79a38 failed attempt at composifying 2023-01-10 11:05:51 +00:00
216 changed files with 52857 additions and 24890 deletions
-3
View File
@@ -15,6 +15,3 @@ vite.config.js
!**/*.mjs !**/*.mjs
**/*.tsx **/*.tsx
**/*.ts **/*.ts
**/*.json
**/dev-dist
**/dist
+2 -3
View File
@@ -9,9 +9,8 @@
"ecmaVersion": "latest", "ecmaVersion": "latest",
"sourceType": "module" "sourceType": "module"
}, },
"plugins": ["import"], "plugins": [],
"rules": { "rules": {
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }], "no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }]
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
} }
} }
+3 -6
View File
@@ -22,18 +22,15 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 7
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
cache: "pnpm" cache: "npm"
- name: Install Dependencies - name: Install Dependencies
run: pnpm install run: npm ci && cd website && npm ci
- name: Build - name: Build
run: pnpm build run: npm run build
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v2 uses: actions/configure-pages@v2
+5 -8
View File
@@ -11,14 +11,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 7
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: "npm"
- run: pnpm install - run: npm install
- run: pnpm run format-check - run: npm run format-check
- run: pnpm run lint - run: npm run lint
- run: pnpm test - run: npm test
-2
View File
@@ -38,5 +38,3 @@ talk/public/samples
server/samples/old server/samples/old
repl/stats.html repl/stats.html
coverage coverage
public/icons/apple-splash-*
dev-dist
-3
View File
@@ -7,6 +7,3 @@
packages/mini/krill-parser.js packages/mini/krill-parser.js
packages/xen/tunejs.js packages/xen/tunejs.js
paper paper
pnpm-lock.yaml
pnpm-workspace.yaml
**/dev-dist
+1 -1
View File
@@ -7,7 +7,7 @@
"jsxSingleQuote": false, "jsxSingleQuote": false,
"trailingComma": "all", "trailingComma": "all",
"bracketSpacing": true, "bracketSpacing": true,
"bracketSameLine": false, "jsxBracketSameLine": false,
"arrowParens": "always", "arrowParens": "always",
"proseWrap": "preserve", "proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css", "htmlWhitespaceSensitivity": "css",
+11 -34
View File
@@ -58,22 +58,23 @@ To fix a bug that has been reported,
## Write Tests ## Write Tests
There are still many tests that have not been written yet! Reading and writing tests is a great opportunity to get familiar with the codebase. There are still many tests that have not been written yet! Reading and writing tests is a great opportunity to get familiar with the codebase.
You can find the tests in each package in the `test` folder. To run all tests, run `pnpm test` from the root folder. You can find the tests in each package in the `test` folder. To run all tests, run `npm test` from the root folder.
## Project Setup ## Project Setup
To get the project up and running for development, make sure you have installed: To get the project up and running for development, make sure you have installed:
- [git](https://git-scm.com/) - git
- [node](https://nodejs.org/en/) >= 18 - node, preferably v16
- [pnpm](https://pnpm.io/) (`npm i pnpm -g`)
then, do the following: then, do the following:
```sh ```sh
git clone https://github.com/tidalcycles/strudel.git && cd strudel git clone https://github.com/tidalcycles/strudel.git && cd strudel
pnpm i # install at root to symlink packages npm i # install at root to symlink packages
pnpm start # start repl npx lerna bootstrap # install all dependencies in packages
cd repl && npm i # install repl dependencies
npm run start # start repl
``` ```
Those commands might look slightly different for your OS. Those commands might look slightly different for your OS.
@@ -82,10 +83,6 @@ Please report any problems you've had with the setup instructions!
## Code Style ## Code Style
To make sure the code changes only where it should, we are using prettier to unify the code style. To make sure the code changes only where it should, we are using prettier to unify the code style.
- You can format all files at once by running `pnpm prettier` from the project root
- Run `pnpm format-check` from the project root to check if all files are well formatted
If you use VSCode, you can If you use VSCode, you can
1. install [the prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 1. install [the prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
@@ -93,29 +90,12 @@ If you use VSCode, you can
3. Choose "Configure Default Formatter..." 3. Choose "Configure Default Formatter..."
4. Select prettier 4. Select prettier
## ESLint
To prevent unwanted runtime errors, this project uses [eslint](https://eslint.org/).
- You can check for lint errors by running `pnpm lint`
There are also eslint extensions / plugins for most editors.
## Running Tests
- Run all tests with `pnpm test`
- Run all tests with UI using `pnpm test-ui`
## Running all CI Checks
When opening a PR, the CI runner will automatically check the code style and eslint, as well as run all tests.
You can run the same check with `pnpm check`
## Package Workflow ## Package Workflow
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning. The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version, When you run `npm i` on the root folder, [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces) will symlink all packages
allowing to develop multiple packages at the same time. in the `node_modules` folder. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
which allows developing multiple packages at the same time
## Package Publishing ## Package Publishing
@@ -126,15 +106,12 @@ npm login
npx lerna publish npx lerna publish
``` ```
To manually publish a single package, increase the version in the `package.json`, then run `pnpm publish`.
Important: Always publish with `pnpm`, as `npm` does not support overriding main files in `publishConfig`, which is done in all the packages.
### New Packages ### New Packages
To add a new package, you have to publish it manually the first time, using: To add a new package, you have to publish it manually the first time, using:
```sh ```sh
cd packages/<package-name> && pnpm publish --access public cd packages/<package-name> && npm publish --access public
``` ```
## Have Fun ## Have Fun
-1
View File
@@ -7,7 +7,6 @@ An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using w
- Try it here: <https://strudel.tidalcycles.org/> - Try it here: <https://strudel.tidalcycles.org/>
- Docs: <https://strudel.tidalcycles.org/learn/> - Docs: <https://strudel.tidalcycles.org/learn/>
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel> - Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
## Running Locally ## Running Locally
+1 -3
View File
@@ -2,7 +2,5 @@
"packages": [ "packages": [
"packages/*" "packages/*"
], ],
"version": "independent", "version": "independent"
"npmClient": "pnpm",
"useWorkspaces": true
} }
+10 -49
View File
@@ -3,57 +3,18 @@
This directory can be used to save your own patterns, which then get This directory can be used to save your own patterns, which then get
made into a pattern swatch. made into a pattern swatch.
Example: <https://felixroos.github.io/strudel/swatch/> 0. fork and clone the strudel repository
1. run `npm run setup` in the strudel folder
1. Save one or more .txt files in this folder
2. run `npm run repl` in the top-level strudel folder
3. open `http://localhost:3000/swatch/` !
## deploy ## deploy
### 1. fork the [strudel repo on github](https://github.com/tidalcycles/strudel.git) 1. in your fork, go to settings -> pages and select "Github Actions" as source
2. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
### 2. clone your fork to your machine `git clone https://github.com/<your-username>/strudel.git strudel && cd strudel` 3. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`
4. go to Actions -> `Build and Deploy` and click `Run workflow`
### 3. create a separate branch like `git branch patternuary && git checkout patternuary` 5. view your patterns at `<your-username>.github.io/strudel/swatch/`
### 4. save one or more .txt files in the my-patterns folder
### 5. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`, like this
```js
const site = 'https://<your-username>.github.io';
const base = '/strudel';
```
### 7. commit & push the changes
```sh
git add . && git commit -m "site config" && git push --set-upstream origin
```
### 8. deploy to github pages
- go to settings -> pages and select "Github Actions" as source
- go to settings -> environments -> github-pages and press the edit button next to `main` and type in `patternuary` (under "Deployment branches")
- go to Actions -> `Build and Deploy` and click `Run workflow` with branch `patternuary`
### 9. view your patterns at `<your-username>.github.io/strudel/swatch/`
Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site). Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site).
### 10. optional: automatic deployment
If you want to automatically deploy your site on push, go to `deploy.yml` and change `workflow_dispatch` to `push`.
## running locally
- install dependencies with `npm run setup`
- run dev server with `npm run repl` and open `http://localhost:3000/strudel/swatch/`
## tests fail?
Your tests might fail if the code does not follow prettiers format.
In that case, run `npm run codeformat`. To disable that, remove `npm run format-check` from `test.yml`
## updating your fork
To update your fork, you can pull the main branch and merge it into your `patternuary` branch.
+22021
View File
File diff suppressed because it is too large Load Diff
+15 -24
View File
@@ -4,20 +4,18 @@
"private": true, "private": true,
"description": "Port of tidalcycles to javascript", "description": "Port of tidalcycles to javascript",
"scripts": { "scripts": {
"setup": "pnpm i",
"pretest": "npm run jsdoc-json", "pretest": "npm run jsdoc-json",
"prebuild": "npm run jsdoc-json", "prebuild": "npm run jsdoc-json",
"prestart": "npm run jsdoc-json", "test": "vitest run --version",
"test": "npm run pretest && vitest run --version", "test-ui": "vitest --ui",
"test-ui": "npm run pretest && vitest --ui", "test-coverage": "vitest --coverage",
"test-coverage": "npm run pretest && vitest --coverage", "bootstrap": "lerna bootstrap",
"snapshot": "npm run pretest && vitest run -u --silent", "setup": "npm i && npm run jsdoc-json && npm run bootstrap && cd website && npm i",
"repl": "npm run prestart && cd website && npm run dev", "snapshot": "vitest run -u --silent",
"start": "npm run prestart && cd website && npm run dev", "repl": "cd website && npm run dev",
"dev": "npm run prestart && cd website && npm run dev",
"build": "npm run prebuild && cd website && npm run build",
"preview": "cd website && npm run preview",
"osc": "cd packages/osc && npm run server", "osc": "cd packages/osc && npm run server",
"build": "cd website && npm run build",
"preview": "cd website && npm run preview",
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out", "deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -c jsdoc.config.json", "jsdoc": "jsdoc packages/ -c jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json", "jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
@@ -28,6 +26,9 @@
"check": "npm run format-check && npm run lint && npm run test", "check": "npm run format-check && npm run lint && npm run test",
"iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf" "iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf"
}, },
"workspaces": [
"packages/*"
],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git" "url": "git+https://github.com/tidalcycles/strudel.git"
@@ -45,23 +46,12 @@
"url": "https://github.com/tidalcycles/strudel/issues" "url": "https://github.com/tidalcycles/strudel/issues"
}, },
"homepage": "https://strudel.tidalcycles.org", "homepage": "https://strudel.tidalcycles.org",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*",
"acorn": "^8.8.1",
"dependency-tree": "^9.0.0",
"vitest": "^0.25.7"
},
"devDependencies": { "devDependencies": {
"@vitest/ui": "^0.25.7", "@vitest/ui": "^0.25.7",
"c8": "^7.12.0", "c8": "^7.12.0",
"canvas": "^2.11.0", "canvas": "^2.11.0",
"dependency-tree": "^9.0.0",
"eslint": "^8.28.0", "eslint": "^8.28.0",
"eslint-plugin-import": "^2.27.5",
"events": "^3.3.0", "events": "^3.3.0",
"gh-pages": "^4.0.0", "gh-pages": "^4.0.0",
"jsdoc": "^3.6.10", "jsdoc": "^3.6.10",
@@ -69,6 +59,7 @@
"jsdoc-to-markdown": "^7.1.1", "jsdoc-to-markdown": "^7.1.1",
"lerna": "^4.0.0", "lerna": "^4.0.0",
"prettier": "^2.8.1", "prettier": "^2.8.1",
"rollup-plugin-visualizer": "^5.8.1" "rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.25.7"
} }
} }
+3 -4
View File
@@ -33,7 +33,6 @@ b: 3/2 - 7/4
c: 7/4 - 2 c: 7/4 - 2
``` ```
- [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js). - [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-qmz6qr?file=/src/index.js).
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html) - [open color pattern example](https://raw.githack.com/tidalcycles/strudel/package-examples/packages/core/examples/canvas.html)
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html) - [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/package-examples/packages/core/examples/metro.html)
- [open minimal vite example](./examples/vite-vanilla-repl/)
+4 -4
View File
@@ -22,11 +22,11 @@ Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } =
ctx.fillStyle = clearColor; ctx.fillStyle = clearColor;
ctx.fillRect(0, 0, ww, wh); ctx.fillRect(0, 0, ww, wh);
frame.forEach((f) => { frame.forEach((f) => {
let { x, y, w, h, s, r, angle = 0, fill = 'darkseagreen' } = f.value; let { x, y, w, h, s, r, a = 0, fill = 'darkseagreen' } = f.value;
w *= ww; w *= ww;
h *= wh; h *= wh;
if (r !== undefined && angle !== undefined) { if (r !== undefined && a !== undefined) {
const radians = angle * 2 * Math.PI; const radians = a * 2 * Math.PI;
const [cx, cy] = [(ww - w) / 2, (wh - h) / 2]; const [cx, cy] = [(ww - w) / 2, (wh - h) / 2];
x = cx + Math.cos(radians) * r * cx; x = cx + Math.cos(radians) * r * cx;
y = cy + Math.sin(radians) * r * cy; y = cy + Math.sin(radians) * r * cy;
@@ -51,7 +51,7 @@ Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } =
return silence; return silence;
}; };
export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'fill', 'smear'); export const { x, y, w, h, a, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'a', 'r', 'fill', 'smear');
export const rescale = register('rescale', function (f, pat) { export const rescale = register('rescale', function (f, pat) {
return pat.mul(x(f).w(f).y(f).h(f)); return pat.mul(x(f).w(f).y(f).h(f));
+342 -277
View File
@@ -11,47 +11,41 @@ const generic_params = [
/** /**
* Select a sound / sample by name. * Select a sound / sample by name.
* *
* <details style={{display:'none'}}>
* <summary>show all sounds</summary>
*
* 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)
*
* <a href="https://tidalcycles.org/docs/configuration/Audio%20Samples/default_library" target="_blank">more info</a>
*
* </details>
*
* @name s * @name s
* @param {string | Pattern} sound The sound / pattern of sounds to pick * @param {string | Pattern} sound The sound / pattern of sounds to pick
* @synonyms sound
* @example * @example
* s("bd hh") * s("bd hh")
* *
*/ */
['s', 'sound'], ['s', 's', 'sound'],
/** /**
* Selects the given index from the sample map. * The note or sample number to choose for a synth or sampleset
* Numbers too high will wrap around. * Note names currently not working yet, but will hopefully soon. Just stick to numbers for now
* `n` can also be used to play midi numbers, but it is recommended to use `note` instead.
* *
* @name n * @name n
* @param {number | Pattern} value sample index starting from 0 * @param {string | number | Pattern} value note name, note number or sample number
* @example * @example
* s("bd sd,hh*3").n("<0 1>") * s('superpiano').n("<0 1 2 3>").osc()
* @example
* s('superpiano').n("<c4 d4 e4 g4>").osc()
* @example
* n("0 1 2 3").s('east').osc()
*/ */
// also see https://github.com/tidalcycles/strudel/pull/63 // also see https://github.com/tidalcycles/strudel/pull/63
['n'], ['f', 'n', 'The note or sample number to choose for a synth or sampleset'],
/** ['f', 'note', 'The note or pitch to play a sound or synth with'],
* Plays the given note name or midi number. A note name consists of //['s', 'toArg', 'for internal sound routing'],
* // ["f", "from", "for internal sound routing"),
* - a letter (a-g or A-G) //['f', 'to', 'for internal sound routing'],
* - optional accidentals (b or #)
* - optional octave number (0-9). Defaults to 3
*
* Examples of valid note names: `c`, `bb`, `Bb`, `f#`, `c3`, `A4`, `Eb2`, `c#5`
*
* You can also use midi numbers instead of note names, where 69 is mapped to A4 440Hz in 12EDO.
*
* @name note
* @example
* note("c a f e")
* @example
* note("c4 a4 f4 e4")
* @example
* note("60 69 65 64")
*/
['note'],
/** /**
* A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt. * A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt.
* *
@@ -62,7 +56,7 @@ const generic_params = [
* s("sax").accelerate("<0 1 2 4 8 16>").slow(2).osc() * s("sax").accelerate("<0 1 2 4 8 16>").slow(2).osc()
* *
*/ */
['accelerate'], ['f', 'accelerate', 'a pattern of numbers that speed up (or slow down) samples while they play.'],
/** /**
* Controls the gain by an exponential amount. * Controls the gain by an exponential amount.
* *
@@ -72,7 +66,11 @@ const generic_params = [
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1") * s("hh*8").gain(".4!2 1 .4!2 1 .4 1")
* *
*/ */
['gain'], [
'f',
'gain',
'a pattern of numbers that specify volume. Values less than 1 make the sound quieter. Values greater than 1 make the sound louder. For the linear equivalent, see @amp@.',
],
/** /**
* Like {@link gain}, but linear. * Like {@link gain}, but linear.
* *
@@ -83,18 +81,22 @@ const generic_params = [
* s("bd*8").amp(".1*2 .5 .1*2 .5 .1 .5").osc() * s("bd*8").amp(".1*2 .5 .1*2 .5 .1 .5").osc()
* *
*/ */
['amp'], ['f', 'amp', 'like @gain@, but linear.'],
/** /**
* Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset. * A pattern of numbers to specify the attack time of an envelope applied to each sample.
* [More info about envelopes](/learn/synths-samples-effects/#envelope)
* *
* @name attack * @name attack
* @param {number | Pattern} attack time in seconds. * @param {number | Pattern} attack time in seconds.
* @synonyms att
* @example * @example
* note("c3 e3").attack("<0 .1 .5>") * note("c3 e3").attack("<0 .1 .5>")
* *
*/ */
['attack', 'att'], [
'f',
'attack',
'a pattern of numbers to specify the attack time (in seconds) of an envelope applied to each sample.',
],
/** /**
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`. * Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
@@ -105,11 +107,11 @@ const generic_params = [
* s("bd sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") * s("bd sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd")
* *
*/ */
['bank'], ['f', 'bank', 'selects sound bank to use'],
/** /**
* Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level. * Gain envelope decay time = the time it takes after the attack time to reach the sustain level.
* Note that the decay is only audible if the sustain value is lower than 1. * [More info about envelopes](/learn/synths-samples-effects/#envelope)
* *
* @name decay * @name decay
* @param {number | Pattern} time decay time in seconds * @param {number | Pattern} time decay time in seconds
@@ -117,58 +119,62 @@ const generic_params = [
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0) * note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
* *
*/ */
['decay'], ['f', 'decay', ''],
/** /**
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * Gain envelope sustain level. [More info about envelopes](/learn/synths-samples-effects/#envelope)
* *
* @name sustain * @name sustain
* @param {number | Pattern} gain sustain level between 0 and 1 * @param {number | Pattern} gain sustain level between 0 and 1
* @synonyms sus
* @example * @example
* note("c3 e3").decay(.2).sustain("<0 .1 .4 .6 1>") * note("c3 e3").decay(.2).sustain("<0 .1 .4 .6 1>")
* *
*/ */
['sustain', 'sus'], ['f', 'sustain', ''],
/** /**
* Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * Gain envelope release time. [More info about envelopes](/learn/synths-samples-effects/#envelope)
* *
* @name release * @name release
* @param {number | Pattern} time release time in seconds * @param {number | Pattern} time release time in seconds
* @synonyms rel
* @example * @example
* note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2") * note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2")
* *
*/ */
['release', 'rel'], [
['hold'], 'f',
'release',
'a pattern of numbers to specify the release time (in seconds) of an envelope applied to each sample.',
],
[
'f',
'hold',
'a pattern of numbers to specify the hold time (in seconds) of an envelope applied to each sample. Only takes effect if `attack` and `release` are also specified.',
],
// TODO: in tidal, it seems to be normalized // TODO: in tidal, it seems to be normalized
/** /**
* Sets the center frequency of the **b**and-**p**ass **f**ilter. * Sets the center frequency of the band-pass filter.
* *
* @name bpf * @name bandf
* @param {number | Pattern} frequency center frequency * @param {number | Pattern} frequency center frequency
* @synonyms bandf * @synonyms bpf
* @example * @example
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>") * s("bd sd,hh*3").bandf("<1000 2000 4000 8000>")
* *
*/ */
// currently an alias of 'bandf' https://github.com/tidalcycles/strudel/issues/496 ['f', 'bandf', 'A pattern of numbers from 0 to 1. Sets the center frequency of the band-pass filter.'],
// ['bpf'], ['f', 'bpf', ''],
['bandf', 'bpf'],
// TODO: in tidal, it seems to be normalized // TODO: in tidal, it seems to be normalized
/** /**
* Sets the **b**and-**p**ass **q**-factor (resonance) * Sets the q-factor of the band-pass filter
* *
* @name bpq * @name bandq
* @param {number | Pattern} q q factor * @param {number | Pattern} q q factor
* @synonyms bandq * @synonyms bpq
* @example * @example
* s("bd sd").bpf(500).bpq("<0 1 2 3>") * s("bd sd").bandf(500).bandq("<0 1 2 3>")
* *
*/ */
// currently an alias of 'bandq' https://github.com/tidalcycles/strudel/issues/496 ['f', 'bandq', 'a pattern of anumbers from 0 to 1. Sets the q-factor of the band-pass filter.'],
// ['bpq'], ['f', 'bpq', ''],
['bandq', 'bpq'],
/** /**
* a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample. * a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
* *
@@ -180,7 +186,11 @@ const generic_params = [
* s("rave").begin("<0 .25 .5 .75>") * s("rave").begin("<0 .25 .5 .75>")
* *
*/ */
['begin'], [
'f',
'begin',
'a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.',
],
/** /**
* The same as .begin, but cuts off the end off each sample. * The same as .begin, but cuts off the end off each sample.
* *
@@ -191,35 +201,22 @@ const generic_params = [
* s("bd*2,oh*4").end("<.1 .2 .5 1>") * s("bd*2,oh*4").end("<.1 .2 .5 1>")
* *
*/ */
['end'], [
'f',
'end',
'the same as `begin`, but cuts the end off samples, shortening them; e.g. `0.75` to cut off the last quarter of each sample.',
],
/** /**
* Loops the sample from `loopBegin` to `loopEnd`. If one or both of those are set, * Loops the sample (from `begin` to `end`) the specified number of times.
* the sample is looped to fill the event duration, instead of playing the whole sample. * Note that the tempo of the loop is not synced with the cycle tempo.
* If only `loopBegin` is set, `loopEnd` defaults to 1.
* If only `loopEnd` is set, `loopBegin` defaults to 0.
* *
* @name loopBegin * @name loop
* @param {number | Pattern} position position from 0 - 1 where the loop starts * @param {number | Pattern} times How often the sample is looped
* @example * @example
* note("<c a f e>").s("piano") * s("bd").loop("<1 2 3 4>").osc()
* .loopBegin("<0 .24 .32>").loopEnd(.5)
* *
*/ */
['loopBegin'], ['f', 'loop', 'loops the sample (from `begin` to `end`) the specified number of times.'],
/**
* Loops the sample from `loopBegin` to `loopEnd`. If one or both of those are set,
* the sample is looped to fill the event duration, instead of playing the whole sample.
* If only `loopBegin` is set, `loopEnd` defaults to 1.
* If only `loopEnd` is set, `loopBegin` defaults to 0.
*
* @name loopEnd
* @param {number | Pattern} position position from 0 - 1 where the loop ends
* @example
* note("<c a f e>").s("piano")
* .loopBegin(.2).loopEnd("<.3 .4 .5 .6>")
*
*/
['loopEnd'],
// TODO: currently duplicated with "native" legato // TODO: currently duplicated with "native" legato
// TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419 // TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419
/** /**
@@ -231,8 +228,8 @@ const generic_params = [
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>") * "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
* *
*/ */
// ['legato'], // ['f', 'legato', 'controls the amount of overlap between two adjacent sounds'],
// ['clhatdecay'], // ['f', 'clhatdecay', ''],
/** /**
* bit crusher effect. * bit crusher effect.
* *
@@ -242,7 +239,11 @@ const generic_params = [
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>") * s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
* *
*/ */
['crush'], [
'f',
'crush',
'bit crushing, a pattern of numbers from 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).',
],
/** /**
* fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
* *
@@ -252,7 +253,11 @@ const generic_params = [
* s("bd sd,hh*4").coarse("<1 4 8 16 32>") * s("bd sd,hh*4").coarse("<1 4 8 16 32>")
* *
*/ */
['coarse'], [
'f',
'coarse',
'fake-resampling, a pattern of numbers for lowering the sample rate, i.e. 1 for original 2 for half, 3 for a third and so on.',
],
/** /**
* choose the channel the pattern is sent to in superdirt * choose the channel the pattern is sent to in superdirt
@@ -261,7 +266,7 @@ const generic_params = [
* @param {number | Pattern} channel channel number * @param {number | Pattern} channel channel number
* *
*/ */
['channel'], ['i', 'channel', 'choose the channel the pattern is sent to in superdirt'],
/** /**
* In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open. * In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open.
* *
@@ -271,54 +276,60 @@ const generic_params = [
* s("rd*4").cut(1) * s("rd*4").cut(1)
* *
*/ */
['cut'], [
'i',
'cut',
'In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open.',
],
/** /**
* Applies the cutoff frequency of the **l**ow-**p**ass **f**ilter. * Applies the cutoff frequency of the low-pass filter.
* *
* @name lpf * @name cutoff
* @param {number | Pattern} frequency audible between 0 and 20000 * @param {number | Pattern} frequency audible between 0 and 20000
* @synonyms cutoff, ctf * @synonyms lpf
* @example * @example
* s("bd sd,hh*3").lpf("<4000 2000 1000 500 200 100>") * s("bd sd,hh*3").cutoff("<4000 2000 1000 500 200 100>")
* *
*/ */
['cutoff', 'ctf', 'lpf'], ['f', 'cutoff', 'a pattern of numbers from 0 to 1. Applies the cutoff frequency of the low-pass filter.'],
['f', 'lpf'],
/** /**
* Applies the cutoff frequency of the **h**igh-**p**ass **f**ilter. * Applies the cutoff frequency of the high-pass filter.
* *
* @name hpf * @name hcutoff
* @param {number | Pattern} frequency audible between 0 and 20000 * @param {number | Pattern} frequency audible between 0 and 20000
* @synonyms hcutoff * @synonyms hpf
* @example * @example
* s("bd sd,hh*4").hpf("<4000 2000 1000 500 200 100>") * s("bd sd,hh*4").hcutoff("<4000 2000 1000 500 200 100>")
* *
*/ */
// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496 ['f', 'hcutoff', ''],
// ['hpf'], ['f', 'hpf', ''],
['hcutoff', 'hpf'],
/** /**
* Controls the **h**igh-**p**ass **q**-value. * Applies the resonance of the high-pass filter.
* *
* @name hpq * @name hresonance
* @param {number | Pattern} q resonance factor between 0 and 50 * @param {number | Pattern} q resonance factor between 0 and 50
* @synonyms hresonance * @synonyms hpq
* @example * @example
* s("bd sd,hh*4").hpf(2000).hpq("<0 10 20 30>") * s("bd sd,hh*4").hcutoff(2000).hresonance("<0 10 20 30>")
* *
*/ */
['hresonance', 'hpq'], ['f', 'hpq', ''],
['f', 'hresonance', ''],
// TODO: add hpq synonym
/** /**
* Controls the **l**ow-**p**ass **q**-value. * Applies the cutoff frequency of the low-pass filter.
* *
* @name lpq * @name resonance
* @param {number | Pattern} q resonance factor between 0 and 50 * @param {number | Pattern} q resonance factor between 0 and 50
* @synonyms resonance * @synonyms lpq
* @example * @example
* s("bd sd,hh*4").lpf(2000).lpq("<0 10 20 30>") * s("bd sd,hh*4").cutoff(2000).resonance("<0 10 20 30>")
* *
*/ */
// currently an alias of 'resonance' https://github.com/tidalcycles/strudel/issues/496 ['f', 'lpq'],
['resonance', 'lpq'], ['f', 'resonance', ''],
/** /**
* DJ filter, below 0.5 is low pass filter, above is high pass filter. * DJ filter, below 0.5 is low pass filter, above is high pass filter.
* *
@@ -328,8 +339,8 @@ const generic_params = [
* n("0 3 7 [10,24]").s('superzow').octave(3).djf("<.5 .25 .5 .75>").osc() * n("0 3 7 [10,24]").s('superzow').octave(3).djf("<.5 .25 .5 .75>").osc()
* *
*/ */
['djf'], ['f', 'djf', 'DJ filter, below 0.5 is low pass filter, above is high pass filter.'],
// ['cutoffegint'], // ['f', 'cutoffegint', ''],
// TODO: does not seem to work // TODO: does not seem to work
/** /**
* Sets the level of the delay signal. * Sets the level of the delay signal.
@@ -340,30 +351,28 @@ const generic_params = [
* s("bd").delay("<0 .25 .5 1>") * s("bd").delay("<0 .25 .5 1>")
* *
*/ */
['delay'], ['f', 'delay', 'a pattern of numbers from 0 to 1. Sets the level of the delay signal.'],
/** /**
* Sets the level of the signal that is fed back into the delay. * Sets the level of the signal that is fed back into the delay.
* Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
* *
* @name delayfeedback * @name delayfeedback
* @param {number | Pattern} feedback between 0 and 1 * @param {number | Pattern} feedback between 0 and 1
* @synonyms delayfb, dfb
* @example * @example
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>").slow(2) * s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>").slow(2)
* *
*/ */
['delayfeedback', 'delayfb', 'dfb'], ['f', 'delayfeedback', 'a pattern of numbers from 0 to 1. Sets the amount of delay feedback.'],
/** /**
* Sets the time of the delay effect. * Sets the time of the delay effect.
* *
* @name delaytime * @name delaytime
* @param {number | Pattern} seconds between 0 and Infinity * @param {number | Pattern} seconds between 0 and Infinity
* @synonyms delayt, dt
* @example * @example
* s("bd").delay(.25).delaytime("<.125 .25 .5 1>").slow(2) * s("bd").delay(.25).delaytime("<.125 .25 .5 1>").slow(2)
* *
*/ */
['delaytime', 'delayt', 'dt'], ['f', 'delaytime', 'a pattern of numbers from 0 to 1. Sets the length of the delay.'],
/* // TODO: test /* // TODO: test
* Specifies whether delaytime is calculated relative to cps. * Specifies whether delaytime is calculated relative to cps.
* *
@@ -373,19 +382,21 @@ const generic_params = [
* s("sd").delay().lock(1).osc() * s("sd").delay().lock(1).osc()
* *
*/ */
['lock'], [
'f',
'lock',
'A pattern of numbers. Specifies whether delaytime is calculated relative to cps. When set to 1, delaytime is a direct multiple of a cycle.',
],
/** /**
* Set detune of oscillators. Works only with some synths, see <a target="_blank" href="https://tidalcycles.org/docs/patternlib/tutorials/synthesizers">tidal doc</a> * Set detune of oscillators. Works only with some synths, see <a target="_blank" href="https://tidalcycles.org/docs/patternlib/tutorials/synthesizers">tidal doc</a>
* *
* @name detune * @name detune
* @param {number | Pattern} amount between 0 and 1 * @param {number | Pattern} amount between 0 and 1
* @synonyms det
* @superdirtOnly
* @example * @example
* n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc() * n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc()
* *
*/ */
['detune', 'det'], ['f', 'detune', ''],
/** /**
* Set dryness of reverb. See {@link room} and {@link size} for more information about reverb. * Set dryness of reverb. See {@link room} and {@link size} for more information about reverb.
* *
@@ -393,10 +404,13 @@ const generic_params = [
* @param {number | Pattern} dry 0 = wet, 1 = dry * @param {number | Pattern} dry 0 = wet, 1 = dry
* @example * @example
* n("[0,3,7](3,8)").s("superpiano").room(.7).dry("<0 .5 .75 1>").osc() * n("[0,3,7](3,8)").s("superpiano").room(.7).dry("<0 .5 .75 1>").osc()
* @superdirtOnly
* *
*/ */
['dry'], [
'f',
'dry',
'when set to `1` will disable all reverb for this pattern. See `room` and `size` for more information about reverb.',
],
// TODO: does not seem to do anything // TODO: does not seem to do anything
/* /*
* Used when using {@link begin}/{@link end} or {@link chop}/{@link striate} and friends, to change the fade out time of the 'grain' envelope. * Used when using {@link begin}/{@link end} or {@link chop}/{@link striate} and friends, to change the fade out time of the 'grain' envelope.
@@ -407,9 +421,17 @@ const generic_params = [
* s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc() * s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc()
* *
*/ */
['fadeTime', 'fadeOutTime'], [
'f',
'fadeTime',
"Used when using begin/end or chop/striate and friends, to change the fade out time of the 'grain' envelope.",
],
// TODO: see above // TODO: see above
['fadeInTime'], [
'f',
'fadeInTime',
'As with fadeTime, but controls the fade in time of the grain envelope. Not used if the grain begins at position 0 in the sample.',
],
/** /**
* Set frequency of sound. * Set frequency of sound.
* *
@@ -421,15 +443,15 @@ const generic_params = [
* freq("110".mul.out(".5 1.5 .6 [2 3]")).s("superzow").osc() * freq("110".mul.out(".5 1.5 .6 [2 3]")).s("superzow").osc()
* *
*/ */
['freq'], ['f', 'freq', ''],
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
['gate', 'gat'], ['f', 'gate', ''],
// ['hatgrain'], // ['f', 'hatgrain', ''],
// ['lagogo'], // ['f', 'lagogo', ''],
// ['lclap'], // ['f', 'lclap', ''],
// ['lclaves'], // ['f', 'lclaves', ''],
// ['lclhat'], // ['f', 'lclhat', ''],
// ['lcrash'], // ['f', 'lcrash', ''],
// TODO: // TODO:
// https://tidalcycles.org/docs/reference/audio_effects/#leslie-1 // https://tidalcycles.org/docs/reference/audio_effects/#leslie-1
// https://tidalcycles.org/docs/reference/audio_effects/#leslie // https://tidalcycles.org/docs/reference/audio_effects/#leslie
@@ -440,10 +462,9 @@ const generic_params = [
* @param {number | Pattern} wet between 0 and 1 * @param {number | Pattern} wet between 0 and 1
* @example * @example
* n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc() * n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc()
* @superdirtOnly
* *
*/ */
['leslie'], ['f', 'leslie', ''],
/** /**
* Rate of modulation / rotation for leslie effect * Rate of modulation / rotation for leslie effect
* *
@@ -451,11 +472,10 @@ const generic_params = [
* @param {number | Pattern} rate 6.7 for fast, 0.7 for slow * @param {number | Pattern} rate 6.7 for fast, 0.7 for slow
* @example * @example
* n("0,4,7").s("supersquare").leslie(1).lrate("<1 2 4 8>").osc() * n("0,4,7").s("supersquare").leslie(1).lrate("<1 2 4 8>").osc()
* @superdirtOnly
* *
*/ */
// TODO: the rate seems to "lag" (in the example, 1 will be fast) // TODO: the rate seems to "lag" (in the example, 1 will be fast)
['lrate'], ['f', 'lrate', ''],
/** /**
* Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble) * Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble)
* *
@@ -463,31 +483,33 @@ const generic_params = [
* @param {number | Pattern} meters somewhere between 0 and 1 * @param {number | Pattern} meters somewhere between 0 and 1
* @example * @example
* n("0,4,7").s("supersquare").leslie(1).lrate(2).lsize("<.1 .5 1>").osc() * n("0,4,7").s("supersquare").leslie(1).lrate(2).lsize("<.1 .5 1>").osc()
* @superdirtOnly
* *
*/ */
['lsize'], ['f', 'lsize', ''],
// ['lfo'], // ['f', 'lfo', ''],
// ['lfocutoffint'], // ['f', 'lfocutoffint', ''],
// ['lfodelay'], // ['f', 'lfodelay', ''],
// ['lfoint'], // ['f', 'lfoint', ''],
// ['lfopitchint'], // ['f', 'lfopitchint', ''],
// ['lfoshape'], // ['f', 'lfoshape', ''],
// ['lfosync'], // ['f', 'lfosync', ''],
// ['lhitom'], // ['f', 'lhitom', ''],
// ['lkick'], // ['f', 'lkick', ''],
// ['llotom'], // ['f', 'llotom', ''],
// ['lophat'], // ['f', 'lophat', ''],
// ['lsnare'], // ['f', 'lsnare', ''],
['degree'], // TODO: what is this? not found in tidal doc ['f', 'degree', ''], // TODO: what is this? not found in tidal doc
['mtranspose'], // TODO: what is this? not found in tidal doc ['f', 'mtranspose', ''], // TODO: what is this? not found in tidal doc
['ctranspose'], // TODO: what is this? not found in tidal doc ['f', 'ctranspose', ''], // TODO: what is this? not found in tidal doc
['harmonic'], // TODO: what is this? not found in tidal doc ['f', 'harmonic', ''], // TODO: what is this? not found in tidal doc
['stepsPerOctave'], // TODO: what is this? not found in tidal doc ['f', 'stepsPerOctave', ''], // TODO: what is this? not found in tidal doc
['octaveR'], // TODO: what is this? not found in tidal doc ['f', 'octaveR', ''], // TODO: what is this? not found in tidal doc
// TODO: why is this needed? what's the difference to late / early? Answer: it's in seconds, and delays the message at // TODO: why is this needed? what's the difference to late / early?
// OSC time (so can't be negative, at least not beyond the latency value) [
['nudge'], 'f',
'nudge',
'Nudges events into the future by the specified number of seconds. Negative numbers work up to a point as well (due to internal latency)',
],
// TODO: the following doc is just a guess, it's not documented in tidal doc. // TODO: the following doc is just a guess, it's not documented in tidal doc.
/** /**
* Sets the default octave of a synth. * Sets the default octave of a synth.
@@ -496,11 +518,10 @@ const generic_params = [
* @param {number | Pattern} octave octave number * @param {number | Pattern} octave octave number
* @example * @example
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc() * n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc()
* @superDirtOnly
*/ */
['octave'], ['i', 'octave', ''],
['offset'], // TODO: what is this? not found in tidal doc ['f', 'offset', ''], // TODO: what is this? not found in tidal doc
// ['ophatdecay'], // ['f', 'ophatdecay', ''],
// TODO: example // TODO: example
/** /**
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects. * An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
@@ -513,9 +534,13 @@ const generic_params = [
* s("~ sd").delay(.5).delaytime(.125).orbit(2) * s("~ sd").delay(.5).delaytime(.125).orbit(2)
* ) * )
*/ */
['orbit'], [
['overgain'], // TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that 'i',
['overshape'], // TODO: what is this? not found in tidal doc. Similar to above, but limited to 1 'orbit',
'a pattern of numbers. An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share hardware output bus offset and global effects, e.g. reverb and delay. The maximum number of orbits is specified in the superdirt startup, numbers higher than maximum will wrap around.',
],
['f', 'overgain', ''], // TODO: what is this? not found in tidal doc
['f', 'overshape', ''], // TODO: what is this? not found in tidal doc
/** /**
* Sets position in stereo. * Sets position in stereo.
* *
@@ -525,7 +550,11 @@ const generic_params = [
* s("[bd hh]*2").pan("<.5 1 .5 0>") * s("[bd hh]*2").pan("<.5 1 .5 0>")
* *
*/ */
['pan'], [
'f',
'pan',
'a pattern of numbers between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)',
],
// TODO: this has no effect (see example) // TODO: this has no effect (see example)
/* /*
* Controls how much multichannel output is fanned out * Controls how much multichannel output is fanned out
@@ -536,7 +565,11 @@ const generic_params = [
* s("[bd hh]*2").pan("<.5 1 .5 0>").panspan("<0 .5 1>").osc() * s("[bd hh]*2").pan("<.5 1 .5 0>").panspan("<0 .5 1>").osc()
* *
*/ */
['panspan'], [
'f',
'panspan',
'a pattern of numbers between -inf and inf, which controls how much multichannel output is fanned out (negative is backwards ordering)',
],
// TODO: this has no effect (see example) // TODO: this has no effect (see example)
/* /*
* Controls how much multichannel output is spread * Controls how much multichannel output is spread
@@ -547,22 +580,34 @@ const generic_params = [
* s("[bd hh]*2").pan("<.5 1 .5 0>").pansplay("<0 .5 1>").osc() * s("[bd hh]*2").pan("<.5 1 .5 0>").pansplay("<0 .5 1>").osc()
* *
*/ */
['pansplay'], [
['panwidth'], 'f',
['panorient'], 'pansplay',
// ['pitch1'], 'a pattern of numbers between 0.0 and 1.0, which controls the multichannel spread range (multichannel only)',
// ['pitch2'], ],
// ['pitch3'], [
// ['portamento'], 'f',
'panwidth',
'a pattern of numbers between 0.0 and inf, which controls how much each channel is distributed over neighbours (multichannel only)',
],
[
'f',
'panorient',
'a pattern of numbers between -1.0 and 1.0, which controls the relative position of the centre pan in a pair of adjacent speakers (multichannel only)',
],
// ['f', 'pitch1', ''],
// ['f', 'pitch2', ''],
// ['f', 'pitch3', ''],
// ['f', 'portamento', ''],
// TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare // TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
['rate'], ['f', 'rate', "used in SuperDirt softsynths as a control rate or 'speed'"],
// TODO: slide param for certain synths // TODO: slide param for certain synths
['slide'], ['f', 'slide', ''],
// TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare // TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
['semitone'], ['f', 'semitone', ''],
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano // TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
// ['velocity'], // ['f', 'velocity', ''],
['voice'], // TODO: synth param ['f', 'voice', ''], // TODO: synth param
/** /**
* Sets the level of reverb. * Sets the level of reverb.
* *
@@ -572,13 +617,13 @@ const generic_params = [
* s("bd sd").room("<0 .2 .4 .6 .8 1>") * s("bd sd").room("<0 .2 .4 .6 .8 1>")
* *
*/ */
['room'], ['f', 'room', 'a pattern of numbers from 0 to 1. Sets the level of reverb.'],
/** /**
* Sets the room size of the reverb, see {@link room}. * Sets the room size of the reverb, see {@link room}.
* *
* @name roomsize * @name roomsize
* @synonyms size
* @param {number | Pattern} size between 0 and 10 * @param {number | Pattern} size between 0 and 10
* @synonyms size, sz
* @example * @example
* s("bd sd").room(.8).roomsize("<0 1 2 4 8>") * s("bd sd").room(.8).roomsize("<0 1 2 4 8>")
* *
@@ -586,11 +631,20 @@ const generic_params = [
// TODO: find out why : // TODO: find out why :
// s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc()
// .. does not work. Is it because room is only one effect? // .. does not work. Is it because room is only one effect?
['size', 'sz', 'roomsize'], [
// ['sagogo'], 'f',
// ['sclap'], 'size',
// ['sclaves'], 'a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb.',
// ['scrash'], ],
[
'f',
'roomsize',
'a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb.',
],
// ['f', 'sagogo', ''],
// ['f', 'sclap', ''],
// ['f', 'sclaves', ''],
// ['f', 'scrash', ''],
/** /**
* Wave shaping distortion. CAUTION: it might get loud * Wave shaping distortion. CAUTION: it might get loud
* *
@@ -600,7 +654,11 @@ const generic_params = [
* s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>") * s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>")
* *
*/ */
['shape'], [
'f',
'shape',
'wave shaping distortion, a pattern of numbers from 0 for no distortion up to 1 for loads of distortion.',
],
/** /**
* Changes the speed of sample playback, i.e. a cheap way of changing pitch. * Changes the speed of sample playback, i.e. a cheap way of changing pitch.
* *
@@ -612,7 +670,11 @@ const generic_params = [
* speed("1 1.5*2 [2 1.1]").s("piano").clip(1) * speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
* *
*/ */
['speed'], [
'f',
'speed',
'a pattern of numbers which changes the speed of sample playback, i.e. a cheap way of changing pitch. Negative values will play the sample backwards!',
],
/** /**
* Used in conjunction with {@link speed}, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. * Used in conjunction with {@link speed}, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
* *
@@ -620,10 +682,13 @@ const generic_params = [
* @param {number | string | Pattern} unit see description above * @param {number | string | Pattern} unit see description above
* @example * @example
* speed("1 2 .5 3").s("bd").unit("c").osc() * speed("1 2 .5 3").s("bd").unit("c").osc()
* @superdirtOnly
* *
*/ */
['unit'], [
's',
'unit',
'used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.',
],
/** /**
* Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as: * Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as:
* *
@@ -633,17 +698,16 @@ const generic_params = [
* @param {number | Pattern} squiz Try passing multiples of 2 to it - 2, 4, 8 etc. * @param {number | Pattern} squiz Try passing multiples of 2 to it - 2, 4, 8 etc.
* @example * @example
* squiz("2 4/2 6 [8 16]").s("bd").osc() * squiz("2 4/2 6 [8 16]").s("bd").osc()
* @superdirtOnly
* *
*/ */
['squiz'], ['f', 'squiz', ''],
// ['stutterdepth'], // TODO: what is this? not found in tidal doc ['f', 'stutterdepth', ''], // TODO: what is this? not found in tidal doc
// ['stuttertime'], // TODO: what is this? not found in tidal doc ['f', 'stuttertime', ''], // TODO: what is this? not found in tidal doc
// ['timescale'], // TODO: what is this? not found in tidal doc ['f', 'timescale', ''], // TODO: what is this? not found in tidal doc
// ['timescalewin'], // TODO: what is this? not found in tidal doc ['f', 'timescalewin', ''], // TODO: what is this? not found in tidal doc
// ['tomdecay'], // ['f', 'tomdecay', ''],
// ['vcfegint'], // ['f', 'vcfegint', ''],
// ['vcoegint'], // ['f', 'vcoegint', ''],
// TODO: Use a rest (~) to override the effect <- vowel // TODO: Use a rest (~) to override the effect <- vowel
/** /**
* *
@@ -656,7 +720,11 @@ const generic_params = [
* .vowel("<a e i <o u>>") * .vowel("<a e i <o u>>")
* *
*/ */
['vowel'], [
's',
'vowel',
'formant filter to make things sound like vowels, a pattern of either `a`, `e`, `i`, `o` or `u`. Use a rest (`~`) for no effect.',
],
/* // TODO: find out how it works /* // TODO: find out how it works
* Made by Calum Gunn. Divides an audio stream into tiny segments, using the signal's zero-crossings as segment boundaries, and discards a fraction of them. Takes a number between 1 and 100, denoted the percentage of segments to drop. The SuperCollider manual describes the Waveloss effect this way: * Made by Calum Gunn. Divides an audio stream into tiny segments, using the signal's zero-crossings as segment boundaries, and discards a fraction of them. Takes a number between 1 and 100, denoted the percentage of segments to drop. The SuperCollider manual describes the Waveloss effect this way:
* *
@@ -667,12 +735,12 @@ const generic_params = [
* *
* @name waveloss * @name waveloss
*/ */
['waveloss'], ['f', 'waveloss', ''],
// TODO: midi effects? // TODO: midi effects?
['dur'], ['f', 'dur', ''],
// ['modwheel'], // ['f', 'modwheel', ''],
['expression'], ['f', 'expression', ''],
['sustainpedal'], ['f', 'sustainpedal', ''],
/* // TODO: doesn't seem to do anything /* // TODO: doesn't seem to do anything
* *
* Tremolo Audio DSP effect * Tremolo Audio DSP effect
@@ -683,69 +751,60 @@ const generic_params = [
* n("0,4,7").tremolodepth("<0 .3 .6 .9>").osc() * n("0,4,7").tremolodepth("<0 .3 .6 .9>").osc()
* *
*/ */
['tremolodepth', 'tremdp'], // TODO: tremdp alias
['tremolorate', 'tremr'], ['f', 'tremolodepth', "Tremolo Audio DSP effect | params are 'tremolorate' and 'tremolodepth'"],
['f', 'tremolorate', "Tremolo Audio DSP effect | params are 'tremolorate' and 'tremolodepth'"],
// TODO: doesn't seem to do anything // TODO: doesn't seem to do anything
['phaserdepth', 'phasdp'], ['f', 'phaserdepth', "Phaser Audio DSP effect | params are 'phaserrate' and 'phaserdepth'"],
['phaserrate', 'phasr'], ['f', 'phaserrate', "Phaser Audio DSP effect | params are 'phaserrate' and 'phaserdepth'"],
['fshift'], ['f', 'fshift', 'frequency shifter'],
['fshiftnote'], ['f', 'fshiftnote', 'frequency shifter'],
['fshiftphase'], ['f', 'fshiftphase', 'frequency shifter'],
['triode'], ['f', 'triode', 'tube distortion'],
['krush'], ['f', 'krush', 'shape/bass enhancer'],
['kcutoff'], ['f', 'kcutoff', ''],
['octer'], ['f', 'octer', 'octaver effect'],
['octersub'], ['f', 'octersub', 'octaver effect'],
['octersubsub'], ['f', 'octersubsub', 'octaver effect'],
['ring'], ['f', 'ring', 'ring modulation'],
['ringf'], ['f', 'ringf', 'ring modulation'],
['ringdf'], ['f', 'ringdf', 'ring modulation'],
['distort'], ['f', 'distort', 'noisy fuzzy distortion'],
['freeze'], ['f', 'freeze', 'Spectral freeze'],
['xsdelay'], ['f', 'xsdelay', ''],
['tsdelay'], ['f', 'tsdelay', ''],
['real'], ['f', 'real', 'Spectral conform'],
['imag'], ['f', 'imag', ''],
['enhance'], ['f', 'enhance', 'Spectral enhance'],
['partials'], ['f', 'partials', ''],
['comb'], ['f', 'comb', 'Spectral comb'],
['smear'], ['f', 'smear', 'Spectral smear'],
['scram'], ['f', 'scram', 'Spectral scramble'],
['binshift'], ['f', 'binshift', 'Spectral binshift'],
['hbrick'], ['f', 'hbrick', 'High pass sort of spectral filter'],
['lbrick'], ['f', 'lbrick', 'Low pass sort of spectral filter'],
['midichan'], ['f', 'midichan', ''],
['control'], ['f', 'control', ''],
['ccn'], ['f', 'ccn', ''],
['ccv'], ['f', 'ccv', ''],
['polyTouch'], ['f', 'polyTouch', ''],
['midibend'], ['f', 'midibend', ''],
['miditouch'], ['f', 'miditouch', ''],
['ctlNum'], ['f', 'ctlNum', ''],
['frameRate'], ['f', 'frameRate', ''],
['frames'], ['f', 'frames', ''],
['hours'], ['f', 'hours', ''],
['midicmd'], ['s', 'midicmd', ''],
['minutes'], ['f', 'minutes', ''],
['progNum'], ['f', 'progNum', ''],
['seconds'], ['f', 'seconds', ''],
['songPtr'], ['f', 'songPtr', ''],
['uid'], ['f', 'uid', ''],
['val'], ['f', 'val', ''],
['cps'], ['f', 'cps', ''],
/** ['f', 'clip', ''],
* If set to 1, samples will be cut to the duration of their event.
* In tidal, this would be done with legato, which [is about to land in strudel too](https://github.com/tidalcycles/strudel/issues/111)
*
* @name clip
* @param {number | Pattern} active 1 or 0
* @example
* note("c a f e ~").s("piano").clip(1)
*
*/
['clip'],
]; ];
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
@@ -760,18 +819,24 @@ const _setter = (func, name) =>
return this.set(func(...pats)); return this.set(func(...pats));
}; };
generic_params.forEach(([name, ...aliases]) => { generic_params.forEach(([type, name, description]) => {
controls[name] = (...pats) => _name(name, ...pats); controls[name] = function (...pats) {
const result = _name(name, ...pats);
// Add a function for composing this control with another pattern
result.__as_function = function (pat) {
return pat[name](...pats);
};
return result;
};
Pattern.__registered.push(name);
Pattern.prototype[name] = _setter(controls[name], name); Pattern.prototype[name] = _setter(controls[name], name);
aliases.forEach((alias) => {
controls[alias] = controls[name];
Pattern.prototype[alias] = Pattern.prototype[name];
});
}); });
// create custom param // create custom param
controls.createParam = (name) => { controls.createParam = (name) => {
const func = (...pats) => _name(name, ...pats); const func = (...pats) => _name(name, ...pats);
Pattern.__registered.push(name);
Pattern.prototype[name] = _setter(func, name); Pattern.prototype[name] = _setter(func, name);
return (...pats) => _name(name, ...pats); return (...pats) => _name(name, ...pats);
}; };
+17 -22
View File
@@ -10,49 +10,44 @@ import { logger } from './logger.mjs';
export class Cyclist { export class Cyclist {
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) { constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
this.started = false; this.started = false;
this.cps = 1; this.cps = 1; // TODO
this.lastTick = 0; // absolute time when last tick (clock callback) happened this.phase = 0;
this.lastBegin = 0; // query begin of last tick this.getTime = getTime;
this.lastEnd = 0; // query end of last tick
this.getTime = getTime; // get absolute time
this.onToggle = onToggle; this.onToggle = onToggle;
this.latency = latency; // fixed trigger time offset this.latency = latency;
const round = (x) => Math.round(x * 1000) / 1000; const round = (x) => Math.round(x * 1000) / 1000;
this.clock = createClock( this.clock = createClock(
getTime, getTime,
// called slightly before each cycle
(phase, duration, tick) => { (phase, duration, tick) => {
if (tick === 0) { if (tick === 0) {
this.origin = phase; this.origin = phase;
} }
const begin = round(phase - this.origin);
this.phase = begin - latency;
const end = round(begin + duration);
const time = getTime();
try { try {
const time = getTime(); const haps = this.pattern.queryArc(begin, end); // get Haps
const begin = this.lastEnd;
this.lastBegin = begin;
const end = round(begin + duration * this.cps);
this.lastEnd = end;
const haps = this.pattern.queryArc(begin, end);
const tickdeadline = phase - time; // time left till phase begins
this.lastTick = time + tickdeadline;
haps.forEach((hap) => { haps.forEach((hap) => {
if (hap.part.begin.equals(hap.whole.begin)) { if (hap.part.begin.equals(hap.whole.begin)) {
const deadline = (hap.whole.begin - begin) / this.cps + tickdeadline + latency; const deadline = hap.whole.begin + this.origin - time + latency;
const duration = hap.duration / this.cps; const duration = hap.duration * 1;
onTrigger?.(hap, deadline, duration, this.cps); onTrigger?.(hap, deadline, duration);
} }
}); });
} catch (e) { } catch (e) {
logger(`[cyclist] error: ${e.message}`); logger(`[cyclist] error: ${e.message}`);
onError?.(e); onError?.(e);
} }
}, }, // called slightly before each cycle
interval, // duration of each cycle interval, // duration of each cycle
); );
} }
getPhase() {
return this.getTime() - this.origin - this.latency;
}
now() { now() {
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration; return this.getTime() - this.origin + this.clock.minLatency;
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
} }
setStarted(v) { setStarted(v) {
this.started = v; this.started = v;
+7 -1
View File
@@ -11,9 +11,15 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Pattern, timeCat, register, silence } from './pattern.mjs'; import { Pattern, timeCat, register, silence } from './pattern.mjs';
import { rotate, flatten, splitAt, zipWith } from './util.mjs'; import { rotate, flatten } from './util.mjs';
import Fraction from './fraction.mjs'; import Fraction from './fraction.mjs';
const splitAt = function (index, value) {
return [value.slice(0, index), value.slice(index)];
};
const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
const left = function (n, x) { const left = function (n, x) {
const [ons, offs] = n; const [ons, offs] = n;
const [xs, ys] = x; const [xs, ys] = x;
+8
View File
@@ -6,7 +6,12 @@ This program is free software: you can redistribute it and/or modify it under th
import { isPattern } from './index.mjs'; import { isPattern } from './index.mjs';
let scoped = false;
export const evalScope = async (...args) => { export const evalScope = async (...args) => {
if (scoped) {
console.warn('evalScope was called more than once.');
}
scoped = true;
const results = await Promise.allSettled(args); const results = await Promise.allSettled(args);
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value); const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
results.forEach((result, i) => { results.forEach((result, i) => {
@@ -37,6 +42,9 @@ function safeEval(str, options = {}) {
} }
export const evaluate = async (code, transpiler) => { export const evaluate = async (code, transpiler) => {
if (!scoped) {
await evalScope(); // at least scope Pattern.prototype.boostrap
}
if (transpiler) { if (transpiler) {
code = transpiler(code); // transform syntactically correct js code to semantically usable code code = transpiler(code); // transform syntactically correct js code to semantically usable code
} }
+2 -2
View File
@@ -1,13 +1,13 @@
<input <input
type="text" type="text"
id="text" id="text"
value="seq('a', ['b', 'c'])" value="cat('a', 'b')"
style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px" style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px"
spellcheck="false" spellcheck="false"
/> />
<div id="output"></div> <div id="output"></div>
<script type="module"> <script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'); const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval
const input = document.getElementById('text'); const input = document.getElementById('text');
const getEvents = () => { const getEvents = () => {
+2 -2
View File
@@ -2,13 +2,13 @@
<input <input
type="text" type="text"
id="text" id="text"
value="seq('tomato', 'indigo', ['white', 'steelblue']).fast(4)" value="cat('orange', 'indigo')"
style="width: 100%; font-size: 2em; background: black; color: white; outline: none; position: absolute; top: 0" style="width: 100%; font-size: 2em; background: black; color: white; outline: none; position: absolute; top: 0"
spellcheck="false" spellcheck="false"
/> />
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<script type="module"> <script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'); const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
// this adds all strudel functions to the global scope, to be used by eval // this adds all strudel functions to the global scope, to be used by eval
Object.assign(window, strudel); Object.assign(window, strudel);
// setup elements // setup elements
+90
View File
@@ -0,0 +1,90 @@
<div style="position: absolute; bottom: 0; right: 0; padding: 4px; width: 100vw; display: flex">
<button id="start" style="font-size: 2em">start</button>
<button id="stop" style="font-size: 2em">stop</button>
<button id="slower" style="font-size: 2em">slower</button>
<button id="faster" style="font-size: 2em">faster</button>
</div>
<textarea
style="font-size: 2em; background: #052b49; color: #fff; height: 100%; width: 100%; outline: none; border: 0"
id="text"
spellcheck="false"
>
Loading...</textarea
>
<script type="module">
document.body.style = 'margin: 0';
import * as strudel from '@strudel.cycles/core';
const { cat, State, TimeSpan, Scheduler, getPlayableNoteValue, getFreq } = strudel;
Object.assign(window, strudel); // add strudel to eval scope
const ctx = new AudioContext();
const scheduler = new Scheduler({
// audioContext: getAudioContext(),
interval: 0.1,
onTrigger: (hap, time, duration) => {
const freq = getFrequency(hap);
const osc = ctx.createOscillator();
const gain = 0.2;
osc.frequency.value = freq;
osc.type = 'triangle';
const onset = ctx.currentTime + time;
const offset = onset + duration;
const attack = 0.05;
const release = 0.05;
osc.start(onset);
osc.stop(offset + release);
const g = ctx.createGain();
g.gain.setValueAtTime(gain, onset);
g.gain.linearRampToValueAtTime(gain, onset + attack);
g.gain.setValueAtTime(gain, offset - release);
g.gain.linearRampToValueAtTime(0, offset);
osc.connect(g);
g.connect(ctx.destination);
},
});
let initialCode = `stack('c4','e4',cat('g4','a4','b4','a4'))
.add(cat(0,1,2,3,4,3,2,1).slow(8))
.fast(2)
.cps(tri.range(1,8).slow(32))`;
try {
const base64 = decodeURIComponent(window.location.href.split('#')[1]);
initialCode = atob(base64);
} catch (err) {
console.warn('failed to decode', err);
}
const input = document.getElementById('text');
input.value = initialCode;
const evaluate = () => {
try {
const pattern = eval(input.value);
scheduler.setPattern(pattern);
window.location.hash = '#' + encodeURIComponent(btoa(input.value)); // update url hash
} catch (err) {
console.warn(err);
}
};
evaluate();
input.addEventListener('input', () => evaluate());
document.getElementById('start').addEventListener('click', async () => {
await ctx.resume();
scheduler.start();
});
document.getElementById('stop').addEventListener('click', () => scheduler.stop());
document.getElementById('slower').addEventListener('click', () => scheduler.setCps(scheduler.cps - 0.1));
document.getElementById('faster').addEventListener('click', () => scheduler.setCps(scheduler.cps + 0.1));
</script>
<!--
sequence(1,2).mul(55/2) // frequencies
.mul(slowcat(1,2))
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
.fast(3)
.freq()
.velocity(.5)
.s('sawtooth')
.cutoff(800)
.out()
-->
+44
View File
@@ -0,0 +1,44 @@
<input
type="text"
id="text"
value="seq('c3','eb3','g3').note().s('sawtooth').out()"
style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px"
spellcheck="false"
/>
<button id="start">play</button>
<div id="output"></div>
<script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@latest');
const controls = await import('https://cdn.skypack.dev/@strudel.cycles/core@latest/controls.mjs');
const { getAudioContext, Scheduler } = await import('https://cdn.skypack.dev/@strudel.cycles/webaudio@latest');
let scheduler;
const audioContext = getAudioContext();
const latency = 0.2;
Object.assign(window, strudel);
Object.assign(window, controls.default);
scheduler = new Scheduler({
audioContext,
interval: 0.1,
latency,
onEvent: (hap) => {
if (!hap.context.onTrigger) {
console.warn('no output chosen. use one of .out() .webdirt() .osc()');
}
},
});
let started;
document.getElementById('start').addEventListener('click', async () => {
const code = document.getElementById('text').value;
const pattern = eval(code);
const events = pattern._firstCycleValues;
console.log(code, '->', events);
scheduler.setPattern(pattern);
if (!started) {
scheduler.start();
started = true;
}
});
</script>
+29 -16
View File
@@ -6,36 +6,46 @@
<title>Buildless Vanilla Strudel REPL</title> <title>Buildless Vanilla Strudel REPL</title>
</head> </head>
<body style="margin: 0; background: #222"> <body style="margin: 0; background: #222">
<div style="display: grid; height: 100vh; grid-template-rows: 32px auto"> <div style="display: grid; height: 100vh">
<button id="start" style="width: 100vw; height: 32px">evaluate</button>
<textarea <textarea
id="text" id="text"
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px" style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
spellcheck="false" spellcheck="false"
></textarea> ></textarea>
</div> </div>
<button
id="start"
style="
position: absolute;
border-radius: 10px;
top: 20px;
right: 20px;
padding: 20px;
border: 2px solid white;
background: transparent;
color: white;
cursor: pointer;
"
>
evaluate
</button>
<div id="output"></div> <div id="output"></div>
<script type="module"> <script type="module">
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'; import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.3.2';
import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0'; import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.3.2';
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.6.0'; import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.3.2';
import { import { getAudioContext, webaudioOutput } from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.3.3';
getAudioContext,
webaudioOutput,
initAudioOnFirstClick,
} from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0';
initAudioOnFirstClick();
const ctx = getAudioContext(); const ctx = getAudioContext();
const input = document.getElementById('text'); const input = document.getElementById('text');
input.innerHTML = getTune(); input.innerHTML = getTune();
evalScope( evalScope(
controls, controls,
import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'), import('https://cdn.skypack.dev/@strudel.cycles/core@0.3.2'),
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0'), import('https://cdn.skypack.dev/@strudel.cycles/mini@0.3.2'),
import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.6.0'), import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.3.3'),
import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0'), import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.3.3'),
); );
const { evaluate } = repl({ const { evaluate } = repl({
@@ -43,7 +53,10 @@
getTime: () => ctx.currentTime, getTime: () => ctx.currentTime,
transpiler, transpiler,
}); });
document.getElementById('start').addEventListener('click', () => evaluate(input.value)); document.getElementById('start').addEventListener('click', () => {
ctx.resume();
evaluate(input.value);
});
function getTune() { function getTune() {
return `await samples('github:tidalcycles/Dirt-Samples/master') return `await samples('github:tidalcycles/Dirt-Samples/master')
@@ -1,12 +1,12 @@
import { controls, repl, evalScope } from '@strudel.cycles/core'; import { controls, repl, evalScope, setStringParser } from '@strudel.cycles/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { mini } from '@strudel.cycles/mini';
import { transpiler } from '@strudel.cycles/transpiler'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
// import { transpiler } from '@strudel.cycles/transpiler';
import tune from './tune.mjs'; import tune from './tune.mjs';
const ctx = getAudioContext(); const ctx = getAudioContext();
const input = document.getElementById('text'); const input = document.getElementById('text');
input.innerHTML = tune; input.innerHTML = tune;
initAudioOnFirstClick();
evalScope( evalScope(
controls, controls,
@@ -16,13 +16,14 @@ evalScope(
import('@strudel.cycles/tonal'), import('@strudel.cycles/tonal'),
); );
setStringParser(mini);
const { evaluate } = repl({ const { evaluate } = repl({
defaultOutput: webaudioOutput, defaultOutput: webaudioOutput,
getTime: () => ctx.currentTime, getTime: () => ctx.currentTime,
transpiler, // transpiler,
}); });
document.getElementById('start').addEventListener('click', () => { document.getElementById('start').addEventListener('click', () => {
ctx.resume(); ctx.resume();
console.log('eval', input.value);
evaluate(input.value); evaluate(input.value);
}); });
+885
View File
@@ -0,0 +1,885 @@
{
"name": "vite-vanilla-repl",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "vite-vanilla-repl",
"version": "0.0.0",
"devDependencies": {
"vite": "^3.2.0"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
"integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
"integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
"integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.15.13",
"@esbuild/linux-loong64": "0.15.13",
"esbuild-android-64": "0.15.13",
"esbuild-android-arm64": "0.15.13",
"esbuild-darwin-64": "0.15.13",
"esbuild-darwin-arm64": "0.15.13",
"esbuild-freebsd-64": "0.15.13",
"esbuild-freebsd-arm64": "0.15.13",
"esbuild-linux-32": "0.15.13",
"esbuild-linux-64": "0.15.13",
"esbuild-linux-arm": "0.15.13",
"esbuild-linux-arm64": "0.15.13",
"esbuild-linux-mips64le": "0.15.13",
"esbuild-linux-ppc64le": "0.15.13",
"esbuild-linux-riscv64": "0.15.13",
"esbuild-linux-s390x": "0.15.13",
"esbuild-netbsd-64": "0.15.13",
"esbuild-openbsd-64": "0.15.13",
"esbuild-sunos-64": "0.15.13",
"esbuild-windows-32": "0.15.13",
"esbuild-windows-64": "0.15.13",
"esbuild-windows-arm64": "0.15.13"
}
},
"node_modules/esbuild-android-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
"integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-android-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
"integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-darwin-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
"integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-darwin-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
"integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-freebsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
"integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-freebsd-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
"integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-32": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
"integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
"integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-arm": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
"integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
"integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-mips64le": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
"integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-ppc64le": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
"integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-riscv64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
"integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-s390x": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
"integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-netbsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
"integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-openbsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
"integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-sunos-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
"integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-windows-32": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
"integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-windows-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
"integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-windows-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
"integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/is-core-module": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/postcss": {
"version": "8.4.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
"dev": true,
"dependencies": {
"is-core-module": "^2.9.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=10.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/vite": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz",
"integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==",
"dev": true,
"dependencies": {
"esbuild": "^0.15.9",
"postcss": "^8.4.18",
"resolve": "^1.22.1",
"rollup": "^2.79.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"less": "*",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"less": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
},
"dependencies": {
"@esbuild/android-arm": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
"integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
"integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
"dev": true,
"optional": true
},
"esbuild": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
"integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
"dev": true,
"requires": {
"@esbuild/android-arm": "0.15.13",
"@esbuild/linux-loong64": "0.15.13",
"esbuild-android-64": "0.15.13",
"esbuild-android-arm64": "0.15.13",
"esbuild-darwin-64": "0.15.13",
"esbuild-darwin-arm64": "0.15.13",
"esbuild-freebsd-64": "0.15.13",
"esbuild-freebsd-arm64": "0.15.13",
"esbuild-linux-32": "0.15.13",
"esbuild-linux-64": "0.15.13",
"esbuild-linux-arm": "0.15.13",
"esbuild-linux-arm64": "0.15.13",
"esbuild-linux-mips64le": "0.15.13",
"esbuild-linux-ppc64le": "0.15.13",
"esbuild-linux-riscv64": "0.15.13",
"esbuild-linux-s390x": "0.15.13",
"esbuild-netbsd-64": "0.15.13",
"esbuild-openbsd-64": "0.15.13",
"esbuild-sunos-64": "0.15.13",
"esbuild-windows-32": "0.15.13",
"esbuild-windows-64": "0.15.13",
"esbuild-windows-arm64": "0.15.13"
}
},
"esbuild-android-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
"integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
"dev": true,
"optional": true
},
"esbuild-android-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
"integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
"dev": true,
"optional": true
},
"esbuild-darwin-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
"integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
"dev": true,
"optional": true
},
"esbuild-darwin-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
"integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
"dev": true,
"optional": true
},
"esbuild-freebsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
"integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
"dev": true,
"optional": true
},
"esbuild-freebsd-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
"integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
"dev": true,
"optional": true
},
"esbuild-linux-32": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
"integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
"dev": true,
"optional": true
},
"esbuild-linux-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
"integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
"dev": true,
"optional": true
},
"esbuild-linux-arm": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
"integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
"dev": true,
"optional": true
},
"esbuild-linux-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
"integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
"dev": true,
"optional": true
},
"esbuild-linux-mips64le": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
"integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
"dev": true,
"optional": true
},
"esbuild-linux-ppc64le": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
"integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
"dev": true,
"optional": true
},
"esbuild-linux-riscv64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
"integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
"dev": true,
"optional": true
},
"esbuild-linux-s390x": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
"integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
"dev": true,
"optional": true
},
"esbuild-netbsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
"integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
"dev": true,
"optional": true
},
"esbuild-openbsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
"integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
"dev": true,
"optional": true
},
"esbuild-sunos-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
"integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
"dev": true,
"optional": true
},
"esbuild-windows-32": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
"integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
"dev": true,
"optional": true
},
"esbuild-windows-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
"integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
"dev": true,
"optional": true
},
"esbuild-windows-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
"integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
"dev": true,
"optional": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"is-core-module": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"postcss": {
"version": "8.4.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
"dev": true,
"requires": {
"is-core-module": "^2.9.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"rollup": {
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
}
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"vite": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz",
"integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==",
"dev": true,
"requires": {
"esbuild": "^0.15.9",
"fsevents": "~2.3.2",
"postcss": "^8.4.18",
"resolve": "^1.22.1",
"rollup": "^2.79.1"
}
}
}
}
@@ -11,12 +11,5 @@
}, },
"devDependencies": { "devDependencies": {
"vite": "^3.2.0" "vite": "^3.2.0"
},
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/tonal": "workspace:*"
} }
} }
@@ -1,4 +1,4 @@
export default `await samples('github:tidalcycles/Dirt-Samples/master') /* export default `await samples('github:tidalcycles/Dirt-Samples/master')
stack( stack(
// amen // amen
@@ -29,3 +29,11 @@ stack(
, ,
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5)) n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
).reset("<x@7 x(5,8)>")`; ).reset("<x@7 x(5,8)>")`;
*/
export default `stack(
n("c3 [eb3,g3]")
.delay("<0 .5>")
.delaytime(".16 | .33")
.delayfeedback(".6 | .8")
).sometimes(x=>x.speed("-1"))`;
+65
View File
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Buildless Vanilla Strudel REPL</title>
</head>
<body style="margin: 0; background: #222">
<div style="display: grid; height: 100vh">
<textarea
id="text"
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
spellcheck="false"
>
// LOADING</textarea
>
</div>
<button
id="start"
style="
position: absolute;
border-radius: 10px;
top: 20px;
right: 20px;
padding: 20px;
border: 2px solid white;
background: transparent;
color: white;
cursor: pointer;
"
>
evaluate
</button>
<div id="output"></div>
<script type="module">
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.4.1';
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.4.1';
const modules = [
import('https://cdn.skypack.dev/@strudel.cycles/core@0.4.1'),
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.4.1'),
];
const input = document.getElementById('text');
Promise.all(modules).then(() => {
input.innerHTML = `note("<c3 [d3 e3]>").cutoff(1000)`;
document.getElementById('start').addEventListener('click', () => {
evaluate(input.value);
});
});
evalScope(controls, ...modules);
const { evaluate } = repl({
defaultOutput: (hap, deadline, duration) => {
console.log(deadline, duration, hap.value);
},
getTime: () => Date.now() / 1000,
transpiler,
beforeEval: (code) => console.log('evaluate', code),
afterEval: (code) => {},
});
</script>
</body>
</html>
-13
View File
@@ -126,19 +126,6 @@ export class Hap {
setContext(context) { setContext(context) {
return new Hap(this.whole, this.part, this.value, context); return new Hap(this.whole, this.part, this.value, context);
} }
ensureObjectValue() {
/* if (isNote(hap.value)) {
// supports primitive hap values that look like notes
hap.value = { note: hap.value };
} */
if (typeof this.value !== 'object') {
throw new Error(
`expected hap.value to be an object, but got "${this.value}". Hint: append .note() or .s() to the end`,
'error',
);
}
}
} }
export default Hap; export default Hap;
+1639
View File
File diff suppressed because it is too large Load Diff
+3 -13
View File
@@ -1,17 +1,11 @@
{ {
"name": "@strudel.cycles/core", "name": "@strudel.cycles/core",
"version": "0.6.8", "version": "0.5.0",
"description": "Port of Tidal Cycles to JavaScript", "description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run"
"build": "vite build",
"prepublishOnly": "pnpm build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -33,9 +27,5 @@
"dependencies": { "dependencies": {
"fraction.js": "^4.2.0" "fraction.js": "^4.2.0"
}, },
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2", "gitHead": "0e26d4e741500f5bae35b023608f062a794905c2"
"devDependencies": {
"vite": "^3.2.2",
"vitest": "^0.25.7"
}
} }
+170 -236
View File
@@ -27,13 +27,25 @@ export class Pattern {
* Create a pattern. As an end user, you will most likely not create a Pattern directly. * Create a pattern. As an end user, you will most likely not create a Pattern directly.
* *
* @param {function} query - The function that maps a {@link State} to an array of {@link Hap}. * @param {function} query - The function that maps a {@link State} to an array of {@link Hap}.
* @noAutocomplete
*/ */
constructor(query) { constructor(query, as_function) {
this.query = query; this.query = query;
this._Pattern = true; // this property is used to detect if a pattern that fails instanceof Pattern is an instance of another Pattern this._Pattern = true; // this property is used to detect if a pattern that fails instanceof Pattern is an instance of another Pattern
this.__as_function = as_function;
} }
// TODO - would a default 'as_function' be useful?
// /**
// * Accessor for pattern-as-function
// */
// get as_function() {
// if (!this.__as_function) {
// // TODO - add other alignments
// this.__as_function = x => x.set(this);
// }
// return this.__as_function;
// }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Haskell-style functor, applicative and monadic operations // Haskell-style functor, applicative and monadic operations
@@ -52,19 +64,21 @@ export class Pattern {
/** /**
* see {@link Pattern#withValue} * see {@link Pattern#withValue}
* @noAutocomplete
*/ */
fmap(func) { fmap(func) {
return this.withValue(func); return this.withValue(func);
} }
applyValue(func) {
return this.withValue((x) => x(func));
}
/** /**
* Assumes 'this' is a pattern of functions, and given a function to * Assumes 'this' is a pattern of functions, and given a function to
* resolve wholes, applies a given pattern of values to that * resolve wholes, applies a given pattern of values to that
* pattern of functions. * pattern of functions.
* @param {Function} whole_func * @param {Function} whole_func
* @param {Function} func * @param {Function} func
* @noAutocomplete
* @returns Pattern * @returns Pattern
*/ */
appWhole(whole_func, pat_val) { appWhole(whole_func, pat_val) {
@@ -100,7 +114,6 @@ export class Pattern {
* are not the same but do intersect, the resulting hap has a timespan of the * are not the same but do intersect, the resulting hap has a timespan of the
* intersection. This applies to both the part and the whole timespan. * intersection. This applies to both the part and the whole timespan.
* @param {Pattern} pat_val * @param {Pattern} pat_val
* @noAutocomplete
* @returns Pattern * @returns Pattern
*/ */
appBoth(pat_val) { appBoth(pat_val) {
@@ -121,7 +134,6 @@ export class Pattern {
* are preserved from the pattern of functions (often referred to as the left * are preserved from the pattern of functions (often referred to as the left
* hand or inner pattern). * hand or inner pattern).
* @param {Pattern} pat_val * @param {Pattern} pat_val
* @noAutocomplete
* @returns Pattern * @returns Pattern
*/ */
appLeft(pat_val) { appLeft(pat_val) {
@@ -152,7 +164,6 @@ export class Pattern {
* pattern of values, i.e. structure is preserved from the right hand/outer * pattern of values, i.e. structure is preserved from the right hand/outer
* pattern. * pattern.
* @param {Pattern} pat_val * @param {Pattern} pat_val
* @noAutocomplete
* @returns Pattern * @returns Pattern
*/ */
appRight(pat_val) { appRight(pat_val) {
@@ -338,7 +349,6 @@ export class Pattern {
* const haps = pattern.queryArc(0, 1) * const haps = pattern.queryArc(0, 1)
* console.log(haps) * console.log(haps)
* silence * silence
* @noAutocomplete
*/ */
queryArc(begin, end) { queryArc(begin, end) {
try { try {
@@ -354,7 +364,6 @@ export class Pattern {
* some calculations easier to express, as all haps are then constrained to * some calculations easier to express, as all haps are then constrained to
* happen within a cycle. * happen within a cycle.
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
splitQueries() { splitQueries() {
const pat = this; const pat = this;
@@ -369,7 +378,6 @@ export class Pattern {
* timespan before passing it to the original pattern. * timespan before passing it to the original pattern.
* @param {Function} func the function to apply * @param {Function} func the function to apply
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
withQuerySpan(func) { withQuerySpan(func) {
return new Pattern((state) => this.query(state.withSpan(func))); return new Pattern((state) => this.query(state.withSpan(func)));
@@ -391,7 +399,6 @@ export class Pattern {
* begin and end time of the query timespan. * begin and end time of the query timespan.
* @param {Function} func the function to apply * @param {Function} func the function to apply
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
withQueryTime(func) { withQueryTime(func) {
return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func)))); return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func))));
@@ -403,7 +410,6 @@ export class Pattern {
* present, `whole` timespans). * present, `whole` timespans).
* @param {Function} func * @param {Function} func
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
withHapSpan(func) { withHapSpan(func) {
return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func))); return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
@@ -414,7 +420,6 @@ export class Pattern {
* begin and end time of the hap timespans. * begin and end time of the hap timespans.
* @param {Function} func the function to apply * @param {Function} func the function to apply
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
withHapTime(func) { withHapTime(func) {
return this.withHapSpan((span) => span.withTime(func)); return this.withHapSpan((span) => span.withTime(func));
@@ -424,7 +429,6 @@ export class Pattern {
* Returns a new pattern with the given function applied to the list of haps returned by every query. * Returns a new pattern with the given function applied to the list of haps returned by every query.
* @param {Function} func * @param {Function} func
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
withHaps(func) { withHaps(func) {
return new Pattern((state) => func(this.query(state))); return new Pattern((state) => func(this.query(state)));
@@ -434,7 +438,6 @@ export class Pattern {
* As with {@link Pattern#withHaps}, but applies the function to every hap, rather than every list of haps. * As with {@link Pattern#withHaps}, but applies the function to every hap, rather than every list of haps.
* @param {Function} func * @param {Function} func
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
withHap(func) { withHap(func) {
return this.withHaps((haps) => haps.map(func)); return this.withHaps((haps) => haps.map(func));
@@ -444,7 +447,6 @@ export class Pattern {
* Returns a new pattern with the context field set to every hap set to the given value. * Returns a new pattern with the context field set to every hap set to the given value.
* @param {*} context * @param {*} context
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
setContext(context) { setContext(context) {
return this.withHap((hap) => hap.setContext(context)); return this.withHap((hap) => hap.setContext(context));
@@ -454,7 +456,6 @@ export class Pattern {
* Returns a new pattern with the given function applied to the context field of every hap. * Returns a new pattern with the given function applied to the context field of every hap.
* @param {Function} func * @param {Function} func
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
withContext(func) { withContext(func) {
return this.withHap((hap) => hap.setContext(func(hap.context))); return this.withHap((hap) => hap.setContext(func(hap.context)));
@@ -463,7 +464,6 @@ export class Pattern {
/** /**
* Returns a new pattern with the context field of every hap set to an empty object. * Returns a new pattern with the context field of every hap set to an empty object.
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
stripContext() { stripContext() {
return this.withHap((hap) => hap.setContext({})); return this.withHap((hap) => hap.setContext({}));
@@ -475,7 +475,6 @@ export class Pattern {
* @param {Number} start * @param {Number} start
* @param {Number} end * @param {Number} end
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
withLocation(start, end) { withLocation(start, end) {
const location = { const location = {
@@ -518,7 +517,6 @@ export class Pattern {
* Returns a new Pattern, which only returns haps that meet the given test. * Returns a new Pattern, which only returns haps that meet the given test.
* @param {Function} hap_test - a function which returns false for haps to be removed from the pattern * @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
filterHaps(hap_test) { filterHaps(hap_test) {
return new Pattern((state) => this.query(state).filter(hap_test)); return new Pattern((state) => this.query(state).filter(hap_test));
@@ -529,7 +527,6 @@ export class Pattern {
* inside haps. * inside haps.
* @param {Function} value_test * @param {Function} value_test
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
filterValues(value_test) { filterValues(value_test) {
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))); return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value)));
@@ -539,7 +536,6 @@ export class Pattern {
* Returns a new pattern, with haps containing undefined values removed from * Returns a new pattern, with haps containing undefined values removed from
* query results. * query results.
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
removeUndefineds() { removeUndefineds() {
return this.filterValues((val) => val != undefined); return this.filterValues((val) => val != undefined);
@@ -550,7 +546,6 @@ export class Pattern {
* with an onset is one with a `whole` timespan that begins at the same time * with an onset is one with a `whole` timespan that begins at the same time
* as its `part` timespan. * as its `part` timespan.
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
onsetsOnly() { onsetsOnly() {
// Returns a new pattern that will only return haps where the start // Returns a new pattern that will only return haps where the start
@@ -563,7 +558,6 @@ export class Pattern {
* Returns a new pattern, with 'continuous' haps (those without 'whole' * Returns a new pattern, with 'continuous' haps (those without 'whole'
* timespans) removed from query results. * timespans) removed from query results.
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
discreteOnly() { discreteOnly() {
// removes continuous haps that don't have a 'whole' timespan // removes continuous haps that don't have a 'whole' timespan
@@ -573,7 +567,6 @@ export class Pattern {
/** /**
* Combines adjacent haps with the same value and whole. Only * Combines adjacent haps with the same value and whole. Only
* intended for use in tests. * intended for use in tests.
* @noAutocomplete
*/ */
defragmentHaps() { defragmentHaps() {
// remove continuous haps // remove continuous haps
@@ -628,7 +621,6 @@ export class Pattern {
* @param {Boolean} with_context - set to true, otherwise the context field * @param {Boolean} with_context - set to true, otherwise the context field
* will be stripped from the resulting haps. * will be stripped from the resulting haps.
* @returns [Hap] * @returns [Hap]
* @noAutocomplete
*/ */
firstCycle(with_context = false) { firstCycle(with_context = false) {
var self = this; var self = this;
@@ -640,7 +632,6 @@ export class Pattern {
/** /**
* Accessor for a list of values returned by querying the first cycle. * Accessor for a list of values returned by querying the first cycle.
* @noAutocomplete
*/ */
get firstCycleValues() { get firstCycleValues() {
return this.firstCycle().map((hap) => hap.value); return this.firstCycle().map((hap) => hap.value);
@@ -648,7 +639,6 @@ export class Pattern {
/** /**
* More human-readable version of the {@link Pattern#firstCycleValues} accessor. * More human-readable version of the {@link Pattern#firstCycleValues} accessor.
* @noAutocomplete
*/ */
get showFirstCycle() { get showFirstCycle() {
return this.firstCycle().map( return this.firstCycle().map(
@@ -660,7 +650,6 @@ export class Pattern {
* Returns a new pattern, which returns haps sorted in temporal order. Mainly * Returns a new pattern, which returns haps sorted in temporal order. Mainly
* of use when comparing two patterns for equality, in tests. * of use when comparing two patterns for equality, in tests.
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
sortHapsByPart() { sortHapsByPart() {
return this.withHaps((haps) => return this.withHaps((haps) =>
@@ -840,7 +829,7 @@ export class Pattern {
} }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// functions relating to chords/patterns of lists/lists of patterns // functions relating to chords/patterns of lists
// returns Array<Hap[]> where each list of haps satisfies eq // returns Array<Hap[]> where each list of haps satisfies eq
function groupHapsBy(eq, haps) { function groupHapsBy(eq, haps) {
@@ -889,35 +878,6 @@ Pattern.prototype.arp = function (pat) {
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length])); return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
}; };
/*
* Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are
* distributed equally over the given time duration. They are then combined with the pattern 'weave' is called on, after it has been stretched out (i.e. slowed down by) the time duration.
* @name weave
* @memberof Pattern
* @example pan(saw).weave(4, s("bd(3,8)"), s("~ sd"))
* @example n("0 1 2 3 4 5 6 7").weave(8, s("bd(3,8)"), s("~ sd"))
addToPrototype('weave', function (t, ...pats) {
return this.weaveWith(t, ...pats.map((x) => set.out(x)));
});
*/
/*
* Like 'weave', but accepts functions rather than patterns, which are applied to the pattern.
* @name weaveWith
* @memberof Pattern
addToPrototype('weaveWith', function (t, ...funcs) {
const pat = this;
const l = funcs.length;
t = Fraction(t);
if (l == 0) {
return silence;
}
return stack(...funcs.map((func, i) => pat.inside(t, func).early(Fraction(i).div(l))))._slow(t);
});
*/
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// compose matrix functions // compose matrix functions
@@ -1160,7 +1120,6 @@ export const silence = new Pattern(() => []);
* @returns {Pattern} * @returns {Pattern}
* @example * @example
* pure('e4') // "e4" * pure('e4') // "e4"
* @noAutocomplete
*/ */
export function pure(value) { export function pure(value) {
function query(state) { function query(state) {
@@ -1401,48 +1360,64 @@ export const func = curry((a, b) => reify(b).func(a));
* *
* @param {string} name name of the function * @param {string} name name of the function
* @param {function} func function with 1 or more params, where last is the current pattern * @param {function} func function with 1 or more params, where last is the current pattern
* @noAutocomplete
* *
*/ */
export function register(name, func, patternify = true) { export function register(name, func, f_params = null) {
if (Array.isArray(name)) { if (Array.isArray(name)) {
const result = {}; const result = {};
for (const name_item of name) { for (const name_item of name) {
result[name_item] = register(name_item, func, patternify); result[name_item] = register(name_item, func, f_params);
} }
return result; return result;
} }
const arity = func.length; const arity = func.length;
var pfunc; // the patternified function var pfunc; // the patternified function
if (patternify) { pfunc = function (...args) {
pfunc = function (...args) { args = args.map(reify);
args = args.map(reify); const pat = args[args.length - 1];
const pat = args[args.length - 1]; if (arity === 1) {
if (arity === 1) { return func(pat);
return func(pat); }
const [left, ...right] = args.slice(0, -1);
let mapFn = (...args) => {
// make sure to call func with the correct argument count
// args.length is expected to be <= arity-1
// so we set undefined args explicitly undefined
Array(arity - 1)
.fill()
.map((_, i) => args[i] ?? undefined);
return func(...args, pat);
};
mapFn = curry(mapFn, null, arity - 1);
// Don't use applicative for arguments that a) have a '__as_function' function and b) are
// marked as being a higher order function parameter
function app(acc, p, i) {
if (f_params != null && f_params.length > i + 1 && f_params[i + 1] && '__as_function' in p) {
return acc.applyValue(p.__as_function);
} }
const [left, ...right] = args.slice(0, -1); return acc.appLeft(p);
let mapFn = (...args) => { }
// make sure to call func with the correct argument count
// args.length is expected to be <= arity-1 var start;
// so we set undefined args explicitly undefined // Do the same check for the first parameter.. a bit repetitive
Array(arity - 1) if (f_params != null && f_params.length > 0 && f_params[0] && '__as_function' in left) {
.fill() start = pure(mapFn(left.__as_function));
.map((_, i) => args[i] ?? undefined); } else {
return func(...args, pat); start = left.fmap(mapFn);
}; }
mapFn = curry(mapFn, null, arity - 1);
return right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)).innerJoin(); return right.reduce(app, start).innerJoin();
}; };
} else {
pfunc = function (...args) { if (!Pattern.__registered) {
args = args.map(reify); Pattern.__registered = [];
return func(...args);
};
} }
Pattern.__registered.push(name);
Pattern.prototype[name] = function (...args) { Pattern.prototype[name] = function (...args) {
args = args.map(reify);
// For methods that take a single argument (plus 'this'), allow // For methods that take a single argument (plus 'this'), allow
// multiple arguments but sequence them // multiple arguments but sequence them
if (arity === 2 && args.length !== 1) { if (arity === 2 && args.length !== 1) {
@@ -1450,8 +1425,19 @@ export function register(name, func, patternify = true) {
} else if (arity !== args.length + 1) { } else if (arity !== args.length + 1) {
throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`); throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`);
} }
args = args.map(reify); const result = pfunc(...args, this);
return pfunc(...args, this);
// speed(2,3).__as_function(pat) = pat.speed(2,3)
// and so
// speed(2,3).fast(2).__as_function(pat) = pat.speed(2,3).fast(2)
if ('__as_function' in this) {
const pata = this;
result.__as_function = function (patb) {
return pata.__as_function(patb)[name](...args);
};
}
return result;
}; };
if (arity > 1) { if (arity > 1) {
@@ -1514,17 +1500,15 @@ export const ceil = register('ceil', function (pat) {
* Assumes a numerical pattern, containing unipolar values in the range 0 .. * Assumes a numerical pattern, containing unipolar values in the range 0 ..
* 1. Returns a new pattern with values scaled to the bipolar range -1 .. 1 * 1. Returns a new pattern with values scaled to the bipolar range -1 .. 1
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
export const toBipolar = register('toBipolar', function (pat) { export const toBipolar = register('toBipolar', function (pat) {
return pat.fmap((x) => x * 2 - 1); return pat.fmap((x) => x * 2 - 1);
}); });
/** /**
* Assumes a numerical pattern, containing bipolar values in the range -1 .. 1 * Assumes a numerical pattern, containing bipolar values in the range -1 ..
* Returns a new pattern with values scaled to the unipolar range 0 .. 1 * 1. Returns a new pattern with values scaled to the unipolar range 0 .. 1
* @returns Pattern * @returns Pattern
* @noAutocomplete
*/ */
export const fromBipolar = register('fromBipolar', function (pat) { export const fromBipolar = register('fromBipolar', function (pat) {
return pat.fmap((x) => (x + 1) / 2); return pat.fmap((x) => (x + 1) / 2);
@@ -1671,15 +1655,6 @@ export const { fast, density } = register(['fast', 'density'], function (factor,
return fastQuery.withHapTime((t) => t.div(factor)); return fastQuery.withHapTime((t) => t.div(factor));
}); });
/**
* Both speeds up the pattern (like 'fast') and the sample playback (like 'speed').
* @example
* s("bd sd:2").hurry("<1 2 4 3>").slow(1.5)
*/
export const hurry = register('hurry', function (r, pat) {
return pat._fast(r).mul(pure({ speed: r }));
});
/** /**
* Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation. * Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation.
* *
@@ -1701,9 +1676,13 @@ export const { slow, sparsity } = register(['slow', 'sparsity'], function (facto
* "0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note() * "0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note()
* // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note() * // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note()
*/ */
export const inside = register('inside', function (factor, f, pat) { export const inside = register(
return f(pat._slow(factor))._fast(factor); 'inside',
}); function (factor, f, pat) {
return f(pat._slow(factor))._fast(factor);
},
[false, true],
);
/** /**
* Carries out an operation 'outside' a cycle. * Carries out an operation 'outside' a cycle.
@@ -1711,9 +1690,13 @@ export const inside = register('inside', function (factor, f, pat) {
* "<[0 1] 2 [3 4] 5>".outside(4, rev).scale('C major').note() * "<[0 1] 2 [3 4] 5>".outside(4, rev).scale('C major').note()
* // "<[0 1] 2 [3 4] 5>".fast(4).rev().slow(4).scale('C major').note() * // "<[0 1] 2 [3 4] 5>".fast(4).rev().slow(4).scale('C major').note()
*/ */
export const outside = register('outside', function (factor, f, pat) { export const outside = register(
return f(pat._fast(factor))._slow(factor); 'outside',
}); function (factor, f, pat) {
return f(pat._fast(factor))._slow(factor);
},
[false, true],
);
/** /**
* Applies the given function every n cycles, starting from the last cycle. * Applies the given function every n cycles, starting from the last cycle.
@@ -1725,11 +1708,16 @@ export const outside = register('outside', function (factor, f, pat) {
* @example * @example
* note("c3 d3 e3 g3").lastOf(4, x=>x.rev()) * note("c3 d3 e3 g3").lastOf(4, x=>x.rev())
*/ */
export const lastOf = register('lastOf', function (n, func, pat) { export const lastOf = register(
const pats = Array(n - 1).fill(pat); 'lastOf',
pats.push(func(pat)); function (n, func, pat) {
return slowcatPrime(...pats); const pats = Array(n - 1).fill(pat);
}); pats.push(func(pat));
return slowcatPrime(...pats);
},
// second parameter is a function
[false, true],
);
/** /**
* Applies the given function every n cycles, starting from the first cycle. * Applies the given function every n cycles, starting from the first cycle.
@@ -1752,11 +1740,16 @@ export const lastOf = register('lastOf', function (n, func, pat) {
* @example * @example
* note("c3 d3 e3 g3").every(4, x=>x.rev()) * note("c3 d3 e3 g3").every(4, x=>x.rev())
*/ */
export const { firstOf, every } = register(['firstOf', 'every'], function (n, func, pat) { export const { firstOf, every } = register(
const pats = Array(n - 1).fill(pat); ['firstOf', 'every'],
pats.unshift(func(pat)); function (n, func, pat) {
return slowcatPrime(...pats); const pats = Array(n - 1).fill(pat);
}); pats.unshift(func(pat));
return slowcatPrime(...pats);
},
// second parameter is a function
[false, true],
);
/** /**
* Like layer, but with a single function: * Like layer, but with a single function:
@@ -1766,9 +1759,13 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note() * "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
*/ */
// TODO: remove or dedupe with layer? // TODO: remove or dedupe with layer?
export const apply = register('apply', function (func, pat) { export const apply = register(
return func(pat); 'apply',
}); function (func, pat) {
return func(pat);
},
[true],
);
/** /**
* Plays the pattern at the given cycles per minute. * Plays the pattern at the given cycles per minute.
@@ -1878,9 +1875,13 @@ export const { invert, inv } = register(['invert', 'inv'], function (pat) {
* @example * @example
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)).note() * "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)).note()
*/ */
export const when = register('when', function (on, func, pat) { export const when = register(
return on ? func(pat) : pat; 'when',
}); function (on, func, pat) {
return on ? func(pat) : pat;
},
[false, true],
);
/** /**
* Superimposes the function result on top of the original pattern, delayed by the given time. * Superimposes the function result on top of the original pattern, delayed by the given time.
@@ -1892,9 +1893,13 @@ export const when = register('when', function (on, func, pat) {
* @example * @example
* "c3 eb3 g3".off(1/8, x=>x.add(7)).note() * "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
*/ */
export const off = register('off', function (time_pat, func, pat) { export const off = register(
return stack(pat, func(pat.late(time_pat))); 'off',
}); function (time_pat, func, pat) {
return stack(pat, func(pat.late(time_pat)));
},
[false, true],
);
/** /**
* Returns a new pattern where every other cycle is played once, twice as * Returns a new pattern where every other cycle is played once, twice as
@@ -1934,29 +1939,6 @@ export const rev = register('rev', function (pat) {
return new Pattern(query).splitQueries(); return new Pattern(query).splitQueries();
}); });
/** Like press, but allows you to specify the amount by which each
* event is shifted. pressBy(0.5) is the same as press, while
* pressBy(1/3) shifts each event by a third of its timespan.
* @example
* stack(s("hh*4"),
* s("bd mt sd ht").pressBy("<0 0.5 0.25>")
* ).slow(2)
*/
export const pressBy = register('pressBy', function (r, pat) {
return pat.fmap((x) => pure(x).compress(r, 1)).squeezeJoin();
});
/**
* Syncopates a rhythm, by shifting each event halfway into its timespan.
* @example
* stack(s("hh*4"),
* s("bd mt sd ht").every(4, press)
* ).slow(2)
*/
export const press = register('press', function (pat) {
return pat._pressBy(0.5);
});
/** /**
* Silences a pattern. * Silences a pattern.
* @example * @example
@@ -1985,28 +1967,36 @@ export const palindrome = register('palindrome', function (pat) {
* @example * @example
* s("lt ht mt ht hh").juxBy("<0 .5 1>/2", rev) * s("lt ht mt ht hh").juxBy("<0 .5 1>/2", rev)
*/ */
export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, pat) { export const { juxBy, juxby } = register(
by /= 2; ['juxBy', 'juxby'],
const elem_or = function (dict, key, dflt) { function (by, func, pat) {
if (key in dict) { by /= 2;
return dict[key]; const elem_or = function (dict, key, dflt) {
} if (key in dict) {
return dflt; return dict[key];
}; }
const left = pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) - by })); return dflt;
const right = pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) + by })); };
const left = pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) - by }));
const right = pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) + by }));
return stack(left, func(right)); return stack(left, func(right));
}); },
[false, true],
);
/** /**
* The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel.
* @example * @example
* s("lt ht mt ht hh").jux(rev) * s("lt ht mt ht hh").jux(rev)
*/ */
export const jux = register('jux', function (func, pat) { export const jux = register(
return pat._juxBy(1, func, pat); 'jux',
}); function (func, pat) {
return pat._juxBy(1, func, pat);
},
[true],
);
/** /**
* Superimpose and offset multiple times, applying the given function each time. * Superimpose and offset multiple times, applying the given function each time.
@@ -2025,6 +2015,7 @@ export const { echoWith, echowith, stutWith, stutwith } = register(
function (times, time, func, pat) { function (times, time, func, pat) {
return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i))); return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i)));
}, },
[false, false, true],
); );
/** /**
@@ -2105,9 +2096,13 @@ const _chunk = function (n, func, pat, back = false) {
return pat.when(binary_pat, func); return pat.when(binary_pat, func);
}; };
export const chunk = register('chunk', function (n, func, pat) { export const chunk = register(
return _chunk(n, func, pat, false); 'chunk',
}); function (n, func, pat) {
return _chunk(n, func, pat, false);
},
[false, true],
);
/** /**
* Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
@@ -2118,9 +2113,13 @@ export const chunk = register('chunk', function (n, func, pat) {
* @example * @example
* "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note() * "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note()
*/ */
export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) { export const { chunkBack, chunkback } = register(
return _chunk(n, func, pat, true); ['chunkBack', 'chunkback'],
}); function (n, func, pat) {
return _chunk(n, func, pat, true);
},
[false, true],
);
// TODO - redefine elsewhere in terms of mask // TODO - redefine elsewhere in terms of mask
export const bypass = register('bypass', function (on, pat) { export const bypass = register('bypass', function (on, pat) {
@@ -2128,16 +2127,6 @@ export const bypass = register('bypass', function (on, pat) {
return on ? silence : pat; return on ? silence : pat;
}); });
/**
* Loops the pattern inside at `offset` for `cycles`.
* @param {number} offset start point of loop in cycles
* @param {number} cycles loop length in cycles
* @example
* // Looping a portion of randomness
* note(irand(8).segment(4).scale('C3 minor')).ribbon(1337, 2)
*/
export const ribbon = register('ribbon', (offset, cycles, pat) => pat.early(offset).restart(pure(1).slow(cycles)));
// sets absolute duration of haps // sets absolute duration of haps
// TODO - fix // TODO - fix
export const duration = register('duration', function (value, pat) { export const duration = register('duration', function (value, pat) {
@@ -2230,61 +2219,6 @@ const _loopAt = function (factor, pat, cps = 1) {
.slow(factor); .slow(factor);
}; };
/*
* Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
* @name slice
* @memberof Pattern
* @returns Pattern
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
*/
const slice = register(
'slice',
function (npat, ipat, opat) {
return npat.innerBind((n) =>
ipat.outerBind((i) =>
opat.outerBind((o) => {
// If it's not an object, assume it's a string and make it a 's' control parameter
o = o instanceof Object ? o : { s: o };
// Remember we must stay pure and avoid editing the object directly
const toAdd = { begin: i / n, end: (i + 1) / n, _slices: n };
return pure({ ...toAdd, ...o });
}),
),
);
},
false, // turns off auto-patternification
);
/*
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
* @name splice
* @memberof Pattern
* @returns Pattern
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65)
*/
const splice = register(
'splice',
function (npat, ipat, opat) {
const sliced = slice(npat, ipat, opat);
return sliced.withHap(function (hap) {
return hap.withValue((v) => ({
...{
speed: (1 / v._slices / hap.whole.duration) * (v.speed || 1),
unit: 'c',
},
...v,
}));
});
},
false, // turns off auto-patternification
);
const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) { const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
return _loopAt(factor, pat, 1); return _loopAt(factor, pat, 1);
}); });
+4 -20
View File
@@ -2,7 +2,6 @@ import { Cyclist } from './cyclist.mjs';
import { evaluate as _evaluate } from './evaluate.mjs'; import { evaluate as _evaluate } from './evaluate.mjs';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
import { setTime } from './time.mjs'; import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs';
export function repl({ export function repl({
interval, interval,
@@ -18,12 +17,13 @@ export function repl({
}) { }) {
const scheduler = new Cyclist({ const scheduler = new Cyclist({
interval, interval,
onTrigger: async (hap, deadline, duration, cps) => { onTrigger: async (hap, deadline, duration) => {
try { try {
if (!hap.context.onTrigger || !hap.context.dominantTrigger) { if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
await defaultOutput(hap, deadline, duration, cps); await defaultOutput(hap, deadline, duration);
} }
if (hap.context.onTrigger) { if (hap.context.onTrigger) {
const cps = 1;
// call signature of output / onTrigger is different... // call signature of output / onTrigger is different...
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps); await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
} }
@@ -42,17 +42,6 @@ export function repl({
} }
try { try {
beforeEval?.({ code }); beforeEval?.({ code });
scheduler.setCps(1); // reset cps in case the code does not contain a setCps call
// problem: when the code does contain a setCps after an awaited promise,
// the cps will be 1 until the promise resolves
// example:
/*
await new Promise(resolve => setTimeout(resolve,1000))
setCps(.5)
note("c a f e")
*/
// to make sure the setCps inside the code is called immediately,
// it has to be placed first
let { pattern } = await _evaluate(code, transpiler); let { pattern } = await _evaluate(code, transpiler);
logger(`[eval] code updated`); logger(`[eval] code updated`);
@@ -69,10 +58,5 @@ export function repl({
const stop = () => scheduler.stop(); const stop = () => scheduler.stop();
const start = () => scheduler.start(); const start = () => scheduler.start();
const pause = () => scheduler.pause(); const pause = () => scheduler.pause();
const setCps = (cps) => scheduler.setCps(cps); return { scheduler, evaluate, start, stop, pause };
evalScope({
setCps,
setcps: setCps,
});
return { scheduler, evaluate, start, stop, pause, setCps };
} }
-8
View File
@@ -114,14 +114,6 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
* *
*/ */
/**
* A discrete pattern of numbers from 0 to n-1
* @example
* run(4).scale('C4 major').note()
* // "0 1 2 3".scale('C4 major').note()
*/
export const run = (n) => saw.range(0, n).floor().segment(n);
/** /**
* A continuous pattern of random numbers, between 0 and 1. * A continuous pattern of random numbers, between 0 and 1.
* *
-67
View File
@@ -44,7 +44,6 @@ import {
ply, ply,
rev, rev,
time, time,
run,
} from '../index.mjs'; } from '../index.mjs';
import { steady } from '../signal.mjs'; import { steady } from '../signal.mjs';
@@ -909,18 +908,6 @@ describe('Pattern', () => {
); );
}); });
}); });
describe('run', () => {
it('Can run', () => {
expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle());
});
});
describe('ribbon', () => {
it('Can ribbon', () => {
expect(cat(0, 1, 2, 3, 4, 5, 6, 7).ribbon(2, 4).fast(4).firstCycle()).toStrictEqual(
sequence(2, 3, 4, 5).firstCycle(),
);
});
});
describe('linger', () => { describe('linger', () => {
it('Can linger on the first quarter of a cycle', () => { it('Can linger on the first quarter of a cycle', () => {
expect(sequence(0, 1, 2, 3, 4, 5, 6, 7).linger(0.25).firstCycle()).toStrictEqual( expect(sequence(0, 1, 2, 3, 4, 5, 6, 7).linger(0.25).firstCycle()).toStrictEqual(
@@ -949,58 +936,4 @@ describe('Pattern', () => {
expect(stack(sequence('a', silence), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2); expect(stack(sequence('a', silence), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
}); });
}); });
describe('press', () => {
it('Can syncopate events', () => {
sameFirst(sequence('a', 'b', 'c', 'd').press(), sequence(silence, 'a', silence, 'b', silence, 'c', silence, 'd'));
});
});
describe('hurry', () => {
it('Can speed up patterns and sounds', () => {
sameFirst(s('a', 'b').hurry(2), s('a', 'b').fast(2).speed(2));
});
});
/*describe('composable functions', () => {
it('Can compose functions', () => {
sameFirst(sequence(3, 4).fast(2).rev().fast(2), fast(2).rev().fast(2)(sequence(3, 4)));
});
it('Can compose by method chaining operators with controls', () => {
sameFirst(s('bd').apply(set.n(3).fast(2)), s('bd').set.n(3).fast(2));
});
it('Can compose by method chaining operators and alignments with controls', () => {
sameFirst(s('bd').apply(set.in.n(3).fast(2)), s('bd').set.n(3).fast(2));
// sameFirst(s('bd').apply(set.squeeze.n(3).fast(2)), s('bd').set.squeeze.n(3).fast(2));
});
});
describe('weave', () => {
it('Can distribute patterns along a pattern', () => {
sameFirst(n(0, 1).weave(2, s('bd', silence), s(silence, 'sd')), sequence(s('bd').n(0), s('sd').n(1)));
});
});
*/
describe('slice', () => {
it('Can slice a sample', () => {
sameFirst(
s('break').slice(4, sequence(0, 1, 2, 3)),
sequence(
{ begin: 0, end: 0.25, s: 'break', _slices: 4 },
{ begin: 0.25, end: 0.5, s: 'break', _slices: 4 },
{ begin: 0.5, end: 0.75, s: 'break', _slices: 4 },
{ begin: 0.75, end: 1, s: 'break', _slices: 4 },
),
);
});
});
describe('splice', () => {
it('Can splice a sample', () => {
sameFirst(
s('break').splice(4, sequence(0, 1, 2, 3)),
sequence(
{ begin: 0, end: 0.25, s: 'break', _slices: 4, unit: 'c', speed: 1 },
{ begin: 0.25, end: 0.5, s: 'break', _slices: 4, unit: 'c', speed: 1 },
{ begin: 0.5, end: 0.75, s: 'break', _slices: 4, unit: 'c', speed: 1 },
{ begin: 0.75, end: 1, s: 'break', _slices: 4, unit: 'c', speed: 1 },
),
);
});
});
}); });
+1 -1
View File
@@ -26,7 +26,7 @@ export const backgroundImage = function (src, animateOptions = {}) {
({ ({
style: () => (container.style = bg + ';' + value), style: () => (container.style = bg + ';' + value),
className: () => (container.className = value + ' ' + initialClassName), className: () => (container.className = value + ' ' + initialClassName),
})[option](); }[option]());
}; };
const funcOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'function'); const funcOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'function');
const stringOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'string'); const stringOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'string');
+21 -8
View File
@@ -4,6 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Pattern } from './pattern.mjs';
// returns true if the given string is a note // returns true if the given string is a note
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name); export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bs]*[0-9]?$/.test(name); export const isNote = (name) => /^[a-gA-G][#bs]*[0-9]?$/.test(name);
@@ -58,7 +60,6 @@ export const valueToMidi = (value, fallbackValue) => {
/** /**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase * @deprecated does not appear to be referenced or invoked anywhere in the codebase
* @noAutocomplete
*/ */
export const getFreq = (noteOrMidi) => { export const getFreq = (noteOrMidi) => {
if (typeof noteOrMidi === 'number') { if (typeof noteOrMidi === 'number') {
@@ -69,7 +70,6 @@ export const getFreq = (noteOrMidi) => {
/** /**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase * @deprecated does not appear to be referenced or invoked anywhere in the codebase
* @noAutocomplete
*/ */
export const midi2note = (n) => { export const midi2note = (n) => {
const oct = Math.floor(n / 12) - 1; const oct = Math.floor(n / 12) - 1;
@@ -151,6 +151,25 @@ export function curry(func, overload, arity = func.length) {
const partial = function (...args2) { const partial = function (...args2) {
return curried.apply(this, args.concat(args2)); return curried.apply(this, args.concat(args2));
}; };
if (args.length == arity - 1) {
// The penultimate arg.. so add some composition magic
// TODO - To make this useful, we also need to add stub functions for every pattern method to
// do the actual composing
for (const r of Pattern.__registered) {
partial[r] = function (...args) {
const result = new Pattern(() => []);
result.__compose = function (pat) {
return partial(pat)[r](...args);
};
return result;
};
}
partial.__compose = function (pat) {
return partial(pat);
};
}
if (overload) { if (overload) {
overload(partial, args); overload(partial, args);
} }
@@ -206,9 +225,3 @@ export function parseFractional(numOrString) {
} }
export const fractionalArgs = (fn) => mapArgs(fn, parseFractional); export const fractionalArgs = (fn) => mapArgs(fn, parseFractional);
export const splitAt = function (index, value) {
return [value.slice(0, index), value.slice(index)];
};
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
-19
View File
@@ -1,19 +0,0 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
+1 -1
View File
@@ -44,6 +44,6 @@ function createClock(
}; };
const getPhase = () => phase; const getPhase = () => phase;
// setCallback // setCallback
return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency }; return { setDuration, start, stop, pause, duration, getPhase, minLatency };
} }
export default createClock; export default createClock;
@@ -28,7 +28,9 @@ export const csound = register('csound', (instrument, pat) => {
logger('[csound] not loaded yet', 'warning'); logger('[csound] not loaded yet', 'warning');
return; return;
} }
hap.ensureObjectValue(); if (typeof hap.value !== 'object') {
throw new Error('csound only support objects as hap values');
}
let { gain = 0.8 } = hap.value; let { gain = 0.8 } = hap.value;
gain *= 0.2; gain *= 0.2;
@@ -90,7 +92,6 @@ async function load() {
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args))); ['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('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
await _csound.setOption('--sample-accurate'); await _csound.setOption('--sample-accurate');
await _csound.setOption('-odac');
await _csound.compileCsdText(csd); await _csound.compileCsdText(csd);
// await _csound.compileOrc(livecodeOrc); // await _csound.compileOrc(livecodeOrc);
await _csound.compileOrc(presetsOrc); await _csound.compileOrc(presetsOrc);
+4 -14
View File
@@ -1,15 +1,10 @@
{ {
"name": "@strudel.cycles/csound", "name": "@strudel.cycles/csound",
"version": "0.6.2", "version": "0.5.1",
"description": "csound bindings for strudel", "description": "csound bindings for strudel",
"main": "index.mjs", "main": "csound.mjs",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": { "scripts": {
"build": "vite build", "test": "echo \"No tests present.\" && exit 0"
"prepublishOnly": "npm run build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -32,11 +27,6 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@csound/browser": "6.18.5", "@csound/browser": "^6.18.3"
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^3.2.2"
} }
} }
-19
View File
@@ -1,19 +0,0 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
-4
View File
@@ -3,10 +3,6 @@
This package contains the strudel code transformer and evaluator. This package contains the strudel code transformer and evaluator.
It allows creating strudel patterns from input code that is optimized for minimal keystrokes and human readability. It allows creating strudel patterns from input code that is optimized for minimal keystrokes and human readability.
## Deprecation Note
This package will not be developed further. Consider using `@strudel.cycles/transpiler` as a replacement.
## Install ## Install
```sh ```sh
+297
View File
@@ -0,0 +1,297 @@
{
"name": "@strudel.cycles/eval",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@strudel.cycles/eval",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"estraverse": "^5.3.0",
"shift-ast": "^6.1.0",
"shift-codegen": "^7.0.3",
"shift-parser": "^7.0.3",
"shift-spec": "^2018.0.2",
"shift-traverser": "^1.0.0"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/multimap": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/multimap/-/multimap-1.1.0.tgz",
"integrity": "sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw=="
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/shift-ast": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.1.0.tgz",
"integrity": "sha512-Vj4XUIJIFPIh6VcBGJ1hjH/kM88XGer94Pr7Rvxa+idEylDsrwtLw268HoxGo5xReL6T3DdRl/9/Pr1XihZ/8Q=="
},
"node_modules/shift-codegen": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/shift-codegen/-/shift-codegen-7.0.3.tgz",
"integrity": "sha512-dfCVVdBF0qZ6pkajQ3bjxRdNEltyxEITVe7tBJkQt2eCI3znUkSxq0VSe/tTWq1LKHeAS4HuOiqYEuHMFkSq9w==",
"dependencies": {
"esutils": "^2.0.2",
"object-assign": "^4.1.0",
"shift-reducer": "6.0.0"
}
},
"node_modules/shift-parser": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/shift-parser/-/shift-parser-7.0.3.tgz",
"integrity": "sha512-uYX2ORyZfKZrUc4iKKkO9KOhzUSxCrSBk7QK6ZmShId+BOo1gh1IwecVy97ynyOTpmhPWUttjC8BzsnQl65Zew==",
"dependencies": {
"multimap": "^1.0.2",
"shift-ast": "6.0.0",
"shift-reducer": "6.0.0",
"shift-regexp-acceptor": "2.0.3"
}
},
"node_modules/shift-parser/node_modules/shift-ast": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.0.0.tgz",
"integrity": "sha512-XXxDcEBWVBzqWXfNYJlLyJ1/9kMvOXVRXiqPjkOrTCC5qRsBvEMJMRLLFhU3tn8ue56Y7IZyBE6bexFum5QLUw=="
},
"node_modules/shift-reducer": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/shift-reducer/-/shift-reducer-6.0.0.tgz",
"integrity": "sha512-2rJraRP8drIOjvaE/sALa+0tGJmMVUzlmS3wIJerJbaYuCjpFAiF0WjkTOFVtz1144Nm/ECmqeG+7yRhuMVsMg==",
"dependencies": {
"shift-ast": "6.0.0"
}
},
"node_modules/shift-reducer/node_modules/shift-ast": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.0.0.tgz",
"integrity": "sha512-XXxDcEBWVBzqWXfNYJlLyJ1/9kMvOXVRXiqPjkOrTCC5qRsBvEMJMRLLFhU3tn8ue56Y7IZyBE6bexFum5QLUw=="
},
"node_modules/shift-regexp-acceptor": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/shift-regexp-acceptor/-/shift-regexp-acceptor-2.0.3.tgz",
"integrity": "sha512-sxL7e5JNUFxm+gutFRXktX2D6KVgDAHNuDsk5XHB9Z+N5yXooZG6pdZ1GEbo3Jz6lF7ETYLBC4WAjIFm2RKTmA==",
"dependencies": {
"unicode-match-property-ecmascript": "1.0.4",
"unicode-match-property-value-ecmascript": "1.0.2",
"unicode-property-aliases-ecmascript": "1.0.4"
}
},
"node_modules/shift-spec": {
"version": "2018.0.2",
"resolved": "https://registry.npmjs.org/shift-spec/-/shift-spec-2018.0.2.tgz",
"integrity": "sha512-5CP/cKDEim4rZ6ViCSipTLY2U7HJr8q/kpDuCBmebFqbx/0DeozWO+9ienHmYjgGLDfHrqj+LBAN67FRK2vE6w=="
},
"node_modules/shift-traverser": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shift-traverser/-/shift-traverser-1.0.0.tgz",
"integrity": "sha512-DMY3512wJbdC+IC+nhLH3/Stgr2BbxbNcg7qyZ6+e5qNnNs8TBQJWdMsRgHlX1JXwF4C0ONKS8VUxsPT0Tf7aw==",
"dependencies": {
"estraverse": "4.2.0",
"shift-spec": "2018.0.0"
}
},
"node_modules/shift-traverser/node_modules/estraverse": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/shift-traverser/node_modules/shift-spec": {
"version": "2018.0.0",
"resolved": "https://registry.npmjs.org/shift-spec/-/shift-spec-2018.0.0.tgz",
"integrity": "sha512-/aiPOkj7dbe+CV2VZhIMTHQToZmgniofpRG7Yr7x2/0sO6CSVC++py1Wzf+s+rWSTDHKcLvziVAxjRRV4i4EoQ=="
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
"integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-match-property-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
"integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
"dependencies": {
"unicode-canonical-property-names-ecmascript": "^1.0.4",
"unicode-property-aliases-ecmascript": "^1.0.4"
},
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-match-property-value-ecmascript": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz",
"integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==",
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-property-aliases-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz",
"integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==",
"engines": {
"node": ">=4"
}
}
},
"dependencies": {
"estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"multimap": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/multimap/-/multimap-1.1.0.tgz",
"integrity": "sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"shift-ast": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.1.0.tgz",
"integrity": "sha512-Vj4XUIJIFPIh6VcBGJ1hjH/kM88XGer94Pr7Rvxa+idEylDsrwtLw268HoxGo5xReL6T3DdRl/9/Pr1XihZ/8Q=="
},
"shift-codegen": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/shift-codegen/-/shift-codegen-7.0.3.tgz",
"integrity": "sha512-dfCVVdBF0qZ6pkajQ3bjxRdNEltyxEITVe7tBJkQt2eCI3znUkSxq0VSe/tTWq1LKHeAS4HuOiqYEuHMFkSq9w==",
"requires": {
"esutils": "^2.0.2",
"object-assign": "^4.1.0",
"shift-reducer": "6.0.0"
}
},
"shift-parser": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/shift-parser/-/shift-parser-7.0.3.tgz",
"integrity": "sha512-uYX2ORyZfKZrUc4iKKkO9KOhzUSxCrSBk7QK6ZmShId+BOo1gh1IwecVy97ynyOTpmhPWUttjC8BzsnQl65Zew==",
"requires": {
"multimap": "^1.0.2",
"shift-ast": "6.0.0",
"shift-reducer": "6.0.0",
"shift-regexp-acceptor": "2.0.3"
},
"dependencies": {
"shift-ast": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.0.0.tgz",
"integrity": "sha512-XXxDcEBWVBzqWXfNYJlLyJ1/9kMvOXVRXiqPjkOrTCC5qRsBvEMJMRLLFhU3tn8ue56Y7IZyBE6bexFum5QLUw=="
}
}
},
"shift-reducer": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/shift-reducer/-/shift-reducer-6.0.0.tgz",
"integrity": "sha512-2rJraRP8drIOjvaE/sALa+0tGJmMVUzlmS3wIJerJbaYuCjpFAiF0WjkTOFVtz1144Nm/ECmqeG+7yRhuMVsMg==",
"requires": {
"shift-ast": "6.0.0"
},
"dependencies": {
"shift-ast": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.0.0.tgz",
"integrity": "sha512-XXxDcEBWVBzqWXfNYJlLyJ1/9kMvOXVRXiqPjkOrTCC5qRsBvEMJMRLLFhU3tn8ue56Y7IZyBE6bexFum5QLUw=="
}
}
},
"shift-regexp-acceptor": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/shift-regexp-acceptor/-/shift-regexp-acceptor-2.0.3.tgz",
"integrity": "sha512-sxL7e5JNUFxm+gutFRXktX2D6KVgDAHNuDsk5XHB9Z+N5yXooZG6pdZ1GEbo3Jz6lF7ETYLBC4WAjIFm2RKTmA==",
"requires": {
"unicode-match-property-ecmascript": "1.0.4",
"unicode-match-property-value-ecmascript": "1.0.2",
"unicode-property-aliases-ecmascript": "1.0.4"
}
},
"shift-spec": {
"version": "2018.0.2",
"resolved": "https://registry.npmjs.org/shift-spec/-/shift-spec-2018.0.2.tgz",
"integrity": "sha512-5CP/cKDEim4rZ6ViCSipTLY2U7HJr8q/kpDuCBmebFqbx/0DeozWO+9ienHmYjgGLDfHrqj+LBAN67FRK2vE6w=="
},
"shift-traverser": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shift-traverser/-/shift-traverser-1.0.0.tgz",
"integrity": "sha512-DMY3512wJbdC+IC+nhLH3/Stgr2BbxbNcg7qyZ6+e5qNnNs8TBQJWdMsRgHlX1JXwF4C0ONKS8VUxsPT0Tf7aw==",
"requires": {
"estraverse": "4.2.0",
"shift-spec": "2018.0.0"
},
"dependencies": {
"estraverse": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
},
"shift-spec": {
"version": "2018.0.0",
"resolved": "https://registry.npmjs.org/shift-spec/-/shift-spec-2018.0.0.tgz",
"integrity": "sha512-/aiPOkj7dbe+CV2VZhIMTHQToZmgniofpRG7Yr7x2/0sO6CSVC++py1Wzf+s+rWSTDHKcLvziVAxjRRV4i4EoQ=="
}
}
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
"integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ=="
},
"unicode-match-property-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
"integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
"requires": {
"unicode-canonical-property-names-ecmascript": "^1.0.4",
"unicode-property-aliases-ecmascript": "^1.0.4"
}
},
"unicode-match-property-value-ecmascript": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz",
"integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ=="
},
"unicode-property-aliases-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz",
"integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg=="
}
}
}
+5 -16
View File
@@ -1,21 +1,15 @@
{ {
"name": "@strudel.cycles/eval", "name": "@strudel.cycles/eval",
"version": "0.6.2", "version": "0.5.0",
"description": "Code evaluator for strudel", "description": "Code evaluator for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
"test": "vitest run",
"prepublishOnly": "npm run build"
},
"type": "module", "type": "module",
"directories": { "directories": {
"test": "test" "test": "test"
}, },
"scripts": {
"test": "vitest run"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git" "url": "git+https://github.com/tidalcycles/strudel.git"
@@ -34,17 +28,12 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel.cycles/core": "^0.5.0",
"estraverse": "^5.3.0", "estraverse": "^5.3.0",
"shift-ast": "^7.0.0", "shift-ast": "^7.0.0",
"shift-codegen": "^8.1.0", "shift-codegen": "^8.1.0",
"shift-parser": "^8.0.0", "shift-parser": "^8.0.0",
"shift-spec": "^2019.0.0", "shift-spec": "^2019.0.0",
"shift-traverser": "^1.0.0" "shift-traverser": "^1.0.0"
},
"devDependencies": {
"@strudel.cycles/mini": "workspace:*",
"vite": "^3.2.2",
"vitest": "^0.25.7"
} }
} }
-19
View File
@@ -1,19 +0,0 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
+4
View File
@@ -7,3 +7,7 @@ This package adds midi functionality to strudel Patterns.
```sh ```sh
npm i @strudel.cycles/midi --save npm i @strudel.cycles/midi --save
``` ```
## Dev Notes
- is this package really necessary? currently, /tone also depends on webmidi through @tonejs/piano. Either move piano out of /tone or merge /midi into /tone...
+15 -30
View File
@@ -5,9 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as _WebMidi from 'webmidi'; import * as _WebMidi from 'webmidi';
import { Pattern, isPattern, logger } from '@strudel.cycles/core'; import { Pattern, isPattern, isNote, getPlayableNoteValue, logger } from '@strudel.cycles/core';
import { getAudioContext } from '@strudel.cycles/webaudio'; import { getAudioContext } from '@strudel.cycles/webaudio';
import { toMidi } from '@strudel.cycles/core';
// if you use WebMidi from outside of this package, make sure to import that instance: // if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi; export const { WebMidi } = _WebMidi;
@@ -64,7 +63,7 @@ function getDevice(output, outputs) {
} }
// Pattern.prototype.midi = function (output: string | number, channel = 1) { // Pattern.prototype.midi = function (output: string | number, channel = 1) {
Pattern.prototype.midi = function (output) { Pattern.prototype.midi = function (output, channel = 1) {
if (!supportsMidi()) { if (!supportsMidi()) {
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`); throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
} }
@@ -91,6 +90,11 @@ Pattern.prototype.midi = function (output) {
); );
} }
return this.onTrigger((time, hap) => { return this.onTrigger((time, hap) => {
let note = getPlayableNoteValue(hap);
const velocity = hap.context?.velocity ?? 0.9;
if (!isNote(note)) {
throw new Error('not a note: ' + note);
}
if (!midiReady) { if (!midiReady) {
return; return;
} }
@@ -102,34 +106,15 @@ Pattern.prototype.midi = function (output) {
.join(' | ')}`, .join(' | ')}`,
); );
} }
hap.ensureObjectValue(); // console.log('midi', value, output);
// calculate time
const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000; const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000;
time = time * 1000 + timingOffset; time = time * 1000 + timingOffset;
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000;
// destructure value // await enableWebMidi()
const { note, nrpnn, nrpv, ccn, ccv, midichan = 1 } = hap.value; device.playNote(note, channel, {
const velocity = hap.context?.velocity ?? 0.9; // TODO: refactor velocity time,
const duration = hap.duration.valueOf() * 1000 - 5; duration: hap.duration.valueOf() * 1000 - 5,
attack: velocity,
if (note) { });
const midiNumber = toMidi(note);
device.playNote(midiNumber, midichan, {
time,
duration,
attack: velocity,
});
}
if (ccv && ccn) {
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
throw new Error('expected ccv to be a number between 0 and 1');
}
if (!['string', 'number'].includes(typeof ccn)) {
throw new Error('expected ccn to be a number or a string');
}
const scaled = Math.round(ccv * 127);
device.sendControlChange(ccn, scaled, midichan, { time });
}
}); });
}; };
+130
View File
@@ -0,0 +1,130 @@
{
"name": "@strudel.cycles/midi",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@strudel.cycles/midi",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"tone": "^14.7.77",
"webmidi": "^2.5.2"
}
},
"node_modules/@babel/runtime": {
"version": "7.17.8",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.8.tgz",
"integrity": "sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/automation-events": {
"version": "4.0.14",
"resolved": "https://registry.npmjs.org/automation-events/-/automation-events-4.0.14.tgz",
"integrity": "sha512-CB2Me0yW8sz7gSGwMiSfgfs1Oqlgs53k+eVESN6axvRyMAD3zlSp2nqndD2TQAtW3yOtSEJWNGsw0r48+f1wtw==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"tslib": "^2.3.1"
},
"engines": {
"node": ">=12.20.1"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"node_modules/standardized-audio-context": {
"version": "25.3.21",
"resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.21.tgz",
"integrity": "sha512-CZEnayJJjNefeU+z1QGDhaid1LAAYxWYa2ipNk75ropwec9rq6fmclyhXb1wGtDsZ402irX3HLt1U/PwP9+1fA==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"automation-events": "^4.0.14",
"tslib": "^2.3.1"
}
},
"node_modules/tone": {
"version": "14.7.77",
"resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz",
"integrity": "sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==",
"dependencies": {
"standardized-audio-context": "^25.1.8",
"tslib": "^2.0.1"
}
},
"node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"node_modules/webmidi": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/webmidi/-/webmidi-2.5.3.tgz",
"integrity": "sha512-PyMGvKcDGpvbQUfnmBORQJciyG3VAZ4aHlGy1iRZ3uEs4kG4HCvI7KRthUpM1vuHDPL98lidRIUaoRomkJtWtg==",
"engines": {
"node": ">0.6.x"
}
}
},
"dependencies": {
"@babel/runtime": {
"version": "7.17.8",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.8.tgz",
"integrity": "sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"automation-events": {
"version": "4.0.14",
"resolved": "https://registry.npmjs.org/automation-events/-/automation-events-4.0.14.tgz",
"integrity": "sha512-CB2Me0yW8sz7gSGwMiSfgfs1Oqlgs53k+eVESN6axvRyMAD3zlSp2nqndD2TQAtW3yOtSEJWNGsw0r48+f1wtw==",
"requires": {
"@babel/runtime": "^7.17.2",
"tslib": "^2.3.1"
}
},
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"standardized-audio-context": {
"version": "25.3.21",
"resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.21.tgz",
"integrity": "sha512-CZEnayJJjNefeU+z1QGDhaid1LAAYxWYa2ipNk75ropwec9rq6fmclyhXb1wGtDsZ402irX3HLt1U/PwP9+1fA==",
"requires": {
"@babel/runtime": "^7.17.2",
"automation-events": "^4.0.14",
"tslib": "^2.3.1"
}
},
"tone": {
"version": "14.7.77",
"resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz",
"integrity": "sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==",
"requires": {
"standardized-audio-context": "^25.1.8",
"tslib": "^2.0.1"
}
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"webmidi": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/webmidi/-/webmidi-2.5.3.tgz",
"integrity": "sha512-PyMGvKcDGpvbQUfnmBORQJciyG3VAZ4aHlGy1iRZ3uEs4kG4HCvI7KRthUpM1vuHDPL98lidRIUaoRomkJtWtg=="
}
}
}
+3 -14
View File
@@ -1,16 +1,8 @@
{ {
"name": "@strudel.cycles/midi", "name": "@strudel.cycles/midi",
"version": "0.6.0", "version": "0.5.0",
"description": "Midi API for strudel", "description": "Midi API for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
"prepublishOnly": "npm run build"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git" "url": "git+https://github.com/tidalcycles/strudel.git"
@@ -29,11 +21,8 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel.cycles/tone": "^0.5.0",
"@strudel.cycles/webaudio": "workspace:*", "tone": "^14.7.77",
"webmidi": "^3.0.21" "webmidi": "^3.0.21"
},
"devDependencies": {
"vite": "^3.2.2"
} }
} }
-19
View File
@@ -1,19 +0,0 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
+1 -1
View File
@@ -40,5 +40,5 @@ The parser [krill-parser.js] is generated from [krill.pegjs](./krill.pegjs) usin
To generate the parser, run To generate the parser, run
```js ```js
npm build:parser npm run build:parser
``` ```
+1031
View File
File diff suppressed because it is too large Load Diff
+6 -12
View File
@@ -1,18 +1,12 @@
{ {
"name": "@strudel.cycles/mini", "name": "@strudel.cycles/mini",
"version": "0.6.0", "version": "0.5.0",
"description": "Mini notation for strudel", "description": "Mini notation for strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run",
"build:parser": "peggy -o krill-parser.js --format es ./krill.pegjs", "build:parser": "peggy -o krill-parser.js --format es ./krill.pegjs"
"build": "vite build",
"prepublishOnly": "npm run build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -32,11 +26,11 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*" "@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/eval": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0"
}, },
"devDependencies": { "devDependencies": {
"peggy": "^2.0.1", "peggy": "^2.0.1"
"vite": "^3.2.2",
"vitest": "^0.25.7"
} }
} }
-19
View File
@@ -1,19 +0,0 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
+2 -2
View File
@@ -34,6 +34,6 @@ Now open the REPL and type:
s("<bd sd> hh").osc() s("<bd sd> hh").osc()
``` ```
or just [click here](https://strudel.tidalcycles.org/#cygiPGJkIHNkPiBoaCIpLm9zYygp)... or just [click here](http://localhost:3000/#cygiPGJkIHNkPiBoaCIpLm9zYygp)...
You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.tidalcycles.org/learn/input-output/#superdirt-api) You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.tidalcycles.org/learn/outputs#superdirt-api)
+3 -5
View File
@@ -39,16 +39,14 @@ let startedAt = -1;
/** /**
* *
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software. * Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
* For more info, read [MIDI & OSC in the docs](https://strudel.tidalcycles.org/learn/input-output)
* *
* @name osc * @name osc
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
*/ */
Pattern.prototype.osc = function () { Pattern.prototype.osc = async function () {
return this.onTrigger(async (time, hap, currentTime, cps = 1) => { const osc = await connect();
hap.ensureObjectValue(); return this.onTrigger((time, hap, currentTime, cps = 1) => {
const osc = await connect();
const cycle = hap.wholeOrPart().begin.valueOf(); const cycle = hap.wholeOrPart().begin.valueOf();
const delta = hap.duration.valueOf(); const delta = hap.duration.valueOf();
// time should be audio time of onset // time should be audio time of onset
+60
View File
@@ -0,0 +1,60 @@
{
"name": "@strudel.cycles/osc",
"version": "0.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@strudel.cycles/osc",
"version": "0.1.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"osc-js": "^2.3.2"
}
},
"node_modules/osc-js": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/osc-js/-/osc-js-2.3.2.tgz",
"integrity": "sha512-9i7J4u1hH+glooGMh+ki1ni0JGqKmylT8r0nXKugHbRK63rR+kl4O+5tGW6+/EszjbCju3KV+eXQQzFDdGrmhg==",
"dependencies": {
"ws": "^8.5.0"
}
},
"node_modules/ws": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
},
"dependencies": {
"osc-js": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/osc-js/-/osc-js-2.3.2.tgz",
"integrity": "sha512-9i7J4u1hH+glooGMh+ki1ni0JGqKmylT8r0nXKugHbRK63rR+kl4O+5tGW6+/EszjbCju3KV+eXQQzFDdGrmhg==",
"requires": {
"ws": "^8.5.0"
}
},
"ws": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
"requires": {}
}
}
}
+4 -11
View File
@@ -1,19 +1,14 @@
{ {
"name": "@strudel.cycles/osc", "name": "@strudel.cycles/osc",
"version": "0.6.0", "version": "0.4.0",
"description": "OSC messaging for strudel", "description": "OSC messaging for strudel",
"main": "osc.mjs", "main": "osc.mjs",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": { "scripts": {
"test": "echo \"No tests present.\" && exit 0",
"server": "node server.js", "server": "node server.js",
"tidal-sniffer": "node tidal-sniffer.js", "tidal-sniffer": "node tidal-sniffer.js",
"client": "npx serve -p 4321", "client": "npx serve -p 4321",
"build-bin": "npx pkg server.js --targets node16-macos-x64,node16-win-x64,node16-linux-x64 --out-path bin", "build": "npx pkg server.js --targets node16-macos-x64,node16-win-x64,node16-linux-x64 --out-path bin"
"build": "vite build",
"prepublishOnly": "npm run build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -36,11 +31,9 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*",
"osc-js": "^2.4.0" "osc-js": "^2.4.0"
}, },
"devDependencies": { "devDependencies": {
"pkg": "^5.7.0", "pkg": "^5.7.0"
"vite": "^3.2.2"
} }
} }
-19
View File
@@ -1,19 +0,0 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: resolve(__dirname, 'osc.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
+2
View File
@@ -11,6 +11,8 @@ node_modules
dist-ssr dist-ssr
*.local *.local
!dist
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
+21 -33
View File
@@ -13,43 +13,31 @@ npm i @strudel.cycles/react
Here is a minimal example of how to set up a MiniRepl: Here is a minimal example of how to set up a MiniRepl:
```jsx ```jsx
import * as React from 'react';
import '@strudel.cycles/react/dist/style.css';
import { MiniRepl } from '@strudel.cycles/react';
import { evalScope, controls } from '@strudel.cycles/core'; import { evalScope, controls } from '@strudel.cycles/core';
import { samples, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { MiniRepl } from '@strudel.cycles/react';
import { prebake } from '../repl/src/prebake.mjs';
async function prebake() { evalScope(
await samples( controls,
'https://strudel.tidalcycles.org/tidal-drum-machines.json', import('@strudel.cycles/core'),
'github:ritchse/tidal-drum-machines/main/machines/' import('@strudel.cycles/tonal'),
); import('@strudel.cycles/mini'),
await samples( import('@strudel.cycles/webaudio'),
'https://strudel.tidalcycles.org/EmuSP12.json', /* probably import other strudel packages */
'https://strudel.tidalcycles.org/EmuSP12/' );
);
}
async function init() { prebake();
await evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/tonal')
);
await prebake();
initAudioOnFirstClick();
}
if (typeof window !== 'undefined') { export function Repl({ tune }) {
init(); return <MiniRepl tune={tune} hideOutsideView={true} />;
}
export default function App() {
return <MiniRepl tune={`s("bd sd,hh*4")`} />;
} }
``` ```
- Open [example on stackblitz](https://stackblitz.com/edit/react-ts-saaair?file=tune.tsx,App.tsx) ## Development
- Also check out the [nano-repl](./examples/nano-repl/) for a more sophisticated example
If you change something in here and want to see the changes in the repl, make sure to run `npm run build` inside this folder!
```js
npm run dev # dev server
npm run build # build package
```
File diff suppressed because one or more lines are too long
+442
View File
@@ -0,0 +1,442 @@
import d, { useCallback as E, useRef as A, useEffect as k, useMemo as Q, useState as _, useLayoutEffect as te } from "react";
import ue from "@uiw/react-codemirror";
import { Decoration as M, EditorView as re } from "@codemirror/view";
import { StateEffect as ne, StateField as oe } from "@codemirror/state";
import { javascript as de } from "@codemirror/lang-javascript";
import { tags as u } from "@lezer/highlight";
import { createTheme as fe } from "@uiw/codemirror-themes";
import { webaudioOutput as me, getAudioContext as he } from "@strudel.cycles/webaudio";
import { useInView as ge } from "react-hook-inview";
import { repl as pe, logger as ve } from "@strudel.cycles/core";
import { transpiler as be } from "@strudel.cycles/transpiler";
const Ee = fe({
theme: "dark",
settings: {
background: "#222",
foreground: "#75baff",
caret: "#ffcc00",
selection: "rgba(128, 203, 196, 0.5)",
selectionMatch: "#036dd626",
lineHighlight: "#00000050",
gutterBackground: "transparent",
gutterForeground: "#8a919966"
},
styles: [
{ tag: u.keyword, color: "#c792ea" },
{ tag: u.operator, color: "#89ddff" },
{ tag: u.special(u.variableName), color: "#eeffff" },
{ tag: u.typeName, color: "#c3e88d" },
{ tag: u.atom, color: "#f78c6c" },
{ tag: u.number, color: "#c3e88d" },
{ tag: u.definition(u.variableName), color: "#82aaff" },
{ tag: u.string, color: "#c3e88d" },
{ tag: u.special(u.string), color: "#c3e88d" },
{ tag: u.comment, color: "#7d8799" },
{ tag: u.variableName, color: "#c792ea" },
{ tag: u.tagName, color: "#c3e88d" },
{ tag: u.bracket, color: "#525154" },
{ tag: u.meta, color: "#ffcb6b" },
{ tag: u.attributeName, color: "#c792ea" },
{ tag: u.propertyName, color: "#c792ea" },
{ tag: u.className, color: "#decb6b" },
{ tag: u.invalid, color: "#ffffff" }
]
});
const X = ne.define(), ye = oe.define({
create() {
return M.none;
},
update(e, t) {
try {
for (let r of t.effects)
if (r.is(X))
if (r.value) {
const n = M.mark({ attributes: { style: "background-color: #FFCA2880" } });
e = M.set([n.range(0, t.newDoc.length)]);
} else
e = M.set([]);
return e;
} catch (r) {
return console.warn("flash error", r), e;
}
},
provide: (e) => re.decorations.from(e)
}), we = (e) => {
e.dispatch({ effects: X.of(!0) }), setTimeout(() => {
e.dispatch({ effects: X.of(!1) });
}, 200);
}, B = ne.define(), ke = oe.define({
create() {
return M.none;
},
update(e, t) {
try {
for (let r of t.effects)
if (r.is(B)) {
const n = r.value.map(
(l) => (l.context.locations || []).map(({ start: m, end: f }) => {
const c = l.context.color || "#FFCA28";
let s = t.newDoc.line(m.line).from + m.column, g = t.newDoc.line(f.line).from + f.column;
const b = t.newDoc.length;
return s > b || g > b ? void 0 : M.mark({ attributes: { style: `outline: 1.5px solid ${c};` } }).range(s, g);
})
).flat().filter(Boolean) || [];
e = M.set(n, !0);
}
return e;
} catch {
return M.set([]);
}
},
provide: (e) => re.decorations.from(e)
}), Fe = [de(), Ee, ke, ye];
function _e({ value: e, onChange: t, onViewChanged: r, onSelectionChange: n, options: l, editorDidMount: m }) {
const f = E(
(g) => {
t?.(g);
},
[t]
), c = E(
(g) => {
r?.(g);
},
[r]
), s = E(
(g) => {
g.selectionSet && n && n?.(g.state.selection);
},
[n]
);
return /* @__PURE__ */ d.createElement(d.Fragment, null, /* @__PURE__ */ d.createElement(ue, {
value: e,
onChange: f,
onCreateEditor: c,
onUpdate: s,
extensions: Fe
}));
}
function T(...e) {
return e.filter(Boolean).join(" ");
}
function Me({ view: e, pattern: t, active: r, getTime: n }) {
const l = A([]), m = A(0);
k(() => {
if (e)
if (t && r) {
m.current = 0;
let f = requestAnimationFrame(function c() {
try {
const s = n(), b = [Math.max(m.current ?? s, s - 1 / 10, -0.01), s + 1 / 60];
m.current = b[1], l.current = l.current.filter((h) => h.whole.end > s);
const i = t.queryArc(...b).filter((h) => h.hasOnset());
l.current = l.current.concat(i), e.dispatch({ effects: B.of(l.current) });
} catch {
e.dispatch({ effects: B.of([]) });
}
f = requestAnimationFrame(c);
});
return () => {
cancelAnimationFrame(f);
};
} else
l.current = [], e.dispatch({ effects: B.of([]) });
}, [t, r, e]);
}
function Ae(e, t = !1) {
const r = A(), n = A(), l = (c) => {
if (n.current !== void 0) {
const s = c - n.current;
e(c, s);
}
n.current = c, r.current = requestAnimationFrame(l);
}, m = () => {
r.current = requestAnimationFrame(l);
}, f = () => {
r.current && cancelAnimationFrame(r.current), delete r.current;
};
return k(() => {
r.current && (f(), m());
}, [e]), k(() => (t && m(), f), []), {
start: m,
stop: f
};
}
function Ne({ pattern: e, started: t, getTime: r, onDraw: n, drawTime: l = [-2, 2] }) {
let [m, f] = l;
m = Math.abs(m);
let c = A([]), s = A(null);
k(() => {
if (e && t) {
const i = r(), h = e.queryArc(Math.max(i, 0), i + f + 0.1);
c.current = c.current.filter((p) => p.whole.begin < i), c.current = c.current.concat(h);
}
}, [e, t]);
const { start: g, stop: b } = Ae(
E(() => {
const i = r() + f;
if (s.current === null) {
s.current = i;
return;
}
const h = e.queryArc(Math.max(s.current, i - 1 / 10), i);
s.current = i, c.current = (c.current || []).filter((p) => p.whole.end >= i - m - f).concat(h.filter((p) => p.hasOnset())), n(e, i - f, c.current, l);
}, [e])
);
return k(() => {
t ? g() : (c.current = [], b());
}, [t]), {
clear: () => {
c.current = [];
}
};
}
function Ce(e) {
return k(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), E((t) => window.postMessage(t, "*"), []);
}
function De({
defaultOutput: e,
interval: t,
getTime: r,
evalOnMount: n = !1,
initialCode: l = "",
autolink: m = !1,
beforeEval: f,
afterEval: c,
editPattern: s,
onEvalError: g,
onToggle: b,
canvasId: i,
drawContext: h,
drawTime: p = [-2, 2]
}) {
const D = Q(() => Re(), []);
i = i || `canvas-${D}`;
const [P, R] = _(), [z, H] = _(), [y, S] = _(l), [V, q] = _(), [x, I] = _(), [N, O] = _(!1), K = y !== V, L = E((a) => !!(a?.context?.onPaint && h), [h]), { scheduler: C, evaluate: o, start: v, stop: j, pause: U } = Q(
() => pe({
interval: t,
defaultOutput: e,
onSchedulerError: R,
onEvalError: (a) => {
H(a), g?.(a);
},
getTime: r,
drawContext: h,
transpiler: be,
editPattern: s,
beforeEval: ({ code: a }) => {
S(a), f?.();
},
afterEval: ({ pattern: a, code: w }) => {
q(w), I(a), H(), R(), m && (window.location.hash = "#" + encodeURIComponent(btoa(w))), c?.();
},
onToggle: (a) => {
O(a), b?.(a);
}
}),
[e, t, r]
), ce = Ce(({ data: { from: a, type: w } }) => {
w === "start" && a !== D && j();
}), Y = E(
async (a = !0) => {
const w = await o(y, a);
return ce({ type: "start", from: D }), w;
},
[o, y]
), W = E(
(a, w, G, J) => {
const { onPaint: ie } = a.context || {}, le = typeof h == "function" ? h(i) : h;
ie?.(le, w, G, J);
},
[h, i]
), $ = E(
(a) => {
if (L(a)) {
const [w, G] = p, J = a.queryArc(0, G);
W(a, -1e-3, J, p);
}
},
[p, W, L]
), Z = A();
k(() => {
!Z.current && n && y && (Z.current = !0, o(y, !1).then((a) => $(a)));
}, [n, y, o, $]), k(() => () => {
C.stop();
}, [C]);
const ae = async () => {
N ? (C.stop(), $(x)) : await Y();
}, se = P || z;
return Ne({
pattern: x,
started: L(x) && N,
getTime: () => C.now(),
drawTime: p,
onDraw: W
}), {
id: D,
canvasId: i,
code: y,
setCode: S,
error: se,
schedulerError: P,
scheduler: C,
evalError: z,
evaluate: o,
activateCode: Y,
activeCode: V,
isDirty: K,
pattern: x,
started: N,
start: v,
stop: j,
pause: U,
togglePlay: ae
};
}
function Re() {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
}
function ee({ type: e }) {
return /* @__PURE__ */ d.createElement("svg", {
xmlns: "http://www.w3.org/2000/svg",
className: "sc-h-5 sc-w-5",
viewBox: "0 0 20 20",
fill: "currentColor"
}, {
refresh: /* @__PURE__ */ d.createElement("path", {
fillRule: "evenodd",
d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",
clipRule: "evenodd"
}),
play: /* @__PURE__ */ d.createElement("path", {
fillRule: "evenodd",
d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",
clipRule: "evenodd"
}),
pause: /* @__PURE__ */ d.createElement("path", {
fillRule: "evenodd",
d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
clipRule: "evenodd"
}),
stop: /* @__PURE__ */ d.createElement("path", {
fillRule: "evenodd",
d: "M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z",
clipRule: "evenodd"
})
}[e]);
}
const xe = "_container_3i85k_1", Le = "_header_3i85k_5", Pe = "_buttons_3i85k_9", qe = "_button_3i85k_9", ze = "_buttonDisabled_3i85k_17", He = "_error_3i85k_21", Se = "_body_3i85k_25", F = {
container: xe,
header: Le,
buttons: Pe,
button: qe,
buttonDisabled: ze,
error: He,
body: Se
}, Ve = () => he().currentTime;
function Ze({ tune: e, hideOutsideView: t = !1, enableKeyboard: r, drawTime: n, punchcard: l, canvasHeight: m = 200 }) {
n = n || (l ? [0, 4] : void 0);
const f = !!n, c = E(
n ? (o) => document.querySelector("#" + o)?.getContext("2d") : null,
[n]
), {
code: s,
setCode: g,
evaluate: b,
activateCode: i,
error: h,
isDirty: p,
activeCode: D,
pattern: P,
started: R,
scheduler: z,
togglePlay: H,
stop: y,
canvasId: S,
id: V
} = De({
initialCode: e,
defaultOutput: me,
editPattern: (o) => l ? o.punchcard() : o,
getTime: Ve,
evalOnMount: f,
drawContext: c,
drawTime: n
}), [q, x] = _(), [I, N] = ge({
threshold: 0.01
}), O = A(), K = Q(() => ((N || !t) && (O.current = !0), N || O.current), [N, t]);
Me({
view: q,
pattern: P,
active: R && !D?.includes("strudel disable-highlighting"),
getTime: () => z.now()
}), te(() => {
if (r) {
const o = async (v) => {
(v.ctrlKey || v.altKey) && (v.code === "Enter" ? (v.preventDefault(), we(q), await i()) : v.code === "Period" && (y(), v.preventDefault()));
};
return window.addEventListener("keydown", o, !0), () => window.removeEventListener("keydown", o, !0);
}
}, [r, P, s, b, y, q]);
const [L, C] = _([]);
return Oe(
E((o) => {
const { data: v } = o.detail;
v?.hap?.context?.id === V && C((U) => U.concat([o.detail]).slice(-10));
}, [])
), /* @__PURE__ */ d.createElement("div", {
className: F.container,
ref: I
}, /* @__PURE__ */ d.createElement("div", {
className: F.header
}, /* @__PURE__ */ d.createElement("div", {
className: F.buttons
}, /* @__PURE__ */ d.createElement("button", {
className: T(F.button, R ? "sc-animate-pulse" : ""),
onClick: () => H()
}, /* @__PURE__ */ d.createElement(ee, {
type: R ? "stop" : "play"
})), /* @__PURE__ */ d.createElement("button", {
className: T(p ? F.button : F.buttonDisabled),
onClick: () => i()
}, /* @__PURE__ */ d.createElement(ee, {
type: "refresh"
}))), h && /* @__PURE__ */ d.createElement("div", {
className: F.error
}, h.message)), /* @__PURE__ */ d.createElement("div", {
className: F.body
}, K && /* @__PURE__ */ d.createElement(_e, {
value: s,
onChange: g,
onViewChanged: x
})), n && /* @__PURE__ */ d.createElement("canvas", {
id: S,
className: "w-full pointer-events-none",
height: m,
ref: (o) => {
o && o.width !== o.clientWidth && (o.width = o.clientWidth);
}
}), !!L.length && /* @__PURE__ */ d.createElement("div", {
className: "sc-bg-gray-800 sc-rounded-md sc-p-2"
}, L.map(({ message: o }, v) => /* @__PURE__ */ d.createElement("div", {
key: v
}, o))));
}
function Oe(e) {
Be(ve.key, e);
}
function Be(e, t, r = !1) {
k(() => (document.addEventListener(e, t, r), () => {
document.removeEventListener(e, t, r);
}), [t]);
}
const Te = (e) => te(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
export {
_e as CodeMirror,
Ze as MiniRepl,
T as cx,
we as flash,
Me as useHighlighting,
Te as useKeydown,
Ce as usePostMessage,
De as useStrudel
};
+1
View File
@@ -0,0 +1 @@
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:18px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.sc-rounded-md{border-radius:.375rem}.sc-bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.sc-p-2{padding:.5rem}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto}
+1 -1
View File
@@ -8,7 +8,7 @@ pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
node_modules node_modules
dist !dist
dist-ssr dist-ssr
*.local *.local
@@ -1,15 +0,0 @@
# nano-repl
this is an example of how to create a repl with strudel and react.
## Usage
after cloning the strudel repo:
```sh
pnpm i
cd packages/react/examples/nano-repl
pnpm dev
```
you should now have a repl running at `http://localhost:5173/`
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:16px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.fixed{position:fixed}.absolute{position:absolute}.bottom-0{bottom:0px}.z-\[12\]{z-index:12}.flex{display:flex}.w-full{width:100%}.justify-center{justify-content:center}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.bg-slate-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity))}.px-2{padding-left:.5rem;padding-right:.5rem}body{background:#123}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{f as s,i as t,h as o,j as d,q as f,l as p,k as u,p as i,o as l,n as r,w as g}from"./index.ec9f9930.js";export{s as getAudioContext,t as getCachedBuffer,o as getDestination,d as getLoadedBuffer,f as getLoadedSamples,p as loadBuffer,u as loadGithubSamples,i as panic,l as resetLoadedSamples,r as samples,g as webaudioOutput};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{P as w}from"./index.ec9f9930.js";var i,f=!1;async function b(a=38400){if(!f){if(f=!0,i)return i;if("serial"in navigator){const r=await navigator.serial.requestPort();await r.open({baudRate:a});const o=new TextEncoderStream;o.readable.pipeTo(r.writable);const s=o.writable.getWriter();i=function(e){s.write(e)}}else throw"Webserial is not available in this browser."}}const g=.1;w.prototype.serial=function(...a){return this._withHap(r=>{i||b(...a);const o=(s,e,u)=>{var t="";if(typeof e.value=="object")if("action"in e.value){t+=e.value.action+"(";var c=!0;for(const[n,l]of Object.entries(e.value))n!=="action"&&(c?c=!1:t+=",",t+=`${n}:${l}`);t+=")"}else for(const[n,l]of Object.entries(e.value))t+=`${n}:${l};`;else t=e.value;const v=(s-u+g)*1e3;window.setTimeout(i,v,t)};return r.setContext({...r.context,onTrigger:o})})};export{b as getWriter};
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Strudel Nano REPL</title>
<script type="module" crossorigin src="./assets/index.ec9f9930.js"></script>
<link rel="stylesheet" href="./assets/index.75f8960b.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
File diff suppressed because it is too large Load Diff
+3 -10
View File
@@ -1,7 +1,7 @@
{ {
"name": "@strudel.cycles/nano-repl", "name": "nano-repl",
"private": true, "private": true,
"version": "0.6.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -10,14 +10,7 @@
}, },
"dependencies": { "dependencies": {
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0"
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/osc": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/react": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
+20 -12
View File
@@ -1,21 +1,27 @@
import { controls, evalScope } from '@strudel.cycles/core'; import { evalScope, controls } from '@strudel.cycles/core';
import { CodeMirror, useHighlighting, useKeydown, useStrudel, flash } from '@strudel.cycles/react'; import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio';
import { getAudioContext, initAudioOnFirstClick, panic, webaudioOutput } from '@strudel.cycles/webaudio';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import CodeMirror, { flash } from '../../../src/components/CodeMirror6';
import useKeydown from '../../../src/hooks/useKeydown.mjs';
import useStrudel from '../../../src/hooks/useStrudel';
import useHighlighting from '../../../src/hooks/useHighlighting';
import './style.css'; import './style.css';
// import { prebake } from '../../../../../repl/src/prebake.mjs'; // import { prebake } from '../../../../../repl/src/prebake.mjs';
initAudioOnFirstClick();
// TODO: only import stuff when play is pressed? // TODO: only import stuff when play is pressed?
evalScope( evalScope(
controls, controls,
import('@strudel.cycles/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'), import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'), import('@strudel.cycles/mini'),
import('@strudel.cycles/xen'), import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'), import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'), import('@strudel.cycles/osc'),
import('@strudel.cycles/webdirt'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
); );
const defaultTune = `samples({ const defaultTune = `samples({
@@ -24,7 +30,7 @@ const defaultTune = `samples({
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'], hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/'); }, 'github:tidalcycles/Dirt-Samples/master/');
stack( stack(
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8") // drums s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
.speed(perlin.range(.7,.9)) // random sample speed variation .speed(perlin.range(.7,.9)) // random sample speed variation
//.hush() //.hush()
,"<a1 b1*2 a1(3,8) e2>" // bassline ,"<a1 b1*2 a1(3,8) e2>" // bassline
@@ -37,7 +43,7 @@ stack(
.gain(.4) // turn down .gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff .cutoff(sine.slow(7).range(300,5000)) // automate cutoff
//.hush() //.hush()
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords ,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice .superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation .add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n" .n() // wrap in "n"
@@ -66,7 +72,7 @@ function App() {
const [code, setCode] = useState(defaultTune); const [code, setCode] = useState(defaultTune);
const [view, setView] = useState(); const [view, setView] = useState();
// const [code, setCode] = useState(`"c3".note().slow(2)`); // const [code, setCode] = useState(`"c3".note().slow(2)`);
const { scheduler, evaluate, schedulerError, evalError, isDirty, activeCode, pattern, started } = useStrudel({ const { scheduler, evaluate, schedulerError, evalError, isDirty, activeCode, pattern } = useStrudel({
code, code,
defaultOutput: webaudioOutput, defaultOutput: webaudioOutput,
getTime, getTime,
@@ -75,8 +81,8 @@ function App() {
useHighlighting({ useHighlighting({
view, view,
pattern, pattern,
active: started && !activeCode?.includes('strudel disable-highlighting'), active: !activeCode?.includes('strudel disable-highlighting'),
getTime: () => scheduler.now(), getTime: () => scheduler.getPhase(),
}); });
const error = evalError || schedulerError; const error = evalError || schedulerError;
@@ -97,7 +103,7 @@ function App() {
scheduler.start(); scheduler.start();
} }
} else if (e.code === 'Period') { } else if (e.code === 'Period') {
scheduler.stop(); scheduler.pause();
panic(); panic();
e.preventDefault(); e.preventDefault();
} }
@@ -108,11 +114,13 @@ function App() {
); );
return ( return (
<div> <div>
<nav className="z-[12] w-full flex justify-center fixed bottom-0"> {/* <textarea value={code} onChange={(e) => setCode(e.target.value)} cols="64" rows="30" /> */}
<nav className="z-[12] w-full flex justify-center absolute bottom-0">
<div className="bg-slate-500 space-x-2 px-2 rounded-t-md"> <div className="bg-slate-500 space-x-2 px-2 rounded-t-md">
<button <button
onClick={async () => { onClick={async () => {
await evaluate(code); await evaluate(code);
await getAudioContext().resume();
scheduler.start(); scheduler.start();
}} }}
> >
@@ -2,18 +2,6 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root {
--background: #222;
--lineBackground: #22222250;
--foreground: #fff;
--caret: #ffcc00;
--selection: rgba(128, 203, 196, 0.5);
--selectionMatch: #036dd626;
--lineHighlight: #00000050;
--gutterBackground: transparent;
--gutterForeground: #8a919966;
}
body { body {
background: #123; background: #123;
} }
@@ -6,23 +6,9 @@ This program is free software: you can redistribute it and/or modify it under th
module.exports = { module.exports = {
// TODO: find out if leaving out tutorial path works now // TODO: find out if leaving out tutorial path works now
content: ['./src/**/*.{js,jsx,ts,tsx}', '../../src/**/*.{html,js,jsx,md,mdx,ts,tsx}'], content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: { theme: {
extend: { extend: {},
colors: {
// codemirror-theme settings
background: 'var(--background)',
lineBackground: 'var(--lineBackground)',
foreground: 'var(--foreground)',
caret: 'var(--caret)',
selection: 'var(--selection)',
selectionMatch: 'var(--selectionMatch)',
gutterBackground: 'var(--gutterBackground)',
gutterForeground: 'var(--gutterForeground)',
gutterBorder: 'var(--gutterBorder)',
lineHighlight: 'var(--lineHighlight)',
},
},
}, },
plugins: [], plugins: [],
}; };
+17 -16
View File
@@ -1,18 +1,24 @@
{ {
"name": "@strudel.cycles/react", "name": "@strudel.cycles/react",
"version": "0.6.4", "version": "0.5.0",
"description": "React components for strudel", "description": "React components for strudel",
"main": "src/index.js", "main": "dist/index.cjs.js",
"publishConfig": { "module": "dist/index.es.js",
"main": "dist/index.js", "exports": {
"module": "dist/index.mjs" ".": {
"require": "./dist/index.cjs.js",
"import": "./dist/index.es.js"
},
"./dist/style.css": "./dist/style.css"
}, },
"files": [
"dist"
],
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"watch": "vite build --watch", "watch": "vite build --watch",
"preview": "vite preview", "preview": "vite preview"
"prepublishOnly": "npm run build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -32,16 +38,11 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.4.0",
"@codemirror/lang-javascript": "^6.1.1", "@codemirror/lang-javascript": "^6.1.1",
"@codemirror/state": "^6.2.0", "@strudel.cycles/core": "^0.5.0",
"@codemirror/view": "^6.7.3", "@strudel.cycles/tone": "^0.5.0",
"@lezer/highlight": "^1.1.3", "@strudel.cycles/transpiler": "^0.5.0",
"@replit/codemirror-emacs": "^6.0.0", "@strudel.cycles/webaudio": "^0.5.0",
"@replit/codemirror-vim": "^6.0.6",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@uiw/codemirror-themes": "^4.12.4", "@uiw/codemirror-themes": "^4.12.4",
"@uiw/react-codemirror": "^4.12.4", "@uiw/react-codemirror": "^4.12.4",
"react-hook-inview": "^4.5.0" "react-hook-inview": "^4.5.0"
+1
View File
@@ -6,6 +6,7 @@ import { controls, evalScope } from '@strudel.cycles/core';
evalScope( evalScope(
controls, controls,
import('@strudel.cycles/core'), import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'), import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'), import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'), import('@strudel.cycles/midi'),
@@ -1,77 +0,0 @@
import { createRoot } from 'react-dom/client';
import jsdoc from '../../../../doc.json';
const getDocLabel = (doc) => doc.name || doc.longname;
const getInnerText = (html) => {
var div = document.createElement('div');
div.innerHTML = html;
return div.textContent || div.innerText || '';
};
export function Autocomplete({ doc }) {
return (
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
<ul>
{doc.params?.map(({ name, type, description }, i) => (
<li key={i}>
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
</li>
))}
</ul>
<div>
{doc.examples?.map((example, i) => (
<div key={i}>
<pre
className="cursor-pointer"
onMouseDown={(e) => {
console.log('ola!');
navigator.clipboard.writeText(example);
e.stopPropagation();
}}
>
{example}
</pre>
</div>
))}
</div>
</div>
);
}
const jsdocCompletions = jsdoc.docs
.filter(
(doc) =>
getDocLabel(doc) &&
!getDocLabel(doc).startsWith('_') &&
!['package'].includes(doc.kind) &&
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
)
// https://codemirror.net/docs/ref/#autocomplete.Completion
.map((doc) /*: Completion */ => ({
label: getDocLabel(doc),
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
info: () => {
const node = document.createElement('div');
// if Autocomplete is non-interactive, it could also be rendered at build time..
// .. using renderToStaticMarkup
createRoot(node).render(<Autocomplete doc={doc} />);
return node;
},
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
}));
export const strudelAutocomplete = (context /* : CompletionContext */) => {
let word = context.matchBefore(/\w*/);
if (word.from == word.to && !context.explicit) return null;
return {
from: word.from,
options: jsdocCompletions,
/* options: [
{ label: 'match', type: 'keyword' },
{ label: 'hello', type: 'variable', info: '(World)' },
{ label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' },
], */
};
};
+9 -43
View File
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React from 'react';
import _CodeMirror from '@uiw/react-codemirror'; import _CodeMirror from '@uiw/react-codemirror';
import { EditorView, Decoration } from '@codemirror/view'; import { EditorView, Decoration } from '@codemirror/view';
import { StateField, StateEffect } from '@codemirror/state'; import { StateField, StateEffect } from '@codemirror/state';
@@ -6,10 +6,6 @@ import { javascript } from '@codemirror/lang-javascript';
import strudelTheme from '../themes/strudel-theme'; import strudelTheme from '../themes/strudel-theme';
import './style.css'; import './style.css';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { autocompletion } from '@codemirror/autocomplete';
//import { strudelAutocomplete } from './Autocomplete';
import { vim } from '@replit/codemirror-vim';
import { emacs } from '@replit/codemirror-emacs';
export const setFlash = StateEffect.define(); export const setFlash = StateEffect.define();
const flashField = StateField.define({ const flashField = StateField.define({
@@ -53,20 +49,19 @@ const highlightField = StateField.define({
try { try {
for (let e of tr.effects) { for (let e of tr.effects) {
if (e.is(setHighlights)) { if (e.is(setHighlights)) {
const { haps } = e.value;
const marks = const marks =
haps e.value
.map((hap) => .map((hap) =>
(hap.context.locations || []).map(({ start, end }) => { (hap.context.locations || []).map(({ start, end }) => {
// const color = hap.context.color || e.value.color || '#FFCA28'; const color = hap.context.color || '#FFCA28';
let from = tr.newDoc.line(start.line).from + start.column; let from = tr.newDoc.line(start.line).from + start.column;
let to = tr.newDoc.line(end.line).from + end.column; let to = tr.newDoc.line(end.line).from + end.column;
const l = tr.newDoc.length; const l = tr.newDoc.length;
if (from > l || to > l) { if (from > l || to > l) {
return; // dont mark outside of range, as it will throw an error return; // dont mark outside of range, as it will throw an error
} }
//const mark = Decoration.mark({ attributes: { style: `outline: 2px solid ${color};` } }); // const mark = Decoration.mark({ attributes: { style: `outline: 1px solid ${color}` } });
const mark = Decoration.mark({ attributes: { class: `outline outline-2 outline-foreground` } }); const mark = Decoration.mark({ attributes: { style: `outline: 1.5px solid ${color};` } });
return mark.range(from, to); return mark.range(from, to);
}), }),
) )
@@ -84,27 +79,9 @@ const highlightField = StateField.define({
provide: (f) => EditorView.decorations.from(f), provide: (f) => EditorView.decorations.from(f),
}); });
const staticExtensions = [ const extensions = [javascript(), strudelTheme, highlightField, flashField];
javascript(),
highlightField,
flashField,
// javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
// autocompletion({ override: [strudelAutocomplete] }),
autocompletion({ override: [] }), // wait for https://github.com/uiwjs/react-codemirror/pull/458
];
export default function CodeMirror({ export default function CodeMirror({ value, onChange, onViewChanged, onSelectionChange, options, editorDidMount }) {
value,
onChange,
onViewChanged,
onSelectionChange,
theme,
keybindings,
fontSize = 18,
fontFamily = 'monospace',
options,
editorDidMount,
}) {
const handleOnChange = useCallback( const handleOnChange = useCallback(
(value) => { (value) => {
onChange?.(value); onChange?.(value);
@@ -125,27 +102,16 @@ export default function CodeMirror({
}, },
[onSelectionChange], [onSelectionChange],
); );
const extensions = useMemo(() => {
let bindings = {
vim,
emacs,
};
if (bindings[keybindings]) {
return [...staticExtensions, bindings[keybindings]()];
}
return staticExtensions;
}, [keybindings]);
return ( return (
<div style={{ fontSize, fontFamily }} className="w-full"> <>
<_CodeMirror <_CodeMirror
value={value} value={value}
theme={theme || strudelTheme}
onChange={handleOnChange} onChange={handleOnChange}
onCreateEditor={handleOnCreateEditor} onCreateEditor={handleOnCreateEditor}
onUpdate={handleOnUpdate} onUpdate={handleOnUpdate}
extensions={extensions} extensions={extensions}
/> />
</div> </>
); );
} }
+1 -1
View File
@@ -2,7 +2,7 @@ import React from 'react';
export function Icon({ type }) { export function Icon({ type }) {
return ( return (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="sc-h-5 sc-w-5" viewBox="0 0 20 20" fill="currentColor">
{ {
{ {
refresh: ( refresh: (
+21 -53
View File
@@ -7,22 +7,13 @@ import useHighlighting from '../hooks/useHighlighting.mjs';
import useStrudel from '../hooks/useStrudel.mjs'; import useStrudel from '../hooks/useStrudel.mjs';
import CodeMirror6, { flash } from './CodeMirror6'; import CodeMirror6, { flash } from './CodeMirror6';
import { Icon } from './Icon'; import { Icon } from './Icon';
import styles from './MiniRepl.module.css';
import './style.css'; import './style.css';
import { logger } from '@strudel.cycles/core'; import { logger } from '@strudel.cycles/core';
import useEvent from '../hooks/useEvent.mjs';
import useKeydown from '../hooks/useKeydown.mjs';
const getTime = () => getAudioContext().currentTime; const getTime = () => getAudioContext().currentTime;
export function MiniRepl({ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTime, punchcard, canvasHeight = 200 }) {
tune,
hideOutsideView = false,
enableKeyboard,
drawTime,
punchcard,
canvasHeight = 200,
theme,
}) {
drawTime = drawTime || (punchcard ? [0, 4] : undefined); drawTime = drawTime || (punchcard ? [0, 4] : undefined);
const evalOnMount = !!drawTime; const evalOnMount = !!drawTime;
const drawContext = useCallback( const drawContext = useCallback(
@@ -72,27 +63,6 @@ export function MiniRepl({
getTime: () => scheduler.now(), getTime: () => scheduler.now(),
}); });
// keyboard shortcuts
useKeydown(
useCallback(
async (e) => {
if (view?.hasFocus) {
if (e.ctrlKey || e.altKey) {
if (e.code === 'Enter') {
e.preventDefault();
flash(view);
await activateCode();
} else if (e.code === 'Period') {
stop();
e.preventDefault();
}
}
}
},
[activateCode, stop, view],
),
);
// set active pattern on ctrl+enter // set active pattern on ctrl+enter
useLayoutEffect(() => { useLayoutEffect(() => {
if (enableKeyboard) { if (enableKeyboard) {
@@ -128,32 +98,20 @@ export function MiniRepl({
); );
return ( return (
<div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight" ref={ref}> <div className={styles.container} ref={ref}>
<div className="flex justify-between bg-lineHighlight"> <div className={styles.header}>
<div className="flex"> <div className={styles.buttons}>
<button <button className={cx(styles.button, started ? 'sc-animate-pulse' : '')} onClick={() => togglePlay()}>
className={cx(
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background',
started ? 'animate-pulse' : '',
)}
onClick={() => togglePlay()}
>
<Icon type={started ? 'stop' : 'play'} /> <Icon type={started ? 'stop' : 'play'} />
</button> </button>
<button <button className={cx(isDirty ? styles.button : styles.buttonDisabled)} onClick={() => activateCode()}>
className={cx(
'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight',
isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed',
)}
onClick={() => activateCode()}
>
<Icon type="refresh" /> <Icon type="refresh" />
</button> </button>
</div> </div>
{error && <div className="text-right p-1 text-sm text-red-200">{error.message}</div>} {error && <div className={styles.error}>{error.message}</div>}
</div> </div>
<div className="overflow-auto relative"> <div className={styles.body}>
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} theme={theme} />} {show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
</div> </div>
{drawTime && ( {drawTime && (
<canvas <canvas
@@ -168,7 +126,7 @@ export function MiniRepl({
></canvas> ></canvas>
)} )}
{!!log.length && ( {!!log.length && (
<div className="bg-gray-800 rounded-md p-2"> <div className="sc-bg-gray-800 sc-rounded-md sc-p-2">
{log.map(({ message }, i) => ( {log.map(({ message }, i) => (
<div key={i}>{message}</div> <div key={i}>{message}</div>
))} ))}
@@ -182,3 +140,13 @@ export function MiniRepl({
function useLogger(onTrigger) { function useLogger(onTrigger) {
useEvent(logger.key, onTrigger); useEvent(logger.key, onTrigger);
} }
// TODO: dedupe
function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
@@ -0,0 +1,27 @@
.container {
@apply sc-rounded-md sc-overflow-hidden sc-bg-[#222222];
}
.header {
@apply sc-flex sc-justify-between sc-bg-slate-700 sc-border-t sc-border-slate-500;
}
.buttons {
@apply sc-flex;
}
.button {
@apply sc-cursor-pointer sc-w-16 sc-flex sc-items-center sc-justify-center sc-p-1 sc-bg-slate-700 sc-border-r sc-border-slate-500 sc-text-white hover:sc-bg-slate-600;
}
.buttonDisabled {
@apply sc-cursor-pointer sc-w-16 sc-flex sc-items-center sc-justify-center sc-p-1 sc-bg-slate-600 sc-text-slate-400 sc-cursor-not-allowed;
}
.error {
@apply sc-text-right sc-p-1 sc-text-sm sc-text-red-200;
}
.body {
@apply sc-overflow-auto sc-relative;
}
+5 -17
View File
@@ -1,26 +1,14 @@
:root {
--background: #222;
--lineBackground: #22222250;
--foreground: #fff;
--caret: #ffcc00;
--selection: rgba(128, 203, 196, 0.5);
--selectionMatch: #036dd626;
--lineHighlight: #00000050;
--gutterBackground: transparent;
--gutterForeground: #8a919966;
}
.cm-editor { .cm-editor {
background-color: transparent !important; background-color: transparent !important;
height: 100%; height: 100%;
z-index: 11; z-index: 11;
} font-size: 18px;
.cm-theme {
width: 100%;
height: 100%;
} }
.cm-theme-light { .cm-theme-light {
width: 100%; width: 100%;
} }
.cm-line > * {
background: #00000095;
}
-12
View File
@@ -1,12 +0,0 @@
import { useEffect } from 'react';
function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
export default useEvent;
+4 -5
View File
@@ -1,6 +1,5 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { setHighlights } from '../components/CodeMirror6'; import { setHighlights } from '../components/CodeMirror6';
const round = (x) => Math.round(x * 1000) / 1000;
function useHighlighting({ view, pattern, active, getTime }) { function useHighlighting({ view, pattern, active, getTime }) {
const highlights = useRef([]); const highlights = useRef([]);
@@ -15,14 +14,14 @@ function useHighlighting({ view, pattern, active, getTime }) {
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away // force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
// see https://github.com/tidalcycles/strudel/issues/108 // see https://github.com/tidalcycles/strudel/issues/108
const begin = Math.max(lastEnd.current ?? audioTime, audioTime - 1 / 10, -0.01); // negative time seems buggy const begin = Math.max(lastEnd.current ?? audioTime, audioTime - 1 / 10, -0.01); // negative time seems buggy
const span = [round(begin), round(audioTime + 1 / 60)]; const span = [begin, audioTime + 1 / 60];
lastEnd.current = span[1]; lastEnd.current = span[1];
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset()); const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
highlights.current = highlights.current.concat(haps); // add potential new onsets highlights.current = highlights.current.concat(haps); // add potential new onsets
view.dispatch({ effects: setHighlights.of({ haps: highlights.current }) }); // highlight all still active + new active haps view.dispatch({ effects: setHighlights.of(highlights.current) }); // highlight all still active + new active haps
} catch (err) { } catch (err) {
view.dispatch({ effects: setHighlights.of({ haps: [] }) }); view.dispatch({ effects: setHighlights.of([]) });
} }
frame = requestAnimationFrame(updateHighlights); frame = requestAnimationFrame(updateHighlights);
}); });
@@ -31,7 +30,7 @@ function useHighlighting({ view, pattern, active, getTime }) {
}; };
} else { } else {
highlights.current = []; highlights.current = [];
view.dispatch({ effects: setHighlights.of({ haps: [] }) }); view.dispatch({ effects: setHighlights.of([]) });
} }
} }
}, [pattern, active, view]); }, [pattern, active, view]);
+7 -5
View File
@@ -10,6 +10,7 @@ function useStrudel({
getTime, getTime,
evalOnMount = false, evalOnMount = false,
initialCode = '', initialCode = '',
autolink = false,
beforeEval, beforeEval,
afterEval, afterEval,
editPattern, editPattern,
@@ -32,7 +33,7 @@ function useStrudel({
const shouldPaint = useCallback((pat) => !!(pat?.context?.onPaint && drawContext), [drawContext]); const shouldPaint = useCallback((pat) => !!(pat?.context?.onPaint && drawContext), [drawContext]);
// TODO: make sure this hook reruns when scheduler.started changes // TODO: make sure this hook reruns when scheduler.started changes
const { scheduler, evaluate, start, stop, pause, setCps } = useMemo( const { scheduler, evaluate, start, stop, pause } = useMemo(
() => () =>
repl({ repl({
interval, interval,
@@ -50,13 +51,15 @@ function useStrudel({
setCode(code); setCode(code);
beforeEval?.(); beforeEval?.();
}, },
afterEval: (res) => { afterEval: ({ pattern: _pattern, code }) => {
const { pattern: _pattern, code } = res;
setActiveCode(code); setActiveCode(code);
setPattern(_pattern); setPattern(_pattern);
setEvalError(); setEvalError();
setSchedulerError(); setSchedulerError();
afterEval?.(res); if (autolink) {
window.location.hash = '#' + encodeURIComponent(btoa(code));
}
afterEval?.();
}, },
onToggle: (v) => { onToggle: (v) => {
setStarted(v); setStarted(v);
@@ -153,7 +156,6 @@ function useStrudel({
stop, stop,
pause, pause,
togglePlay, togglePlay,
setCps,
}; };
} }
+4 -6
View File
@@ -1,11 +1,9 @@
// import 'tailwindcss/tailwind.css'; // import 'tailwindcss/tailwind.css';
export { default as CodeMirror, flash } from './components/CodeMirror6'; // !SSR export { default as CodeMirror, flash } from './components/CodeMirror6';
export * from './components/MiniRepl'; // !SSR export * from './components/MiniRepl';
export { default as useHighlighting } from './hooks/useHighlighting'; // !SSR export { default as useHighlighting } from './hooks/useHighlighting';
export { default as useStrudel } from './hooks/useStrudel'; // !SSR
export { default as usePostMessage } from './hooks/usePostMessage'; export { default as usePostMessage } from './hooks/usePostMessage';
export { default as useStrudel } from './hooks/useStrudel';
export { default as useKeydown } from './hooks/useKeydown'; export { default as useKeydown } from './hooks/useKeydown';
export { default as useEvent } from './hooks/useEvent';
export { default as strudelTheme } from './themes/strudel-theme';
export { default as cx } from './cx'; export { default as cx } from './cx';
-39
View File
@@ -1,39 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: '#9bbc0f',
foreground: '#0f380f', // whats that?
caret: '#0f380f',
selection: '#306230',
selectionMatch: '#ffffff26',
lineHighlight: '#8bac0f',
lineBackground: '#9bbc0f50',
//lineBackground: 'transparent',
gutterBackground: 'transparent',
gutterForeground: '#0f380f',
light: true,
};
export default createTheme({
theme: 'light',
settings,
styles: [
{ tag: t.keyword, color: '#0f380f' },
{ tag: t.operator, color: '#0f380f' },
{ tag: t.special(t.variableName), color: '#0f380f' },
{ tag: t.typeName, color: '#0f380f' },
{ tag: t.atom, color: '#0f380f' },
{ tag: t.number, color: '#0f380f' },
{ tag: t.definition(t.variableName), color: '#0f380f' },
{ tag: t.string, color: '#0f380f' },
{ tag: t.special(t.string), color: '#0f380f' },
{ tag: t.comment, color: '#0f380f' },
{ tag: t.variableName, color: '#0f380f' },
{ tag: t.tagName, color: '#0f380f' },
{ tag: t.bracket, color: '#0f380f' },
{ tag: t.meta, color: '#0f380f' },
{ tag: t.attributeName, color: '#0f380f' },
{ tag: t.propertyName, color: '#0f380f' },
{ tag: t.className, color: '#0f380f' },
{ tag: t.invalid, color: '#0f380f' },
],
});
-37
View File
@@ -1,37 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: 'black',
foreground: 'white', // whats that?
caret: 'white',
selection: '#ffffff20',
selectionMatch: '#036dd626',
lineHighlight: '#ffffff10',
lineBackground: '#00000050',
gutterBackground: 'transparent',
gutterForeground: '#8a919966',
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
{ tag: t.typeName, color: 'white' },
{ tag: t.atom, color: 'white' },
{ tag: t.number, color: 'white' },
{ tag: t.definition(t.variableName), color: 'white' },
{ tag: t.string, color: 'white' },
{ tag: t.special(t.string), color: 'white' },
{ tag: t.comment, color: 'white' },
{ tag: t.variableName, color: 'white' },
{ tag: t.tagName, color: 'white' },
{ tag: t.bracket, color: 'white' },
{ tag: t.meta, color: 'white' },
{ tag: t.attributeName, color: 'white' },
{ tag: t.propertyName, color: 'white' },
{ tag: t.className, color: 'white' },
{ tag: t.invalid, color: 'white' },
],
});
-40
View File
@@ -1,40 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: '#051DB5',
lineBackground: '#051DB550',
foreground: 'white', // whats that?
caret: 'white',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
{ tag: t.typeName, color: 'white' },
{ tag: t.atom, color: 'white' },
{ tag: t.number, color: 'white' },
{ tag: t.definition(t.variableName), color: 'white' },
{ tag: t.string, color: 'white' },
{ tag: t.special(t.string), color: 'white' },
{ tag: t.comment, color: 'white' },
{ tag: t.variableName, color: 'white' },
{ tag: t.tagName, color: 'white' },
{ tag: t.bracket, color: 'white' },
{ tag: t.meta, color: 'white' },
{ tag: t.attributeName, color: 'white' },
{ tag: t.propertyName, color: 'white' },
{ tag: t.className, color: 'white' },
{ tag: t.invalid, color: 'white' },
],
});
-36
View File
@@ -1,36 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: 'black',
foreground: '#41FF00', // whats that?
caret: '#41FF00',
selection: '#ffffff20',
selectionMatch: '#036dd626',
lineHighlight: '#ffffff10',
gutterBackground: 'transparent',
gutterForeground: '#8a919966',
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.keyword, color: '#41FF00' },
{ tag: t.operator, color: '#41FF00' },
{ tag: t.special(t.variableName), color: '#41FF00' },
{ tag: t.typeName, color: '#41FF00' },
{ tag: t.atom, color: '#41FF00' },
{ tag: t.number, color: '#41FF00' },
{ tag: t.definition(t.variableName), color: '#41FF00' },
{ tag: t.string, color: '#41FF00' },
{ tag: t.special(t.string), color: '#41FF00' },
{ tag: t.comment, color: '#41FF00' },
{ tag: t.variableName, color: '#41FF00' },
{ tag: t.tagName, color: '#41FF00' },
{ tag: t.bracket, color: '#41FF00' },
{ tag: t.meta, color: '#41FF00' },
{ tag: t.attributeName, color: '#41FF00' },
{ tag: t.propertyName, color: '#41FF00' },
{ tag: t.className, color: '#41FF00' },
{ tag: t.invalid, color: '#41FF00' },
],
});

Some files were not shown because too many files have changed in this diff Show More