mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-12 06:08:37 +00:00
Merge branch 'main' into vim-mode
This commit is contained in:
commit
70d1eb17a7
@ -9,6 +9,12 @@ out/**
|
||||
postcss.config.js
|
||||
postcss.config.cjs
|
||||
tailwind.config.js
|
||||
tailwind.config.cjs
|
||||
vite.config.js
|
||||
/**/dist/**/*
|
||||
!**/*.mjs
|
||||
**/*.tsx
|
||||
**/*.ts
|
||||
**/*.json
|
||||
**/dev-dist
|
||||
**/dist
|
||||
@ -9,8 +9,9 @@
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [],
|
||||
"plugins": ["import"],
|
||||
"rules": {
|
||||
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }]
|
||||
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }],
|
||||
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
|
||||
}
|
||||
}
|
||||
|
||||
13
.github/workflows/deploy.yml
vendored
13
.github/workflows/deploy.yml
vendored
@ -22,15 +22,18 @@ jobs:
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: "npm"
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install Dependencies
|
||||
run: npm ci && cd repl && npm ci && cd ../tutorial && npm ci
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
run: pnpm build
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v2
|
||||
@ -39,7 +42,7 @@ jobs:
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: "./out"
|
||||
path: "./website/dist"
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
|
||||
13
.github/workflows/test.yml
vendored
13
.github/workflows/test.yml
vendored
@ -11,11 +11,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "npm"
|
||||
- run: npm install
|
||||
- run: npm run format-check
|
||||
- run: npm run lint
|
||||
- run: npm test
|
||||
cache: 'pnpm'
|
||||
- run: pnpm install
|
||||
- run: pnpm run format-check
|
||||
- run: pnpm run lint
|
||||
- run: pnpm test
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -37,4 +37,6 @@ talk/public/EmuSP12
|
||||
talk/public/samples
|
||||
server/samples/old
|
||||
repl/stats.html
|
||||
coverage
|
||||
coverage
|
||||
public/icons/apple-splash-*
|
||||
dev-dist
|
||||
@ -6,3 +6,7 @@
|
||||
**/dist
|
||||
packages/mini/krill-parser.js
|
||||
packages/xen/tunejs.js
|
||||
paper
|
||||
pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
**/dev-dist
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -5,5 +5,6 @@
|
||||
],
|
||||
"yaml.schemas": {
|
||||
"https://json.schemastore.org/github-workflow.json": "file:///home/felix/projects/strudel/.github/workflows/deploy.yml"
|
||||
}
|
||||
},
|
||||
"testing.automaticallyOpenPeekView": "never"
|
||||
}
|
||||
@ -12,8 +12,8 @@ To get in touch with the contributors, either
|
||||
|
||||
## Ask a Question
|
||||
|
||||
If you have any questions about strudel, make sure you've read the
|
||||
[tutorial](https://strudel.tidalcycles.org/tutorial/) to find out if it answers your question.
|
||||
If you have any questions about strudel, make sure you've glanced through the
|
||||
[docs](https://strudel.tidalcycles.org/learn/) to find out if it answers your question.
|
||||
If not, use one of the Communication Channels above!
|
||||
|
||||
Don't be afraid to ask! Your question might be of great value for other people too.
|
||||
@ -29,12 +29,10 @@ If you made some music with strudel, you can give back some love and share what
|
||||
Your creation could also be part of the random selection in the REPL if you want.
|
||||
Use one of the Communication Channels listed above.
|
||||
|
||||
## Improve the Tutorial
|
||||
## Improve the Docs
|
||||
|
||||
If you find some weak spots in the [tutorial](https://strudel.tidalcycles.org/),
|
||||
you are welcome to improve them by editing [this file](https://github.com/tidalcycles/strudel/blob/main/tutorial/tutorial.mdx).
|
||||
|
||||
This will even work without setting up a development environment, only a github account is required.
|
||||
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/learn/getting-started),
|
||||
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
|
||||
|
||||
## Propose a Feature
|
||||
|
||||
@ -60,23 +58,22 @@ To fix a bug that has been reported,
|
||||
## 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.
|
||||
You can find the tests in each package in the `test` folder. To run all tests, run `npm test` from the root folder.
|
||||
You can find the tests in each package in the `test` folder. To run all tests, run `pnpm test` from the root folder.
|
||||
|
||||
## Project Setup
|
||||
|
||||
To get the project up and running for development, make sure you have installed:
|
||||
|
||||
- git
|
||||
- node, preferably v16
|
||||
- [git](https://git-scm.com/)
|
||||
- [node](https://nodejs.org/en/) >= 18
|
||||
- [pnpm](https://pnpm.io/) (`npm i pnpm -g`)
|
||||
|
||||
then, do the following:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/tidalcycles/strudel.git && cd strudel
|
||||
npm i # install at root to symlink packages
|
||||
npx lerna bootstrap # install all dependencies in packages
|
||||
cd repl && npm i # install repl dependencies
|
||||
npm run start # start repl
|
||||
pnpm i # install at root to symlink packages
|
||||
pnpm start # start repl
|
||||
```
|
||||
|
||||
Those commands might look slightly different for your OS.
|
||||
@ -85,6 +82,10 @@ Please report any problems you've had with the setup instructions!
|
||||
## 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
|
||||
|
||||
1. install [the prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||
@ -92,12 +93,29 @@ If you use VSCode, you can
|
||||
3. Choose "Configure Default Formatter..."
|
||||
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
|
||||
|
||||
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
|
||||
When you run `npm i` on the root folder, [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces) will symlink all packages
|
||||
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
|
||||
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,
|
||||
allowing to develop multiple packages at the same time.
|
||||
|
||||
## Package Publishing
|
||||
|
||||
@ -108,12 +126,15 @@ npm login
|
||||
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
|
||||
|
||||
To add a new package, you have to publish it manually the first time, using:
|
||||
|
||||
```sh
|
||||
cd packages/<package-name> && npm publish --access public
|
||||
cd packages/<package-name> && pnpm publish --access public
|
||||
```
|
||||
|
||||
## Have Fun
|
||||
|
||||
@ -5,8 +5,9 @@
|
||||
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is slowly stabilising, but please continue to tread carefully.
|
||||
|
||||
- Try it here: <https://strudel.tidalcycles.org/>
|
||||
- Tutorial: <https://strudel.tidalcycles.org/tutorial/>
|
||||
- Docs: <https://strudel.tidalcycles.org/learn/>
|
||||
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
||||
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
|
||||
|
||||
## Running Locally
|
||||
|
||||
|
||||
17
index.mjs
Normal file
17
index.mjs
Normal file
@ -0,0 +1,17 @@
|
||||
// this barrel export is currently only used to find undocumented exports
|
||||
export * from './packages/core/index.mjs';
|
||||
export * from './packages/csound/index.mjs';
|
||||
export * from './packages/embed/index.mjs';
|
||||
export * from './packages/eval/index.mjs';
|
||||
export * from './packages/midi/index.mjs';
|
||||
export * from './packages/mini/index.mjs';
|
||||
export * from './packages/osc/index.mjs';
|
||||
export * from './packages/react/index.mjs';
|
||||
export * from './packages/serial/index.mjs';
|
||||
export * from './packages/soundfonts/index.mjs';
|
||||
export * from './packages/tonal/index.mjs';
|
||||
export * from './packages/tone/index.mjs';
|
||||
export * from './packages/transpiler/index.mjs';
|
||||
export * from './packages/webaudio/index.mjs';
|
||||
export * from './packages/webdirt/index.mjs';
|
||||
export * from './packages/xen/index.mjs';
|
||||
@ -2,5 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent"
|
||||
"version": "independent",
|
||||
"npmClient": "pnpm",
|
||||
"useWorkspaces": true
|
||||
}
|
||||
|
||||
59
my-patterns/README.md
Normal file
59
my-patterns/README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# my-patterns
|
||||
|
||||
This directory can be used to save your own patterns, which then get
|
||||
made into a pattern swatch.
|
||||
|
||||
Example: <https://felixroos.github.io/strudel/swatch/>
|
||||
|
||||
## deploy
|
||||
|
||||
### 1. fork the [strudel repo on github](https://github.com/tidalcycles/strudel.git)
|
||||
|
||||
### 2. clone your fork to your machine `git clone https://github.com/<your-username>/strudel.git strudel && cd strudel`
|
||||
|
||||
### 3. create a separate branch like `git branch patternuary && git checkout patternuary`
|
||||
|
||||
### 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).
|
||||
|
||||
### 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.
|
||||
20478
package-lock.json
generated
20478
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@ -4,28 +4,30 @@
|
||||
"private": true,
|
||||
"description": "Port of tidalcycles to javascript",
|
||||
"scripts": {
|
||||
"pretest": "cd tutorial && npm run jsdoc-json",
|
||||
"test": "vitest run --version",
|
||||
"test-ui": "vitest --ui",
|
||||
"test-coverage": "vitest --coverage",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"setup": "npm i && npm run bootstrap && cd repl && npm i && cd ../tutorial && npm i",
|
||||
"snapshot": "vitest run -u --silent",
|
||||
"repl": "cd repl && npm run dev",
|
||||
"setup": "pnpm i",
|
||||
"pretest": "npm run jsdoc-json",
|
||||
"prebuild": "npm run jsdoc-json",
|
||||
"prestart": "npm run jsdoc-json",
|
||||
"test": "npm run pretest && vitest run --version",
|
||||
"test-ui": "npm run pretest && vitest --ui",
|
||||
"test-coverage": "npm run pretest && vitest --coverage",
|
||||
"snapshot": "npm run pretest && vitest run -u --silent",
|
||||
"repl": "npm run prestart && cd website && npm run dev",
|
||||
"start": "npm run prestart && 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",
|
||||
"build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build",
|
||||
"preview": "npx serve ./out",
|
||||
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
|
||||
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
|
||||
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
|
||||
"lint": "eslint . --ext mjs,js --quiet",
|
||||
"codeformat": "prettier --write .",
|
||||
"format-check": "prettier --check .",
|
||||
"check": "npm run format-check && npm run lint && npm run test"
|
||||
"report-undocumented": "npm run jsdoc-json && node undocumented.mjs > undocumented.json",
|
||||
"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"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
@ -43,10 +45,22 @@
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://strudel.tidalcycles.org",
|
||||
"dependencies": {
|
||||
"dependency-tree": "^9.0.0",
|
||||
"vitest": "^0.25.7",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/mini": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel.cycles/xen": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.25.7",
|
||||
"c8": "^7.12.0",
|
||||
"canvas": "^2.11.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"events": "^3.3.0",
|
||||
"gh-pages": "^4.0.0",
|
||||
"jsdoc": "^3.6.10",
|
||||
@ -54,7 +68,6 @@
|
||||
"jsdoc-to-markdown": "^7.1.1",
|
||||
"lerna": "^4.0.0",
|
||||
"prettier": "^2.8.1",
|
||||
"rollup-plugin-visualizer": "^5.8.1",
|
||||
"vitest": "^0.25.7"
|
||||
"rollup-plugin-visualizer": "^5.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ b: 3/2 - 7/4
|
||||
c: 7/4 - 2
|
||||
```
|
||||
|
||||
- [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/package-examples/packages/core/examples/canvas.html)
|
||||
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/package-examples/packages/core/examples/metro.html)
|
||||
- [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js).
|
||||
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html)
|
||||
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html)
|
||||
- [open minimal vite example](./examples/vite-vanilla-repl/)
|
||||
67
packages/core/animate.mjs
Normal file
67
packages/core/animate.mjs
Normal file
@ -0,0 +1,67 @@
|
||||
import { controls, Pattern, getDrawContext, silence, register, pure } from './index.mjs';
|
||||
const { createParams } = controls;
|
||||
|
||||
let clearColor = '#22222210';
|
||||
|
||||
Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } = {}) {
|
||||
window.frame && cancelAnimationFrame(window.frame);
|
||||
const ctx = getDrawContext();
|
||||
const { clientWidth: ww, clientHeight: wh } = ctx.canvas;
|
||||
let smearPart = smear === 0 ? '99' : Number((1 - smear) * 100).toFixed(0);
|
||||
smearPart = smearPart.length === 1 ? `0${smearPart}` : smearPart;
|
||||
clearColor = `#200010${smearPart}`;
|
||||
const render = (t) => {
|
||||
let frame;
|
||||
/* if (sync) {
|
||||
t = scheduler.now();
|
||||
frame = this.queryArc(t, t);
|
||||
} else { */
|
||||
t = Math.round(t);
|
||||
frame = this.slow(1000).queryArc(t, t);
|
||||
// }
|
||||
ctx.fillStyle = clearColor;
|
||||
ctx.fillRect(0, 0, ww, wh);
|
||||
frame.forEach((f) => {
|
||||
let { x, y, w, h, s, r, angle = 0, fill = 'darkseagreen' } = f.value;
|
||||
w *= ww;
|
||||
h *= wh;
|
||||
if (r !== undefined && angle !== undefined) {
|
||||
const radians = angle * 2 * Math.PI;
|
||||
const [cx, cy] = [(ww - w) / 2, (wh - h) / 2];
|
||||
x = cx + Math.cos(radians) * r * cx;
|
||||
y = cy + Math.sin(radians) * r * cy;
|
||||
} else {
|
||||
x *= ww - w;
|
||||
y *= wh - h;
|
||||
}
|
||||
const val = { ...f.value, x, y, w, h };
|
||||
ctx.fillStyle = fill;
|
||||
if (s === 'rect') {
|
||||
ctx.fillRect(x, y, w, h);
|
||||
} else if (s === 'ellipse') {
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x + w / 2, y + h / 2, w / 2, h / 2, 0, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
callback && callback(ctx, val, f);
|
||||
});
|
||||
window.frame = requestAnimationFrame(render);
|
||||
};
|
||||
window.frame = requestAnimationFrame(render);
|
||||
return silence;
|
||||
};
|
||||
|
||||
export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'fill', 'smear');
|
||||
|
||||
export const rescale = register('rescale', function (f, pat) {
|
||||
return pat.mul(x(f).w(f).y(f).h(f));
|
||||
});
|
||||
|
||||
export const moveXY = register('moveXY', function (dx, dy, pat) {
|
||||
return pat.add(x(dx).y(dy));
|
||||
});
|
||||
|
||||
export const zoomIn = register('zoomIn', function (f, pat) {
|
||||
const d = pure(1).sub(f).div(2);
|
||||
return pat.rescale(f).move(d, d);
|
||||
});
|
||||
175
packages/core/color.mjs
Normal file
175
packages/core/color.mjs
Normal file
@ -0,0 +1,175 @@
|
||||
export const colorMap = {
|
||||
aliceblue: '#f0f8ff',
|
||||
antiquewhite: '#faebd7',
|
||||
aqua: '#00ffff',
|
||||
aquamarine: '#7fffd4',
|
||||
azure: '#f0ffff',
|
||||
beige: '#f5f5dc',
|
||||
bisque: '#ffe4c4',
|
||||
black: '#000000',
|
||||
blanchedalmond: '#ffebcd',
|
||||
blue: '#0000ff',
|
||||
blueviolet: '#8a2be2',
|
||||
brown: '#a52a2a',
|
||||
burlywood: '#deb887',
|
||||
cadetblue: '#5f9ea0',
|
||||
chartreuse: '#7fff00',
|
||||
chocolate: '#d2691e',
|
||||
coral: '#ff7f50',
|
||||
cornflowerblue: '#6495ed',
|
||||
cornsilk: '#fff8dc',
|
||||
crimson: '#dc143c',
|
||||
cyan: '#00ffff',
|
||||
darkblue: '#00008b',
|
||||
darkcyan: '#008b8b',
|
||||
darkgoldenrod: '#b8860b',
|
||||
darkgray: '#a9a9a9',
|
||||
darkgreen: '#006400',
|
||||
darkgrey: '#a9a9a9',
|
||||
darkkhaki: '#bdb76b',
|
||||
darkmagenta: '#8b008b',
|
||||
darkolivegreen: '#556b2f',
|
||||
darkorange: '#ff8c00',
|
||||
darkorchid: '#9932cc',
|
||||
darkred: '#8b0000',
|
||||
darksalmon: '#e9967a',
|
||||
darkseagreen: '#8fbc8f',
|
||||
darkslateblue: '#483d8b',
|
||||
darkslategray: '#2f4f4f',
|
||||
darkslategrey: '#2f4f4f',
|
||||
darkturquoise: '#00ced1',
|
||||
darkviolet: '#9400d3',
|
||||
deeppink: '#ff1493',
|
||||
deepskyblue: '#00bfff',
|
||||
dimgray: '#696969',
|
||||
dimgrey: '#696969',
|
||||
dodgerblue: '#1e90ff',
|
||||
firebrick: '#b22222',
|
||||
floralwhite: '#fffaf0',
|
||||
forestgreen: '#228b22',
|
||||
fuchsia: '#ff00ff',
|
||||
gainsboro: '#dcdcdc',
|
||||
ghostwhite: '#f8f8ff',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
grey: '#808080',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
lawngreen: '#7cfc00',
|
||||
lemonchiffon: '#fffacd',
|
||||
lightblue: '#add8e6',
|
||||
lightcoral: '#f08080',
|
||||
lightcyan: '#e0ffff',
|
||||
lightgoldenrodyellow: '#fafad2',
|
||||
lightgray: '#d3d3d3',
|
||||
lightgreen: '#90ee90',
|
||||
lightgrey: '#d3d3d3',
|
||||
lightpink: '#ffb6c1',
|
||||
lightsalmon: '#ffa07a',
|
||||
lightseagreen: '#20b2aa',
|
||||
lightskyblue: '#87cefa',
|
||||
lightslategray: '#778899',
|
||||
lightslategrey: '#778899',
|
||||
lightsteelblue: '#b0c4de',
|
||||
lightyellow: '#ffffe0',
|
||||
lime: '#00ff00',
|
||||
limegreen: '#32cd32',
|
||||
linen: '#faf0e6',
|
||||
magenta: '#ff00ff',
|
||||
maroon: '#800000',
|
||||
mediumaquamarine: '#66cdaa',
|
||||
mediumblue: '#0000cd',
|
||||
mediumorchid: '#ba55d3',
|
||||
mediumpurple: '#9370db',
|
||||
mediumseagreen: '#3cb371',
|
||||
mediumslateblue: '#7b68ee',
|
||||
mediumspringgreen: '#00fa9a',
|
||||
mediumturquoise: '#48d1cc',
|
||||
mediumvioletred: '#c71585',
|
||||
midnightblue: '#191970',
|
||||
mintcream: '#f5fffa',
|
||||
mistyrose: '#ffe4e1',
|
||||
moccasin: '#ffe4b5',
|
||||
navajowhite: '#ffdead',
|
||||
navy: '#000080',
|
||||
oldlace: '#fdf5e6',
|
||||
olive: '#808000',
|
||||
olivedrab: '#6b8e23',
|
||||
orange: '#ffa500',
|
||||
orangered: '#ff4500',
|
||||
orchid: '#da70d6',
|
||||
palegoldenrod: '#eee8aa',
|
||||
palegreen: '#98fb98',
|
||||
paleturquoise: '#afeeee',
|
||||
palevioletred: '#db7093',
|
||||
papayawhip: '#ffefd5',
|
||||
peachpuff: '#ffdab9',
|
||||
peru: '#cd853f',
|
||||
pink: '#ffc0cb',
|
||||
plum: '#dda0dd',
|
||||
powderblue: '#b0e0e6',
|
||||
purple: '#800080',
|
||||
red: '#ff0000',
|
||||
rosybrown: '#bc8f8f',
|
||||
royalblue: '#4169e1',
|
||||
saddlebrown: '#8b4513',
|
||||
salmon: '#fa8072',
|
||||
sandybrown: '#f4a460',
|
||||
seagreen: '#2e8b57',
|
||||
seashell: '#fff5ee',
|
||||
sienna: '#a0522d',
|
||||
silver: '#c0c0c0',
|
||||
skyblue: '#87ceeb',
|
||||
slateblue: '#6a5acd',
|
||||
slategray: '#708090',
|
||||
slategrey: '#708090',
|
||||
snow: '#fffafa',
|
||||
springgreen: '#00ff7f',
|
||||
steelblue: '#4682b4',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
tomato: '#ff6347',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
wheat: '#f5deb3',
|
||||
white: '#ffffff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ffff00',
|
||||
yellowgreen: '#9acd32',
|
||||
};
|
||||
|
||||
export function convertColorToNumber(color) {
|
||||
// Convert color to lowercase for easier matching
|
||||
color = color.toLowerCase();
|
||||
|
||||
// If the color is a hex code, convert it to a number
|
||||
if (color[0] === '#') {
|
||||
return convertHexToNumber(color);
|
||||
}
|
||||
|
||||
// If the color is a named color, return the corresponding number
|
||||
if (colorMap[color] !== undefined) {
|
||||
return convertHexToNumber(colorMap[color]);
|
||||
}
|
||||
|
||||
// If the color is not recognized, return null
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function convertHexToNumber(hex) {
|
||||
// Remove the leading '#' from the hex code
|
||||
hex = hex.slice(1);
|
||||
|
||||
// Convert the hex code to a number
|
||||
return parseInt(hex, 16);
|
||||
}
|
||||
@ -11,15 +11,6 @@ const generic_params = [
|
||||
/**
|
||||
* 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
|
||||
* @param {string | Pattern} sound The sound / pattern of sounds to pick
|
||||
* @example
|
||||
@ -28,20 +19,36 @@ const generic_params = [
|
||||
*/
|
||||
['s', 's', 'sound'],
|
||||
/**
|
||||
* The note or sample number to choose for a synth or sampleset
|
||||
* Note names currently not working yet, but will hopefully soon. Just stick to numbers for now
|
||||
* Selects the given index from the sample map.
|
||||
* Numbers too high will wrap around.
|
||||
* `n` can also be used to play midi numbers, but it is recommended to use `note` instead.
|
||||
*
|
||||
* @name n
|
||||
* @param {string | number | Pattern} value note name, note number or sample number
|
||||
* @param {number | Pattern} value sample index starting from 0
|
||||
* @example
|
||||
* 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()
|
||||
* s("bd sd,hh*3").n("<0 1>")
|
||||
*/
|
||||
// also see https://github.com/tidalcycles/strudel/pull/63
|
||||
['f', 'n', 'The note or sample number to choose for a synth or sampleset'],
|
||||
['f', 'n', 'The sample number to choose for a synth or sampleset'],
|
||||
/**
|
||||
* Plays the given note name or midi number. A note name consists of
|
||||
*
|
||||
* - a letter (a-g or A-G)
|
||||
* - 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")
|
||||
*/
|
||||
['f', 'note', 'The note or pitch to play a sound or synth with'],
|
||||
//['s', 'toArg', 'for internal sound routing'],
|
||||
// ["f", "from", "for internal sound routing"),
|
||||
@ -51,6 +58,7 @@ const generic_params = [
|
||||
*
|
||||
* @name accelerate
|
||||
* @param {number | Pattern} amount acceleration.
|
||||
* @superdirtOnly
|
||||
* @example
|
||||
* s("sax").accelerate("<0 1 2 4 8 16>").slow(2).osc()
|
||||
*
|
||||
@ -75,26 +83,22 @@ const generic_params = [
|
||||
*
|
||||
* @name amp
|
||||
* @param {number | Pattern} amount gain.
|
||||
* @superdirtOnly
|
||||
* @example
|
||||
* s("bd*8").amp(".1*2 .5 .1*2 .5 .1 .5").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'amp', 'like @gain@, but linear.'],
|
||||
// TODO: find out why 0 does not work, and it generally seems not right
|
||||
/*
|
||||
* A pattern of numbers to specify the attack time of an envelope applied to each sample.
|
||||
/**
|
||||
* Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset.
|
||||
*
|
||||
* @name attack
|
||||
* @param {number | Pattern} attack time in seconds.
|
||||
* @example
|
||||
* n("c5 e5").s('superpiano').attack("<0 .1>").osc()
|
||||
* note("c3 e3").attack("<0 .1 .5>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'attack',
|
||||
'a pattern of numbers to specify the attack time (in seconds) of an envelope applied to each sample.',
|
||||
],
|
||||
['f', 'attack'],
|
||||
|
||||
/**
|
||||
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
|
||||
@ -107,18 +111,36 @@ const generic_params = [
|
||||
*/
|
||||
['f', 'bank', 'selects sound bank to use'],
|
||||
|
||||
// TODO: find out how this works?
|
||||
/*
|
||||
* Envelope decay time = the time it takes after the attack time to reach the sustain level.
|
||||
/**
|
||||
* Amplitude 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.
|
||||
*
|
||||
* @name decay
|
||||
* @param {number | Pattern} time decay time in seconds
|
||||
* @example
|
||||
* s("sax").cut(1).decay("<.1 .2 .3 .4>").sustain(0).osc()
|
||||
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
|
||||
*
|
||||
*/
|
||||
['f', 'decay', ''],
|
||||
/**
|
||||
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
|
||||
*
|
||||
* @name sustain
|
||||
* @param {number | Pattern} gain sustain level between 0 and 1
|
||||
* @example
|
||||
* note("c3 e3").decay(.2).sustain("<0 .1 .4 .6 1>")
|
||||
*
|
||||
*/
|
||||
['f', 'sustain', ''],
|
||||
/**
|
||||
* Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero.
|
||||
*
|
||||
* @name release
|
||||
* @param {number | Pattern} time release time in seconds
|
||||
* @example
|
||||
* note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'release',
|
||||
@ -131,25 +153,29 @@ const generic_params = [
|
||||
],
|
||||
// TODO: in tidal, it seems to be normalized
|
||||
/**
|
||||
* Sets the center frequency of the band-pass filter.
|
||||
* Sets the center frequency of the **b**and-**p**ass **f**ilter.
|
||||
*
|
||||
* @name bandf
|
||||
* @name bpf
|
||||
* @param {number | Pattern} frequency center frequency
|
||||
* @synonyms bandf
|
||||
* @example
|
||||
* s("bd sd,hh*3").bandf("<1000 2000 4000 8000>")
|
||||
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>")
|
||||
*
|
||||
*/
|
||||
['f', 'bpf', ''],
|
||||
['f', 'bandf', 'A pattern of numbers from 0 to 1. Sets the center frequency of the band-pass filter.'],
|
||||
// TODO: in tidal, it seems to be normalized
|
||||
/**
|
||||
* Sets the q-factor of the band-pass filter
|
||||
* Sets the **b**and-**p**ass **q**-factor (resonance)
|
||||
*
|
||||
* @name bandq
|
||||
* @name bpq
|
||||
* @param {number | Pattern} q q factor
|
||||
* @synonyms bandq
|
||||
* @example
|
||||
* s("bd sd").bandf(500).bandq("<0 1 2 3>")
|
||||
* s("bd sd").bpf(500).bpq("<0 1 2 3>")
|
||||
*
|
||||
*/
|
||||
['f', 'bpq', ''],
|
||||
['f', 'bandq', 'a pattern of anumbers from 0 to 1. Sets the q-factor of the band-pass filter.'],
|
||||
/**
|
||||
* 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.
|
||||
@ -249,7 +275,7 @@ const generic_params = [
|
||||
* @name cut
|
||||
* @param {number | Pattern} group cut group number
|
||||
* @example
|
||||
* s("bd sax").cut(1).osc()
|
||||
* s("rd*4").cut(1)
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -258,57 +284,53 @@ const generic_params = [
|
||||
'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 low-pass filter.
|
||||
* Applies the cutoff frequency of the **l**ow-**p**ass **f**ilter.
|
||||
*
|
||||
* @name cutoff
|
||||
* @name lpf
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @synonyms cutoff
|
||||
* @example
|
||||
* s("bd sd,hh*3").cutoff("<4000 2000 1000 500 200 100>")
|
||||
* s("bd sd,hh*3").lpf("<4000 2000 1000 500 200 100>")
|
||||
*
|
||||
*/
|
||||
// TODO: add lpf synonym
|
||||
['f', 'lpf'],
|
||||
['f', 'cutoff', 'a pattern of numbers from 0 to 1. Applies the cutoff frequency of the low-pass filter.'],
|
||||
/**
|
||||
* Applies the cutoff frequency of the high-pass filter.
|
||||
* Applies the cutoff frequency of the **h**igh-**p**ass **f**ilter.
|
||||
*
|
||||
* @name hcutoff
|
||||
* @name hpf
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @synonyms hcutoff
|
||||
* @example
|
||||
* s("bd sd,hh*4").hcutoff("<4000 2000 1000 500 200 100>")
|
||||
* s("bd sd,hh*4").hpf("<4000 2000 1000 500 200 100>")
|
||||
*
|
||||
*/
|
||||
// TODO: add hpf synonym
|
||||
[
|
||||
'f',
|
||||
'hcutoff',
|
||||
'a pattern of numbers from 0 to 1. Applies the cutoff frequency of the high-pass filter. Also has alias @hpf@',
|
||||
],
|
||||
['f', 'hpf', ''],
|
||||
['f', 'hcutoff', ''],
|
||||
/**
|
||||
* Applies the resonance of the high-pass filter.
|
||||
* Controls the **h**igh-**p**ass **q**-value.
|
||||
*
|
||||
* @name hresonance
|
||||
* @name hpq
|
||||
* @param {number | Pattern} q resonance factor between 0 and 50
|
||||
* @synonyms hresonance
|
||||
* @example
|
||||
* s("bd sd,hh*4").hcutoff(2000).hresonance("<0 10 20 30>")
|
||||
* s("bd sd,hh*4").hpf(2000).hpq("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'hresonance',
|
||||
'a pattern of numbers from 0 to 1. Applies the resonance of the high-pass filter. Has alias @hpq@',
|
||||
],
|
||||
// TODO: add hpq synonym
|
||||
['f', 'hresonance', ''],
|
||||
['f', 'hpq', ''],
|
||||
/**
|
||||
* Applies the cutoff frequency of the low-pass filter.
|
||||
* Controls the **l**ow-**p**ass **q**-value.
|
||||
*
|
||||
* @name resonance
|
||||
* @name lpq
|
||||
* @param {number | Pattern} q resonance factor between 0 and 50
|
||||
* @synonyms resonance
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(2000).resonance("<0 10 20 30>")
|
||||
* s("bd sd,hh*4").lpf(2000).lpq("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
['f', 'resonance', 'a pattern of numbers from 0 to 1. Specifies the resonance of the low-pass filter.'],
|
||||
// TODO: add lpq synonym?
|
||||
['f', 'lpq'],
|
||||
['f', 'resonance', ''],
|
||||
/**
|
||||
* DJ filter, below 0.5 is low pass filter, above is high pass filter.
|
||||
*
|
||||
@ -321,17 +343,36 @@ const generic_params = [
|
||||
['f', 'djf', 'DJ filter, below 0.5 is low pass filter, above is high pass filter.'],
|
||||
// ['f', 'cutoffegint', ''],
|
||||
// TODO: does not seem to work
|
||||
/*
|
||||
/**
|
||||
* Sets the level of the delay signal.
|
||||
*
|
||||
* @name delay
|
||||
* @param {number | Pattern} level between 0 and 1
|
||||
* @example
|
||||
* s("bd").delay("<0 .5 .75 1>").osc()
|
||||
* s("bd").delay("<0 .25 .5 1>")
|
||||
*
|
||||
*/
|
||||
['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.
|
||||
* Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
|
||||
*
|
||||
* @name delayfeedback
|
||||
* @param {number | Pattern} feedback between 0 and 1
|
||||
* @example
|
||||
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>").slow(2)
|
||||
*
|
||||
*/
|
||||
['f', 'delayfeedback', 'a pattern of numbers from 0 to 1. Sets the amount of delay feedback.'],
|
||||
/**
|
||||
* Sets the time of the delay effect.
|
||||
*
|
||||
* @name delaytime
|
||||
* @param {number | Pattern} seconds between 0 and Infinity
|
||||
* @example
|
||||
* s("bd").delay(.25).delaytime("<.125 .25 .5 1>").slow(2)
|
||||
*
|
||||
*/
|
||||
['f', 'delaytime', 'a pattern of numbers from 0 to 1. Sets the length of the delay.'],
|
||||
/* // TODO: test
|
||||
* Specifies whether delaytime is calculated relative to cps.
|
||||
@ -352,6 +393,7 @@ const generic_params = [
|
||||
*
|
||||
* @name detune
|
||||
* @param {number | Pattern} amount between 0 and 1
|
||||
* @superdirtOnly
|
||||
* @example
|
||||
* n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc()
|
||||
*
|
||||
@ -364,6 +406,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} dry 0 = wet, 1 = dry
|
||||
* @example
|
||||
* n("[0,3,7](3,8)").s("superpiano").room(.7).dry("<0 .5 .75 1>").osc()
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -422,6 +465,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} wet between 0 and 1
|
||||
* @example
|
||||
* n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc()
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
['f', 'leslie', ''],
|
||||
@ -432,6 +476,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} rate 6.7 for fast, 0.7 for slow
|
||||
* @example
|
||||
* 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)
|
||||
@ -443,6 +488,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} meters somewhere between 0 and 1
|
||||
* @example
|
||||
* n("0,4,7").s("supersquare").leslie(1).lrate(2).lsize("<.1 .5 1>").osc()
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
['f', 'lsize', ''],
|
||||
@ -478,17 +524,22 @@ const generic_params = [
|
||||
* @param {number | Pattern} octave octave number
|
||||
* @example
|
||||
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc()
|
||||
* @superDirtOnly
|
||||
*/
|
||||
['i', 'octave', ''],
|
||||
['f', 'offset', ''], // TODO: what is this? not found in tidal doc
|
||||
// ['f', 'ophatdecay', ''],
|
||||
// TODO: example
|
||||
/**
|
||||
* 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.
|
||||
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
||||
*
|
||||
* @name orbit
|
||||
* @param {number | Pattern} number
|
||||
*
|
||||
* @example
|
||||
* stack(
|
||||
* s("hh*3").delay(.5).delaytime(.25).orbit(1),
|
||||
* s("~ sd").delay(.5).delaytime(.125).orbit(2)
|
||||
* )
|
||||
*/
|
||||
[
|
||||
'i',
|
||||
@ -570,21 +621,22 @@ const generic_params = [
|
||||
* @name room
|
||||
* @param {number | Pattern} level between 0 and 1
|
||||
* @example
|
||||
* s("bd sd").room("<0 .2 .4 .6 .8 1>").osc()
|
||||
* s("bd sd").room("<0 .2 .4 .6 .8 1>")
|
||||
*
|
||||
*/
|
||||
['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}.
|
||||
*
|
||||
* @name size
|
||||
* @param {number | Pattern} size between 0 and 1
|
||||
* @name roomsize
|
||||
* @synonyms size
|
||||
* @param {number | Pattern} size between 0 and 10
|
||||
* @example
|
||||
* s("bd sd").room(.8).size("<0 .2 .4 .6 .8 1>").osc()
|
||||
* s("bd sd").room(.8).roomsize("<0 1 2 4 8>")
|
||||
*
|
||||
*/
|
||||
// TODO: find out why :
|
||||
// s("bd sd").room(.8).size("<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?
|
||||
[
|
||||
'f',
|
||||
@ -620,9 +672,9 @@ const generic_params = [
|
||||
* @name speed
|
||||
* @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards.
|
||||
* @example
|
||||
* s("bd").speed("<1 2 4 1 -2 -4>").osc()
|
||||
* s("bd").speed("<1 2 4 1 -2 -4>")
|
||||
* @example
|
||||
* speed("1 1.5*2 [2 1.1]").s("sax").cut(1).osc()
|
||||
* speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -637,6 +689,7 @@ const generic_params = [
|
||||
* @param {number | string | Pattern} unit see description above
|
||||
* @example
|
||||
* speed("1 2 .5 3").s("bd").unit("c").osc()
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -653,6 +706,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} squiz Try passing multiples of 2 to it - 2, 4, 8 etc.
|
||||
* @example
|
||||
* squiz("2 4/2 6 [8 16]").s("bd").osc()
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
['f', 'squiz', ''],
|
||||
@ -759,6 +813,16 @@ const generic_params = [
|
||||
['f', 'uid', ''],
|
||||
['f', 'val', ''],
|
||||
['f', 'cps', ''],
|
||||
/**
|
||||
* 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)
|
||||
*
|
||||
*/
|
||||
['f', 'clip', ''],
|
||||
];
|
||||
|
||||
|
||||
@ -8,13 +8,10 @@ import createClock from './zyklus.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
|
||||
export class Cyclist {
|
||||
worker;
|
||||
pattern;
|
||||
started = false;
|
||||
cps = 1; // TODO
|
||||
getTime;
|
||||
phase = 0;
|
||||
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
||||
this.started = false;
|
||||
this.cps = 1; // TODO
|
||||
this.phase = 0;
|
||||
this.getTime = getTime;
|
||||
this.onToggle = onToggle;
|
||||
this.latency = latency;
|
||||
@ -49,6 +46,9 @@ export class Cyclist {
|
||||
getPhase() {
|
||||
return this.getTime() - this.origin - this.latency;
|
||||
}
|
||||
now() {
|
||||
return this.getTime() - this.origin + this.clock.minLatency;
|
||||
}
|
||||
setStarted(v) {
|
||||
this.started = v;
|
||||
this.onToggle?.(v);
|
||||
|
||||
@ -59,3 +59,9 @@ export const cleanupDraw = (clearScreen = true) => {
|
||||
clearInterval(window.strudelScheduler);
|
||||
}
|
||||
};
|
||||
|
||||
Pattern.prototype.onPaint = function (onPaint) {
|
||||
// this is evil! TODO: add pattern.context
|
||||
this.context = { onPaint };
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -1,47 +1,97 @@
|
||||
/*
|
||||
euclid.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/euclid.mjs>
|
||||
euclid.mjs - Bjorklund/Euclidean/Diaspora rhythms
|
||||
Copyright (C) 2023 Rohan Drape and strudel contributors
|
||||
|
||||
See <https://github.com/tidalcycles/strudel/blob/main/packages/core/euclid.mjs> for authors of this file.
|
||||
|
||||
The Bjorklund algorithm implementation is ported from the Haskell Music Theory Haskell module by Rohan Drape -
|
||||
https://rohandrape.net/?t=hmt
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, timeCat } from './pattern.mjs';
|
||||
import bjork from 'bjork';
|
||||
import { rotate } from './util.mjs';
|
||||
import { Pattern, timeCat, register, silence } from './pattern.mjs';
|
||||
import { rotate, flatten } from './util.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
const euclid = (pulses, steps, rotation = 0) => {
|
||||
const b = bjork(steps, pulses);
|
||||
if (rotation) {
|
||||
return rotate(b, -rotation);
|
||||
}
|
||||
return b;
|
||||
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 [ons, offs] = n;
|
||||
const [xs, ys] = x;
|
||||
const [_xs, __xs] = splitAt(offs, xs);
|
||||
return [
|
||||
[offs, ons - offs],
|
||||
[zipWith((a, b) => a.concat(b), _xs, ys), __xs],
|
||||
];
|
||||
};
|
||||
|
||||
const right = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
const [xs, ys] = x;
|
||||
const [_ys, __ys] = splitAt(ons, ys);
|
||||
const result = [
|
||||
[ons, offs - ons],
|
||||
[zipWith((a, b) => a.concat(b), xs, _ys), __ys],
|
||||
];
|
||||
return result;
|
||||
};
|
||||
|
||||
const _bjork = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x)));
|
||||
};
|
||||
|
||||
export const bjork = function (ons, steps) {
|
||||
const offs = steps - ons;
|
||||
const x = Array(ons).fill([1]);
|
||||
const y = Array(offs).fill([0]);
|
||||
const result = _bjork([ons, offs], [x, y]);
|
||||
return flatten(result[1][0]).concat(flatten(result[1][1]));
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the structure of the pattern to form an euclidean rhythm.
|
||||
* Euclidian rhythms are rhythms obtained using the greatest common divisor of two numbers.
|
||||
* They were described in 2004 by Godfried Toussaint, a canadian computer scientist.
|
||||
* Euclidian rhythms are really useful for computer/algorithmic music because they can accurately
|
||||
* describe a large number of rhythms used in the most important music world traditions.
|
||||
* Euclidian rhythms are rhythms obtained using the greatest common
|
||||
* divisor of two numbers. They were described in 2004 by Godfried
|
||||
* Toussaint, a canadian computer scientist. Euclidian rhythms are
|
||||
* really useful for computer/algorithmic music because they can
|
||||
* describe a large number of rhythms with a couple of numbers.
|
||||
*
|
||||
* @memberof Pattern
|
||||
* @name euclid
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @param {number} rotation (optional) offset in steps
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* // The Cuban tresillo pattern.
|
||||
* note("c3").euclid(3,8)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
|
||||
* @memberof Pattern
|
||||
* @name euclidRot
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @param {number} rotation offset in steps
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* // A Samba rhythm necklace from Brazil
|
||||
* note("c3").euclidRot(3,16,14)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @example // A thirteenth century Persian rhythm called Khafif-e-ramal.
|
||||
* note("c3").euclid(2,5)
|
||||
* @example // The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad.
|
||||
* note("c3").euclid(3,4)
|
||||
* @example // Another thirteenth century Persian rhythm by the name of Khafif-e-ramal, as well as a Rumanian folk-dance rhythm.
|
||||
* note("c3").euclid(3,5,2)
|
||||
* note("c3").euclidRot(3,5,2)
|
||||
* @example // A Ruchenitza rhythm used in a Bulgarian folk-dance.
|
||||
* note("c3").euclid(3,7)
|
||||
* @example // The Cuban tresillo pattern.
|
||||
@ -71,34 +121,57 @@ const euclid = (pulses, steps, rotation = 0) => {
|
||||
* @example // A common West African bell pattern.
|
||||
* note("c3").euclid(7,12)
|
||||
* @example // A Samba rhythm necklace from Brazil.
|
||||
* note("c3").euclid(7,16,14)
|
||||
* note("c3").euclidRot(7,16,14)
|
||||
* @example // A rhythm necklace used in the Central African Republic.
|
||||
* note("c3").euclid(9,16)
|
||||
* @example // A rhythm necklace of the Aka Pygmies of Central Africa.
|
||||
* note("c3").euclid(11,24,14)
|
||||
* note("c3").euclidRot(11,24,14)
|
||||
* @example // Another rhythm necklace of the Aka Pygmies of the upper Sangha.
|
||||
* note("c3").euclid(13,24,5)
|
||||
* note("c3").euclidRot(13,24,5)
|
||||
*/
|
||||
Pattern.prototype.euclid = function (pulses, steps, rotation = 0) {
|
||||
return this.struct(euclid(pulses, steps, rotation));
|
||||
|
||||
const _euclidRot = function (pulses, steps, rotation) {
|
||||
const b = bjork(pulses, steps);
|
||||
if (rotation) {
|
||||
return rotate(b, -rotation);
|
||||
}
|
||||
return b;
|
||||
};
|
||||
|
||||
export const euclid = register('euclid', function (pulses, steps, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, 0));
|
||||
});
|
||||
|
||||
export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], function (pulses, steps, rotation, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, rotation));
|
||||
});
|
||||
|
||||
/**
|
||||
* Similar to `.euclid`, but each pulse is held until the next pulse, so there will be no gaps.
|
||||
* Similar to `euclid`, but each pulse is held until the next pulse,
|
||||
* so there will be no gaps.
|
||||
* @name euclidLegato
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* n("g2").decay(.1).sustain(.3).euclidLegato(3,8)
|
||||
*/
|
||||
Pattern.prototype.euclidLegato = function (pulses, steps, rotation = 0) {
|
||||
const bin_pat = euclid(pulses, steps, rotation);
|
||||
const firstOne = bin_pat.indexOf(1);
|
||||
const gapless = rotate(bin_pat, firstOne)
|
||||
|
||||
const _euclidLegato = function (pulses, steps, rotation, pat) {
|
||||
if (pulses < 1) {
|
||||
return silence;
|
||||
}
|
||||
const bin_pat = _euclidRot(pulses, steps, rotation);
|
||||
const gapless = bin_pat
|
||||
.join('')
|
||||
.split('1')
|
||||
.slice(1)
|
||||
.map((s) => [s.length + 1, true]);
|
||||
return this.struct(timeCat(...gapless)).late(Fraction(firstOne).div(steps));
|
||||
return pat.struct(timeCat(...gapless));
|
||||
};
|
||||
|
||||
export default euclid;
|
||||
export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) {
|
||||
return _euclidLegato(pulses, steps, 0, pat);
|
||||
});
|
||||
|
||||
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
||||
return _euclidLegato(pulses, steps, rotation, pat);
|
||||
});
|
||||
|
||||
@ -19,7 +19,14 @@ export const evalScope = async (...args) => {
|
||||
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
|
||||
}
|
||||
});
|
||||
Object.assign(globalThis, ...modules);
|
||||
// Object.assign(globalThis, ...modules);
|
||||
// below is a fix for above commented out line
|
||||
// same error as https://github.com/vitest-dev/vitest/issues/1807 when running this on astro server
|
||||
modules.forEach((module) => {
|
||||
Object.entries(module).forEach(([name, value]) => {
|
||||
globalThis[name] = value;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function safeEval(str, options = {}) {
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<input
|
||||
type="text"
|
||||
id="text"
|
||||
value="cat('a', 'b')"
|
||||
value="seq('a', ['b', 'c'])"
|
||||
style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<div id="output"></div>
|
||||
<script type="module">
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
|
||||
Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval
|
||||
const input = document.getElementById('text');
|
||||
const getEvents = () => {
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
<input
|
||||
type="text"
|
||||
id="text"
|
||||
value="cat('orange', 'indigo')"
|
||||
value="seq('tomato', 'indigo', ['white', 'steelblue']).fast(4)"
|
||||
style="width: 100%; font-size: 2em; background: black; color: white; outline: none; position: absolute; top: 0"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
|
||||
// this adds all strudel functions to the global scope, to be used by eval
|
||||
Object.assign(window, strudel);
|
||||
// setup elements
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
<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()
|
||||
-->
|
||||
@ -1,44 +0,0 @@
|
||||
<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>
|
||||
@ -6,46 +6,36 @@
|
||||
<title>Buildless Vanilla Strudel REPL</title>
|
||||
</head>
|
||||
<body style="margin: 0; background: #222">
|
||||
<div style="display: grid; height: 100vh">
|
||||
<div style="display: grid; height: 100vh; grid-template-rows: 32px auto">
|
||||
<button id="start" style="width: 100vw; height: 32px">evaluate</button>
|
||||
<textarea
|
||||
id="text"
|
||||
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
|
||||
spellcheck="false"
|
||||
></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.3.2';
|
||||
import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.3.2';
|
||||
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.3.2';
|
||||
import { getAudioContext, webaudioOutput } from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.3.3';
|
||||
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.6.8';
|
||||
import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0';
|
||||
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.6.0';
|
||||
import {
|
||||
getAudioContext,
|
||||
webaudioOutput,
|
||||
initAudioOnFirstClick,
|
||||
} from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0';
|
||||
|
||||
initAudioOnFirstClick();
|
||||
const ctx = getAudioContext();
|
||||
const input = document.getElementById('text');
|
||||
input.innerHTML = getTune();
|
||||
|
||||
evalScope(
|
||||
controls,
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/core@0.3.2'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.3.2'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.3.3'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.3.3'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.6.0'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0'),
|
||||
);
|
||||
|
||||
const { evaluate } = repl({
|
||||
@ -53,10 +43,7 @@
|
||||
getTime: () => ctx.currentTime,
|
||||
transpiler,
|
||||
});
|
||||
document.getElementById('start').addEventListener('click', () => {
|
||||
ctx.resume();
|
||||
evaluate(input.value);
|
||||
});
|
||||
document.getElementById('start').addEventListener('click', () => evaluate(input.value));
|
||||
|
||||
function getTune() {
|
||||
return `await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { controls, repl, evalScope, setStringParser } from '@strudel.cycles/core';
|
||||
import { mini } from '@strudel.cycles/mini';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
// import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import { controls, repl, evalScope } from '@strudel.cycles/core';
|
||||
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import tune from './tune.mjs';
|
||||
|
||||
const ctx = getAudioContext();
|
||||
const input = document.getElementById('text');
|
||||
input.innerHTML = tune;
|
||||
initAudioOnFirstClick();
|
||||
|
||||
evalScope(
|
||||
controls,
|
||||
@ -16,14 +16,13 @@ evalScope(
|
||||
import('@strudel.cycles/tonal'),
|
||||
);
|
||||
|
||||
setStringParser(mini);
|
||||
|
||||
const { evaluate } = repl({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => ctx.currentTime,
|
||||
// transpiler,
|
||||
transpiler,
|
||||
});
|
||||
document.getElementById('start').addEventListener('click', () => {
|
||||
ctx.resume();
|
||||
console.log('eval', input.value);
|
||||
evaluate(input.value);
|
||||
});
|
||||
|
||||
@ -1,885 +0,0 @@
|
||||
{
|
||||
"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,5 +11,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"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(
|
||||
// amen
|
||||
@ -29,11 +29,3 @@ stack(
|
||||
,
|
||||
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
|
||||
).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"))`;
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
<!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>
|
||||
@ -21,6 +21,7 @@ export * from './repl.mjs';
|
||||
export * from './logger.mjs';
|
||||
export * from './time.mjs';
|
||||
export * from './draw.mjs';
|
||||
export * from './animate.mjs';
|
||||
export * from './pianoroll.mjs';
|
||||
export * from './ui.mjs';
|
||||
export { default as drawLine } from './drawLine.mjs';
|
||||
|
||||
@ -2,7 +2,7 @@ export const logKey = 'strudel.log';
|
||||
|
||||
export function logger(message, type, data = {}) {
|
||||
console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px');
|
||||
if (typeof CustomEvent !== 'undefined') {
|
||||
if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(logKey, {
|
||||
detail: {
|
||||
|
||||
1639
packages/core/package-lock.json
generated
1639
packages/core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,17 @@
|
||||
{
|
||||
"name": "@strudel.cycles/core",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.8",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run"
|
||||
"test": "vitest run",
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "pnpm build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -25,8 +31,11 @@
|
||||
},
|
||||
"homepage": "https://strudel.tidalcycles.org",
|
||||
"dependencies": {
|
||||
"bjork": "^0.0.1",
|
||||
"fraction.js": "^4.2.0"
|
||||
},
|
||||
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2"
|
||||
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
|
||||
"devDependencies": {
|
||||
"vite": "^3.2.2",
|
||||
"vitest": "^0.25.7"
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,14 +23,15 @@ export const setStringParser = (parser) => (stringParser = parser);
|
||||
|
||||
/** @class Class representing a pattern. */
|
||||
export class Pattern {
|
||||
_Pattern = true; // this property is used to detect if a pattern that fails instanceof Pattern is an instance of another Pattern
|
||||
/**
|
||||
* 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}.
|
||||
* @noAutocomplete
|
||||
*/
|
||||
constructor(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
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -39,8 +40,11 @@ export class Pattern {
|
||||
/**
|
||||
* Returns a new pattern, with the function applied to the value of
|
||||
* each hap. It has the alias {@link Pattern#fmap}.
|
||||
* @param {Function} func
|
||||
* @synonyms fmap
|
||||
* @param {Function} func to to apply to the value
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "0 1 2".withValue(v => v + 10).log()
|
||||
*/
|
||||
withValue(func) {
|
||||
return new Pattern((state) => this.query(state).map((hap) => hap.withValue(func)));
|
||||
@ -48,15 +52,22 @@ export class Pattern {
|
||||
|
||||
/**
|
||||
* see {@link Pattern#withValue}
|
||||
* @noAutocomplete
|
||||
*/
|
||||
fmap(func) {
|
||||
return this.withValue(func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes 'this' is a pattern of functions, and given a function to
|
||||
* resolve wholes, applies a given pattern of values to that
|
||||
* pattern of functions.
|
||||
* @param {Function} whole_func
|
||||
* @param {Function} func
|
||||
* @noAutocomplete
|
||||
* @returns Pattern
|
||||
*/
|
||||
appWhole(whole_func, pat_val) {
|
||||
// Assumes 'this' is a pattern of functions, and given a function to
|
||||
// resolve wholes, applies a given pattern of values to that
|
||||
// pattern of functions.
|
||||
const pat_func = this;
|
||||
const query = function (state) {
|
||||
const hap_funcs = pat_func.query(state);
|
||||
@ -89,6 +100,7 @@ export class Pattern {
|
||||
* 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.
|
||||
* @param {Pattern} pat_val
|
||||
* @noAutocomplete
|
||||
* @returns Pattern
|
||||
*/
|
||||
appBoth(pat_val) {
|
||||
@ -109,6 +121,7 @@ export class Pattern {
|
||||
* are preserved from the pattern of functions (often referred to as the left
|
||||
* hand or inner pattern).
|
||||
* @param {Pattern} pat_val
|
||||
* @noAutocomplete
|
||||
* @returns Pattern
|
||||
*/
|
||||
appLeft(pat_val) {
|
||||
@ -139,6 +152,7 @@ export class Pattern {
|
||||
* pattern of values, i.e. structure is preserved from the right hand/outer
|
||||
* pattern.
|
||||
* @param {Pattern} pat_val
|
||||
* @noAutocomplete
|
||||
* @returns Pattern
|
||||
*/
|
||||
appRight(pat_val) {
|
||||
@ -324,9 +338,15 @@ export class Pattern {
|
||||
* const haps = pattern.queryArc(0, 1)
|
||||
* console.log(haps)
|
||||
* silence
|
||||
* @noAutocomplete
|
||||
*/
|
||||
queryArc(begin, end) {
|
||||
return this.query(new State(new TimeSpan(begin, end)));
|
||||
try {
|
||||
return this.query(new State(new TimeSpan(begin, end)));
|
||||
} catch (err) {
|
||||
logger(`[query]: ${err.message}`, 'error');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -334,6 +354,7 @@ export class Pattern {
|
||||
* some calculations easier to express, as all haps are then constrained to
|
||||
* happen within a cycle.
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
splitQueries() {
|
||||
const pat = this;
|
||||
@ -348,6 +369,7 @@ export class Pattern {
|
||||
* timespan before passing it to the original pattern.
|
||||
* @param {Function} func the function to apply
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withQuerySpan(func) {
|
||||
return new Pattern((state) => this.query(state.withSpan(func)));
|
||||
@ -369,6 +391,7 @@ export class Pattern {
|
||||
* begin and end time of the query timespan.
|
||||
* @param {Function} func the function to apply
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withQueryTime(func) {
|
||||
return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func))));
|
||||
@ -380,6 +403,7 @@ export class Pattern {
|
||||
* present, `whole` timespans).
|
||||
* @param {Function} func
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withHapSpan(func) {
|
||||
return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
|
||||
@ -390,6 +414,7 @@ export class Pattern {
|
||||
* begin and end time of the hap timespans.
|
||||
* @param {Function} func the function to apply
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withHapTime(func) {
|
||||
return this.withHapSpan((span) => span.withTime(func));
|
||||
@ -399,6 +424,7 @@ export class Pattern {
|
||||
* Returns a new pattern with the given function applied to the list of haps returned by every query.
|
||||
* @param {Function} func
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withHaps(func) {
|
||||
return new Pattern((state) => func(this.query(state)));
|
||||
@ -408,6 +434,7 @@ export class Pattern {
|
||||
* As with {@link Pattern#withHaps}, but applies the function to every hap, rather than every list of haps.
|
||||
* @param {Function} func
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withHap(func) {
|
||||
return this.withHaps((haps) => haps.map(func));
|
||||
@ -417,6 +444,7 @@ export class Pattern {
|
||||
* Returns a new pattern with the context field set to every hap set to the given value.
|
||||
* @param {*} context
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
setContext(context) {
|
||||
return this.withHap((hap) => hap.setContext(context));
|
||||
@ -426,6 +454,7 @@ export class Pattern {
|
||||
* Returns a new pattern with the given function applied to the context field of every hap.
|
||||
* @param {Function} func
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withContext(func) {
|
||||
return this.withHap((hap) => hap.setContext(func(hap.context)));
|
||||
@ -434,6 +463,7 @@ export class Pattern {
|
||||
/**
|
||||
* Returns a new pattern with the context field of every hap set to an empty object.
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
stripContext() {
|
||||
return this.withHap((hap) => hap.setContext({}));
|
||||
@ -445,6 +475,7 @@ export class Pattern {
|
||||
* @param {Number} start
|
||||
* @param {Number} end
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withLocation(start, end) {
|
||||
const location = {
|
||||
@ -487,6 +518,7 @@ export class Pattern {
|
||||
* 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
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
filterHaps(hap_test) {
|
||||
return new Pattern((state) => this.query(state).filter(hap_test));
|
||||
@ -497,6 +529,7 @@ export class Pattern {
|
||||
* inside haps.
|
||||
* @param {Function} value_test
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
filterValues(value_test) {
|
||||
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value)));
|
||||
@ -506,6 +539,7 @@ export class Pattern {
|
||||
* Returns a new pattern, with haps containing undefined values removed from
|
||||
* query results.
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
removeUndefineds() {
|
||||
return this.filterValues((val) => val != undefined);
|
||||
@ -516,6 +550,7 @@ export class Pattern {
|
||||
* with an onset is one with a `whole` timespan that begins at the same time
|
||||
* as its `part` timespan.
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
onsetsOnly() {
|
||||
// Returns a new pattern that will only return haps where the start
|
||||
@ -528,6 +563,7 @@ export class Pattern {
|
||||
* Returns a new pattern, with 'continuous' haps (those without 'whole'
|
||||
* timespans) removed from query results.
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
discreteOnly() {
|
||||
// removes continuous haps that don't have a 'whole' timespan
|
||||
@ -537,6 +573,7 @@ export class Pattern {
|
||||
/**
|
||||
* Combines adjacent haps with the same value and whole. Only
|
||||
* intended for use in tests.
|
||||
* @noAutocomplete
|
||||
*/
|
||||
defragmentHaps() {
|
||||
// remove continuous haps
|
||||
@ -591,6 +628,7 @@ export class Pattern {
|
||||
* @param {Boolean} with_context - set to true, otherwise the context field
|
||||
* will be stripped from the resulting haps.
|
||||
* @returns [Hap]
|
||||
* @noAutocomplete
|
||||
*/
|
||||
firstCycle(with_context = false) {
|
||||
var self = this;
|
||||
@ -602,6 +640,7 @@ export class Pattern {
|
||||
|
||||
/**
|
||||
* Accessor for a list of values returned by querying the first cycle.
|
||||
* @noAutocomplete
|
||||
*/
|
||||
get firstCycleValues() {
|
||||
return this.firstCycle().map((hap) => hap.value);
|
||||
@ -609,6 +648,7 @@ export class Pattern {
|
||||
|
||||
/**
|
||||
* More human-readable version of the {@link Pattern#firstCycleValues} accessor.
|
||||
* @noAutocomplete
|
||||
*/
|
||||
get showFirstCycle() {
|
||||
return this.firstCycle().map(
|
||||
@ -620,6 +660,7 @@ export class Pattern {
|
||||
* Returns a new pattern, which returns haps sorted in temporal order. Mainly
|
||||
* of use when comparing two patterns for equality, in tests.
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
sortHapsByPart() {
|
||||
return this.withHaps((haps) =>
|
||||
@ -676,9 +717,10 @@ export class Pattern {
|
||||
// Methods without corresponding toplevel functions
|
||||
|
||||
/**
|
||||
* Layers the result of the given function(s). Like {@link superimpose}, but without the original pattern:
|
||||
* Layers the result of the given function(s). Like {@link Pattern.superimpose}, but without the original pattern:
|
||||
* @name layer
|
||||
* @memberof Pattern
|
||||
* @synonyms apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
|
||||
@ -712,7 +754,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* s("hh*2").stack(
|
||||
* n("c2(3,8)")
|
||||
* note("c2(3,8)")
|
||||
* )
|
||||
*/
|
||||
stack(...pats) {
|
||||
@ -724,12 +766,13 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given pattern(s) to the current pattern. Synonyms: .sequence .fastcat
|
||||
* Appends the given pattern(s) to the current pattern.
|
||||
* @name seq
|
||||
* @memberof Pattern
|
||||
* @synonyms sequence, fastcat
|
||||
* @example
|
||||
* s("hh*2").seq(
|
||||
* n("c2(3,8)")
|
||||
* note("c2(3,8)")
|
||||
* )
|
||||
*/
|
||||
seq(...pats) {
|
||||
@ -737,12 +780,13 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given pattern(s) to the next cycle. Synonym: .slowcat
|
||||
* Appends the given pattern(s) to the next cycle.
|
||||
* @name cat
|
||||
* @memberof Pattern
|
||||
* @synonyms slowcat
|
||||
* @example
|
||||
* s("hh*2").cat(
|
||||
* n("c2(3,8)")
|
||||
* note("c2(3,8)")
|
||||
* )
|
||||
*/
|
||||
cat(...pats) {
|
||||
@ -776,8 +820,10 @@ export class Pattern {
|
||||
);
|
||||
}
|
||||
|
||||
log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`) {
|
||||
return this.onTrigger((...args) => logger(func(...args)), false);
|
||||
log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`, getData = (_, hap) => ({ hap })) {
|
||||
return this.onTrigger((...args) => {
|
||||
logger(func(...args), undefined, getData(...args));
|
||||
}, false);
|
||||
}
|
||||
|
||||
logValues(func = id) {
|
||||
@ -820,15 +866,25 @@ Pattern.prototype.collect = function () {
|
||||
);
|
||||
};
|
||||
|
||||
// applies func to each array of congruent haps
|
||||
/**
|
||||
* Selects indices in in stacked notes.
|
||||
* @example
|
||||
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
|
||||
* .arpWith(haps => haps[2])
|
||||
* */
|
||||
Pattern.prototype.arpWith = function (func) {
|
||||
return this.collect()
|
||||
.fmap((v) => reify(func(v)))
|
||||
.squeezeJoin()
|
||||
.innerJoin()
|
||||
.withHap((h) => new Hap(h.whole, h.part, h.value.value, h.combineContext(h.value)));
|
||||
};
|
||||
|
||||
// applies pattern of indices to each array of congruent haps
|
||||
/**
|
||||
* Selects indices in in stacked notes.
|
||||
* @example
|
||||
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
|
||||
* .arp("0 [0,2] 1 [0,2]").slow(2)
|
||||
* */
|
||||
Pattern.prototype.arp = function (pat) {
|
||||
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
|
||||
};
|
||||
@ -988,9 +1044,6 @@ function _composeOp(a, b, func) {
|
||||
/**
|
||||
* Applies the given structure to the pattern:
|
||||
*
|
||||
* @name struct
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* note("c3,eb3,g3")
|
||||
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
|
||||
@ -1002,18 +1055,37 @@ function _composeOp(a, b, func) {
|
||||
Pattern.prototype.structAll = function (...args) {
|
||||
return this.keep.out(...args);
|
||||
};
|
||||
/**
|
||||
* Returns silence when mask is 0 or "~"
|
||||
*
|
||||
* @example
|
||||
* note("c [eb,g] d [eb,g]").mask("<1 [0 1]>").slow(2)
|
||||
*/
|
||||
Pattern.prototype.mask = function (...args) {
|
||||
return this.keepif.in(...args);
|
||||
};
|
||||
Pattern.prototype.maskAll = function (...args) {
|
||||
return this.keep.in(...args);
|
||||
};
|
||||
/**
|
||||
* Resets the pattern to the start of the cycle for each onset of the reset pattern.
|
||||
*
|
||||
* @example
|
||||
* s("<bd lt> sd, hh*4").reset("<x@3 x(3,8)>")
|
||||
*/
|
||||
Pattern.prototype.reset = function (...args) {
|
||||
return this.keepif.trig(...args);
|
||||
};
|
||||
Pattern.prototype.resetAll = function (...args) {
|
||||
return this.keep.trig(...args);
|
||||
};
|
||||
/**
|
||||
* Restarts the pattern for each onset of the restart pattern.
|
||||
* While reset will only reset the current cycle, restart will start from cycle 0.
|
||||
*
|
||||
* @example
|
||||
* s("<bd lt> sd, hh*4").restart("<x@3 x(3,8)>")
|
||||
*/
|
||||
Pattern.prototype.restart = function (...args) {
|
||||
return this.keepif.trigzero(...args);
|
||||
};
|
||||
@ -1027,6 +1099,7 @@ export const polyrhythm = stack;
|
||||
export const pr = stack;
|
||||
|
||||
// methods that create patterns, which are added to patternified Pattern methods
|
||||
// TODO: remove? this is only used in old transpiler (shapeshifter)
|
||||
Pattern.prototype.factories = {
|
||||
pure,
|
||||
stack,
|
||||
@ -1045,7 +1118,12 @@ Pattern.prototype.factories = {
|
||||
|
||||
// Elemental patterns
|
||||
|
||||
// Nothing
|
||||
/**
|
||||
* Does absolutely nothing..
|
||||
* @name silence
|
||||
* @example
|
||||
* silence // "~"
|
||||
*/
|
||||
export const silence = new Pattern(() => []);
|
||||
|
||||
/** A discrete value that repeats once per cycle.
|
||||
@ -1053,6 +1131,7 @@ export const silence = new Pattern(() => []);
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* pure('e4') // "e4"
|
||||
* @noAutocomplete
|
||||
*/
|
||||
export function pure(value) {
|
||||
function query(state) {
|
||||
@ -1091,6 +1170,7 @@ export function reify(thing) {
|
||||
/** The given items are played at the same time at the same length.
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @synonyms polyrhythm, pr
|
||||
* @example
|
||||
* stack(g3, b3, [e4, d4]).note() // "g3,b3,[e4,d4]".note()
|
||||
*/
|
||||
@ -1145,24 +1225,10 @@ export function slowcatPrime(...pats) {
|
||||
return new Pattern(query).splitQueries();
|
||||
}
|
||||
|
||||
/** Concatenation: as with {@link slowcat}, but squashes a cycle from each pattern into one cycle
|
||||
*
|
||||
* Synonyms: {@link seq}, {@link sequence}
|
||||
*
|
||||
* @param {...any} items - The items to concatenate
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* fastcat(e5, b4, [d5, c5])
|
||||
* // sequence(e5, b4, [d5, c5])
|
||||
* // seq(e5, b4, [d5, c5])
|
||||
*/
|
||||
export function fastcat(...pats) {
|
||||
return slowcat(...pats)._fast(pats.length);
|
||||
}
|
||||
|
||||
/** The given items are con**cat**enated, where each one takes one cycle. Synonym: slowcat
|
||||
/** The given items are con**cat**enated, where each one takes one cycle.
|
||||
*
|
||||
* @param {...any} items - The items to concatenate
|
||||
* @synonyms slowcat
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* cat(e5, b4, [d5, c5]).note() // "<e5 b4 [d5 c5]>".note()
|
||||
@ -1172,7 +1238,7 @@ export function cat(...pats) {
|
||||
return slowcat(...pats);
|
||||
}
|
||||
|
||||
/** Like {@link seq}, but each step has a length, relative to the whole.
|
||||
/** Like {@link Pattern.seq}, but each step has a length, relative to the whole.
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* timeCat([3,e3],[1, g3]).note() // "e3@3 g3".note()
|
||||
@ -1189,12 +1255,17 @@ export function timeCat(...timepats) {
|
||||
return stack(...pats);
|
||||
}
|
||||
|
||||
export function fastcat(...pats) {
|
||||
return slowcat(...pats)._fast(pats.length);
|
||||
}
|
||||
|
||||
/** See {@link fastcat} */
|
||||
export function sequence(...pats) {
|
||||
return fastcat(...pats);
|
||||
}
|
||||
|
||||
/** Like **cat**, but the items are crammed into one cycle. Synonyms: fastcat, sequence
|
||||
/** Like **cat**, but the items are crammed into one cycle.
|
||||
* @synonyms fastcat, sequence
|
||||
* @example
|
||||
* seq(e5, b4, [d5, c5]).note() // "e5 b4 [d5 c5]".note()
|
||||
*
|
||||
@ -1215,7 +1286,17 @@ function _sequenceCount(x) {
|
||||
}
|
||||
return [reify(x), 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns one or more given sequences to the given number of steps per cycle.
|
||||
*
|
||||
* @name polymeterSteps
|
||||
* @param {number} steps how many items are placed in one cycle
|
||||
* @param {any[]} sequences one or more arrays of Patterns / values
|
||||
* @example
|
||||
* polymeterSteps(2, ["c", "d", "e", "f", "g", "f", "e", "d"])
|
||||
* .note().stack(s("bd")) // 1 cycle = 1 bd = 2 notes
|
||||
* // note("{c d e f g f e d}%2").stack(s("bd"))
|
||||
*/
|
||||
export function polymeterSteps(steps, ...args) {
|
||||
const seqs = args.map((a) => _sequenceCount(a));
|
||||
if (seqs.length == 0) {
|
||||
@ -1238,6 +1319,14 @@ export function polymeterSteps(steps, ...args) {
|
||||
return stack(...pats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the given lists of patterns with the same pulse. This will create so called polymeters when different sized sequences are used.
|
||||
* @synonyms pm
|
||||
* @example
|
||||
* polymeter(["c", "eb", "g"], ["c2", "g2"]).note()
|
||||
* // "{c eb g, c2 g2}".note()
|
||||
*
|
||||
*/
|
||||
export function polymeter(...args) {
|
||||
return polymeterSteps(0, ...args);
|
||||
}
|
||||
@ -1278,6 +1367,14 @@ export const and = curry((a, b) => reify(b).and(a));
|
||||
export const or = curry((a, b) => reify(b).or(a));
|
||||
export const func = curry((a, b) => reify(b).func(a));
|
||||
|
||||
/**
|
||||
* Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
|
||||
*
|
||||
* @param {string} name name of the function
|
||||
* @param {function} func function with 1 or more params, where last is the current pattern
|
||||
* @noAutocomplete
|
||||
*
|
||||
*/
|
||||
export function register(name, func) {
|
||||
if (Array.isArray(name)) {
|
||||
const result = {};
|
||||
@ -1354,7 +1451,11 @@ export const round = register('round', function (pat) {
|
||||
* Assumes a numerical pattern. Returns a new pattern with all values set to
|
||||
* their mathematical floor. E.g. `3.7` replaced with to `3`, and `-4.2`
|
||||
* replaced with `-5`.
|
||||
* @name floor
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "42 42.1 42.5 43".floor().note()
|
||||
*/
|
||||
export const floor = register('floor', function (pat) {
|
||||
return pat.asNumber().fmap((v) => Math.floor(v));
|
||||
@ -1364,7 +1465,11 @@ export const floor = register('floor', function (pat) {
|
||||
* Assumes a numerical pattern. Returns a new pattern with all values set to
|
||||
* their mathematical ceiling. E.g. `3.2` replaced with `4`, and `-4.2`
|
||||
* replaced with `-4`.
|
||||
* @name ceil
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "42 42.1 42.5 43".ceil().note()
|
||||
*/
|
||||
export const ceil = register('ceil', function (pat) {
|
||||
return pat.asNumber().fmap((v) => Math.ceil(v));
|
||||
@ -1373,15 +1478,17 @@ export const ceil = register('ceil', function (pat) {
|
||||
* 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
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
export const toBipolar = register('toBipolar', function (pat) {
|
||||
return pat.fmap((x) => x * 2 - 1);
|
||||
});
|
||||
|
||||
/**
|
||||
* Assumes a numerical pattern, containing bipolar values in the range -1 ..
|
||||
* 1. Returns a new pattern with values scaled to the unipolar range 0 .. 1
|
||||
* Assumes a numerical pattern, containing bipolar values in the range -1 .. 1
|
||||
* Returns a new pattern with values scaled to the unipolar range 0 .. 1
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
*/
|
||||
export const fromBipolar = register('fromBipolar', function (pat) {
|
||||
return pat.fmap((x) => (x + 1) / 2);
|
||||
@ -1402,23 +1509,27 @@ export const range = register('range', function (min, max, pat) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Assumes a numerical pattern, containing unipolar values in the range 0 ..
|
||||
* 1. Returns a new pattern with values scaled to the given min/max range,
|
||||
* Assumes a numerical pattern, containing unipolar values in the range 0 .. 1
|
||||
* Returns a new pattern with values scaled to the given min/max range,
|
||||
* following an exponential curve.
|
||||
* @param {Number} min
|
||||
* @param {Number} max
|
||||
* @name rangex
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(sine.rangex(500,2000).slow(4))
|
||||
*/
|
||||
export const rangex = register('rangex', function (min, max, pat) {
|
||||
return pat._range(Math.log(min), Math.log(max)).fmap(Math.exp);
|
||||
});
|
||||
|
||||
/**
|
||||
* Assumes a numerical pattern, containing bipolar values in the range -1 ..
|
||||
* 1. Returns a new pattern with values scaled to the given min/max range.
|
||||
* @param {Number} min
|
||||
* @param {Number} max
|
||||
* Assumes a numerical pattern, containing bipolar values in the range -1 .. 1
|
||||
* Returns a new pattern with values scaled to the given min/max range.
|
||||
* @name range2
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(sine2.range2(500,2000).slow(4))
|
||||
*/
|
||||
export const range2 = register('range2', function (min, max, pat) {
|
||||
return pat.fromBipolar()._range(min, max);
|
||||
@ -1427,8 +1538,16 @@ export const range2 = register('range2', function (min, max, pat) {
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Structural and temporal transformations
|
||||
|
||||
// Compress each cycle into the given timespan, leaving a gap
|
||||
/** Compress each cycle into the given timespan, leaving a gap
|
||||
* @example
|
||||
* cat(
|
||||
* s("bd sd").compress(.25,.75),
|
||||
* s("~ bd sd ~")
|
||||
* )
|
||||
*/
|
||||
export const compress = register('compress', function (b, e, pat) {
|
||||
b = Fraction(b);
|
||||
e = Fraction(e);
|
||||
if (b.gt(e) || b.gt(1) || e.gt(1) || b.lt(0) || e.lt(0)) {
|
||||
return silence;
|
||||
}
|
||||
@ -1439,6 +1558,13 @@ export const { compressSpan, compressspan } = register(['compressSpan', 'compres
|
||||
return pat._compress(span.begin, span.end);
|
||||
});
|
||||
|
||||
/**
|
||||
* speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast.
|
||||
* @name fastGap
|
||||
* @synonyms fastgap
|
||||
* @example
|
||||
* s("bd sd").fastGap(2)
|
||||
*/
|
||||
export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (factor, pat) {
|
||||
// A bit fiddly, to drop zero-width queries at the start of the next cycle
|
||||
const qf = function (span) {
|
||||
@ -1469,10 +1595,14 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f
|
||||
return pat.withQuerySpanMaybe(qf).withHap(ef).splitQueries();
|
||||
});
|
||||
|
||||
// Similar to compress, but doesn't leave gaps, and the 'focus' can be
|
||||
// bigger than a cycle
|
||||
|
||||
/**
|
||||
* Similar to compress, but doesn't leave gaps, and the 'focus' can be bigger than a cycle
|
||||
* @example
|
||||
* s("bd hh sd hh").focus(1/4, 3/4)
|
||||
*/
|
||||
export const focus = register('focus', function (b, e, pat) {
|
||||
b = Fraction(b);
|
||||
e = Fraction(e);
|
||||
return pat._fast(Fraction(1).div(e.sub(b))).late(b.cyclePos());
|
||||
});
|
||||
|
||||
@ -1480,6 +1610,10 @@ export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], fun
|
||||
return pat._focus(span.begin, span.end);
|
||||
});
|
||||
|
||||
/** The ply function repeats each event the given number of times.
|
||||
* @example
|
||||
* s("bd ~ sd cp").ply("<1 2 3>")
|
||||
*/
|
||||
export const ply = register('ply', function (factor, pat) {
|
||||
return pat.fmap((x) => pure(x)._fast(factor)).squeezeJoin();
|
||||
});
|
||||
@ -1488,6 +1622,7 @@ export const ply = register('ply', function (factor, pat) {
|
||||
* Speed up a pattern by the given factor. Used by "*" in mini notation.
|
||||
*
|
||||
* @name fast
|
||||
* @synonyms density
|
||||
* @memberof Pattern
|
||||
* @param {number | Pattern} factor speed up factor
|
||||
* @returns Pattern
|
||||
@ -1500,10 +1635,20 @@ export const { fast, density } = register(['fast', 'density'], function (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.
|
||||
*
|
||||
* @name slow
|
||||
* @synonyms sparsity
|
||||
* @memberof Pattern
|
||||
* @param {number | Pattern} factor slow down factor
|
||||
* @returns Pattern
|
||||
@ -1514,12 +1659,22 @@ export const { slow, sparsity } = register(['slow', 'sparsity'], function (facto
|
||||
return pat._fast(Fraction(1).div(factor));
|
||||
});
|
||||
|
||||
// Should these really be in alphabetical order? a shame to split
|
||||
// fast/slow, inside/outside..
|
||||
/**
|
||||
* Carries out an operation 'inside' a cycle.
|
||||
* @example
|
||||
* "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()
|
||||
*/
|
||||
export const inside = register('inside', function (factor, f, pat) {
|
||||
return f(pat._slow(factor))._fast(factor);
|
||||
});
|
||||
|
||||
/**
|
||||
* Carries out an operation 'outside' a cycle.
|
||||
* @example
|
||||
* "<[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()
|
||||
*/
|
||||
export const outside = register('outside', function (factor, f, pat) {
|
||||
return f(pat._fast(factor))._slow(factor);
|
||||
});
|
||||
@ -1574,11 +1729,16 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu
|
||||
* @example
|
||||
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
|
||||
*/
|
||||
// TODO: remove or dedupe with layer?
|
||||
export const apply = register('apply', function (func, pat) {
|
||||
return func(pat);
|
||||
});
|
||||
|
||||
// cpm = cycles per minute
|
||||
/**
|
||||
* Plays the pattern at the given cycles per minute.
|
||||
* @example
|
||||
* s("<bd sd>,hh*2").cpm(90) // = 90 bpm
|
||||
*/
|
||||
// TODO - global clock
|
||||
export const cpm = register('cpm', function (cpm, pat) {
|
||||
return pat._fast(cpm / 60);
|
||||
@ -1614,6 +1774,13 @@ export const late = register('late', function (offset, pat) {
|
||||
return pat._early(Fraction(0).sub(offset));
|
||||
});
|
||||
|
||||
/**
|
||||
* Plays a portion of a pattern, specified by the beginning and end of a time span. The new resulting pattern is played over the time period of the original pattern:
|
||||
*
|
||||
* @example
|
||||
* s("bd*2 hh*3 [sd bd]*2 perc").zoom(0.25, 0.75)
|
||||
* // s("hh*3 [sd bd]*2") // equivalent
|
||||
*/
|
||||
export const zoom = register('zoom', function (s, e, pat) {
|
||||
e = Fraction(e);
|
||||
s = Fraction(s);
|
||||
@ -1628,6 +1795,12 @@ export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a
|
||||
return pat.zoom(a.begin, a.end);
|
||||
});
|
||||
|
||||
/**
|
||||
* Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle.
|
||||
* @param {number} fraction fraction to select
|
||||
* @example
|
||||
* s("lt ht mt cp, [hh oh]*2").linger("<1 .5 .25 .125>")
|
||||
*/
|
||||
export const linger = register('linger', function (t, pat) {
|
||||
if (t == 0) {
|
||||
return silence;
|
||||
@ -1637,10 +1810,23 @@ export const linger = register('linger', function (t, pat) {
|
||||
return pat._zoom(0, t)._slow(t);
|
||||
});
|
||||
|
||||
/**
|
||||
* Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one.
|
||||
* @param {number} segments number of segments per cycle
|
||||
* @example
|
||||
* note(saw.range(0,12).segment(24)).add(40)
|
||||
*/
|
||||
export const segment = register('segment', function (rate, pat) {
|
||||
return pat.struct(pure(true)._fast(rate));
|
||||
});
|
||||
|
||||
/**
|
||||
* Swaps 1s and 0s in a binary pattern.
|
||||
* @name invert
|
||||
* @synonyms inv
|
||||
* @example
|
||||
* s("bd").struct("1 0 0 1 0 0 1 0".lastOf(4, invert))
|
||||
*/
|
||||
export const { invert, inv } = register(['invert', 'inv'], function (pat) {
|
||||
// Swap true/false in a binary pattern
|
||||
return pat.fmap((x) => !x);
|
||||
@ -1712,14 +1898,57 @@ export const rev = register('rev', function (pat) {
|
||||
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.
|
||||
* @example
|
||||
* stack(
|
||||
* s("bd").hush(),
|
||||
* s("hh*3")
|
||||
* )
|
||||
*/
|
||||
export const hush = register('hush', function (pat) {
|
||||
return silence;
|
||||
});
|
||||
|
||||
/**
|
||||
* Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards.
|
||||
* @example
|
||||
* note("c d e g").palindrome()
|
||||
*/
|
||||
export const palindrome = register('palindrome', function (pat) {
|
||||
return pat.every(2, rev);
|
||||
});
|
||||
|
||||
/**
|
||||
* Jux with adjustable stereo width. 0 = mono, 1 = full stereo.
|
||||
* @name juxBy
|
||||
* @synonyms juxby
|
||||
* @example
|
||||
* s("lt ht mt ht hh").juxBy("<0 .5 1>/2", rev)
|
||||
*/
|
||||
export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, pat) {
|
||||
by /= 2;
|
||||
const elem_or = function (dict, key, dflt) {
|
||||
@ -1734,23 +1963,19 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func,
|
||||
return stack(left, func(right));
|
||||
});
|
||||
|
||||
/**
|
||||
* The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel.
|
||||
* @example
|
||||
* s("lt ht mt ht hh").jux(rev)
|
||||
*/
|
||||
export const jux = register('jux', function (func, pat) {
|
||||
return pat._juxBy(1, func, pat);
|
||||
});
|
||||
|
||||
export const { stutWith, stutwith } = register(['stutWith', 'stutwith'], function (times, time, func, pat) {
|
||||
return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i)));
|
||||
});
|
||||
|
||||
export const stut = register('stut', function (times, feedback, time, pat) {
|
||||
return pat._stutWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i)));
|
||||
});
|
||||
|
||||
/**
|
||||
* Superimpose and offset multiple times, applying the given function each time.
|
||||
* @name echoWith
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @synonyms echowith, stutWith, stutwith
|
||||
* @param {number} times how many times to repeat
|
||||
* @param {number} time cycle offset between iterations
|
||||
* @param {function} func function to apply, given the pattern and the iteration index
|
||||
@ -1759,9 +1984,12 @@ export const stut = register('stut', function (times, feedback, time, pat) {
|
||||
* .echoWith(4, 1/8, (p,n) => p.add(n*2))
|
||||
* .scale('C minor').note().legato(.2)
|
||||
*/
|
||||
export const { echoWith, echowith } = register(['echoWith', 'echowith'], function (times, time, func, pat) {
|
||||
return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i)));
|
||||
});
|
||||
export const { echoWith, echowith, stutWith, stutwith } = register(
|
||||
['echoWith', 'echowith', 'stutWith', 'stutwith'],
|
||||
function (times, time, func, pat) {
|
||||
return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i)));
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Superimpose and offset multiple times, gradually decreasing the velocity
|
||||
@ -1778,6 +2006,19 @@ export const echo = register('echo', function (times, time, feedback, pat) {
|
||||
return pat._echoWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i)));
|
||||
});
|
||||
|
||||
/**
|
||||
* Deprecated. Like echo, but the last 2 parameters are flipped.
|
||||
* @name stut
|
||||
* @param {number} times how many times to repeat
|
||||
* @param {number} feedback velocity multiplicator for each iteration
|
||||
* @param {number} time cycle offset between iterations
|
||||
* @example
|
||||
* s("bd sd").stut(3, .8, 1/6)
|
||||
*/
|
||||
export const stut = register('stut', function (times, feedback, time, pat) {
|
||||
return pat._echoWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i)));
|
||||
});
|
||||
|
||||
/**
|
||||
* Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played.
|
||||
* @name iter
|
||||
@ -1803,6 +2044,7 @@ export const iter = register('iter', function (times, pat) {
|
||||
/**
|
||||
* Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles
|
||||
* @name iterBack
|
||||
* @synonyms iterback
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
@ -1834,6 +2076,7 @@ export const chunk = register('chunk', function (n, func, pat) {
|
||||
/**
|
||||
* Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
|
||||
* @name chunkBack
|
||||
* @synonyms chunkback
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
@ -1846,16 +2089,29 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
|
||||
// TODO - redefine elsewhere in terms of mask
|
||||
export const bypass = register('bypass', function (on, pat) {
|
||||
on = Boolean(parseInt(on));
|
||||
return on ? silence : this;
|
||||
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
|
||||
// TODO - fix
|
||||
export const duration = register('duration', function (value, pat) {
|
||||
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
||||
});
|
||||
|
||||
// TODO - make control?
|
||||
/**
|
||||
* Sets the color of the hap in visualizations like pianoroll or highlighting.
|
||||
*/
|
||||
// TODO: move this to controls https://github.com/tidalcycles/strudel/issues/288
|
||||
export const { color, colour } = register(['color', 'colour'], function (color, pat) {
|
||||
return pat.withContext((context) => ({ ...context, color }));
|
||||
});
|
||||
@ -1883,6 +2139,7 @@ export const velocity = register('velocity', function (velocity, pat) {
|
||||
*/
|
||||
// TODO - fix
|
||||
export const legato = register('legato', function (value, pat) {
|
||||
value = Fraction(value);
|
||||
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
||||
});
|
||||
|
||||
|
||||
@ -4,13 +4,27 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, toMidi, getDrawContext } from './index.mjs';
|
||||
import { Pattern, toMidi, getDrawContext, freqToMidi, isNote } from './index.mjs';
|
||||
|
||||
const scale = (normalized, min, max) => normalized * (max - min) + min;
|
||||
const getValue = (e) => {
|
||||
let value = typeof e.value === 'object' ? e.value.note ?? e.value.n : e.value;
|
||||
if (typeof value === 'string') {
|
||||
value = toMidi(value);
|
||||
let { value } = e;
|
||||
if (typeof e.value !== 'object') {
|
||||
value = { value };
|
||||
}
|
||||
let { note, n, freq, s } = value;
|
||||
if (freq) {
|
||||
return freqToMidi(freq);
|
||||
}
|
||||
note = note ?? n;
|
||||
if (typeof note === 'string') {
|
||||
return toMidi(note);
|
||||
}
|
||||
if (typeof note === 'number') {
|
||||
return note;
|
||||
}
|
||||
if (s) {
|
||||
return '_' + s;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
@ -48,9 +62,6 @@ Pattern.prototype.pianoroll = function ({
|
||||
from = 0;
|
||||
to = timeframeProp;
|
||||
}
|
||||
if (!autorange && fold) {
|
||||
console.warn('disabling autorange has no effect when fold is enabled');
|
||||
}
|
||||
const timeAxis = vertical ? h : w;
|
||||
const valueAxis = vertical ? w : h;
|
||||
let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time
|
||||
@ -138,10 +149,149 @@ Pattern.prototype.pianoroll = function ({
|
||||
maxMidi = max;
|
||||
valueExtent = maxMidi - minMidi + 1;
|
||||
}
|
||||
foldValues = values.sort((a, b) => a - b);
|
||||
foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
|
||||
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
||||
},
|
||||
},
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
// this function allows drawing a pianoroll without ties to Pattern.prototype
|
||||
// it will probably replace the above in the future
|
||||
export function pianoroll({
|
||||
time,
|
||||
haps,
|
||||
cycles = 4,
|
||||
playhead = 0.5,
|
||||
flipTime = 0,
|
||||
flipValues = 0,
|
||||
hideNegative = false,
|
||||
// inactive = '#C9E597',
|
||||
// inactive = '#FFCA28',
|
||||
inactive = '#7491D2',
|
||||
active = '#FFCA28',
|
||||
// background = '#2A3236',
|
||||
background = 'transparent',
|
||||
smear = 0,
|
||||
playheadColor = 'white',
|
||||
minMidi = 10,
|
||||
maxMidi = 90,
|
||||
autorange = 0,
|
||||
timeframe: timeframeProp,
|
||||
fold = 0,
|
||||
vertical = 0,
|
||||
ctx,
|
||||
} = {}) {
|
||||
const w = ctx.canvas.width;
|
||||
const h = ctx.canvas.height;
|
||||
let from = -cycles * playhead;
|
||||
let to = cycles * (1 - playhead);
|
||||
|
||||
if (timeframeProp) {
|
||||
console.warn('timeframe is deprecated! use from/to instead');
|
||||
from = 0;
|
||||
to = timeframeProp;
|
||||
}
|
||||
const timeAxis = vertical ? h : w;
|
||||
const valueAxis = vertical ? w : h;
|
||||
let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time
|
||||
const timeExtent = to - from; // number of seconds that fit inside the canvas frame
|
||||
const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0]; // pixel range for values
|
||||
let valueExtent = maxMidi - minMidi + 1; // number of "slots" for values, overwritten if autorange true
|
||||
let barThickness = valueAxis / valueExtent; // pixels per value, overwritten if autorange true
|
||||
let foldValues = [];
|
||||
flipTime && timeRange.reverse();
|
||||
flipValues && valueRange.reverse();
|
||||
|
||||
// onQuery
|
||||
const { min, max, values } = haps.reduce(
|
||||
({ min, max, values }, e) => {
|
||||
const v = getValue(e);
|
||||
return {
|
||||
min: v < min ? v : min,
|
||||
max: v > max ? v : max,
|
||||
values: values.includes(v) ? values : [...values, v],
|
||||
};
|
||||
},
|
||||
{ min: Infinity, max: -Infinity, values: [] },
|
||||
);
|
||||
if (autorange) {
|
||||
minMidi = min;
|
||||
maxMidi = max;
|
||||
valueExtent = maxMidi - minMidi + 1;
|
||||
}
|
||||
// foldValues = values.sort((a, b) => a - b);
|
||||
foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
|
||||
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
||||
|
||||
ctx.fillStyle = background;
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
if (!smear) {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
}
|
||||
/* const inFrame = (event) =>
|
||||
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= time + to && event.whole.end >= time + from; */
|
||||
haps
|
||||
// .filter(inFrame)
|
||||
.forEach((event) => {
|
||||
const isActive = event.whole.begin <= time && event.whole.end > time;
|
||||
const color = event.value?.color || event.context?.color;
|
||||
ctx.fillStyle = color || inactive;
|
||||
ctx.strokeStyle = color || active;
|
||||
ctx.globalAlpha = event.context.velocity ?? 1;
|
||||
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
|
||||
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
||||
const value = getValue(event);
|
||||
const valuePx = scale(
|
||||
fold ? foldValues.indexOf(value) / foldValues.length : (Number(value) - minMidi) / valueExtent,
|
||||
...valueRange,
|
||||
);
|
||||
let margin = 0;
|
||||
const offset = scale(time / timeExtent, ...timeRange);
|
||||
let coords;
|
||||
if (vertical) {
|
||||
coords = [
|
||||
valuePx + 1 - (flipValues ? barThickness : 0), // x
|
||||
timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y
|
||||
barThickness - 2, // width
|
||||
durationPx - 2, // height
|
||||
];
|
||||
} else {
|
||||
coords = [
|
||||
timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x
|
||||
valuePx + 1 - (flipValues ? 0 : barThickness), // y
|
||||
durationPx - 2, // widith
|
||||
barThickness - 2, // height
|
||||
];
|
||||
}
|
||||
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
||||
});
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
||||
// draw playhead
|
||||
ctx.strokeStyle = playheadColor;
|
||||
ctx.beginPath();
|
||||
if (vertical) {
|
||||
ctx.moveTo(0, playheadPosition);
|
||||
ctx.lineTo(valueAxis, playheadPosition);
|
||||
} else {
|
||||
ctx.moveTo(playheadPosition, 0);
|
||||
ctx.lineTo(playheadPosition, valueAxis);
|
||||
}
|
||||
ctx.stroke();
|
||||
return this;
|
||||
}
|
||||
|
||||
function getOptions(drawTime, options = {}) {
|
||||
let [lookbehind, lookahead] = drawTime;
|
||||
lookbehind = Math.abs(lookbehind);
|
||||
const cycles = lookahead + lookbehind;
|
||||
const playhead = lookbehind / cycles;
|
||||
return { fold: 1, ...options, cycles, playhead };
|
||||
}
|
||||
|
||||
Pattern.prototype.punchcard = function (options) {
|
||||
return this.onPaint((ctx, time, haps, drawTime) => pianoroll({ ctx, time, haps, ...getOptions(drawTime, options) }));
|
||||
};
|
||||
|
||||
@ -13,6 +13,7 @@ export function repl({
|
||||
getTime,
|
||||
transpiler,
|
||||
onToggle,
|
||||
editPattern,
|
||||
}) {
|
||||
const scheduler = new Cyclist({
|
||||
interval,
|
||||
@ -34,15 +35,17 @@ export function repl({
|
||||
getTime,
|
||||
onToggle,
|
||||
});
|
||||
setTime(() => scheduler.getPhase()); // TODO: refactor?
|
||||
setTime(() => scheduler.now()); // TODO: refactor?
|
||||
const evaluate = async (code, autostart = true) => {
|
||||
if (!code) {
|
||||
throw new Error('no code to evaluate');
|
||||
}
|
||||
try {
|
||||
beforeEval?.({ code });
|
||||
const { pattern } = await _evaluate(code, transpiler);
|
||||
let { pattern } = await _evaluate(code, transpiler);
|
||||
|
||||
logger(`[eval] code updated`);
|
||||
pattern = editPattern?.(pattern) || pattern;
|
||||
scheduler.setPattern(pattern, autostart);
|
||||
afterEval?.({ code, pattern });
|
||||
return pattern;
|
||||
|
||||
@ -114,6 +114,14 @@ 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.
|
||||
*
|
||||
|
||||
@ -44,6 +44,7 @@ import {
|
||||
ply,
|
||||
rev,
|
||||
time,
|
||||
run,
|
||||
} from '../index.mjs';
|
||||
|
||||
import { steady } from '../signal.mjs';
|
||||
@ -154,6 +155,11 @@ describe('Pattern', () => {
|
||||
).toBe(7);
|
||||
});
|
||||
});
|
||||
describe('out()', () => {
|
||||
it('is an alias for set.out()', () => {
|
||||
sameFirst(sequence(1, 2).out(5, 6, 7, 8), sequence(1, 2).set.out(5, 6, 7, 8));
|
||||
});
|
||||
});
|
||||
describe('add()', () => {
|
||||
it('works as toplevel function', () => {
|
||||
expect(add(pure(4), pure(5)).query(st(0, 1))[0].value).toBe(9);
|
||||
@ -903,6 +909,18 @@ 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', () => {
|
||||
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(
|
||||
@ -931,4 +949,14 @@ describe('Pattern', () => {
|
||||
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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -33,9 +33,12 @@ describe('isNote', () => {
|
||||
expect(isNote(note)).toBe(true);
|
||||
});
|
||||
});
|
||||
it('should recognize notes without octave', () => {
|
||||
expect(isNote('C')).toBe(true);
|
||||
expect(isNote('Bb')).toBe(true);
|
||||
});
|
||||
it('should not recognize invalid notes', () => {
|
||||
expect(isNote('H5')).toBe(false);
|
||||
expect(isNote('C')).toBe(false);
|
||||
expect(isNote('X')).toBe(false);
|
||||
expect(isNote(1)).toBe(false);
|
||||
});
|
||||
@ -189,6 +192,7 @@ describe('parseNumeral', () => {
|
||||
expect(parseNumeral(1.5)).toBe(1.5);
|
||||
});
|
||||
it('should parse notes', () => {
|
||||
expect(parseNumeral('c')).toBe(48);
|
||||
expect(parseNumeral('c4')).toBe(60);
|
||||
expect(parseNumeral('c#4')).toBe(61);
|
||||
expect(parseNumeral('db4')).toBe(61);
|
||||
|
||||
@ -26,7 +26,7 @@ export const backgroundImage = function (src, animateOptions = {}) {
|
||||
({
|
||||
style: () => (container.style = bg + ';' + value),
|
||||
className: () => (container.className = value + ' ' + initialClassName),
|
||||
}[option]());
|
||||
})[option]();
|
||||
};
|
||||
const funcOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'function');
|
||||
const stringOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'string');
|
||||
|
||||
@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
// returns true if the given string is a note
|
||||
export const isNote = (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 tokenizeNote = (note) => {
|
||||
if (typeof note !== 'string') {
|
||||
return [];
|
||||
@ -19,7 +20,7 @@ export const tokenizeNote = (note) => {
|
||||
|
||||
// turns the given note into its midi number representation
|
||||
export const toMidi = (note) => {
|
||||
const [pc, acc, oct] = tokenizeNote(note);
|
||||
const [pc, acc, oct = 3] = tokenizeNote(note);
|
||||
if (!pc) {
|
||||
throw new Error('not a note: "' + note + '"');
|
||||
}
|
||||
@ -57,6 +58,7 @@ export const valueToMidi = (value, fallbackValue) => {
|
||||
|
||||
/**
|
||||
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
|
||||
* @noAutocomplete
|
||||
*/
|
||||
export const getFreq = (noteOrMidi) => {
|
||||
if (typeof noteOrMidi === 'number') {
|
||||
@ -67,6 +69,7 @@ export const getFreq = (noteOrMidi) => {
|
||||
|
||||
/**
|
||||
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
|
||||
* @noAutocomplete
|
||||
*/
|
||||
export const midi2note = (n) => {
|
||||
const oct = Math.floor(n / 12) - 1;
|
||||
|
||||
19
packages/core/vite.config.js
Normal file
19
packages/core/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
@ -44,6 +44,6 @@ function createClock(
|
||||
};
|
||||
const getPhase = () => phase;
|
||||
// setCallback
|
||||
return { setDuration, start, stop, pause, duration, getPhase };
|
||||
return { setDuration, start, stop, pause, duration, getPhase, minLatency };
|
||||
}
|
||||
export default createClock;
|
||||
|
||||
@ -92,6 +92,7 @@ async function load() {
|
||||
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args)));
|
||||
await _csound.setOption('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
|
||||
await _csound.setOption('--sample-accurate');
|
||||
await _csound.setOption('-odac');
|
||||
await _csound.compileCsdText(csd);
|
||||
// await _csound.compileOrc(livecodeOrc);
|
||||
await _csound.compileOrc(presetsOrc);
|
||||
@ -1,10 +1,15 @@
|
||||
{
|
||||
"name": "@strudel.cycles/csound",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.2",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "csound.mjs",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"No tests present.\" && exit 0"
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -27,6 +32,11 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@csound/browser": "^6.18.3"
|
||||
"@csound/browser": "6.18.5",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
19
packages/csound/vite.config.js
Normal file
19
packages/csound/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
@ -3,6 +3,10 @@
|
||||
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.
|
||||
|
||||
## Deprecation Note
|
||||
|
||||
This package will not be developed further. Consider using `@strudel.cycles/transpiler` as a replacement.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
|
||||
297
packages/eval/package-lock.json
generated
297
packages/eval/package-lock.json
generated
@ -1,297 +0,0 @@
|
||||
{
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,21 @@
|
||||
{
|
||||
"name": "@strudel.cycles/eval",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.2",
|
||||
"description": "Code evaluator for strudel",
|
||||
"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",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
@ -28,12 +34,17 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.5.0",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"estraverse": "^5.3.0",
|
||||
"shift-ast": "^7.0.0",
|
||||
"shift-codegen": "^8.1.0",
|
||||
"shift-parser": "^8.0.0",
|
||||
"shift-spec": "^2019.0.0",
|
||||
"shift-traverser": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@strudel.cycles/mini": "workspace:*",
|
||||
"vite": "^3.2.2",
|
||||
"vitest": "^0.25.7"
|
||||
}
|
||||
}
|
||||
|
||||
19
packages/eval/vite.config.js
Normal file
19
packages/eval/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
@ -7,7 +7,3 @@ This package adds midi functionality to strudel Patterns.
|
||||
```sh
|
||||
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...
|
||||
|
||||
@ -11,10 +11,14 @@ import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||
// if you use WebMidi from outside of this package, make sure to import that instance:
|
||||
export const { WebMidi } = _WebMidi;
|
||||
|
||||
function supportsMidi() {
|
||||
return typeof navigator.requestMIDIAccess === 'function';
|
||||
}
|
||||
|
||||
export function enableWebMidi(options = {}) {
|
||||
const { onReady, onConnected, onDisconnected } = options;
|
||||
|
||||
if (typeof navigator.requestMIDIAccess !== 'function') {
|
||||
if (!supportsMidi()) {
|
||||
throw new Error('Your Browser does not support WebMIDI.');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -42,23 +46,43 @@ export function enableWebMidi(options = {}) {
|
||||
// const outputByName = (name: string) => WebMidi.getOutputByName(name);
|
||||
const outputByName = (name) => WebMidi.getOutputByName(name);
|
||||
|
||||
let midiReady;
|
||||
|
||||
// output?: string | number, outputs: typeof WebMidi.outputs
|
||||
function getDevice(output, outputs) {
|
||||
if (!outputs.length) {
|
||||
throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
||||
}
|
||||
if (typeof output === 'number') {
|
||||
return outputs[output];
|
||||
}
|
||||
if (typeof output === 'string') {
|
||||
return outputByName(output);
|
||||
}
|
||||
return outputs[0];
|
||||
}
|
||||
|
||||
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
|
||||
Pattern.prototype.midi = async function (output, channel = 1) {
|
||||
await enableWebMidi({
|
||||
Pattern.prototype.midi = function (output, channel = 1) {
|
||||
if (!supportsMidi()) {
|
||||
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
||||
}
|
||||
/* await */ enableWebMidi({
|
||||
onConnected: ({ outputs }) =>
|
||||
logger(`Midi device connected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`),
|
||||
onDisconnected: ({ outputs }) =>
|
||||
logger(`Midi device disconnected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`),
|
||||
onReady: ({ outputs }) => {
|
||||
const chosenOutput = output ?? outputs[0];
|
||||
const device = getDevice(output, outputs);
|
||||
const otherOutputs = outputs
|
||||
.filter((o) => o.name !== chosenOutput.name)
|
||||
.filter((o) => o.name !== device.name)
|
||||
.map((o) => `'${o.name}'`)
|
||||
.join(' | ');
|
||||
logger(`Midi connected! Using "${chosenOutput.name}". ${otherOutputs ? `Also available: ${otherOutputs}` : ''}`);
|
||||
midiReady = true;
|
||||
logger(`Midi connected! Using "${device.name}". ${otherOutputs ? `Also available: ${otherOutputs}` : ''}`);
|
||||
},
|
||||
});
|
||||
if (isPattern(output?.constructor?.name)) {
|
||||
if (isPattern(output)) {
|
||||
throw new Error(
|
||||
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
|
||||
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
|
||||
@ -71,20 +95,10 @@ Pattern.prototype.midi = async function (output, channel = 1) {
|
||||
if (!isNote(note)) {
|
||||
throw new Error('not a note: ' + note);
|
||||
}
|
||||
if (!WebMidi.enabled) {
|
||||
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
||||
}
|
||||
if (!WebMidi.outputs.length) {
|
||||
throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
||||
}
|
||||
let device;
|
||||
if (typeof output === 'number') {
|
||||
device = WebMidi.outputs[output];
|
||||
} else if (typeof output === 'string') {
|
||||
device = outputByName(output);
|
||||
} else {
|
||||
device = WebMidi.outputs[0];
|
||||
if (!midiReady) {
|
||||
return;
|
||||
}
|
||||
const device = getDevice(output, WebMidi.outputs);
|
||||
if (!device) {
|
||||
throw new Error(
|
||||
`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs
|
||||
|
||||
130
packages/midi/package-lock.json
generated
130
packages/midi/package-lock.json
generated
@ -1,130 +0,0 @@
|
||||
{
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,16 @@
|
||||
{
|
||||
"name": "@strudel.cycles/midi",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"description": "Midi API for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
@ -21,8 +29,11 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/tone": "^0.5.0",
|
||||
"tone": "^14.7.77",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"webmidi": "^3.0.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
19
packages/midi/vite.config.js
Normal file
19
packages/midi/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
@ -32,7 +32,7 @@ yields:
|
||||
|
||||
## Mini Notation API
|
||||
|
||||
See "Mini Notation" in the [Strudel Tutorial](https://strudel.tidalcycles.org/tutorial/)
|
||||
See "Mini Notation" in the [Strudel Tutorial](https://strudel.tidalcycles.org/learn/mini-notation)
|
||||
|
||||
## Building the Parser
|
||||
|
||||
@ -40,5 +40,5 @@ The parser [krill-parser.js] is generated from [krill.pegjs](./krill.pegjs) usin
|
||||
To generate the parser, run
|
||||
|
||||
```js
|
||||
npm run build:parser
|
||||
npm build:parser
|
||||
```
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -5,13 +5,20 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
// Some terminology:
|
||||
// a sequence = a serie of elements placed between quotes
|
||||
// a stack = a serie of vertically aligned slices sharing the same overall length
|
||||
// a slice = a serie of horizontally aligned elements
|
||||
// a choose = a serie of elements, one of which is chosen at random
|
||||
// mini(notation) = a series of elements placed between quotes
|
||||
// a stack = a series of vertically aligned slices sharing the same overall length
|
||||
// a sequence = a series of horizontally aligned elements
|
||||
// a choose = a series of elements, one of which is chosen at random
|
||||
|
||||
|
||||
{
|
||||
var AtomStub = function(source)
|
||||
{
|
||||
this.type_ = "atom";
|
||||
this.source_ = source;
|
||||
this.location_ = location();
|
||||
}
|
||||
|
||||
var PatternStub = function(source, alignment)
|
||||
{
|
||||
this.type_ = "pattern";
|
||||
@ -90,82 +97,99 @@ quote = '"' / "'"
|
||||
|
||||
// single step definition (e.g bd)
|
||||
step_char = [0-9a-zA-Z~] / "-" / "#" / "." / "^" / "_" / ":"
|
||||
step = ws chars:step_char+ ws { return chars.join("") }
|
||||
step = ws chars:step_char+ ws { return new AtomStub(chars.join("")) }
|
||||
|
||||
// define a sub cycle e.g. [1 2, 3 [4]]
|
||||
sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s}
|
||||
sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s }
|
||||
|
||||
// define a timeline e.g <1 3 [3 5]>. We simply defer to a stack and change the alignement
|
||||
timeline = ws "<" ws sc:single_cycle ws ">" ws
|
||||
{ sc.arguments_.alignment = "t"; return sc;}
|
||||
// define a polymeter e.g. {1 2, 3 4 5}
|
||||
polymeter = ws "{" ws s:polymeter_stack ws "}" stepsPerCycle:polymeter_steps? ws
|
||||
{ s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }
|
||||
|
||||
polymeter_steps = "%"a:slice
|
||||
{ return a }
|
||||
|
||||
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
|
||||
// change the alignment to slowcat
|
||||
slow_sequence = ws "<" ws s:sequence ws ">" ws
|
||||
{ s.arguments_.alignment = 'slowcat'; return s; }
|
||||
|
||||
// a slice is either a single step or a sub cycle
|
||||
slice = step / sub_cycle / timeline
|
||||
slice = step / sub_cycle / polymeter / slow_sequence
|
||||
|
||||
// slice modifier affects the timing/size of a slice (e.g. [a b c]@3)
|
||||
// at this point, we assume we can represent them as regular sequence operators
|
||||
slice_modifier = slice_weight / slice_bjorklund / slice_slow / slice_fast / slice_fixed_step / slice_replicate / slice_degrade
|
||||
slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade
|
||||
|
||||
slice_weight = "@" a:number
|
||||
{ return { weight: a} }
|
||||
op_weight = "@" a:number
|
||||
{ return x => x.options_['weight'] = a }
|
||||
|
||||
slice_replicate = "!"a:number
|
||||
{ return { replicate: a } }
|
||||
op_replicate = "!"a:number
|
||||
{ return x => x.options_['reps'] = a }
|
||||
|
||||
slice_bjorklund = "(" ws p:number ws comma ws s:number ws comma? ws r:number? ws ")"
|
||||
{ return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r || 0 } } } }
|
||||
op_bjorklund = "(" ws p:slice_with_ops ws comma ws s:slice_with_ops ws comma? ws r:slice_with_ops? ws ")"
|
||||
{ return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }
|
||||
|
||||
slice_slow = "/"a:number
|
||||
{ return { operator : { type_: "stretch", arguments_ :{ amount:a } } } }
|
||||
op_slow = "/"a:slice
|
||||
{ return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) }
|
||||
|
||||
slice_fast = "*"a:number
|
||||
{ return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } }
|
||||
op_fast = "*"a:slice
|
||||
{ return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) }
|
||||
|
||||
slice_fixed_step = "%"a:number
|
||||
{ return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } }
|
||||
|
||||
slice_degrade = "?"a:number?
|
||||
{ return { operator : { type_: "degradeBy", arguments_ :{ amount:(a? a : 0.5) } } } }
|
||||
op_degrade = "?"a:number?
|
||||
{ return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a } }) }
|
||||
|
||||
// a slice with an modifier applied i.e [bd@4 sd@3]@2 hh]
|
||||
slice_with_modifier = s:slice o:slice_modifier?
|
||||
{ return new ElementStub(s, o);}
|
||||
slice_with_ops = s:slice ops:slice_op*
|
||||
{ const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
|
||||
for (const op of ops) {
|
||||
op(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// a single cycle is a combination of one or more successive slices (as an array). If we
|
||||
// have only one element, we skip the array and return the element itself
|
||||
single_cycle = s:(slice_with_modifier)+
|
||||
{ return new PatternStub(s,"h"); }
|
||||
// a sequence is a combination of one or more successive slices (as an array)
|
||||
sequence = s:(slice_with_ops)+
|
||||
{ return new PatternStub(s, 'fastcat'); }
|
||||
|
||||
// a stack is a serie of vertically aligned single cycles, separated by a comma
|
||||
stack_tail = tail:(comma @single_cycle)+
|
||||
{ return { alignment: 'v', list: tail }; }
|
||||
// a stack is a series of vertically aligned sequence, separated by a comma
|
||||
stack_tail = tail:(comma @sequence)+
|
||||
{ return { alignment: 'stack', list: tail }; }
|
||||
|
||||
// a choose is a serie of pipe-separated single cycles, one of which is chosen
|
||||
// at random each time through the pattern
|
||||
choose_tail = tail:(pipe @single_cycle)+
|
||||
{ return { alignment: 'r', list: tail }; }
|
||||
// a choose is a series of pipe-separated sequence, one of which is
|
||||
// chosen at random, each cycle
|
||||
choose_tail = tail:(pipe @sequence)+
|
||||
{ return { alignment: 'rand', list: tail }; }
|
||||
|
||||
// if the stack contains only one element, we don't create a stack but return the
|
||||
// underlying element
|
||||
stack_or_choose = head:single_cycle tail:(stack_tail / choose_tail)?
|
||||
stack_or_choose = head:sequence tail:(stack_tail / choose_tail)?
|
||||
{ if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment); } else { return head; } }
|
||||
|
||||
// a sequence is a quoted stack
|
||||
sequence = ws quote sc:stack_or_choose quote
|
||||
polymeter_stack = head:sequence tail:stack_tail?
|
||||
{ return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); }
|
||||
|
||||
|
||||
// Mini-notation innards ends
|
||||
// ---------->8---------->8---------->8---------->8---------->8----------
|
||||
// Experimental haskellish parser begins
|
||||
|
||||
// mini-notation = a quoted stack
|
||||
mini = ws quote sc:stack_or_choose quote
|
||||
{ return sc; }
|
||||
|
||||
// ------------------ operators ---------------------------
|
||||
|
||||
operator = scale / slow / fast / target / bjorklund / struct / rotR / rotL
|
||||
|
||||
struct = "struct" ws s:sequence_or_operator
|
||||
{ return { name: "struct", args: { sequence:s }}}
|
||||
struct = "struct" ws s:mini_or_operator
|
||||
{ return { name: "struct", args: { mini:s }}}
|
||||
|
||||
target = "target" ws quote s:step quote
|
||||
{ return { name: "target", args : { name:s}}}
|
||||
|
||||
bjorklund = "euclid" ws p:int ws s:int ws r:int?
|
||||
{ return { name: "bjorklund", args :{ pulse: parseInt(p), step:parseInt(s) }}}
|
||||
{ return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}}
|
||||
|
||||
slow = "slow" ws a:number
|
||||
{ return { name: "stretch", args :{ amount: a}}}
|
||||
@ -189,27 +213,27 @@ comment = '//' p:([^\n]*)
|
||||
group_operator = cat
|
||||
|
||||
// cat is another form of timeline
|
||||
cat = "cat" ws "[" ws s:sequence_or_operator ss:(comma v:sequence_or_operator { return v})* ws "]"
|
||||
{ ss.unshift(s); return new PatternStub(ss,"t"); }
|
||||
cat = "cat" ws "[" ws s:mini_or_operator ss:(comma v:mini_or_operator { return v})* ws "]"
|
||||
{ ss.unshift(s); return new PatternStub(ss, 'slowcat'); }
|
||||
|
||||
// ------------------ high level sequence ---------------------------
|
||||
// ------------------ high level mini ---------------------------
|
||||
|
||||
sequence_or_group =
|
||||
mini_or_group =
|
||||
group_operator /
|
||||
sequence
|
||||
mini
|
||||
|
||||
sequence_or_operator =
|
||||
sg:sequence_or_group ws (comment)*
|
||||
mini_or_operator =
|
||||
sg:mini_or_group ws (comment)*
|
||||
{return sg}
|
||||
/ o:operator ws "$" ws soc:sequence_or_operator
|
||||
/ o:operator ws "$" ws soc:mini_or_operator
|
||||
{ return new OperatorStub(o.name,o.args,soc)}
|
||||
|
||||
sequ_or_operator_or_comment =
|
||||
sc: sequence_or_operator
|
||||
sc: mini_or_operator
|
||||
{ return sc }
|
||||
/ comment
|
||||
|
||||
sequence_definition = s:sequ_or_operator_or_comment
|
||||
mini_definition = s:sequ_or_operator_or_comment
|
||||
|
||||
// ---------------------- statements ----------------------------
|
||||
|
||||
@ -227,4 +251,4 @@ hush = "hush"
|
||||
|
||||
// ---------------------- statements ----------------------------
|
||||
|
||||
statement = sequence_definition / command
|
||||
statement = mini_definition / command
|
||||
|
||||
@ -7,8 +7,6 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
import * as krill from './krill-parser.js';
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
|
||||
const { pure, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel;
|
||||
|
||||
/* var _seedState = 0;
|
||||
const randOffset = 0.0002;
|
||||
|
||||
@ -16,148 +14,150 @@ function _nextSeed() {
|
||||
return _seedState++;
|
||||
} */
|
||||
|
||||
const applyOptions = (parent) => (pat, i) => {
|
||||
const applyOptions = (parent, code) => (pat, i) => {
|
||||
const ast = parent.source_[i];
|
||||
const options = ast.options_;
|
||||
const operator = options?.operator;
|
||||
if (operator) {
|
||||
switch (operator.type_) {
|
||||
case 'stretch': {
|
||||
const speed = Fraction(operator.arguments_.amount).inverse();
|
||||
return reify(pat).fast(speed);
|
||||
}
|
||||
case 'bjorklund':
|
||||
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
|
||||
case 'degradeBy':
|
||||
// TODO: find out what is right here
|
||||
// example:
|
||||
/*
|
||||
const ops = options?.ops;
|
||||
if (ops) {
|
||||
for (const op of ops) {
|
||||
switch (op.type_) {
|
||||
case 'stretch': {
|
||||
const legalTypes = ['fast', 'slow'];
|
||||
const { type, amount } = op.arguments_;
|
||||
if (!legalTypes.includes(type)) {
|
||||
throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`);
|
||||
}
|
||||
pat = strudel.reify(pat)[type](patternifyAST(amount, code));
|
||||
break;
|
||||
}
|
||||
case 'bjorklund': {
|
||||
if (op.arguments_.rotation) {
|
||||
pat = pat.euclidRot(
|
||||
patternifyAST(op.arguments_.pulse, code),
|
||||
patternifyAST(op.arguments_.step, code),
|
||||
patternifyAST(op.arguments_.rotation, code),
|
||||
);
|
||||
} else {
|
||||
pat = pat.euclid(patternifyAST(op.arguments_.pulse, code), patternifyAST(op.arguments_.step, code));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'degradeBy': {
|
||||
// TODO: find out what is right here
|
||||
// example:
|
||||
/*
|
||||
stack(
|
||||
s("hh*8").degrade(),
|
||||
s("[ht*8]?")
|
||||
)
|
||||
*/
|
||||
// above example will only be in sync when _degradeBy is used...
|
||||
// it also seems that the nextSeed will create undeterministic behaviour
|
||||
// as it uses a global _seedState. This is probably the reason for
|
||||
// https://github.com/tidalcycles/strudel/issues/245
|
||||
// above example will only be in sync when _degradeBy is used...
|
||||
// it also seems that the nextSeed will create undeterministic behaviour
|
||||
// as it uses a global _seedState. This is probably the reason for
|
||||
// https://github.com/tidalcycles/strudel/issues/245
|
||||
|
||||
// this is how it was:
|
||||
/*
|
||||
return reify(pat)._degradeByWith(
|
||||
// this is how it was:
|
||||
/*
|
||||
return strudel.reify(pat)._degradeByWith(
|
||||
strudel.rand.early(randOffset * _nextSeed()).segment(1),
|
||||
operator.arguments_.amount ?? 0.5,
|
||||
op.arguments_.amount ?? 0.5,
|
||||
);
|
||||
*/
|
||||
return reify(pat)._degradeBy(operator.arguments_.amount ?? 0.5);
|
||||
|
||||
// TODO: case 'fixed-step': "%"
|
||||
pat = strudel.reify(pat).degradeBy(op.arguments_.amount === null ? 0.5 : op.arguments_.amount);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn(`operator "${op.type_}" not implemented`);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.warn(`operator "${operator.type_}" not implemented`);
|
||||
}
|
||||
if (options?.weight) {
|
||||
// weight is handled by parent
|
||||
return pat;
|
||||
}
|
||||
// TODO: bjorklund e.g. "c3(5,8)"
|
||||
const unimplemented = Object.keys(options || {}).filter((key) => key !== 'operator');
|
||||
if (unimplemented.length) {
|
||||
console.warn(
|
||||
`option${unimplemented.length > 1 ? 's' : ''} ${unimplemented.map((o) => `"${o}"`).join(', ')} not implemented`,
|
||||
);
|
||||
}
|
||||
|
||||
return pat;
|
||||
};
|
||||
|
||||
function resolveReplications(ast) {
|
||||
// the general idea here: x!3 = [x*3]@3
|
||||
// could this be made easier?!
|
||||
ast.source_ = ast.source_.map((child) => {
|
||||
const { replicate, ...options } = child.options_ || {};
|
||||
if (replicate) {
|
||||
return {
|
||||
...child,
|
||||
options_: { ...options, weight: replicate },
|
||||
source_: {
|
||||
type_: 'pattern',
|
||||
arguments_: {
|
||||
alignment: 'h',
|
||||
},
|
||||
source_: [
|
||||
{
|
||||
type_: 'element',
|
||||
source_: child.source_,
|
||||
location_: child.location_,
|
||||
options_: {
|
||||
operator: {
|
||||
type_: 'stretch',
|
||||
arguments_: { amount: Fraction(replicate).inverse().toString() },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
return child;
|
||||
});
|
||||
ast.source_ = strudel.flatten(
|
||||
ast.source_.map((child) => {
|
||||
const { reps } = child.options_ || {};
|
||||
if (!reps) {
|
||||
return [child];
|
||||
}
|
||||
delete child.options_.reps;
|
||||
return Array(reps).fill(child);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function patternifyAST(ast, code) {
|
||||
switch (ast.type_) {
|
||||
case 'pattern': {
|
||||
resolveReplications(ast);
|
||||
const children = ast.source_.map((child) => patternifyAST(child, code)).map(applyOptions(ast));
|
||||
const children = ast.source_.map((child) => patternifyAST(child, code)).map(applyOptions(ast, code));
|
||||
const alignment = ast.arguments_.alignment;
|
||||
if (alignment === 'v') {
|
||||
return stack(...children);
|
||||
if (alignment === 'stack') {
|
||||
return strudel.stack(...children);
|
||||
}
|
||||
if (alignment === 'r') {
|
||||
if (alignment === 'polymeter') {
|
||||
// polymeter
|
||||
const stepsPerCycle = ast.arguments_.stepsPerCycle
|
||||
? patternifyAST(ast.arguments_.stepsPerCycle, code).fmap((x) => strudel.Fraction(x))
|
||||
: strudel.pure(strudel.Fraction(children.length > 0 ? children[0].__weight : 1));
|
||||
|
||||
const aligned = children.map((child) => child.fast(stepsPerCycle.fmap((x) => x.div(child.__weight || 1))));
|
||||
return strudel.stack(...aligned);
|
||||
}
|
||||
if (alignment === 'rand') {
|
||||
// https://github.com/tidalcycles/strudel/issues/245#issuecomment-1345406422
|
||||
// return strudel.chooseInWith(strudel.rand.early(randOffset * _nextSeed()).segment(1), children);
|
||||
return strudel.chooseCycles(...children);
|
||||
}
|
||||
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
||||
if (!weightedChildren && alignment === 't') {
|
||||
return slowcat(...children);
|
||||
if (!weightedChildren && alignment === 'slowcat') {
|
||||
return strudel.slowcat(...children);
|
||||
}
|
||||
if (weightedChildren) {
|
||||
const pat = timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
||||
if (alignment === 't') {
|
||||
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
||||
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
||||
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
||||
if (alignment === 'slowcat') {
|
||||
return pat._slow(weightSum); // timecat + slow
|
||||
}
|
||||
pat.__weight = weightSum;
|
||||
return pat;
|
||||
}
|
||||
return sequence(...children);
|
||||
const pat = strudel.sequence(...children);
|
||||
pat.__weight = children.length;
|
||||
return pat;
|
||||
}
|
||||
case 'element': {
|
||||
return patternifyAST(ast.source_, code);
|
||||
}
|
||||
case 'atom': {
|
||||
if (ast.source_ === '~') {
|
||||
return silence;
|
||||
return strudel.silence;
|
||||
}
|
||||
if (typeof ast.source_ !== 'object') {
|
||||
if (!ast.location_) {
|
||||
console.warn('no location for', ast);
|
||||
return ast.source_;
|
||||
}
|
||||
const { start, end } = ast.location_;
|
||||
const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
|
||||
// the following line expects the shapeshifter append .withMiniLocation
|
||||
// because location_ is only relative to the mini string, but we need it relative to whole code
|
||||
// make sure whitespaces are not part of the highlight:
|
||||
const actual = code?.split('').slice(start.offset, end.offset).join('');
|
||||
const [offsetStart = 0, offsetEnd = 0] = actual
|
||||
? actual.split(ast.source_).map((p) => p.split('').filter((c) => c === ' ').length)
|
||||
: [];
|
||||
return pure(value).withLocation(
|
||||
if (!ast.location_) {
|
||||
console.warn('no location for', ast);
|
||||
return ast.source_;
|
||||
}
|
||||
const { start, end } = ast.location_;
|
||||
const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
|
||||
// the following line expects the shapeshifter append .withMiniLocation
|
||||
// because location_ is only relative to the mini string, but we need it relative to whole code
|
||||
// make sure whitespaces are not part of the highlight:
|
||||
const actual = code?.split('').slice(start.offset, end.offset).join('');
|
||||
const [offsetStart = 0, offsetEnd = 0] = actual
|
||||
? actual.split(ast.source_).map((p) => p.split('').filter((c) => c === ' ').length)
|
||||
: [];
|
||||
return strudel
|
||||
.pure(value)
|
||||
.withLocation(
|
||||
[start.line, start.column + offsetStart, start.offset + offsetStart],
|
||||
[start.line, end.column - offsetEnd, end.offset - offsetEnd],
|
||||
);
|
||||
}
|
||||
return patternifyAST(ast.source_, code);
|
||||
}
|
||||
case 'stretch':
|
||||
return patternifyAST(ast.source_, code).slow(ast.arguments_.amount);
|
||||
return patternifyAST(ast.source_, code).slow(patternifyAST(ast.arguments_.amount, code));
|
||||
/* case 'scale':
|
||||
let [tonic, scale] = Scale.tokenize(ast.arguments_.scale);
|
||||
const intervals = Scale.get(scale).intervals;
|
||||
@ -179,10 +179,10 @@ export function patternifyAST(ast, code) {
|
||||
}); */
|
||||
/* case 'struct':
|
||||
// TODO:
|
||||
return silence; */
|
||||
return strudel.silence; */
|
||||
default:
|
||||
console.warn(`node type "${ast.type_}" not implemented -> returning silence`);
|
||||
return silence;
|
||||
return strudel.silence;
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +193,7 @@ export const mini = (...strings) => {
|
||||
const ast = krill.parse(code);
|
||||
return patternifyAST(ast, code);
|
||||
});
|
||||
return sequence(...pats);
|
||||
return strudel.sequence(...pats);
|
||||
};
|
||||
|
||||
// includes haskell style (raw krill parsing)
|
||||
@ -207,5 +207,5 @@ export function minify(thing) {
|
||||
if (typeof thing === 'string') {
|
||||
return mini(thing);
|
||||
}
|
||||
return reify(thing);
|
||||
return strudel.reify(thing);
|
||||
}
|
||||
|
||||
1031
packages/mini/package-lock.json
generated
1031
packages/mini/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,18 @@
|
||||
{
|
||||
"name": "@strudel.cycles/mini",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"description": "Mini notation for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"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": {
|
||||
"type": "git",
|
||||
@ -26,11 +32,11 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.5.0",
|
||||
"@strudel.cycles/eval": "^0.5.0",
|
||||
"@strudel.cycles/tone": "^0.5.0"
|
||||
"@strudel.cycles/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"peggy": "^2.0.1"
|
||||
"peggy": "^2.0.1",
|
||||
"vite": "^3.2.2",
|
||||
"vitest": "^0.25.7"
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,8 @@ import '@strudel.cycles/core/euclid.mjs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('mini', () => {
|
||||
const minV = (v) => mini(v).firstCycleValues;
|
||||
const minS = (v) => mini(v).showFirstCycle;
|
||||
const minV = (v) => mini(v).sortHapsByPart().firstCycleValues;
|
||||
const minS = (v) => mini(v).sortHapsByPart().showFirstCycle;
|
||||
it('supports single elements', () => {
|
||||
expect(minV('a')).toEqual(['a']);
|
||||
});
|
||||
@ -21,6 +21,21 @@ describe('mini', () => {
|
||||
expect(minS('a b')).toEqual(['a: 0 - 1/2', 'b: 1/2 - 1']);
|
||||
expect(minS('a b c')).toEqual(['a: 0 - 1/3', 'b: 1/3 - 2/3', 'c: 2/3 - 1']);
|
||||
});
|
||||
it('supports fast', () => {
|
||||
expect(minS('a*3 b')).toEqual(minS('[a a a] b'));
|
||||
});
|
||||
it('supports patterned fast', () => {
|
||||
expect(minS('[a*<3 5>]*2')).toEqual(minS('[a a a] [a a a a a]'));
|
||||
});
|
||||
it('supports slow', () => {
|
||||
expect(minS('[a a a]/3 b')).toEqual(minS('a b'));
|
||||
});
|
||||
it('supports patterned slow', () => {
|
||||
expect(minS('[a a a a a a a a]/[2 4]')).toEqual(minS('[a a] a'));
|
||||
});
|
||||
it('supports patterned fast', () => {
|
||||
expect(minS('[a*<3 5>]*2')).toEqual(minS('[a a a] [a a a a a]'));
|
||||
});
|
||||
it('supports slowcat', () => {
|
||||
expect(minV('<a b>')).toEqual(['a']);
|
||||
});
|
||||
@ -36,6 +51,16 @@ describe('mini', () => {
|
||||
expect(minS('c3 [d3 e3]')).toEqual(['c3: 0 - 1/2', 'd3: 1/2 - 3/4', 'e3: 3/4 - 1']);
|
||||
expect(minS('c3 [d3 [e3 f3]]')).toEqual(['c3: 0 - 1/2', 'd3: 1/2 - 3/4', 'e3: 3/4 - 7/8', 'f3: 7/8 - 1']);
|
||||
});
|
||||
it('supports curly brackets', () => {
|
||||
expect(minS('{a b, c d e}*3')).toEqual(minS('[a b a b a b, c d e c d e]'));
|
||||
expect(minS('{a b, c [d e] f}*3')).toEqual(minS('[a b a b a b, c [d e] f c [d e] f]'));
|
||||
expect(minS('{a b c, d e}*2')).toEqual(minS('[a b c a b c, d e d e d e]'));
|
||||
});
|
||||
it('supports curly brackets with explicit step-per-cycle', () => {
|
||||
expect(minS('{a b, c d e}%3')).toEqual(minS('[a b a, c d e]'));
|
||||
expect(minS('{a b, c d e}%5')).toEqual(minS('[a b a b a, c d e c d]'));
|
||||
expect(minS('{a b, c d e}%6')).toEqual(minS('[a b a b a b, c d e c d e]'));
|
||||
});
|
||||
it('supports commas', () => {
|
||||
expect(minS('c3,e3,g3')).toEqual(['c3: 0 - 1', 'e3: 0 - 1', 'g3: 0 - 1']);
|
||||
expect(minS('[c3,e3,g3] f3')).toEqual(['c3: 0 - 1/2', 'e3: 0 - 1/2', 'g3: 0 - 1/2', 'f3: 1/2 - 1']);
|
||||
@ -46,10 +71,48 @@ describe('mini', () => {
|
||||
});
|
||||
it('supports replication', () => {
|
||||
expect(minS('a!3 b')).toEqual(['a: 0 - 1/4', 'a: 1/4 - 1/2', 'a: 1/2 - 3/4', 'b: 3/4 - 1']);
|
||||
expect(minS('[<a b c>]!3 d')).toEqual(minS('<a b c> <a b c> <a b c> d'));
|
||||
});
|
||||
it('supports euclidean rhythms', () => {
|
||||
expect(minS('a(3, 8)')).toEqual(['a: 0 - 1/8', 'a: 3/8 - 1/2', 'a: 3/4 - 7/8']);
|
||||
});
|
||||
it('supports patterning euclidean rhythms', () => {
|
||||
expect(minS('[a(<3 5>, <8 16>)]*2')).toEqual(minS('a(3,8) a(5,16)'));
|
||||
});
|
||||
it("reproduces Toussaint's example euclidean algorithms", () => {
|
||||
const checkEuclid = function (spec, target) {
|
||||
expect(minS(`x(${spec[0]},${spec[1]})`)).toEqual(minS(target));
|
||||
};
|
||||
checkEuclid([1, 2], 'x ~');
|
||||
checkEuclid([1, 3], 'x ~ ~');
|
||||
checkEuclid([1, 4], 'x ~ ~ ~');
|
||||
checkEuclid([4, 12], 'x ~ ~ x ~ ~ x ~ ~ x ~ ~');
|
||||
checkEuclid([2, 5], 'x ~ x ~ ~');
|
||||
// checkEuclid([3, 4], "x ~ x x"); // Toussaint is wrong..
|
||||
checkEuclid([3, 4], 'x x x ~'); // correction
|
||||
checkEuclid([3, 5], 'x ~ x ~ x');
|
||||
checkEuclid([3, 7], 'x ~ x ~ x ~ ~');
|
||||
checkEuclid([3, 8], 'x ~ ~ x ~ ~ x ~');
|
||||
checkEuclid([4, 7], 'x ~ x ~ x ~ x');
|
||||
checkEuclid([4, 9], 'x ~ x ~ x ~ x ~ ~');
|
||||
checkEuclid([4, 11], 'x ~ ~ x ~ ~ x ~ ~ x ~');
|
||||
// checkEuclid([5, 6], "x ~ x x x x"); // Toussaint is wrong..
|
||||
checkEuclid([5, 6], 'x x x x x ~'); // correction
|
||||
checkEuclid([5, 7], 'x ~ x x ~ x x');
|
||||
checkEuclid([5, 8], 'x ~ x x ~ x x ~');
|
||||
checkEuclid([5, 9], 'x ~ x ~ x ~ x ~ x');
|
||||
checkEuclid([5, 11], 'x ~ x ~ x ~ x ~ x ~ ~');
|
||||
checkEuclid([5, 12], 'x ~ ~ x ~ x ~ ~ x ~ x ~');
|
||||
// checkEuclid([5, 16], "x ~ ~ x ~ ~ x ~ ~ x ~ ~ x ~ ~ ~ ~"); // Toussaint is wrong..
|
||||
checkEuclid([5, 16], 'x ~ ~ x ~ ~ x ~ ~ x ~ ~ x ~ ~ ~'); // correction
|
||||
// checkEuclid([7, 8], "x ~ x x x x x x"); // Toussaint is wrong..
|
||||
checkEuclid([7, 8], 'x x x x x x x ~'); // Correction
|
||||
checkEuclid([7, 12], 'x ~ x x ~ x ~ x x ~ x ~');
|
||||
checkEuclid([7, 16], 'x ~ ~ x ~ x ~ x ~ ~ x ~ x ~ x ~');
|
||||
checkEuclid([9, 16], 'x ~ x x ~ x ~ x ~ x x ~ x ~ x ~');
|
||||
checkEuclid([11, 24], 'x ~ ~ x ~ x ~ x ~ x ~ x ~ ~ x ~ x ~ x ~ x ~ x ~');
|
||||
checkEuclid([13, 24], 'x ~ x x ~ x ~ x ~ x ~ x ~ x x ~ x ~ x ~ x ~ x ~');
|
||||
});
|
||||
it('supports the ? operator', () => {
|
||||
expect(
|
||||
mini('a?')
|
||||
|
||||
19
packages/mini/vite.config.js
Normal file
19
packages/mini/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
@ -34,6 +34,6 @@ Now open the REPL and type:
|
||||
s("<bd sd> hh").osc()
|
||||
```
|
||||
|
||||
or just [click here](http://localhost:3000/#cygiPGJkIHNkPiBoaCIpLm9zYygp)...
|
||||
or just [click here](https://strudel.tidalcycles.org/#cygiPGJkIHNkPiBoaCIpLm9zYygp)...
|
||||
|
||||
You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.tidalcycles.org/tutorial/#superdirt-api)
|
||||
You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.tidalcycles.org/learn/input-output/#superdirt-api)
|
||||
|
||||
@ -39,6 +39,7 @@ let startedAt = -1;
|
||||
/**
|
||||
*
|
||||
* 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
|
||||
* @memberof Pattern
|
||||
|
||||
60
packages/osc/package-lock.json
generated
60
packages/osc/package-lock.json
generated
@ -1,60 +0,0 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,19 @@
|
||||
{
|
||||
"name": "@strudel.cycles/osc",
|
||||
"version": "0.4.0",
|
||||
"version": "0.6.0",
|
||||
"description": "OSC messaging for strudel",
|
||||
"main": "osc.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"No tests present.\" && exit 0",
|
||||
"server": "node server.js",
|
||||
"tidal-sniffer": "node tidal-sniffer.js",
|
||||
"client": "npx serve -p 4321",
|
||||
"build": "npx pkg server.js --targets node16-macos-x64,node16-win-x64,node16-linux-x64 --out-path bin"
|
||||
"build-bin": "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": {
|
||||
"type": "git",
|
||||
@ -31,9 +36,11 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"osc-js": "^2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pkg": "^5.7.0"
|
||||
"pkg": "^5.7.0",
|
||||
"vite": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
19
packages/osc/vite.config.js
Normal file
19
packages/osc/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
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
packages/react/.gitignore
vendored
2
packages/react/.gitignore
vendored
@ -11,8 +11,6 @@ node_modules
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
!dist
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
@ -33,11 +33,4 @@ export function Repl({ tune }) {
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
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
|
||||
```
|
||||
For a more sophisticated example, check out the [nano-repl](./examples/nano-repl/)!
|
||||
|
||||
1
packages/react/dist/index.cjs.js
vendored
1
packages/react/dist/index.cjs.js
vendored
File diff suppressed because one or more lines are too long
320
packages/react/dist/index.es.js
vendored
320
packages/react/dist/index.es.js
vendored
@ -1,320 +0,0 @@
|
||||
import n, { useCallback as _, useRef as H, useEffect as L, useMemo as V, useState as w, useLayoutEffect as j } from "react";
|
||||
import X from "@uiw/react-codemirror";
|
||||
import { Decoration as E, EditorView as U } from "@codemirror/view";
|
||||
import { StateEffect as $, StateField as G } from "@codemirror/state";
|
||||
import { javascript as Y } from "@codemirror/lang-javascript";
|
||||
import { tags as r } from "@lezer/highlight";
|
||||
import { createTheme as Z } from "@uiw/codemirror-themes";
|
||||
import { useInView as ee } from "react-hook-inview";
|
||||
import { webaudioOutput as te, getAudioContext as re } from "@strudel.cycles/webaudio";
|
||||
import { repl as oe } from "@strudel.cycles/core";
|
||||
import { transpiler as ne } from "@strudel.cycles/transpiler";
|
||||
const ae = Z({
|
||||
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: r.keyword, color: "#c792ea" },
|
||||
{ tag: r.operator, color: "#89ddff" },
|
||||
{ tag: r.special(r.variableName), color: "#eeffff" },
|
||||
{ tag: r.typeName, color: "#c3e88d" },
|
||||
{ tag: r.atom, color: "#f78c6c" },
|
||||
{ tag: r.number, color: "#c3e88d" },
|
||||
{ tag: r.definition(r.variableName), color: "#82aaff" },
|
||||
{ tag: r.string, color: "#c3e88d" },
|
||||
{ tag: r.special(r.string), color: "#c3e88d" },
|
||||
{ tag: r.comment, color: "#7d8799" },
|
||||
{ tag: r.variableName, color: "#c792ea" },
|
||||
{ tag: r.tagName, color: "#c3e88d" },
|
||||
{ tag: r.bracket, color: "#525154" },
|
||||
{ tag: r.meta, color: "#ffcb6b" },
|
||||
{ tag: r.attributeName, color: "#c792ea" },
|
||||
{ tag: r.propertyName, color: "#c792ea" },
|
||||
{ tag: r.className, color: "#decb6b" },
|
||||
{ tag: r.invalid, color: "#ffffff" }
|
||||
]
|
||||
});
|
||||
const B = $.define(), se = G.define({
|
||||
create() {
|
||||
return E.none;
|
||||
},
|
||||
update(e, t) {
|
||||
try {
|
||||
for (let o of t.effects)
|
||||
if (o.is(B))
|
||||
if (o.value) {
|
||||
const a = E.mark({ attributes: { style: "background-color: #FFCA2880" } });
|
||||
e = E.set([a.range(0, t.newDoc.length)]);
|
||||
} else
|
||||
e = E.set([]);
|
||||
return e;
|
||||
} catch (o) {
|
||||
return console.warn("flash error", o), e;
|
||||
}
|
||||
},
|
||||
provide: (e) => U.decorations.from(e)
|
||||
}), ce = (e) => {
|
||||
e.dispatch({ effects: B.of(!0) }), setTimeout(() => {
|
||||
e.dispatch({ effects: B.of(!1) });
|
||||
}, 200);
|
||||
}, z = $.define(), ie = G.define({
|
||||
create() {
|
||||
return E.none;
|
||||
},
|
||||
update(e, t) {
|
||||
try {
|
||||
for (let o of t.effects)
|
||||
if (o.is(z)) {
|
||||
const a = o.value.map(
|
||||
(s) => (s.context.locations || []).map(({ start: u, end: d }) => {
|
||||
const f = s.context.color || "#FFCA28";
|
||||
let c = t.newDoc.line(u.line).from + u.column, i = t.newDoc.line(d.line).from + d.column;
|
||||
const m = t.newDoc.length;
|
||||
return c > m || i > m ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${f};` } }).range(c, i);
|
||||
})
|
||||
).flat().filter(Boolean) || [];
|
||||
e = E.set(a, !0);
|
||||
}
|
||||
return e;
|
||||
} catch {
|
||||
return E.set([]);
|
||||
}
|
||||
},
|
||||
provide: (e) => U.decorations.from(e)
|
||||
}), le = [Y(), ae, ie, se];
|
||||
function de({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: u }) {
|
||||
const d = _(
|
||||
(i) => {
|
||||
t?.(i);
|
||||
},
|
||||
[t]
|
||||
), f = _(
|
||||
(i) => {
|
||||
o?.(i);
|
||||
},
|
||||
[o]
|
||||
), c = _(
|
||||
(i) => {
|
||||
i.selectionSet && a && a?.(i.state.selection);
|
||||
},
|
||||
[a]
|
||||
);
|
||||
return /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement(X, {
|
||||
value: e,
|
||||
onChange: d,
|
||||
onCreateEditor: f,
|
||||
onUpdate: c,
|
||||
extensions: le
|
||||
}));
|
||||
}
|
||||
function K(...e) {
|
||||
return e.filter(Boolean).join(" ");
|
||||
}
|
||||
function ue({ view: e, pattern: t, active: o, getTime: a }) {
|
||||
const s = H([]), u = H();
|
||||
L(() => {
|
||||
if (e)
|
||||
if (t && o) {
|
||||
let d = requestAnimationFrame(function f() {
|
||||
try {
|
||||
const c = a(), m = [Math.max(u.current || c, c - 1 / 10, 0), c + 1 / 60];
|
||||
u.current = m[1], s.current = s.current.filter((g) => g.whole.end > c);
|
||||
const h = t.queryArc(...m).filter((g) => g.hasOnset());
|
||||
s.current = s.current.concat(h), e.dispatch({ effects: z.of(s.current) });
|
||||
} catch {
|
||||
e.dispatch({ effects: z.of([]) });
|
||||
}
|
||||
d = requestAnimationFrame(f);
|
||||
});
|
||||
return () => {
|
||||
cancelAnimationFrame(d);
|
||||
};
|
||||
} else
|
||||
s.current = [], e.dispatch({ effects: z.of([]) });
|
||||
}, [t, o, e]);
|
||||
}
|
||||
const fe = "_container_3i85k_1", me = "_header_3i85k_5", ge = "_buttons_3i85k_9", pe = "_button_3i85k_9", he = "_buttonDisabled_3i85k_17", be = "_error_3i85k_21", ve = "_body_3i85k_25", v = {
|
||||
container: fe,
|
||||
header: me,
|
||||
buttons: ge,
|
||||
button: pe,
|
||||
buttonDisabled: he,
|
||||
error: be,
|
||||
body: ve
|
||||
};
|
||||
function O({ type: e }) {
|
||||
return /* @__PURE__ */ n.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__ */ n.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__ */ n.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__ */ n.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"
|
||||
})
|
||||
}[e]);
|
||||
}
|
||||
function Ee(e) {
|
||||
return L(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), _((t) => window.postMessage(t, "*"), []);
|
||||
}
|
||||
function we({
|
||||
defaultOutput: e,
|
||||
interval: t,
|
||||
getTime: o,
|
||||
evalOnMount: a = !1,
|
||||
initialCode: s = "",
|
||||
autolink: u = !1,
|
||||
beforeEval: d,
|
||||
afterEval: f,
|
||||
onEvalError: c,
|
||||
onToggle: i
|
||||
}) {
|
||||
const m = V(() => ye(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V(
|
||||
() => oe({
|
||||
interval: t,
|
||||
defaultOutput: e,
|
||||
onSchedulerError: g,
|
||||
onEvalError: (l) => {
|
||||
N(l), c?.(l);
|
||||
},
|
||||
getTime: o,
|
||||
transpiler: ne,
|
||||
beforeEval: ({ code: l }) => {
|
||||
y(l), d?.();
|
||||
},
|
||||
afterEval: ({ pattern: l, code: P }) => {
|
||||
S(P), D(l), N(), g(), u && (window.location.hash = "#" + encodeURIComponent(btoa(P))), f?.();
|
||||
},
|
||||
onToggle: (l) => {
|
||||
x(l), i?.(l);
|
||||
}
|
||||
}),
|
||||
[e, t, o]
|
||||
), W = Ee(({ data: { from: l, type: P } }) => {
|
||||
P === "start" && l !== m && q();
|
||||
}), R = _(
|
||||
async (l = !0) => {
|
||||
await T(p, l), W({ type: "start", from: m });
|
||||
},
|
||||
[T, p]
|
||||
), I = H();
|
||||
return L(() => {
|
||||
!I.current && a && p && (I.current = !0, R());
|
||||
}, [R, a, p]), L(() => () => {
|
||||
A.stop();
|
||||
}, [A]), {
|
||||
code: p,
|
||||
setCode: y,
|
||||
error: h || C,
|
||||
schedulerError: h,
|
||||
scheduler: A,
|
||||
evalError: C,
|
||||
evaluate: T,
|
||||
activateCode: R,
|
||||
activeCode: M,
|
||||
isDirty: b,
|
||||
pattern: k,
|
||||
started: F,
|
||||
start: J,
|
||||
stop: q,
|
||||
pause: Q,
|
||||
togglePlay: async () => {
|
||||
F ? A.pause() : await R();
|
||||
}
|
||||
};
|
||||
}
|
||||
function ye() {
|
||||
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
|
||||
}
|
||||
const ke = () => re().currentTime;
|
||||
function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
|
||||
const {
|
||||
code: s,
|
||||
setCode: u,
|
||||
evaluate: d,
|
||||
activateCode: f,
|
||||
error: c,
|
||||
isDirty: i,
|
||||
activeCode: m,
|
||||
pattern: h,
|
||||
started: g,
|
||||
scheduler: C,
|
||||
togglePlay: N,
|
||||
stop: p
|
||||
} = we({
|
||||
initialCode: e,
|
||||
defaultOutput: te,
|
||||
getTime: ke
|
||||
}), [y, M] = w(), [S, k] = ee({
|
||||
threshold: 0.01
|
||||
}), D = H(), F = V(() => ((k || !t) && (D.current = !0), k || D.current), [k, t]);
|
||||
return ue({
|
||||
view: y,
|
||||
pattern: h,
|
||||
active: g && !m?.includes("strudel disable-highlighting"),
|
||||
getTime: () => C.getPhase()
|
||||
}), j(() => {
|
||||
if (a) {
|
||||
const x = async (b) => {
|
||||
(b.ctrlKey || b.altKey) && (b.code === "Enter" ? (b.preventDefault(), ce(y), await f()) : b.code === "Period" && (p(), b.preventDefault()));
|
||||
};
|
||||
return window.addEventListener("keydown", x, !0), () => window.removeEventListener("keydown", x, !0);
|
||||
}
|
||||
}, [a, h, s, d, p, y]), /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.container,
|
||||
ref: S
|
||||
}, /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.header
|
||||
}, /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.buttons
|
||||
}, /* @__PURE__ */ n.createElement("button", {
|
||||
className: K(v.button, g ? "sc-animate-pulse" : ""),
|
||||
onClick: () => N()
|
||||
}, /* @__PURE__ */ n.createElement(O, {
|
||||
type: g ? "pause" : "play"
|
||||
})), /* @__PURE__ */ n.createElement("button", {
|
||||
className: K(i ? v.button : v.buttonDisabled),
|
||||
onClick: () => f()
|
||||
}, /* @__PURE__ */ n.createElement(O, {
|
||||
type: "refresh"
|
||||
}))), c && /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.error
|
||||
}, c.message)), /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.body
|
||||
}, F && /* @__PURE__ */ n.createElement(de, {
|
||||
value: s,
|
||||
onChange: u,
|
||||
onViewChanged: M
|
||||
})));
|
||||
}
|
||||
const Te = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
|
||||
export {
|
||||
de as CodeMirror,
|
||||
Se as MiniRepl,
|
||||
K as cx,
|
||||
ce as flash,
|
||||
ue as useHighlighting,
|
||||
Te as useKeydown,
|
||||
Ee as usePostMessage,
|
||||
we as useStrudel
|
||||
};
|
||||
1
packages/react/dist/style.css
vendored
1
packages/react/dist/style.css
vendored
@ -1 +0,0 @@
|
||||
.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}._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}
|
||||
2
packages/react/examples/nano-repl/.gitignore
vendored
2
packages/react/examples/nano-repl/.gitignore
vendored
@ -8,7 +8,7 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
!dist
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
.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
@ -1 +0,0 @@
|
||||
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
@ -1 +0,0 @@
|
||||
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};
|
||||
@ -1,14 +0,0 @@
|
||||
<!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>
|
||||
3520
packages/react/examples/nano-repl/package-lock.json
generated
3520
packages/react/examples/nano-repl/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "nano-repl",
|
||||
"name": "@strudel.cycles/nano-repl",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.6.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -10,7 +10,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"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": {
|
||||
"@types/react": "^18.0.17",
|
||||
|
||||
@ -1,27 +1,21 @@
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import { controls, evalScope } from '@strudel.cycles/core';
|
||||
import { CodeMirror, useHighlighting, useKeydown, useStrudel, flash } from '@strudel.cycles/react';
|
||||
import { getAudioContext, initAudioOnFirstClick, panic, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
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 { prebake } from '../../../../../repl/src/prebake.mjs';
|
||||
|
||||
initAudioOnFirstClick();
|
||||
|
||||
// TODO: only import stuff when play is pressed?
|
||||
evalScope(
|
||||
controls,
|
||||
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/mini'),
|
||||
import('@strudel.cycles/xen'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/osc'),
|
||||
import('@strudel.cycles/webdirt'),
|
||||
import('@strudel.cycles/serial'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
);
|
||||
|
||||
const defaultTune = `samples({
|
||||
@ -30,7 +24,7 @@ const defaultTune = `samples({
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
stack(
|
||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
|
||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8") // drums
|
||||
.speed(perlin.range(.7,.9)) // random sample speed variation
|
||||
//.hush()
|
||||
,"<a1 b1*2 a1(3,8) e2>" // bassline
|
||||
@ -43,7 +37,7 @@ stack(
|
||||
.gain(.4) // turn down
|
||||
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
|
||||
//.hush()
|
||||
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
|
||||
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
|
||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.n() // wrap in "n"
|
||||
@ -103,7 +97,7 @@ function App() {
|
||||
scheduler.start();
|
||||
}
|
||||
} else if (e.code === 'Period') {
|
||||
scheduler.pause();
|
||||
scheduler.stop();
|
||||
panic();
|
||||
e.preventDefault();
|
||||
}
|
||||
@ -114,13 +108,11 @@ function App() {
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
{/* <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">
|
||||
<nav className="z-[12] w-full flex justify-center fixed bottom-0">
|
||||
<div className="bg-slate-500 space-x-2 px-2 rounded-t-md">
|
||||
<button
|
||||
onClick={async () => {
|
||||
await evaluate(code);
|
||||
await getAudioContext().resume();
|
||||
scheduler.start();
|
||||
}}
|
||||
>
|
||||
|
||||
@ -1,24 +1,18 @@
|
||||
{
|
||||
"name": "@strudel.cycles/react",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"description": "React components for strudel",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.es.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./dist/index.cjs.js",
|
||||
"import": "./dist/index.es.js"
|
||||
},
|
||||
"./dist/style.css": "./dist/style.css"
|
||||
"main": "src/index.js",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -38,11 +32,14 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.4.0",
|
||||
"@codemirror/lang-javascript": "^6.1.1",
|
||||
"@strudel.cycles/core": "^0.5.0",
|
||||
"@strudel.cycles/tone": "^0.5.0",
|
||||
"@strudel.cycles/transpiler": "^0.5.0",
|
||||
"@strudel.cycles/webaudio": "^0.5.0",
|
||||
"@codemirror/state": "^6.2.0",
|
||||
"@codemirror/view": "^6.7.3",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@uiw/codemirror-themes": "^4.12.4",
|
||||
"@uiw/react-codemirror": "^4.12.4",
|
||||
"react-hook-inview": "^4.5.0"
|
||||
|
||||
@ -6,7 +6,6 @@ import { controls, evalScope } from '@strudel.cycles/core';
|
||||
evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
// import('@strudel.cycles/tone'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/midi'),
|
||||
|
||||
77
packages/react/src/components/Autocomplete.jsx
Normal file
77
packages/react/src/components/Autocomplete.jsx
Normal file
@ -0,0 +1,77 @@
|
||||
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' },
|
||||
], */
|
||||
};
|
||||
};
|
||||
@ -6,6 +6,8 @@ import { javascript } from '@codemirror/lang-javascript';
|
||||
import strudelTheme from '../themes/strudel-theme';
|
||||
import './style.css';
|
||||
import { useCallback } from 'react';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import { strudelAutocomplete } from './Autocomplete';
|
||||
|
||||
export const setFlash = StateEffect.define();
|
||||
const flashField = StateField.define({
|
||||
@ -49,11 +51,12 @@ const highlightField = StateField.define({
|
||||
try {
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setHighlights)) {
|
||||
const { haps } = e.value;
|
||||
const marks =
|
||||
e.value
|
||||
haps
|
||||
.map((hap) =>
|
||||
(hap.context.locations || []).map(({ start, end }) => {
|
||||
const color = hap.context.color || '#FFCA28';
|
||||
const color = hap.context.color || e.value.color || '#FFCA28';
|
||||
let from = tr.newDoc.line(start.line).from + start.column;
|
||||
let to = tr.newDoc.line(end.line).from + end.column;
|
||||
const l = tr.newDoc.length;
|
||||
@ -79,9 +82,24 @@ const highlightField = StateField.define({
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
const extensions = [javascript(), strudelTheme, highlightField, flashField];
|
||||
const extensions = [
|
||||
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({ value, onChange, onViewChanged, onSelectionChange, options, editorDidMount }) {
|
||||
export default function CodeMirror({
|
||||
value,
|
||||
onChange,
|
||||
onViewChanged,
|
||||
onSelectionChange,
|
||||
theme,
|
||||
options,
|
||||
editorDidMount,
|
||||
}) {
|
||||
const handleOnChange = useCallback(
|
||||
(value) => {
|
||||
onChange?.(value);
|
||||
@ -106,6 +124,7 @@ export default function CodeMirror({ value, onChange, onViewChanged, onSelection
|
||||
<>
|
||||
<_CodeMirror
|
||||
value={value}
|
||||
theme={theme || strudelTheme}
|
||||
onChange={handleOnChange}
|
||||
onCreateEditor={handleOnCreateEditor}
|
||||
onUpdate={handleOnUpdate}
|
||||
|
||||
@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
export function Icon({ type }) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="sc-h-5 sc-w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
{
|
||||
{
|
||||
refresh: (
|
||||
@ -26,6 +26,13 @@ export function Icon({ type }) {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
stop: (
|
||||
<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"
|
||||
/>
|
||||
),
|
||||
}[type]
|
||||
}
|
||||
</svg>
|
||||
|
||||
@ -1,18 +1,36 @@
|
||||
import React, { useState, useMemo, useRef, useEffect, useLayoutEffect } from 'react';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import React, { useLayoutEffect, useMemo, useRef, useState, useCallback, useEffect } from 'react';
|
||||
import { useInView } from 'react-hook-inview';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import cx from '../cx';
|
||||
import useHighlighting from '../hooks/useHighlighting.mjs';
|
||||
import CodeMirror6, { flash } from './CodeMirror6';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import './style.css';
|
||||
import styles from './MiniRepl.module.css';
|
||||
import { Icon } from './Icon';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import useStrudel from '../hooks/useStrudel.mjs';
|
||||
import CodeMirror6, { flash } from './CodeMirror6';
|
||||
import { Icon } from './Icon';
|
||||
import styles from './MiniRepl.module.css';
|
||||
import './style.css';
|
||||
import { logger } from '@strudel.cycles/core';
|
||||
import useEvent from '../hooks/useEvent.mjs';
|
||||
import useKeydown from '../hooks/useKeydown.mjs';
|
||||
|
||||
const getTime = () => getAudioContext().currentTime;
|
||||
|
||||
export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }) {
|
||||
export function MiniRepl({
|
||||
tune,
|
||||
hideOutsideView = false,
|
||||
enableKeyboard,
|
||||
drawTime,
|
||||
punchcard,
|
||||
canvasHeight = 200,
|
||||
theme,
|
||||
highlightColor,
|
||||
}) {
|
||||
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
||||
const evalOnMount = !!drawTime;
|
||||
const drawContext = useCallback(
|
||||
!!drawTime ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null,
|
||||
[drawTime],
|
||||
);
|
||||
const {
|
||||
code,
|
||||
setCode,
|
||||
@ -26,14 +44,18 @@ export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }
|
||||
scheduler,
|
||||
togglePlay,
|
||||
stop,
|
||||
canvasId,
|
||||
id: replId,
|
||||
} = useStrudel({
|
||||
initialCode: tune,
|
||||
defaultOutput: webaudioOutput,
|
||||
editPattern: (pat) => (punchcard ? pat.punchcard() : pat),
|
||||
getTime,
|
||||
evalOnMount,
|
||||
drawContext,
|
||||
drawTime,
|
||||
});
|
||||
/* useEffect(() => {
|
||||
init && activateCode();
|
||||
}, [init, activateCode]); */
|
||||
|
||||
const [view, setView] = useState();
|
||||
const [ref, isVisible] = useInView({
|
||||
threshold: 0.01,
|
||||
@ -49,9 +71,31 @@ export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }
|
||||
view,
|
||||
pattern,
|
||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||
getTime: () => scheduler.getPhase(),
|
||||
getTime: () => scheduler.now(),
|
||||
color: highlightColor,
|
||||
});
|
||||
|
||||
// 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
|
||||
useLayoutEffect(() => {
|
||||
if (enableKeyboard) {
|
||||
@ -72,12 +116,26 @@ export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }
|
||||
}
|
||||
}, [enableKeyboard, pattern, code, evaluate, stop, view]);
|
||||
|
||||
const [log, setLog] = useState([]);
|
||||
useLogger(
|
||||
useCallback((e) => {
|
||||
const { data } = e.detail;
|
||||
const logId = data?.hap?.context?.id;
|
||||
// const logId = data?.pattern?.meta?.id;
|
||||
if (logId === replId) {
|
||||
setLog((l) => {
|
||||
return l.concat([e.detail]).slice(-10);
|
||||
});
|
||||
}
|
||||
}, []),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.container} ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.buttons}>
|
||||
<button className={cx(styles.button, started ? 'sc-animate-pulse' : '')} onClick={() => togglePlay()}>
|
||||
<Icon type={started ? 'pause' : 'play'} />
|
||||
<button className={cx(styles.button, started ? 'animate-pulse' : '')} onClick={() => togglePlay()}>
|
||||
<Icon type={started ? 'stop' : 'play'} />
|
||||
</button>
|
||||
<button className={cx(isDirty ? styles.button : styles.buttonDisabled)} onClick={() => activateCode()}>
|
||||
<Icon type="refresh" />
|
||||
@ -86,8 +144,32 @@ export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }
|
||||
{error && <div className={styles.error}>{error.message}</div>}
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
|
||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} theme={theme} />}
|
||||
</div>
|
||||
{drawTime && (
|
||||
<canvas
|
||||
id={canvasId}
|
||||
className="w-full pointer-events-none"
|
||||
height={canvasHeight}
|
||||
ref={(el) => {
|
||||
if (el && el.width !== el.clientWidth) {
|
||||
el.width = el.clientWidth;
|
||||
}
|
||||
}}
|
||||
></canvas>
|
||||
)}
|
||||
{!!log.length && (
|
||||
<div className="bg-gray-800 rounded-md p-2">
|
||||
{log.map(({ message }, i) => (
|
||||
<div key={i}>{message}</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: dedupe
|
||||
function useLogger(onTrigger) {
|
||||
useEvent(logger.key, onTrigger);
|
||||
}
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
.container {
|
||||
@apply sc-rounded-md sc-overflow-hidden sc-bg-[#222222];
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply sc-flex sc-justify-between sc-bg-slate-700 sc-border-t sc-border-slate-500;
|
||||
@apply flex justify-between bg-lineHighlight border-t border-l border-r border-lineHighlight rounded-t-md overflow-hidden;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
@apply sc-flex;
|
||||
@apply 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;
|
||||
@apply cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground hover:bg-background;
|
||||
}
|
||||
|
||||
.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;
|
||||
@apply w-16 flex items-center justify-center p-1 opacity-50 cursor-not-allowed border-r border-lineHighlight;
|
||||
}
|
||||
|
||||
.error {
|
||||
@apply sc-text-right sc-p-1 sc-text-sm sc-text-red-200;
|
||||
@apply text-right p-1 text-sm text-red-200;
|
||||
}
|
||||
|
||||
.body {
|
||||
@apply sc-overflow-auto sc-relative;
|
||||
@apply overflow-auto relative;
|
||||
}
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.cm-theme-light {
|
||||
.cm-theme {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cm-line > * {
|
||||
background: #00000095;
|
||||
.cm-theme-light {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
12
packages/react/src/hooks/useEvent.mjs
Normal file
12
packages/react/src/hooks/useEvent.mjs
Normal file
@ -0,0 +1,12 @@
|
||||
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;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user