Compare commits

...

137 Commits

Author SHA1 Message Date
Felix Roos 5a42adc366 Merge remote-tracking branch 'origin/main' into objectify-midi 2022-10-29 17:55:53 +02:00
Felix Roos d18116fcbe Merge pull request #232 from tidalcycles/out-by-default
Out by default
2022-10-29 17:54:05 +02:00
Felix Roos e38513f077 more fixes 2022-10-29 17:51:46 +02:00
Felix Roos 08c4c641ec add note calls everywhere 2022-10-29 17:38:54 +02:00
Felix Roos 5d7e46b246 started removing primitive haps from tutorial 2022-10-29 14:58:50 +02:00
Felix Roos dac906a79d fix sample name 2022-10-27 21:20:09 +02:00
Felix Roos ecfeac4d63 clearer error 2022-10-27 21:20:01 +02:00
Felix Roos db1fc399a1 add baseDir to prebake + obj params 2022-10-27 21:19:50 +02:00
Felix Roos 60616a5228 remove old defaultSynth 2022-10-27 21:19:17 +02:00
Felix Roos 699c8d5b60 different default adsr for synths and samples 2022-10-27 20:55:12 +02:00
Felix Roos 4264f013af remove webdirt + tone from tutorial
+ do not load webdirt in repl
2022-10-27 20:51:14 +02:00
Felix Roos ecc4e1803c remove all occurrences of .out() 2022-10-27 19:47:07 +02:00
Felix Roos 5c230c2d65 use webaudio output by default 2022-10-27 19:40:00 +02:00
Felix Roos 6af681354b support primitive notes + tame default gain 2022-10-27 19:39:33 +02:00
Alex McLean 5f381cf153 Patternify range (#231)
* patternify range

* patternify range

* fix test
2022-10-26 22:53:49 +01:00
Felix Roos 2ed290a214 Merge pull request #228 from tidalcycles/just-another-docs-branch
Just another docs branch
2022-10-26 21:42:12 +02:00
Felix Roos cf11472348 Merge pull request #230 from tidalcycles/reafactor-tunes
Refactor tunes away from tone
2022-10-26 21:41:12 +02:00
Felix Roos 2c14514c56 update snapshot 2022-10-26 21:37:41 +02:00
Felix Roos 053e6d8325 refactor remaining usages of tone 2022-10-26 21:30:27 +02:00
Felix Roos 603c55fda7 doc: in source value modifiers 2022-10-26 20:29:12 +02:00
MysteryHouse 113adcc5cc Core util tests (#226)
* Error message formatting, deprecate unused functions
* Add 'test-coverage' script
* Additional test coverage for core util module
* Additional tests for core util module

by @mystery-house
2022-10-20 08:26:28 +01:00
Alex McLean a5bf8f6732 fix fastgap for events that go across cycle boundaries (#225) 2022-10-06 21:35:45 +01:00
Felix Roos c4b482634f doc: begin end 2022-09-25 23:35:30 +02:00
Felix Roos 4b083d6f81 add reverb link 2022-09-25 23:23:37 +02:00
Felix Roos 04973d153b doc: legato gain velocity 2022-09-25 22:39:16 +02:00
Felix Roos 69ce7c78a4 doc: euclid + euclidLegato 2022-09-25 22:09:39 +02:00
Felix Roos b64cf197f2 doc: improve chop + loopAt examples 2022-09-25 21:57:56 +02:00
Felix Roos a8b0279853 Merge remote-tracking branch 'origin/main' into reafactor-tunes 2022-09-25 21:18:03 +02:00
Felix Roos cd3765b8fa Merge remote-tracking branch 'origin/main' into objectify-midi 2022-09-25 21:17:29 +02:00
Felix Roos 985de36647 Merge remote-tracking branch 'origin/main' into just-another-docs-branch 2022-09-25 21:16:05 +02:00
Felix Roos b7b576e38b Merge pull request #224 from tidalcycles/reverb
Reverb
2022-09-25 21:15:36 +02:00
Felix Roos 5018669ded roomsize 2022-09-25 20:08:02 +02:00
Felix Roos c5342d3eb0 snaps 2022-09-25 16:40:50 +02:00
Felix Roos 0e7a0b5754 more tunes 2022-09-25 16:37:10 +02:00
Felix Roos 933ef02df0 really simple reverb 2022-09-25 16:36:59 +02:00
Alex McLean 513e0d748d focus tweak for squeezeJoin - another go at fixing #216 (#221)
Fixes squeezeJoin, and in the process struct/keepif. fixes #216

Co-authored-by: Felix Roos <flix91@gmail.com>
2022-09-24 23:27:25 +01:00
Felix Roos 852f01fddd Merge pull request #222 from tidalcycles/negative-speed
support negative speeds
2022-09-24 23:17:21 +02:00
Felix Roos 9ebada63e9 support negative speeds 2022-09-24 23:14:43 +02:00
Felix Roos dbda70eee9 Merge pull request #213 from tidalcycles/feedbackdelay
Feedback Delay
2022-09-24 21:55:14 +02:00
Felix Roos 18bd10dfed clean up getDelay args 2022-09-24 21:51:55 +02:00
Felix Roos f76d03a0dc delay as global send via orbit 2022-09-24 21:45:23 +02:00
Alex McLean 86ba5c9c1a Merge pull request #220 from tidalcycles/fix-squeezeJoin
Fix squeeze join
2022-09-23 11:06:16 +01:00
alex 5b56fbed97 adjust use of focus in squeezeJoin - fixes #216 2022-09-23 08:00:53 +01:00
alex dfdd918ea0 attempt at fixing #216 2022-09-22 22:52:02 +01:00
Felix Roos df2c7ec1a1 doc: chop + loopAt 2022-09-22 23:20:15 +02:00
Felix Roos 0d179f2042 tutorial is now dark 2022-09-22 23:20:03 +02:00
Felix Roos f7e3f7d187 Merge branch 'main' into feedbackdelay 2022-09-22 21:25:12 +02:00
Felix Roos cc54da5980 Merge pull request #219 from tidalcycles/webaudio-output
encapsulate webaudio output
2022-09-22 21:23:11 +02:00
Felix Roos ca82796081 getDestination collides with tone
-> do not export for now (not needed)
-> can be refactored when tone is removed
-> also prevent loading worklets in node
2022-09-22 21:19:44 +02:00
Felix Roos d092068bd2 migrate to encapsulated out 2022-09-22 20:24:59 +02:00
Felix Roos 6dbb2cb98a encapsulate out
+ encapsulate destination
+ error handling
to simplify merge scheduler branch
2022-09-22 19:50:07 +02:00
Felix Roos ead763b909 Merge remote-tracking branch 'origin/main' into reafactor-tunes 2022-09-22 19:24:28 +02:00
Felix Roos 415139851e Merge remote-tracking branch 'origin/HEAD' into just-another-docs-branch 2022-09-22 19:22:35 +02:00
Felix Roos 5c99b72e2f Merge remote-tracking branch 'origin/HEAD' into feedbackdelay 2022-09-22 19:22:08 +02:00
Felix Roos 787ba9872f Merge remote-tracking branch 'origin/main' into objectify-midi 2022-09-22 19:20:31 +02:00
Felix Roos 82cb6c93cf Merge pull request #218 from tidalcycles/webdirt-feature-parity
samples now have envelopes
2022-09-22 19:18:18 +02:00
Felix Roos d21734ccf2 update snapshots 2022-09-22 19:15:53 +02:00
Felix Roos 91934006d5 adjust default envelope 2022-09-22 19:09:38 +02:00
Felix Roos 014c258c57 sample adsr support 2022-09-22 15:33:06 +02:00
Felix Roos 5e76bc3f2f Merge pull request #217 from tidalcycles/webdirt-feature-parity
sampler features + fixes
2022-09-22 00:03:30 +02:00
Felix Roos 7f6940f3a5 cut precision 2022-09-21 22:47:16 +02:00
Felix Roos 8d5682deb0 cut + loop 2022-09-21 22:44:56 +02:00
Felix Roos 819abbfa7a fix sample slicing + unit + nudge 2022-09-21 22:25:35 +02:00
Felix Roos 227ad332b8 add missing params to echo 2022-09-19 23:42:04 +02:00
Felix Roos 17c83e1a85 Merge pull request #215 from tidalcycles/just-another-docs-branch
Just another docs PR
2022-09-19 23:32:55 +02:00
Felix Roos 5d699496ca doc: chunk + chunkBack 2022-09-19 23:30:37 +02:00
Felix Roos e4f4fe81ae doc: iterBack 2022-09-19 23:21:55 +02:00
Felix Roos abe28367fb doc: iter 2022-09-19 23:19:39 +02:00
Felix Roos cc394d9f74 doc: echo + echoWith 2022-09-19 23:13:36 +02:00
Felix Roos 476b51ee31 fix deployment permissions 2022-09-19 22:40:04 +02:00
Felix Roos ec823ade14 do everything in one step 2022-09-19 22:35:56 +02:00
Felix Roos 0c3777f7d7 wait for build before deploy 2022-09-19 22:31:41 +02:00
Felix Roos a679e47c0d fix workflow 2022-09-19 22:27:56 +02:00
Felix Roos 1e82735241 rename deploy workflow 2022-09-19 22:26:34 +02:00
Felix Roos a09ec6b0b5 deployment: next try 2022-09-19 22:22:11 +02:00
Felix Roos 0134a50ffa add update button 2022-09-19 22:09:23 +02:00
Felix Roos 1da005e811 deploy after build 2022-09-19 21:58:12 +02:00
Felix Roos 8ab912429b build: npm i inside tutorial 2022-09-19 21:54:34 +02:00
Felix Roos 88d12223a5 build: add npm i in repl folder 2022-09-19 21:52:31 +02:00
Felix Roos 6c363d9c4b add rollup-plugin-visualizer in root pkg 2022-09-19 21:50:38 +02:00
Felix Roos 6d3d9659e2 try build workflow 2022-09-19 21:48:47 +02:00
Felix Roos 8977c5cef4 revert exponential adsr 2022-09-18 22:55:33 +02:00
Felix Roos d3645f6c63 fix tests 2022-09-18 22:42:30 +02:00
Felix Roos dbb057621d refactor caverave + throw out some tunes 2022-09-18 22:40:19 +02:00
Felix Roos 3a65bc0650 improve adsr 2022-09-18 22:39:40 +02:00
Felix Roos 1eb6ff8c17 add feedback delay effect 2022-09-18 21:17:16 +02:00
Felix Roos 505cc01a18 remove empty heading 2022-09-17 23:59:16 +02:00
Felix Roos fdc4bb455f fix: formatting 2022-09-17 23:54:28 +02:00
Felix Roos c1a27aa6e4 Merge pull request #212 from tidalcycles/even-more-docs
Even more docs
2022-09-17 23:47:43 +02:00
Felix Roos a14c7233d4 fix: tests 2022-09-17 23:40:56 +02:00
Felix Roos c786d77b07 make polyrhythm alias of stack #211 2022-09-17 23:38:42 +02:00
Felix Roos e4913dfb73 mark tone + webdirt deprecated
+ add superdirt param list
2022-09-17 23:35:35 +02:00
Felix Roos dfd33bab8e dedupe accumulation modifiers 2022-09-17 23:16:13 +02:00
Felix Roos 19b8f073b1 dedupe most of core api doc 2022-09-17 22:46:30 +02:00
Felix Roos b38b16f70e doc: early, late, each, every, rev + doc structure 2022-09-17 21:45:39 +02:00
Felix Roos dd69e4caff doc: rand irand perlin 2022-09-17 21:03:58 +02:00
Felix Roos 762a17d949 fix: remove TLA 2022-09-17 15:42:34 +02:00
Felix Roos d90cb23f22 Merge pull request #207 from tidalcycles/webaudio-guide
Webaudio guide
2022-09-17 15:36:53 +02:00
Felix Roos 1ccc391c8e proper disconnect onend 2022-09-17 15:32:36 +02:00
Felix Roos cafcead62e doc: web audio effects 2022-09-17 15:18:57 +02:00
Felix Roos 71ca46b21b remove headings for generated doc 2022-09-17 15:18:38 +02:00
Felix Roos acf339ad13 load worklets on startup 2022-09-17 15:18:16 +02:00
Felix Roos de4726ec8d improve sampler section 2022-09-16 23:59:46 +02:00
Felix Roos e58aff41f9 begin webaudio tutorial 2022-09-16 23:25:42 +02:00
Felix Roos 3c8acf9e0e comment out timeout:
it will cut of samples that are longer than hap..
2022-09-16 23:25:33 +02:00
Felix Roos 782e8522c5 add all default samples to mini repl 2022-09-16 23:24:47 +02:00
Felix Roos dd4464cb08 fix build: dont add sourcemaps 2022-09-16 11:05:38 +02:00
Felix Roos 3fdfd12448 hotfix: disconnect chain after use 2022-09-16 00:53:43 +02:00
Felix Roos a11d23ba62 hotfix: out should not be async 2022-09-16 00:30:37 +02:00
Felix Roos 6bab362171 Merge pull request #205 from tidalcycles/coarse-crush-shape
Coarse crush shape
2022-09-16 00:21:28 +02:00
Felix Roos 5d28c5c825 add license info 2022-09-16 00:17:42 +02:00
Felix Roos b9173ebe50 add coarse, crush and shape 2022-09-16 00:14:28 +02:00
Felix Roos 0a1577ff43 refactor kalimba 2022-09-15 21:17:39 +02:00
Felix Roos a616d6fd3e Merge pull request #201 from tidalcycles/vowel
add vowel to .out
2022-09-15 21:04:28 +02:00
Felix Roos 149b2ab9f9 make vowel work with node 2022-09-15 20:11:10 +02:00
Felix Roos 4406dd88d1 add vowel 2022-09-15 20:09:07 +02:00
Felix Roos 48a950faf8 Merge pull request #200 from tidalcycles/build-analyze
add rollup-plugin-visualizer to build
2022-09-15 16:55:52 +02:00
Felix Roos 0ffbb5cb2c ignore stats 2022-09-15 16:54:27 +02:00
Felix Roos f4c31bcf2c Merge pull request #199 from tidalcycles/docs-update
document random functions
2022-09-14 23:46:39 +02:00
Felix Roos f0e412a443 doc: chooseCycles + mini shorthands 2022-09-14 23:40:48 +02:00
Felix Roos ce91d36b7a doc: random functions 2022-09-14 23:16:24 +02:00
Felix Roos a022e5bb93 add missing external deps to react pkg 2022-09-14 21:55:09 +02:00
Felix Roos 8e61673bd6 doc: degradeBy 2022-09-14 21:54:42 +02:00
Felix Roos 88e39d22f1 add drums to mini repl 2022-09-14 21:53:41 +02:00
Felix Roos 1441374f6e Create README.md 2022-09-13 22:45:03 +02:00
Felix Roos d1cedaa3e8 add dependencies graphic
npx depcruise --include-only "^packages" -X "node_modules" --output-type dot packages | dot -T svg > dependencies.svg
2022-09-13 22:40:52 +02:00
Felix Roos 5b721f6453 package-lock 2022-09-10 23:32:33 +02:00
Felix Roos a5f05b4715 hotfix: regression of #196
- samples loaded as arrays would not be repitched
2022-09-10 23:19:23 +02:00
Felix Roos 96f49f117a add build analyzer 2022-09-10 23:04:21 +02:00
Felix Roos 88e1211762 Merge pull request #196 from tidalcycles/fix-numbers-in-sampler
Fix numbers in sampler
2022-09-09 22:04:40 +02:00
Felix Roos 59e1664223 pull in midi logic 2022-09-09 22:02:24 +02:00
Felix Roos 42922a0d75 fix: support numbers in piano / sampler 2022-09-09 21:56:07 +02:00
Felix Roos fd2c7fb4bb allow non notes 2022-08-16 23:12:49 +02:00
Felix Roos 50c84a973c object support for midi #192 2022-08-16 22:31:04 +02:00
Felix Roos 227dda921c bump to 0.0.4 2022-08-14 18:06:50 +02:00
Felix Roos bdf2047ea1 add npte for new packages 2022-08-14 18:05:08 +02:00
Felix Roos 0522996b7f bump soundfont package for rerelease 2022-08-14 18:02:06 +02:00
49 changed files with 13851 additions and 19165 deletions
+46
View File
@@ -0,0 +1,46 @@
name: Build and Deploy
on: [workflow_dispatch]
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
deployments: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
cache: "npm"
- name: Install Dependencies
run: npm ci && cd repl && npm ci && cd ../tutorial && npm ci
- name: Build
run: npm run build
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
# Upload entire repository
path: "./out"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
+3 -1
View File
@@ -35,4 +35,6 @@ tutorial.rendered.mdx
doc.json
talk/public/EmuSP12
talk/public/samples
server/samples/old
server/samples/old
repl/stats.html
coverage
+4 -1
View File
@@ -2,5 +2,8 @@
"cSpell.words": [
"subspan",
"vals"
]
],
"yaml.schemas": {
"https://json.schemastore.org/github-workflow.json": "file:///home/felix/projects/strudel/.github/workflows/deploy.yml"
}
}
+18 -10
View File
@@ -12,7 +12,7 @@ To get in touch with the contributors, either
## Ask a Question
If you have any questions about strudel, make sure you've read the
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 not, use one of the Communication Channels above!
@@ -31,10 +31,9 @@ Use one of the Communication Channels listed above.
## Improve the Tutorial
If you find some weak spots in the [tutorial](https://strudel.tidalcycles.org/),
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.
## Propose a Feature
@@ -52,11 +51,11 @@ Please check that it has not been reported before.
To fix a bug that has been reported,
1. check that nobody else is already fixing it and respond to the issue to let people know you're on it
3. fork the repository
4. make sure you've setup the project (see below)
5. hopefully fix the bug
6. make sure the tests pass
7. send a pull request
2. fork the repository
3. make sure you've setup the project (see below)
4. hopefully fix the bug
5. make sure the tests pass
6. send a pull request
## Write Tests
@@ -80,7 +79,7 @@ cd repl && npm i # install repl dependencies
npm run start # start repl
```
Those commands might look slightly different for your OS.
Those commands might look slightly different for your OS.
Please report any problems you've had with the setup instructions!
## Code Style
@@ -97,7 +96,7 @@ If you use VSCode, you can
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,
in the `node_modules` folder. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
which allows developing multiple packages at the same time
## Package Publishing
@@ -105,9 +104,18 @@ which allows developing multiple packages at the same time
To publish all packages that have been changed since the last release, run:
```sh
npm login
npx lerna publish
```
### 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
```
## Have Fun
Remember to have fun, and that this project is driven by the passion of volunteers!
+1806
View File
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 150 KiB

+704 -112
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -1,11 +1,12 @@
{
"name": "@strudel.cycles/monorepo",
"version": "0.0.1",
"version": "0.0.4",
"private": true,
"description": "Port of tidalcycles to javascript",
"scripts": {
"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": "cd repl && npm run snapshot",
@@ -13,7 +14,7 @@
"osc": "cd packages/osc && npm run server",
"build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build",
"preview": "npx serve ./out",
"deploy": "gh-pages -d out",
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json"
},
@@ -39,6 +40,7 @@
"homepage": "https://strudel.tidalcycles.org",
"devDependencies": {
"@vitest/ui": "^0.21.1",
"c8": "^7.12.0",
"events": "^3.3.0",
"gh-pages": "^4.0.0",
"happy-dom": "^6.0.4",
@@ -46,6 +48,7 @@
"jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^7.1.1",
"lerna": "^4.0.0",
"rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.21.1"
}
}
+15
View File
@@ -0,0 +1,15 @@
# Packages
Each folder represents one of the @strudel.cycles/* packages [published to npm](https://www.npmjs.com/org/strudel.cycles).
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
This is a graphical view of all the packages: [full screen](https://raw.githubusercontent.com/tidalcycles/strudel/main/dependencies.svg)
![dependencies](https://raw.githubusercontent.com/tidalcycles/strudel/main/dependencies.svg)
Generated with
```sh
npx depcruise --include-only "^packages" -X "node_modules" --output-type dot packages | dot -T svg > dependencygraph.svg
```
+35 -27
View File
@@ -11,7 +11,7 @@ const generic_params = [
/**
* Select a sound / sample by name.
*
* <details>
* <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)
@@ -23,7 +23,7 @@ const generic_params = [
* @name s
* @param {string | Pattern} sound The sound / pattern of sounds to pick
* @example
* s("bd hh").osc()
* s("bd hh")
*
*/
['s', 's', 'sound'],
@@ -62,12 +62,12 @@ const generic_params = [
*/
['f', 'accelerate', 'a pattern of numbers that speed up (or slow down) samples while they play.'],
/**
* Like {@link amp}, but exponential.
* Controls the gain by an exponential amount.
*
* @name gain
* @param {number | Pattern} amount gain.
* @example
* s("bd*8").gain(".7*2 1 .7*2 1 .7 1").osc()
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1")
*
*/
[
@@ -129,7 +129,7 @@ const generic_params = [
* @name bandf
* @param {number | Pattern} frequency center frequency
* @example
* s("bd sd").bandf("<1000 2000 4000 8000>").osc()
* s("bd sd,hh*3").bandf("<1000 2000 4000 8000>")
*
*/
['f', 'bandf', 'A pattern of numbers from 0 to 1. Sets the center frequency of the band-pass filter.'],
@@ -140,17 +140,19 @@ const generic_params = [
* @name bandq
* @param {number | Pattern} q q factor
* @example
* s("bd sd").bandf(2000).bandq("<.2 .9>").osc()
* s("bd sd").bandf(500).bandq("<0 1 2 3>")
*
*/
['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.
*
* @memberof Pattern
* @name begin
* @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
* @example
* s("rave").begin("<0 .25 .5 .75>").osc()
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/Dirt-Samples/master/')
* s("rave").begin("<0 .25 .5 .75>")
*
*/
[
@@ -159,12 +161,13 @@ const generic_params = [
'a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.',
],
/**
* The same as {@link begin}, but cuts off the end off each sample.
* The same as .begin, but cuts off the end off each sample.
*
* @memberof Pattern
* @name end
* @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
* @example
* s("bd*2,ho*4").end("<.1 .2 .5 1>").osc()
* s("bd*2,oh*4").end("<.1 .2 .5 1>")
*
*/
[
@@ -202,7 +205,7 @@ const generic_params = [
* @name crush
* @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).
* @example
* s("<bd sd>,hh*3,jvbass*2").fast(2).crush("<16 8 7 6 5 4 3 2>").osc()
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
*
*/
[
@@ -211,12 +214,12 @@ const generic_params = [
'bit crushing, a pattern of numbers from 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).',
],
/**
* fake-resampling for lowering the sample rate
* fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
*
* @name coarse
* @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
* @example
* s("xmas").coarse("<1 4 8 16 32>").osc()
* s("bd sd,hh*4").coarse("<1 4 8 16 32>")
*
*/
[
@@ -253,7 +256,7 @@ const generic_params = [
* @name cutoff
* @param {number | Pattern} frequency audible between 0 and 20000
* @example
* s("bd,hh*2,<~ sd>").fast(2).cutoff("<4000 2000 1000 500 200 100>").osc()
* s("bd sd,hh*3").cutoff("<4000 2000 1000 500 200 100>")
*
*/
// TODO: add lpf synonym
@@ -264,7 +267,7 @@ const generic_params = [
* @name hcutoff
* @param {number | Pattern} frequency audible between 0 and 20000
* @example
* s("bd,hh*2,<~ sd>").fast(2).hcutoff("<4000 2000 1000 500 200 100>").osc()
* s("bd sd,hh*4").hcutoff("<4000 2000 1000 500 200 100>")
*
*/
// TODO: add hpf synonym
@@ -274,12 +277,12 @@ const generic_params = [
'a pattern of numbers from 0 to 1. Applies the cutoff frequency of the high-pass filter. Also has alias @hpf@',
],
/**
* Applies the cutoff frequency of the high-pass filter.
* Applies the resonance of the high-pass filter.
*
* @name hresonance
* @param {number | Pattern} q resonance factor between 0 and 1
* @param {number | Pattern} q resonance factor between 0 and 50
* @example
* s("bd,hh*2,<~ sd>").fast(2).hcutoff(2000).hresonance("<0 .2 .4 .6>").osc()
* s("bd sd,hh*4").hcutoff(2000).hresonance("<0 10 20 30>")
*
*/
[
@@ -292,15 +295,15 @@ const generic_params = [
* Applies the cutoff frequency of the low-pass filter.
*
* @name resonance
* @param {number | Pattern} q resonance factor between 0 and 1
* @param {number | Pattern} q resonance factor between 0 and 50
* @example
* s("bd,hh*2,<~ sd>").fast(2).cutoff(2000).resonance("<0 .2 .4 .6>").osc()
* s("bd sd,hh*4").cutoff(2000).resonance("<0 10 20 30>")
*
*/
['f', 'resonance', 'a pattern of numbers from 0 to 1. Specifies the resonance of the low-pass filter.'],
// TODO: add lpq synonym?
/**
* Set detune of oscillators. Works only with some synths, see <a target="_blank" href="https://tidalcycles.org/docs/patternlib/tutorials/synthesizers">tidal doc</a>
* DJ filter, below 0.5 is low pass filter, above is high pass filter.
*
* @name djf
* @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter
@@ -368,7 +371,7 @@ const generic_params = [
* @name fadeTime
* @param {number | Pattern} time between 0 and 1
* @example
* s("ho*4").end(.1).fadeTime("<0 .2 .4 .8>").osc()
* s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc()
*
*/
[
@@ -493,7 +496,7 @@ const generic_params = [
* @name pan
* @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)
* @example
* s("[bd hh]*2").pan("<.5 1 .5 0>").osc()
* s("[bd hh]*2").pan("<.5 1 .5 0>")
*
*/
[
@@ -581,6 +584,11 @@ const generic_params = [
'size',
'a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb.',
],
[
'f',
'roomsize',
'a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb.',
],
// ['f', 'sagogo', ''],
// ['f', 'sclap', ''],
// ['f', 'sclaves', ''],
@@ -591,7 +599,7 @@ const generic_params = [
* @name shape
* @param {number | Pattern} distortion between 0 and 1
* @example
* s("bd sd").shape("<0 .2 .4 .6 .8 1>").osc()
* s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>")
*
*/
[
@@ -648,16 +656,16 @@ const generic_params = [
// ['f', 'tomdecay', ''],
// ['f', 'vcfegint', ''],
// ['f', 'vcoegint', ''],
// TODO: Use a rest (~) to override the effect <- vowel
/**
*
* Formant filter to make things sound like vowels.
*
* @name vowel
* @param {string | Pattern} vowel You can use a e i o u. Use a rest (~) to override the effect
* @param {string | Pattern} vowel You can use a e i o u.
* @example
* vowel("a e i [o u]").slow(2)
* .n("<[0,7]!4 [2,7]!4>")
* .s('supersquare').osc()
* note("c2 <eb2 <g2 g1>>").s('sawtooth')
* .vowel("<a e i <o u>>")
*
*/
[
+33 -24
View File
@@ -25,61 +25,70 @@ const euclid = (pulses, steps, rotation = 0) => {
* describe a large number of rhythms used in the most important music world traditions.
*
* @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.
* "c3".euclid(3,8)
* @example
* // The Cuban tresillo pattern.
* note("c3").euclid(3,8)
*/
/**
* @example // A thirteenth century Persian rhythm called Khafif-e-ramal.
* "c3".euclid(2,5)
* note("c3").euclid(2,5)
* @example // The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad.
* "c3".euclid(3,4)
* 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.
* "c3".euclid(3,5,2)
* note("c3").euclid(3,5,2)
* @example // A Ruchenitza rhythm used in a Bulgarian folk-dance.
* "c3".euclid(3,7)
* note("c3").euclid(3,7)
* @example // The Cuban tresillo pattern.
* "c3".euclid(3,8)
* note("c3").euclid(3,8)
* @example // Another Ruchenitza Bulgarian folk-dance rhythm.
* "c3".euclid(4,7)
* note("c3").euclid(4,7)
* @example // The Aksak rhythm of Turkey.
* "c3".euclid(4,9)
* note("c3").euclid(4,9)
* @example // The metric pattern used by Frank Zappa in his piece titled Outside Now.
* "c3".euclid(4,11)
* note("c3").euclid(4,11)
* @example // Yields the York-Samai pattern, a popular Arab rhythm.
* "c3".euclid(5,6)
* note("c3").euclid(5,6)
* @example // The Nawakhat pattern, another popular Arab rhythm.
* "c3".euclid(5,7)
* note("c3").euclid(5,7)
* @example // The Cuban cinquillo pattern.
* "c3".euclid(5,8)
* note("c3").euclid(5,8)
* @example // A popular Arab rhythm called Agsag-Samai.
* "c3".euclid(5,9)
* note("c3").euclid(5,9)
* @example // The metric pattern used by Moussorgsky in Pictures at an Exhibition.
* "c3".euclid(5,11)
* note("c3").euclid(5,11)
* @example // The Venda clapping pattern of a South African childrens song.
* "c3".euclid(5,12)
* note("c3").euclid(5,12)
* @example // The Bossa-Nova rhythm necklace of Brazil.
* "c3".euclid(5,16)
* note("c3").euclid(5,16)
* @example // A typical rhythm played on the Bendir (frame drum).
* "c3".euclid(7,8)
* note("c3").euclid(7,8)
* @example // A common West African bell pattern.
* "c3".euclid(7,12)
* note("c3").euclid(7,12)
* @example // A Samba rhythm necklace from Brazil.
* "c3".euclid(7,16,14)
* note("c3").euclid(7,16,14)
* @example // A rhythm necklace used in the Central African Republic.
* "c3".euclid(9,16)
* note("c3").euclid(9,16)
* @example // A rhythm necklace of the Aka Pygmies of Central Africa.
* "c3".euclid(11,24,14)
* note("c3").euclid(11,24,14)
* @example // Another rhythm necklace of the Aka Pygmies of the upper Sangha.
* "c3".euclid(13,24,5)
* note("c3").euclid(13,24,5)
*/
Pattern.prototype.euclid = function (pulses, steps, rotation = 0) {
return this.struct(euclid(pulses, steps, rotation));
};
/**
* Similar to {@link Pattern#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);
+399 -60
View File
@@ -468,7 +468,11 @@ export class Pattern {
/**
* Assumes a numerical pattern. Returns a new pattern with all values rounded
* to the nearest integer.
* @name round
* @memberof Pattern
* @returns Pattern
* @example
* "0.5 1.5 2.5".round().scale('C major').note()
*/
round() {
return this._asNumber().fmap((v) => Math.round(v));
@@ -513,13 +517,16 @@ export class Pattern {
}
/**
* Assumes a numerical pattern, containing unipolar values in the range 0 ..
* 1. Returns a new pattern with values scaled to the given min/max range.
* @param {Number} min
* @param {Number} max
* Assumes a numerical pattern, containing unipolar values in the range 0 .. 1.
* Returns a new pattern with values scaled to the given min/max range.
* Most useful in combination with continuous patterns.
* @name range
* @memberof Pattern
* @returns Pattern
* @example
* s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4))
*/
range(min, max) {
_range(min, max) {
return this.mul(max - min).add(min);
}
@@ -531,8 +538,8 @@ export class Pattern {
* @param {Number} max
* @returns Pattern
*/
rangex(min, max) {
return this.range(Math.log(min), Math.log(max)).fmap(Math.exp);
_rangex(min, max) {
return this._range(Math.log(min), Math.log(max)).fmap(Math.exp);
}
/**
@@ -542,8 +549,8 @@ export class Pattern {
* @param {Number} max
* @returns Pattern
*/
range2(min, max) {
return this._fromBipolar().range(min, max);
_range2(min, max) {
return this._fromBipolar()._range(min, max);
}
_bindWhole(choose_whole, func) {
@@ -642,13 +649,26 @@ export class Pattern {
return this._trigJoin(true);
}
// Like the other joins above, joins a pattern of patterns of values, into a flatter
// pattern of values. In this case it takes whole cycles of the inner pattern to fit each event
// in the outer pattern.
_squeezeJoin() {
// A pattern of patterns, which we call the 'outer' pattern, with patterns
// as values which we call the 'inner' patterns.
const pat_of_pats = this;
function query(state) {
// Get the events with the inner patterns. Ignore continuous events (without 'wholes')
const haps = pat_of_pats.discreteOnly().query(state);
// A function to map over the events from the outer pattern.
function flatHap(outerHap) {
const pat = outerHap.value._compressSpan(outerHap.wholeOrPart().cycleArc());
const innerHaps = pat.query(state.setSpan(outerHap.part));
// Get the inner pattern, slowed and shifted so that the 'whole'
// timespan of the outer event corresponds to the first cycle of the
// inner event
const inner_pat = outerHap.value._focusSpan(outerHap.wholeOrPart());
// Get the inner events, from the timespan of the outer event's part
const innerHaps = inner_pat.query(state.setSpan(outerHap.part));
// A function to map over the inner events, to combine them with the
// outer event
function munge(outer, inner) {
let whole = undefined;
if (inner.whole && outer.whole) {
@@ -679,10 +699,27 @@ export class Pattern {
return this.fmap(func)._squeezeJoin();
}
/**
* Like layer, but with a single function:
* @name apply
* @memberof Pattern
* @example
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
*/
_apply(func) {
return func(this);
}
/**
* Layers the result of the given function(s). Like {@link superimpose}, but without the original pattern:
* @name layer
* @memberof Pattern
* @returns Pattern
* @example
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
* .layer(x=>x.add("0,2"))
* .scale('C minor').note()
*/
layer(...funcs) {
return stack(...funcs.map((func) => func(this)));
}
@@ -713,15 +750,25 @@ export class Pattern {
const end = cycle.add(span.end.sub(cycle).mul(factor).min(1));
return new TimeSpan(begin, end);
};
const ef = function (span) {
const cycle = span.begin.sam();
const begin = cycle.add(span.begin.sub(cycle).div(factor).min(1));
const end = cycle.add(span.end.sub(cycle).div(factor).min(1));
return new TimeSpan(begin, end);
const ef = function (hap) {
const begin = hap.part.begin;
const end = hap.part.end;
const cycle = begin.sam();
const beginPos = begin.sub(cycle).div(factor).min(1);
const endPos = end.sub(cycle).div(factor).min(1);
const newPart = new TimeSpan(cycle.add(beginPos), cycle.add(endPos));
const newWhole = !hap.whole
? undefined
: new TimeSpan(
newPart.begin.sub(begin.sub(hap.whole.begin).div(factor)),
newPart.end.add(hap.whole.end.sub(end).div(factor)),
);
return new Hap(newWhole, newPart, hap.value, hap.context);
};
return this.withQuerySpan(qf).withHapSpan(ef)._splitQueries();
return this.withQuerySpan(qf)._withHap(ef)._splitQueries();
}
// Compress each cycle into the given timespan, leaving a gap
_compress(b, e) {
if (b.gt(e) || b.gt(1) || e.gt(1) || b.lt(0) || e.lt(0)) {
return silence;
@@ -733,15 +780,25 @@ export class Pattern {
return this._compress(span.begin, span.end);
}
// Similar to compress, but doesn't leave gaps, and the 'focus' can be
// bigger than a cycle
_focus(b, e) {
return this._fast(Fraction(1).div(e.sub(b))).late(b.cyclePos());
}
_focusSpan(span) {
return this._focus(span.begin, span.end);
}
/**
* Speed up a pattern by the given factor.
* Speed up a pattern by the given factor. Used by "*" in mini notation.
*
* @name fast
* @memberof Pattern
* @param {number | Pattern} factor speed up factor
* @returns Pattern
* @example
* seq(e5, b4, d5, c5).fast(2)
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
*/
_fast(factor) {
const fastQuery = this.withQueryTime((t) => t.mul(factor));
@@ -749,14 +806,14 @@ export class Pattern {
}
/**
* Slow down a pattern over the given number of cycles.
* Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation.
*
* @name slow
* @memberof Pattern
* @param {number | Pattern} factor slow down factor
* @returns Pattern
* @example
* seq(e5, b4, d5, c5).slow(2)
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
*/
_slow(factor) {
return this._fast(Fraction(1).div(factor));
@@ -774,6 +831,20 @@ export class Pattern {
return this.fmap((x) => pure(x)._fast(factor))._squeezeJoin();
}
/**
* Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'.
* It turns a pattern of samples into a pattern of parts of samples.
* @name chop
* @memberof Pattern
* @returns Pattern
* @example
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
* s("rhodes")
* .chop(4)
* .rev() // reverse order of chops
* .loopAt(4,1) // fit sample into 4 cycles
*
*/
_chop(n) {
const slices = Array.from({ length: n }, (x, i) => i);
const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
@@ -795,14 +866,32 @@ export class Pattern {
return this._fast(cpm / 60);
}
/**
* Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator
*
* @name early
* @memberof Pattern
* @param {number | Pattern} cycles number of cycles to nudge left
* @returns Pattern
* @example
* "bd ~".stack("hh ~".early(.1)).s()
*/
_early(offset) {
// Equivalent of Tidal's <~ operator
offset = Fraction(offset);
return this.withQueryTime((t) => t.add(offset)).withHapTime((t) => t.sub(offset));
}
/**
* Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator
*
* @name late
* @memberof Pattern
* @param {number | Pattern} cycles number of cycles to nudge right
* @returns Pattern
* @example
* "bd ~".stack("hh ~".late(.1)).s()
*/
_late(offset) {
// Equivalent of Tidal's ~> operator
offset = Fraction(offset);
return this._early(Fraction(0).sub(offset));
}
@@ -829,6 +918,17 @@ export class Pattern {
return this._zoom(0, t)._slow(t);
}
/**
* Applies the given structure to the pattern:
*
* @name struct
* @memberof Pattern
* @returns Pattern
* @example
* note("c3,eb3,g3")
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
* .slow(4)
*/
// struct(...binary_pats) {
// // Re structure the pattern according to a binary pattern (false values are dropped)
// const binary_pat = sequence(binary_pats);
@@ -876,6 +976,16 @@ export class Pattern {
return this.invert();
}
/**
* Applies the given function whenever the given pattern is in a true state.
* @name when
* @memberof Pattern
* @param {Pattern} binary_pat
* @param {function} func
* @returns Pattern
* @example
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)).note()
*/
when(binary_pat, func) {
//binary_pat = sequence(binary_pat)
const true_pat = binary_pat._filterValues(id);
@@ -885,10 +995,47 @@ export class Pattern {
return stack(with_pat, without_pat);
}
/**
* Superimposes the function result on top of the original pattern, delayed by the given time.
* @name off
* @memberof Pattern
* @param {Pattern | number} time offset time
* @param {function} func function to apply
* @returns Pattern
* @example
* "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
*/
off(time_pat, func) {
return stack(this, func(this.late(time_pat)));
}
/**
* Applies the given function every n cycles.
* @name every
* @memberof Pattern
* @param {number} n how many cycles
* @param {function} func function to apply
* @returns Pattern
* @example
* note("c3 d3 e3 g3").every(4, x=>x.rev())
*/
every(n, func) {
const pat = this;
const pats = Array(n - 1).fill(pat);
// pats.unshift(func(pat));
pats.push(func(pat));
return slowcatPrime(...pats);
}
/**
* Applies the given function every n cycles, starting from the first cycle.
* @name every
* @memberof Pattern
* @param {number} n how many cycles
* @param {function} func function to apply
* @returns Pattern
* @example
* note("c3 d3 e3 g3").every(4, x=>x.rev())
*/
every(n, func) {
const pat = this;
const pats = Array(n - 1).fill(pat);
@@ -896,6 +1043,23 @@ export class Pattern {
return slowcatPrime(...pats);
}
/**
* Applies the given function every n cycles, starting from the last cycle.
* @name each
* @memberof Pattern
* @param {number} n how many cycles
* @param {function} func function to apply
* @returns Pattern
* @example
* note("c3 d3 e3 g3").every(4, x=>x.rev())
*/
each(n, func) {
const pat = this;
const pats = Array(n - 1).fill(pat);
pats.push(func(pat));
return slowcatPrime(...pats);
}
/**
* Returns a new pattern where every other cycle is played once, twice as
* fast, and offset in time by one quarter of a cycle. Creates a kind of
@@ -906,6 +1070,15 @@ export class Pattern {
return this.when(slowcat(false, true), (x) => fastcat(x, silence)._late(0.25));
}
/**
* Reverse all haps in a pattern
*
* @name rev
* @memberof Pattern
* @returns Pattern
* @example
* note("c3 d3 e3 g3").rev()
*/
rev() {
const pat = this;
const query = function (state) {
@@ -948,6 +1121,15 @@ export class Pattern {
return this.juxBy(1, func);
}
/**
* Stacks the given pattern(s) to the current pattern.
* @name stack
* @memberof Pattern
* @example
* s("hh*2").stack(
* n("c2(3,8)")
* )
*/
stack(...pats) {
return stack(this, ...pats);
}
@@ -956,11 +1138,28 @@ export class Pattern {
return sequence(this, ...pats);
}
// shorthand for sequence
/**
* Appends the given pattern(s) to the current pattern. Synonyms: .sequence .fastcat
* @name seq
* @memberof Pattern
* @example
* s("hh*2").seq(
* n("c2(3,8)")
* )
*/
seq(...pats) {
return sequence(this, ...pats);
}
/**
* Appends the given pattern(s) to the next cycle. Synonym: .slowcat
* @name cat
* @memberof Pattern
* @example
* s("hh*2").cat(
* n("c2(3,8)")
* )
*/
cat(...pats) {
return cat(this, ...pats);
}
@@ -973,6 +1172,16 @@ export class Pattern {
return slowcat(this, ...pats);
}
/**
* Superimposes the result of the given function(s) on top of the original pattern:
* @name superimpose
* @memberof Pattern
* @returns Pattern
* @example
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
* .superimpose(x=>x.add(2))
* .scale('C minor').note()
*/
superimpose(...funcs) {
return this.stack(...funcs.map((func) => func(this)));
}
@@ -985,24 +1194,70 @@ export class Pattern {
return this.stutWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i)));
}
// these might change with: https://github.com/tidalcycles/Tidal/issues/902
/**
* Superimpose and offset multiple times, applying the given function each time.
* @name echoWith
* @memberof Pattern
* @returns Pattern
* @param {number} times how many times to repeat
* @param {number} time cycle offset between iterations
* @param {function} func function to apply, given the pattern and the iteration index
* @example
* "<0 [2 4]>"
* .echoWith(4, 1/8, (p,n) => p.add(n*2))
* .scale('C minor').note().legato(.2)
*/
_echoWith(times, time, func) {
return stack(...listRange(0, times - 1).map((i) => func(this.late(Fraction(time).mul(i)), i)));
}
/**
* Superimpose and offset multiple times, gradually decreasing the velocity
* @name echo
* @memberof Pattern
* @returns Pattern
* @param {number} times how many times to repeat
* @param {number} time cycle offset between iterations
* @param {number} feedback velocity multiplicator for each iteration
* @example
* s("bd sd").echo(3, 1/6, .8)
*/
_echo(times, time, feedback) {
return this._echoWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i)));
}
/**
* Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played.
* @name iter
* @memberof Pattern
* @returns Pattern
* @example
* note("0 1 2 3".scale('A minor')).iter(4)
*/
iter(times, back = false) {
return slowcat(...listRange(0, times - 1).map((i) => (back ? this.late(i / times) : this.early(i / times))));
}
// known as iter' in tidalcycles
/**
* Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles
* @name iterBack
* @memberof Pattern
* @returns Pattern
* @example
* note("0 1 2 3".scale('A minor')).iterBack(4)
*/
iterBack(times) {
return this.iter(times, true);
}
/**
* Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle).
* @name chunk
* @memberof Pattern
* @returns Pattern
* @example
* "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note()
*/
_chunk(n, func, back = false) {
const binary = Array(n - 1).fill(false);
binary.unshift(true);
@@ -1010,6 +1265,14 @@ export class Pattern {
return this.when(binary_pat, func);
}
/**
* Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
* @name chunkBack
* @memberof Pattern
* @returns Pattern
* @example
* "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note()
*/
_chunkBack(n, func) {
return this._chunk(n, func, true);
}
@@ -1024,20 +1287,45 @@ export class Pattern {
}
// sets absolute duration of haps
_duration(value) {
/* _duration(value) {
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
}
// sets hap relative duration of haps
/**
*
* Multiplies the hap duration with the given factor.
* @name legato
* @memberof Pattern
* @example
* note("c3 eb3 g3 c4").legato("<.25 .5 1 2>")
*/
_legato(value) {
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
}
}
/**
*
* Sets the velocity from 0 to 1. Is multiplied together with gain.
* @name velocity
* @example
* s("hh*8")
* .gain(".4!2 1 .4!2 1 .4 1")
* .velocity(".4 1")
*/
_velocity(velocity) {
return this._withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity }));
}
*/
// move this to controls? (speed and unit are controls)
/**
* Makes the sample fit the given number of cycles by changing the speed.
* @name loopAt
* @memberof Pattern
* @returns Pattern
* @example
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
* s("rhodes").loopAt(4,1)
*/
_loopAt(factor, cps = 1) {
return this.speed((1 / factor) * cps)
.unit('c')
@@ -1093,9 +1381,48 @@ function _composeOp(a, b, func) {
keepif: [(a, b) => (b ? a : undefined)],
// numerical functions
/**
*
* Assumes a pattern of numbers. Adds the given number to each item in the pattern.
* @name add
* @memberof Pattern
* @example
* // Here, the triad 0, 2, 4 is shifted by different amounts
* "0 2 4".add("<0 3 4 0>").scale('C major').note()
* // Without add, the equivalent would be:
* // "<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>".scale('C major').note()
* @example
* // You can also use add with notes:
* "c3 e3 g3".add("<0 5 7 0>").note()
* // Behind the scenes, the notes are converted to midi numbers:
* // "48 52 55".add("<0 5 7 0>").note()
*/
add: [(a, b) => a + b, numOrString], // support string concatenation
/**
*
* Like add, but the given numbers are subtracted.
* @name sub
* @memberof Pattern
* @example
* "0 2 4".sub("<0 1 2 3>").scale('C4 minor').note()
* // See add for more information.
*/
sub: [(a, b) => a - b, num],
/**
*
* Multiplies each number by the given factor.
* @name mul
* @memberof Pattern
* @example
* "1 1.5 [1.66, <2 2.33>]".mul(150).freq()
*/
mul: [(a, b) => a * b, num],
/**
*
* Divides each number by the given factor.
* @name div
* @memberof Pattern
*/
div: [(a, b) => a / b, num],
mod: [mod, num],
pow: [Math.pow, num],
@@ -1131,10 +1458,14 @@ function _composeOp(a, b, func) {
pat = preprocess(pat);
other = preprocess(other);
}
var result = pat['_op' + how](other, (a) => (b) => _composeOp(a, b, op));
var result;
// hack to remove undefs when doing 'keepif'
if (what === 'keepif') {
// avoid union, as we want to throw away the value of 'b' completely
result = pat['_op' + how](other, (a) => (b) => op(a, b));
result = result._removeUndefineds();
} else {
result = pat['_op' + how](other, (a) => (b) => _composeOp(a, b, op));
}
return result;
};
@@ -1171,19 +1502,21 @@ Pattern.prototype.patternified = [
'chop',
'color',
'cpm',
'duration',
'early',
'fast',
'jux',
'late',
'legato',
'linger',
'ply',
'segment',
'striate',
'slow',
'velocity',
];
// aliases
export const polyrhythm = stack;
export const pr = stack;
// methods that create patterns, which are added to patternified Pattern methods
Pattern.prototype.factories = {
pure,
@@ -1206,12 +1539,11 @@ Pattern.prototype.factories = {
// Nothing
export const silence = new Pattern((_) => []);
/** A discrete value that repeats once per cycle:
/** A discrete value that repeats once per cycle.
*
* @param {any} value - The value to repeat
* @returns {Pattern}
* @example
* pure('e4')
* pure('e4') // "e4"
*/
export function pure(value) {
function query(state) {
@@ -1241,12 +1573,11 @@ export function reify(thing) {
return pure(thing);
}
/** The given items are played at the same time at the same length:
/** The given items are played at the same time at the same length.
*
* @param {...any} items - The items to stack
* @return {Pattern}
* @example
* stack(g3, b3, [e4, d4])
* stack(g3, b3, [e4, d4]).note() // "g3,b3,[e4,d4]".note()
*/
export function stack(...pats) {
// Array test here is to avoid infinite recursions..
@@ -1259,7 +1590,6 @@ export function stack(...pats) {
*
* synonyms: {@link cat}
*
* @param {...any} items - The items to concatenate
* @return {Pattern}
* @example
* slowcat(e5, b4, [d5, c5])
@@ -1315,16 +1645,22 @@ export function fastcat(...pats) {
return slowcat(...pats)._fast(pats.length);
}
/** See {@link slowcat} */
/** The given items are con**cat**enated, where each one takes one cycle. Synonym: slowcat
*
* @param {...any} items - The items to concatenate
* @return {Pattern}
* @example
* cat(e5, b4, [d5, c5]).note() // "<e5 b4 [d5 c5]>".note()
*
*/
export function cat(...pats) {
return slowcat(...pats);
}
/** Like {@link fastcat}, but where each step has a temporal weight:
* @param {...Array} items - The items to concatenate
/** Like {@link seq}, but each step has a length, relative to the whole.
* @return {Pattern}
* @example
* timeCat([3,e3],[1, g3])
* timeCat([3,e3],[1, g3]).note() // "e3@3 g3".note()
*/
export function timeCat(...timepats) {
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
@@ -1343,7 +1679,11 @@ export function sequence(...pats) {
return fastcat(...pats);
}
/** See {@link fastcat} */
/** Like **cat**, but the items are crammed into one cycle. Synonyms: fastcat, sequence
* @example
* seq(e5, b4, [d5, c5]).note() // "e5 b4 [d5 c5]".note()
*
*/
export function seq(...pats) {
return fastcat(...pats);
}
@@ -1392,20 +1732,6 @@ export function pm(...args) {
polymeter(...args);
}
export function polyrhythm(...xs) {
const seqs = xs.map((a) => sequence(a));
if (seqs.length == 0) {
return silence;
}
return stack(...seqs);
}
// alias
export function pr(args) {
polyrhythm(args);
}
export const add = curry((a, pat) => pat.add(a));
export const chop = curry((a, pat) => pat.chop(a));
export const chunk = curry((a, pat) => pat.chunk(a));
@@ -1428,6 +1754,7 @@ export const mul = curry((a, pat) => pat.mul(a));
export const off = curry((t, f, pat) => pat.off(t, f));
export const ply = curry((a, pat) => pat.ply(a));
export const range = curry((a, b, pat) => pat.range(a, b));
export const rangex = curry((a, b, pat) => pat.rangex(a, b));
export const range2 = curry((a, b, pat) => pat.range2(a, b));
export const rev = (pat) => pat.rev();
export const slow = curry((a, pat) => pat.slow(a));
@@ -1514,6 +1841,18 @@ Pattern.prototype.inside = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._inside)(...args, this);
};
Pattern.prototype.range = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._range)(...args, this);
};
Pattern.prototype.rangex = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._rangex)(...args, this);
};
Pattern.prototype.range2 = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._range2)(...args, this);
};
// call this after all Patter.prototype.define calls have been executed! (right before evaluate)
Pattern.prototype.bootstrap = function () {
+201 -10
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Hap } from './hap.mjs';
import { Pattern, fastcat, reify, silence, stack } from './pattern.mjs';
import { Pattern, fastcat, reify, silence, stack, isPattern } from './pattern.mjs';
import Fraction from './fraction.mjs';
import { id } from './util.mjs';
@@ -27,9 +27,9 @@ export const isaw2 = isaw._toBipolar();
*
* @return {Pattern}
* @example
* "c3 [eb3,g3] g2 [g3,bb3]".legato(saw.slow(4))
* "c3 [eb3,g3] g2 [g3,bb3]".legato(saw.slow(4)).note()
* @example
* saw.range(0,8).segment(8).scale('C major').slow(4)
* saw.range(0,8).segment(8).scale('C major').slow(4).note()
*
*/
export const saw = signal((t) => t % 1);
@@ -42,7 +42,7 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
*
* @return {Pattern}
* @example
* sine.segment(16).range(0,15).slow(2).scale('C minor')
* sine.segment(16).range(0,15).slow(2).scale('C minor').note()
*
*/
export const sine = sine2._fromBipolar();
@@ -52,7 +52,7 @@ export const sine = sine2._fromBipolar();
*
* @return {Pattern}
* @example
* stack(sine,cosine).segment(16).range(0,15).slow(2).scale('C minor')
* stack(sine,cosine).segment(16).range(0,15).slow(2).scale('C minor').note()
*
*/
export const cosine = sine._early(Fraction(1).div(4));
@@ -63,7 +63,7 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
*
* @return {Pattern}
* @example
* square.segment(2).range(0,7).scale('C minor')
* square.segment(2).range(0,7).scale('C minor').note()
*
*/
export const square = signal((t) => Math.floor((t * 2) % 2));
@@ -74,7 +74,7 @@ export const square2 = square._toBipolar();
*
* @return {Pattern}
* @example
* triangle.segment(2).range(0,7).scale('C minor')
* tri.segment(8).range(0,7).scale('C minor').note()
*
*/
export const tri = fastcat(isaw, saw);
@@ -111,7 +111,17 @@ const timeToRandsPrime = (seed, n) => {
const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
/**
* A continuous pattern of random numbers, between 0 and 1
*
*/
/**
* A continuous pattern of random numbers, between 0 and 1.
*
* @name rand
* @example
* // randomly change the cutoff
* s("bd sd,hh*4").cutoff(rand.range(500,2000))
*
*/
export const rand = signal(timeToRand);
/**
@@ -124,6 +134,17 @@ export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
export const brand = _brandBy(0.5);
export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
/**
* A continuous pattern of random integers, between 0 and n-1.
*
* @name irand
* @param {number} n max value (exclusive)
* @example
* // randomly select scale notes from 0 - 7 (= C to C)
* irand(8).struct("x(3,8)").scale('C minor').note()
*
*/
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
export const __chooseWith = (pat, xs) => {
@@ -156,8 +177,8 @@ export const chooseInWith = (pat, xs) => {
};
/**
* Chooses randomly from the given list of values.
* @param {...any} xs
* Chooses randomly from the given list of elements.
* @param {...any} xs values / patterns to choose from.
* @returns {Pattern} - a continuous pattern.
*/
export const choose = (...xs) => chooseWith(rand, xs);
@@ -183,6 +204,14 @@ Pattern.prototype.choose2 = function (...xs) {
return chooseWith(this._fromBipolar(), xs);
};
/**
* Picks one of the elements at random each cycle.
* @returns {Pattern}
* @example
* chooseCycles("bd", "hh", "sd").s().fast(4)
* @example
* "bd | hh | sd".s().fast(4)
*/
export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
export const randcat = chooseCycles;
@@ -217,20 +246,68 @@ export const perlinWith = (pat) => {
return pat.sub(pata).fmap(interp).appBoth(pata.fmap(timeToRand)).appBoth(patb.fmap(timeToRand));
};
/**
* Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1.
*
* @name perlin
* @example
* // randomly change the cutoff
* s("bd sd,hh*4").cutoff(perlin.range(500,2000))
*
*/
export const perlin = perlinWith(time);
Pattern.prototype._degradeByWith = function (withPat, x) {
return this.fmap((a) => (_) => a).appLeft(withPat._filterValues((v) => v > x));
};
/**
* Randomly removes events from the pattern by a given amount.
* 0 = 0% chance of removal
* 1 = 100% chance of removal
*
* @name degradeBy
* @memberof Pattern
* @param {number} amount - a number between 0 and 1
* @returns Pattern
* @example
* s("hh*8").degradeBy(0.2)
* @example
* s("[hh?0.2]*8")
*/
Pattern.prototype._degradeBy = function (x) {
return this._degradeByWith(rand, x);
};
/**
*
* Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)`
*
* @name degrade
* @memberof Pattern
* @returns Pattern
* @example
* s("hh*8").degrade()
* @example
* s("[hh?]*8")
*/
Pattern.prototype.degrade = function () {
return this._degradeBy(0.5);
};
/**
* Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
* 0 = 100% chance of removal
* 1 = 0% chance of removal
* Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example).
*
* @name undegradeBy
* @memberof Pattern
* @param {number} amount - a number between 0 and 1
* @returns Pattern
* @example
* s("hh*8").undegradeBy(0.2)
*/
Pattern.prototype._undegradeBy = function (x) {
return this._degradeByWith(
rand.fmap((r) => 1 - r),
@@ -246,6 +323,25 @@ Pattern.prototype._sometimesBy = function (x, func) {
return stack(this._degradeBy(x), func(this._undegradeBy(1 - x)));
};
// https://github.com/tidalcycles/strudel/discussions/198
/* Pattern.prototype._sometimesBy = function (x, other) {
other = typeof other === 'function' ? other(this._undegradeBy(1 - x)) : reify(other)._undegradeBy(1 - x);
return stack(this._degradeBy(x), other);
}; */
/**
*
* Randomly applies the given function by the given probability.
* Similar to {@link Pattern#someCyclesBy}
*
* @name sometimesBy
* @memberof Pattern
* @param {number | Pattern} probability - a number between 0 and 1
* @param {function} function - the transformation to apply
* @returns Pattern
* @example
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
*/
Pattern.prototype.sometimesBy = function (patx, func) {
const pat = this;
return reify(patx)
@@ -253,6 +349,7 @@ Pattern.prototype.sometimesBy = function (patx, func) {
.innerJoin();
};
// why does this exist? it is identical to sometimesBy
Pattern.prototype._sometimesByPre = function (x, func) {
return stack(this._degradeBy(x), func(this).undegradeBy(1 - x));
};
@@ -264,6 +361,17 @@ Pattern.prototype.sometimesByPre = function (patx, func) {
.innerJoin();
};
/**
*
* Applies the given function with a 50% chance
*
* @name sometimes
* @memberof Pattern
* @param {function} function - the transformation to apply
* @returns Pattern
* @example
* s("hh*4").sometimes(x=>x.speed("0.5"))
*/
Pattern.prototype.sometimes = function (func) {
return this._sometimesBy(0.5, func);
};
@@ -279,6 +387,19 @@ Pattern.prototype._someCyclesBy = function (x, func) {
);
};
/**
*
* Randomly applies the given function by the given probability on a cycle by cycle basis.
* Similar to {@link Pattern#sometimesBy}
*
* @name someCyclesBy
* @memberof Pattern
* @param {number | Pattern} probability - a number between 0 and 1
* @param {function} function - the transformation to apply
* @returns Pattern
* @example
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
*/
Pattern.prototype.someCyclesBy = function (patx, func) {
const pat = this;
return reify(patx)
@@ -286,30 +407,100 @@ Pattern.prototype.someCyclesBy = function (patx, func) {
.innerJoin();
};
/**
*
* Shorthand for `.someCyclesBy(0.5, fn)`
*
* @name someCycles
* @memberof Pattern
* @returns Pattern
* @example
* s("hh(3,8)").someCycles(x=>x.speed("0.5"))
*/
Pattern.prototype.someCycles = function (func) {
return this._someCyclesBy(0.5, func);
};
/**
*
* Shorthand for `.sometimesBy(0.75, fn)`
*
* @name often
* @memberof Pattern
* @returns Pattern
* @example
* s("hh*8").often(x=>x.speed("0.5"))
*/
Pattern.prototype.often = function (func) {
return this.sometimesBy(0.75, func);
};
/**
*
* Shorthand for `.sometimesBy(0.25, fn)`
*
* @name rarely
* @memberof Pattern
* @returns Pattern
* @example
* s("hh*8").rarely(x=>x.speed("0.5"))
*/
Pattern.prototype.rarely = function (func) {
return this.sometimesBy(0.25, func);
};
/**
*
* Shorthand for `.sometimesBy(0.1, fn)`
*
* @name almostNever
* @memberof Pattern
* @returns Pattern
* @example
* s("hh*8").almostNever(x=>x.speed("0.5"))
*/
Pattern.prototype.almostNever = function (func) {
return this.sometimesBy(0.1, func);
};
/**
*
* Shorthand for `.sometimesBy(0.9, fn)`
*
* @name almostAlways
* @memberof Pattern
* @returns Pattern
* @example
* s("hh*8").almostAlways(x=>x.speed("0.5"))
*/
Pattern.prototype.almostAlways = function (func) {
return this.sometimesBy(0.9, func);
};
/**
*
* Shorthand for `.sometimesBy(0, fn)` (never calls fn)
*
* @name never
* @memberof Pattern
* @returns Pattern
* @example
* s("hh*8").never(x=>x.speed("0.5"))
*/
Pattern.prototype.never = function (func) {
return this;
};
/**
*
* Shorthand for `.sometimesBy(1, fn)` (always calls fn)
*
* @name always
* @memberof Pattern
* @returns Pattern
* @example
* s("hh*8").always(x=>x.speed("0.5"))
*/
Pattern.prototype.always = function (func) {
return func(this);
};
+20
View File
@@ -42,6 +42,7 @@ import {
id,
ply,
rev,
time,
} from '../index.mjs';
import { steady } from '../signal.mjs';
@@ -371,6 +372,11 @@ describe('Pattern', () => {
sequence(['a', 'b', 'c'], silence, ['a', 'b', 'c'], silence).firstCycle(),
);
});
it('copes with breaking up events across cycles', () => {
expect(pure('a').slow(2)._fastGap(2)._setContext({}).query(st(0, 2))).toStrictEqual(
[hap(ts(0, 1), ts(0, 0.5), 'a'), hap(ts(0.5, 1.5), ts(1, 1.5), 'a')]
);
});
});
describe('_compressSpan()', () => {
it('Can squash cycles of a pattern into a given timespan', () => {
@@ -791,6 +797,13 @@ describe('Pattern', () => {
).firstCycle(),
);
});
it('Squeezes to the correct cycle', () => {
expect(
pure(time.struct(true))._squeezeJoin().queryArc(3,4).map(x => x.value)
).toStrictEqual(
[Fraction(3.5)]
)
});
});
describe('ply', () => {
it('Can ply(3)', () => {
@@ -840,6 +853,13 @@ describe('Pattern', () => {
);
});
});
describe('range', () => {
it('Can be patterned', () => {
expect(sequence(0, 0).range(sequence(0, 0.5), 1).firstCycle()).toStrictEqual(
sequence(0, 0.5).firstCycle(),
);
});
});
describe('range2', () => {
it('Can change the range of a bipolar pattern', () => {
expect(sequence(-1, -0.5, 0, 0.5).range2(1000, 1100).firstCycle()).toStrictEqual(
+46 -5
View File
@@ -1,11 +1,11 @@
/*
util.test.mjs - <short description TODO>
util.test.mjs - Tests for the core 'util' module
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/util.test.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { pure } from '../pattern.mjs';
import { isNote, tokenizeNote, toMidi, fromMidi, mod, compose, getFrequency } from '../util.mjs';
import { isNote, tokenizeNote, toMidi, fromMidi, mod, compose, getFrequency, getPlayableNoteValue } from '../util.mjs';
import { describe, it, expect } from 'vitest';
describe('isNote', () => {
@@ -70,6 +70,10 @@ describe('toMidi', () => {
expect(toMidi('C#3')).toEqual(49);
expect(toMidi('C##3')).toEqual(50);
});
it('should throw an error when given a non-note', () => {
expect(() => toMidi('Q')).toThrowError(`not a note: "Q"`);
expect(() => toMidi('Z')).toThrowError(`not a note: "Z"`);
});
});
describe('fromMidi', () => {
it('should turn midi into frequency', () => {
@@ -78,13 +82,27 @@ describe('fromMidi', () => {
});
});
describe('getFrequency', () => {
it('should turn midi into frequency', () => {
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
it('should turn note into frequency', () => {
expect(getFrequency(happify('a4'))).toEqual(440);
expect(getFrequency(happify('a3'))).toEqual(220);
expect(getFrequency(happify(440, { type: 'frequency' }))).toEqual(440); // TODO: migrate when values are objects..
});
it('should turn midi into frequency', () => {
expect(getFrequency(happify(69, { type: 'midi' }))).toEqual(440);
expect(getFrequency(happify(57, { type: 'midi' }))).toEqual(220);
});
it('should return frequencies unchanged', () => {
expect(getFrequency(happify(440, { type: 'frequency' }))).toEqual(440);
expect(getFrequency(happify(432, { type: 'frequency' }))).toEqual(432);
});
it('should turn object with a "freq" property into frequency', () => {
expect(getFrequency(happify({freq: 220}))).toEqual(220)
expect(getFrequency(happify({freq: 440}))).toEqual(440)
});
it('should throw an error when given a non-note', () => {
expect(() => getFrequency(happify('Q'))).toThrowError(`not a note or frequency: Q`)
expect(() => getFrequency(happify('Z'))).toThrowError(`not a note or frequency: Z`)
});
});
describe('mod', () => {
@@ -118,3 +136,26 @@ describe('compose', () => {
expect(compose(addS('a'), addS('b'))('x')).toEqual('xab');
});
});
describe('getPlayableNoteValue', () => {
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
it('should return object "note" property', () => {
expect(getPlayableNoteValue(happify({note: "a4"}))).toEqual('a4')
});
it('should return object "n" property', () => {
expect(getPlayableNoteValue(happify({n: "a4"}))).toEqual('a4')
});
it('should return object "value" property', () => {
expect(getPlayableNoteValue(happify({value: "a4"}))).toEqual('a4')
});
it('should turn midi into frequency', () => {
expect(getPlayableNoteValue(happify(57, {type: 'midi'}))).toEqual(220)
})
it('should return frequency value', () => {
expect(getPlayableNoteValue(happify(220, {type: 'frequency'}))).toEqual(220)
})
it('should throw an error if value is not an object, number, or string', () => {
expect(() => getPlayableNoteValue(happify(false))).toThrowError(`not a note: false`)
expect(() => getPlayableNoteValue(happify(undefined))).toThrowError(`not a note: undefined`)
})
});
+14 -1
View File
@@ -31,6 +31,9 @@ export const fromMidi = (n) => {
return Math.pow(2, (n - 69) / 12) * 440;
};
/**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
*/
export const getFreq = (noteOrMidi) => {
if (typeof noteOrMidi === 'number') {
return fromMidi(noteOrMidi);
@@ -38,6 +41,9 @@ export const getFreq = (noteOrMidi) => {
return fromMidi(toMidi(noteOrMidi));
};
/**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
*/
export const midi2note = (n) => {
const oct = Math.floor(n / 12) - 1;
const pc = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'][n % 12];
@@ -75,7 +81,7 @@ export const getFrequency = (hap) => {
} else if (typeof value === 'string' && isNote(value)) {
value = fromMidi(toMidi(hap.value));
} else if (typeof value !== 'number') {
throw new Error('not a note or frequency:' + value);
throw new Error('not a note or frequency: ' + value);
}
return value;
};
@@ -124,3 +130,10 @@ export function curry(func, overload) {
}
return fn;
}
export function objectify(value) {
if (typeof value === 'object' && !Array.isArray(value)) {
return value; // do nothing if its already an object
}
return { value };
}
-1
View File
@@ -18,7 +18,6 @@ Either install with `npm i @strudel.cycles/embed` or just use a cdn to import th
.legato(sine.range(0.3, 2).slow(28))
.wave("sawtooth square".fast(2))
.filter('lowpass', cosine.range(500,4000).slow(16))
.out()
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})
-->
</strudel-repl>
-1
View File
@@ -10,7 +10,6 @@
.legato(sine.range(0.3, 2).slow(28))
.wave("sawtooth square".fast(2))
.filter('lowpass', cosine.range(500,4000).slow(16))
.out()
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})
-->
</strudel-repl>
+44 -41
View File
@@ -4,10 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { isNote } from 'tone';
import _WebMidi from 'webmidi';
import { Pattern, isPattern } from '@strudel.cycles/core';
import { Tone } from '@strudel.cycles/tone';
import { Pattern, isPattern, isNote, getPlayableNoteValue, objectify } from '@strudel.cycles/core';
// if you use WebMidi from outside of this package, make sure to import that instance:
export const WebMidi = _WebMidi;
@@ -32,53 +30,58 @@ const outputByName = (name) => WebMidi.getOutputByName(name);
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
Pattern.prototype.midi = function (output, channel = 1) {
if (isPattern(output?.constructor?.name)) {
if (output?.constructor?.name && isPattern(output?.constructor?.name)) {
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'
}')`,
);
}
return this._withHap((hap) => {
// const onTrigger = (time: number, hap: any) => {
const onTrigger = (time, hap) => {
let note = hap.value;
const velocity = hap.context?.velocity ?? 0.9;
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 (!device) {
throw new Error(
`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs
.map((o) => `'${o.name}'`)
.join(' | ')}`,
);
}
// console.log('midi', value, output);
const timingOffset = WebMidi.time - Tone.getContext().currentTime * 1000;
time = time * 1000 + timingOffset;
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000;
// await enableWebMidi()
return this.onTrigger((time, hap, currentTime) => {
let note;
try {
note = getPlayableNoteValue(hap);
} catch (err) {
// dont care if no note is found as we can still send cvs
}
const { velocity, nrpnn, nrpv, ccn, ccv, legato, duration: durationValue } = objectify(hap.value);
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 (!device) {
throw new Error(
`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs
.map((o) => `'${o.name}'`)
.join(' | ')}`,
);
}
// console.log('midi', value, output);
const deadline = (time - currentTime) * 1000;
time = WebMidi.time + deadline;
/* const timingOffset = WebMidi.time - currentTime * 1000;
time = time * 1000 + timingOffset; */
const duration = (durationValue ?? hap.duration.valueOf()) * (legato ?? 1) * 1000 - 5;
if (note) {
device.playNote(note, channel, {
time,
duration: hap.duration.valueOf() * 1000 - 5,
duration,
velocity,
});
};
return hap.setContext({ ...hap.context, onTrigger });
}
if (ccn && ccv) {
device.sendControlChange(ccn, ccv, channel, { time });
}
});
};
+2 -2
View File
File diff suppressed because one or more lines are too long
+6 -18
View File
@@ -7,9 +7,9 @@ import { tags } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import { useInView } from 'react-hook-inview';
import { evaluate } from '@strudel.cycles/eval';
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
import { Tone } from '@strudel.cycles/tone';
import { TimeSpan, State } from '@strudel.cycles/core';
import { webaudioOutputTrigger } from '@strudel.cycles/webaudio';
import { WebMidi, enableWebMidi } from '@strudel.cycles/midi';
var strudelTheme = createTheme({
@@ -251,7 +251,7 @@ let s4 = () => {
};
const generateHash = (code) => encodeURIComponent(btoa(code));
function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawProp }) {
function useRepl({ tune, autolink = true, onEvent, onDraw: onDrawProp }) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState(tune);
const [activeCode, setActiveCode] = useState();
@@ -282,26 +282,15 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
if (event.context.logs?.length) {
event.context.logs.forEach(pushLog);
}
const { onTrigger, velocity } = event.context;
if (!onTrigger) {
if (defaultSynth) {
const note = getPlayableNoteValue(event);
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} else {
throw new Error('no defaultSynth passed to useRepl.');
}
/* console.warn('no instrument chosen', event);
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
} else {
onTrigger(time, event, currentTime, 1 /* cps */);
}
const { onTrigger = webaudioOutputTrigger } = event.context;
onTrigger(time, event, currentTime, 1 /* cps */);
} catch (err) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
}
},
[onEvent, pushLog, defaultSynth],
[onEvent, pushLog],
),
onQuery: useCallback(
(state) => {
@@ -484,10 +473,9 @@ function Icon({ type }) {
}[type]);
}
function MiniRepl({ tune, defaultSynth, hideOutsideView = false, theme, init, onEvent, enableKeyboard }) {
function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableKeyboard }) {
const { code, setCode, pattern, activeCode, activateCode, evaluateOnly, error, cycle, dirty, togglePlay, stop } = useRepl({
tune,
defaultSynth,
autolink: false,
onEvent
});
+1 -5
View File
@@ -1,13 +1,9 @@
import React from 'react';
import { MiniRepl } from './components/MiniRepl';
import 'tailwindcss/tailwind.css';
import { Tone, getDefaultSynth } from '@strudel.cycles/tone';
import { evalScope } from '@strudel.cycles/eval';
const defaultSynth = getDefaultSynth();
evalScope(
Tone,
import('@strudel.cycles/core'),
import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'),
@@ -20,7 +16,7 @@ evalScope(
function App() {
return (
<div>
<MiniRepl tune={`"c3"`} defaultSynth={defaultSynth} />
<MiniRepl tune={`"c3"`} />
</div>
);
}
+1 -2
View File
@@ -9,11 +9,10 @@ import './style.css';
import styles from './MiniRepl.module.css';
import { Icon } from './Icon';
export function MiniRepl({ tune, defaultSynth, hideOutsideView = false, theme, init, onEvent, enableKeyboard }) {
export function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableKeyboard }) {
const { code, setCode, pattern, activeCode, activateCode, evaluateOnly, error, cycle, dirty, togglePlay, stop } =
useRepl({
tune,
defaultSynth,
autolink: false,
onEvent,
});
+5 -16
View File
@@ -6,9 +6,9 @@ This program is free software: you can redistribute it and/or modify it under th
import { useCallback, useState, useMemo } from 'react';
import { evaluate } from '@strudel.cycles/eval';
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
import useCycle from './useCycle.mjs';
import usePostMessage from './usePostMessage.mjs';
import { webaudioOutputTrigger } from '@strudel.cycles/webaudio';
let s4 = () => {
return Math.floor((1 + Math.random()) * 0x10000)
@@ -17,7 +17,7 @@ let s4 = () => {
};
const generateHash = (code) => encodeURIComponent(btoa(code));
function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawProp }) {
function useRepl({ tune, autolink = true, onEvent, onDraw: onDrawProp }) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState(tune);
const [activeCode, setActiveCode] = useState();
@@ -48,26 +48,15 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
if (event.context.logs?.length) {
event.context.logs.forEach(pushLog);
}
const { onTrigger, velocity } = event.context;
if (!onTrigger) {
if (defaultSynth) {
const note = getPlayableNoteValue(event);
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} else {
throw new Error('no defaultSynth passed to useRepl.');
}
/* console.warn('no instrument chosen', event);
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
} else {
onTrigger(time, event, currentTime, 1 /* cps */);
}
const { onTrigger = webaudioOutputTrigger } = event.context;
onTrigger(time, event, currentTime, 1 /* cps */);
} catch (err) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
}
},
[onEvent, pushLog, defaultSynth],
[onEvent, pushLog],
),
onQuery: useCallback(
(state) => {
+3 -1
View File
@@ -38,7 +38,9 @@ export default defineConfig({
'@codemirror/commands',
'@lezer/highlight',
'@codemirror/language',
'@uiw/codemirror-themes'
'@uiw/codemirror-themes',
'@uiw/react-codemirror',
'@lezer/highlight',
],
},
target: 'esnext',
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.2.0",
"version": "0.2.1",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"type": "module",
+5 -3
View File
@@ -69,9 +69,9 @@ function scaleOffset(scale, offset, note) {
* @memberof Pattern
* @name transpose
* @example
* "c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).transpose(0)
* "c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).note()
* @example
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).transpose(0)
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
*/
Pattern.prototype._transpose = function (intervalOrSemitones) {
@@ -107,6 +107,7 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
* "-8 [2,4,6]"
* .scale('C4 bebop major')
* .scaleTranspose("<0 -1 -2 -3 -4 -5 -6 -4>")
* .note()
*/
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
@@ -134,9 +135,10 @@ Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
* @name scale
* @param {string} scale Name of scale
* @returns Pattern
* @example
* @example
* "0 2 4 6 4 2"
* .scale(seq('C2 major', 'C2 minor').slow(2))
* .note()
*/
Pattern.prototype._scale = function (scale /* : string */) {
+1 -1
View File
@@ -40,7 +40,7 @@ Pattern.prototype.fmapNested = function (func) {
* @param {range} range note range for possible voicings (optional, defaults to `['F3', 'A4']`)
* @returns Pattern
* @example
* stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")
* stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note()
*/
Pattern.prototype.voicings = function (range) {
+6 -6
View File
@@ -15,13 +15,13 @@ npm i @strudel.cycles/webaudio --save
import { Scheduler, getAudioContext } from '@strudel.cycles/webaudio';
const scheduler = new Scheduler({
audioContext: getAudioContext(),
interval: 0.1,
onEvent: (e) => e.context?.createAudioNode?.(e),
});
const pattern = sequence([55, 99], 110).osc('sawtooth').out()
audioContext: getAudioContext(),
interval: 0.1,
onEvent: (e) => e.context?.createAudioNode?.(e),
});
const pattern = sequence([55, 99], 110).osc('sawtooth');
scheduler.setPattern(pattern);
scheduler.start()
scheduler.start();
//scheduler.stop()
```
+31
View File
@@ -0,0 +1,31 @@
if (typeof DelayNode !== 'undefined') {
class FeedbackDelayNode extends DelayNode {
constructor(ac, wet, time, feedback) {
super(ac);
wet = Math.abs(wet);
this.delayTime.value = time;
const feedbackGain = ac.createGain();
feedbackGain.gain.value = Math.min(Math.abs(feedback), 0.995);
this.feedback = feedbackGain.gain;
const delayGain = ac.createGain();
delayGain.gain.value = wet;
this.delayGain = delayGain;
this.connect(feedbackGain);
this.connect(delayGain);
feedbackGain.connect(this);
this.connect = (target) => delayGain.connect(target);
return this;
}
start(t) {
this.delayGain.gain.setValueAtTime(this.delayGain.gain.value, t + this.delayTime.value);
}
}
AudioContext.prototype.createFeedbackDelay = function (wet, time, feedback) {
return new FeedbackDelayNode(this, wet, time, feedback);
};
}
+23
View File
@@ -0,0 +1,23 @@
if (typeof AudioContext !== 'undefined') {
AudioContext.prototype.impulseResponse = function (duration) {
const length = this.sampleRate * duration;
const impulse = this.createBuffer(2, length, this.sampleRate);
const IR = impulse.getChannelData(0);
for (let i = 0; i < length; i++) IR[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, duration);
return impulse;
};
AudioContext.prototype.createReverb = function (duration) {
const convolver = this.createConvolver();
convolver.setDuration = (d) => {
convolver.buffer = this.impulseResponse(d);
convolver.duration = duration;
return convolver;
};
convolver.setDuration(duration);
return convolver;
};
}
// TODO: make the reverb more exciting
// check out https://blog.gskinner.com/archives/2019/02/reverb-web-audio-api.html
+14 -5
View File
@@ -16,6 +16,15 @@ export const loadBuffer = (url, ac) => {
return loadCache[url];
};
export function reverseBuffer(buffer) {
const ac = getAudioContext();
const reversed = ac.createBuffer(buffer.numberOfChannels, buffer.length, ac.sampleRate);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
reversed.copyToChannel(buffer.getChannelData(channel).slice().reverse(), channel, channel);
}
return reversed;
}
export const getLoadedBuffer = (url) => {
return bufferCache[url];
};
@@ -82,14 +91,14 @@ export const loadGithubSamples = async (path, nameFn) => {
};
/**
* load the given sample map for webdirt
* Loads a collection of samples to use with `s`
*
* @example
* loadSamples({
* bd: '808bd/BD0000.WAV',
* sd: ['808sd/SD0000.WAV','808sd/SD0010.WAV','808sd/SD0050.WAV']
* samples({
* bd: '808bd/BD0000.WAV',
* sd: '808sd/SD0010.WAV'
* }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
* s("bd <sd!7 sd(3,4,2)>").n(2).webdirt()
* s("[bd ~]*2, [~ hh]*2, ~ sd")
*
*/
+38
View File
@@ -0,0 +1,38 @@
// credits to webdirt: https://github.com/dktr0/WebDirt/blob/41342e81d6ad694a2310d491fef7b7e8b0929efe/js-src/Graph.js#L597
export var vowelFormant = {
a: { freqs: [660, 1120, 2750, 3000, 3350], gains: [1, 0.5012, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
e: { freqs: [440, 1800, 2700, 3000, 3300], gains: [1, 0.1995, 0.1259, 0.1, 0.1], qs: [70, 80, 100, 120, 120] },
i: { freqs: [270, 1850, 2900, 3350, 3590], gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], qs: [40, 90, 100, 120, 120] },
o: { freqs: [430, 820, 2700, 3000, 3300], gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], qs: [40, 80, 100, 120, 120] },
u: { freqs: [370, 630, 2750, 3000, 3400], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
};
if (typeof GainNode !== 'undefined') {
class VowelNode extends GainNode {
constructor(ac, letter) {
super(ac);
if (!vowelFormant[letter]) {
throw new Error('vowel: unknown vowel ' + letter);
}
const { gains, qs, freqs } = vowelFormant[letter];
const makeupGain = ac.createGain();
for (let i = 0; i < 5; i++) {
const gain = ac.createGain();
gain.gain.value = gains[i];
const filter = ac.createBiquadFilter();
filter.type = 'bandpass';
filter.Q.value = qs[i];
filter.frequency.value = freqs[i];
this.connect(filter);
filter.connect(gain);
gain.connect(makeupGain);
}
makeupGain.gain.value = 8; // how much makeup gain to add?
this.connect = (target) => makeupGain.connect(target);
return this;
}
}
AudioContext.prototype.createVowelFilter = function (letter) {
return new VowelNode(this, letter);
};
}
+317 -155
View File
@@ -6,9 +6,13 @@ This program is free software: you can redistribute it and/or modify it under th
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core';
import { fromMidi, toMidi } from '@strudel.cycles/core';
import { loadBuffer } from './sampler.mjs';
import { fromMidi, isNote, toMidi } from '@strudel.cycles/core';
import './feedbackdelay.mjs';
import './reverb.mjs';
import { loadBuffer, reverseBuffer } from './sampler.mjs';
const { Pattern } = strudel;
import './vowel.mjs';
import workletsUrl from './worklets.mjs?url';
// export const getAudioContext = () => Tone.getContext().rawContext;
@@ -20,6 +24,21 @@ export const getAudioContext = () => {
return audioContext;
};
let destination;
const getDestination = () => {
const ctx = getAudioContext();
if (!destination) {
destination = ctx.createGain();
destination.connect(ctx.destination);
}
return destination;
};
export const panic = () => {
getDestination().gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01);
destination = null;
};
const getFilter = (type, frequency, Q) => {
const filter = getAudioContext().createBiquadFilter();
filter.type = type;
@@ -36,6 +55,17 @@ const getADSR = (attack, decay, sustain, release, velocity, begin, end) => {
gainNode.gain.setValueAtTime(sustain * velocity, end); // sustain end
gainNode.gain.linearRampToValueAtTime(0, end + release); // release
// for some reason, using exponential ramping creates little cracklings
/* let t = begin;
gainNode.gain.setValueAtTime(0, t);
gainNode.gain.exponentialRampToValueAtTime(velocity, (t += attack));
const sustainGain = Math.max(sustain * velocity, 0.001);
gainNode.gain.exponentialRampToValueAtTime(sustainGain, (t += decay));
if (end - begin < attack + decay) {
gainNode.gain.cancelAndHoldAtTime(end);
} else {
gainNode.gain.setValueAtTime(sustainGain, end);
}
gainNode.gain.exponentialRampToValueAtTime(0.001, end + release); // release */
return gainNode;
};
@@ -68,11 +98,15 @@ const getSoundfontKey = (s) => {
return;
};
const getSampleBufferSource = async (s, n, note) => {
const getSampleBufferSource = async (s, n, note, speed) => {
let transpose = 0;
if (note) {
transpose = toMidi(note) - 36; // C3 is middle C
let midi;
if (note !== undefined) {
midi = typeof note === 'string' ? toMidi(note) : note;
transpose = midi - 36; // C3 is middle C
}
const ac = getAudioContext();
// is sample from loaded samples(..)
const samples = getLoadedSamples();
@@ -81,7 +115,11 @@ const getSampleBufferSource = async (s, n, note) => {
}
const bank = samples?.[s];
if (!bank) {
throw new Error('sample not found:', s, 'try one of ' + Object.keys(samples));
throw new Error(
`sample not found: "${s}", try one of ${Object.keys(samples)
.map((s) => `"${s}"`)
.join(', ')}.`,
);
}
if (typeof bank !== 'object') {
throw new Error('wrong format for sample bank:', s);
@@ -93,7 +131,7 @@ const getSampleBufferSource = async (s, n, note) => {
if (!note) {
throw new Error('no note(...) set for sound', s);
}
const midiDiff = (noteA) => toMidi(noteA) - toMidi(note);
const midiDiff = (noteA) => toMidi(noteA) - midi;
// object format will expect keys as notes
const closest = Object.keys(bank)
.filter((k) => !k.startsWith('_'))
@@ -104,7 +142,11 @@ const getSampleBufferSource = async (s, n, note) => {
transpose = -midiDiff(closest); // semitones to repitch
sampleUrl = bank[closest][n % bank[closest].length];
}
const buffer = await loadBuffer(sampleUrl, ac);
let buffer = await loadBuffer(sampleUrl, ac);
if (speed < 0) {
// should this be cached?
buffer = reverseBuffer(buffer);
}
const bufferSource = ac.createBufferSource();
bufferSource.buffer = buffer;
const playbackRate = 1.0 * Math.pow(2, transpose / 12);
@@ -124,153 +166,273 @@ const splitSN = (s, n) => {
return [s2, n2];
};
Pattern.prototype.out = function () {
return this.onTrigger(async (t, hap, ct, cps) => {
const hapDuration = hap.duration / cps;
try {
const ac = getAudioContext();
// calculate correct time (tone.js workaround)
t = ac.currentTime + t - ct;
// destructure value
let {
freq,
s,
sf,
clip = 0, // if 1, samples will be cut off when the hap ends
n = 0,
note,
gain = 1,
cutoff,
resonance = 1,
hcutoff,
hresonance = 1,
bandf,
bandq = 1,
pan,
attack = 0.001,
decay = 0.05,
sustain = 0.5,
release = 0.001,
speed = 1, // sample playback speed
begin = 0,
end = 1,
} = hap.value;
const { velocity = 1 } = hap.context;
gain *= velocity; // legacy fix for velocity
// the chain will hold all audio nodes that connect to each other
const chain = [];
if (typeof s === 'string') {
[s, n] = splitSN(s, n);
}
if (typeof note === 'string') {
[note, n] = splitSN(note, n);
}
if (!s || ['sine', 'square', 'triangle', 'sawtooth'].includes(s)) {
// with synths, n and note are the same thing
n = note || n;
if (typeof n === 'string') {
n = toMidi(n); // e.g. c3 => 48
}
// get frequency
if (!freq && typeof n === 'number') {
freq = fromMidi(n); // + 48);
}
// make oscillator
const o = getOscillator({ t, s, freq, duration: hapDuration, release });
chain.push(o);
// level down oscillators as they are really loud compared to samples i've tested
const g = ac.createGain();
g.gain.value = 0.3;
chain.push(g);
// TODO: make adsr work with samples without pops
// envelope
const adsr = getADSR(attack, decay, sustain, release, 1, t, t + hapDuration);
chain.push(adsr);
} else {
// load sample
if (speed === 0) {
// no playback
return;
}
if (!s) {
console.warn('no sample specified');
return;
}
const soundfont = getSoundfontKey(s);
let bufferSource;
let workletsLoading;
function loadWorklets() {
if (workletsLoading) {
return workletsLoading;
}
workletsLoading = getAudioContext().audioWorklet.addModule(workletsUrl);
return workletsLoading;
}
try {
if (soundfont) {
// is soundfont
bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac);
} else {
// is sample from loaded samples(..)
bufferSource = await getSampleBufferSource(s, n, note);
}
} catch (err) {
console.warn(err);
return;
}
// asny stuff above took too long?
if (ac.currentTime > t) {
console.warn('sample still loading:', s, n);
return;
}
if (!bufferSource) {
console.warn('no buffer source');
return;
}
bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value;
// TODO: nudge, unit, cut, loop
let duration = soundfont || clip ? hapDuration : bufferSource.buffer.duration;
// let duration = bufferSource.buffer.duration;
const offset = begin * duration;
duration = ((end - begin) * duration) / Math.abs(speed);
if (soundfont || clip) {
bufferSource.start(t, offset); // duration does not work here for some reason
} else {
bufferSource.start(t, offset, duration);
}
chain.push(bufferSource);
if (soundfont || clip) {
const env = ac.createGain();
const releaseLength = 0.1;
env.gain.value = 0.6;
env.gain.setValueAtTime(env.gain.value, t + duration);
env.gain.linearRampToValueAtTime(0, t + duration + releaseLength);
// env.gain.linearRampToValueAtTime(0, t + duration + releaseLength);
chain.push(env);
bufferSource.stop(t + duration + releaseLength);
} else {
bufferSource.stop(t + duration);
}
}
// master out
const master = ac.createGain();
master.gain.value = gain;
chain.push(master);
// filters
cutoff !== undefined && chain.push(getFilter('lowpass', cutoff, resonance));
hcutoff !== undefined && chain.push(getFilter('highpass', hcutoff, hresonance));
bandf !== undefined && chain.push(getFilter('bandpass', bandf, bandq));
// TODO vowel
// TODO delay / delaytime / delayfeedback
// panning
if (pan !== undefined) {
const panner = ac.createStereoPanner();
panner.pan.value = 2 * pan - 1;
chain.push(panner);
}
// master out
/* const master = ac.createGain();
master.gain.value = 0.8 * gain;
chain.push(master); */
chain.push(ac.destination);
// connect chain elements together
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
} catch (e) {
console.warn('.out error:', e);
}
function getWorklet(ac, processor, params) {
const node = new AudioWorkletNode(ac, processor);
Object.entries(params).forEach(([key, value]) => {
node.parameters.get(key).value = value;
});
return node;
}
if (typeof window !== 'undefined') {
try {
loadWorklets();
} catch (err) {
console.warn('could not load AudioWorklet effects coarse, crush and shape', err);
}
}
function gainNode(value) {
const node = getAudioContext().createGain();
node.gain.value = value;
return node;
}
const cutGroups = [];
let delays = {};
function getDelay(orbit, delaytime, delayfeedback, t) {
if (!delays[orbit]) {
const ac = getAudioContext();
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
dly.start(t);
dly.connect(getDestination());
delays[orbit] = dly;
}
delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t);
delays[orbit].feedback.value !== delayfeedback && delays[orbit].feedback.setValueAtTime(delayfeedback, t);
return delays[orbit];
}
let reverbs = {};
function getReverb(orbit, duration = 2) {
if (!reverbs[orbit]) {
const ac = getAudioContext();
const reverb = ac.createReverb(duration);
reverb.connect(getDestination());
reverbs[orbit] = reverb;
}
if (reverbs[orbit].duration !== duration) {
reverbs[orbit] = reverbs[orbit].setDuration(duration);
reverbs[orbit].duration = duration;
}
return reverbs[orbit];
}
function effectSend(input, effect, wet) {
const send = gainNode(wet);
input.connect(send);
send.connect(effect);
return send;
}
// export const webaudioOutput = async (t, hap, ct, cps) => {
export const webaudioOutput = async (hap, deadline, hapDuration) => {
try {
const ac = getAudioContext();
/* if (isNote(hap.value)) {
// supports primitive hap values that look like notes
hap.value = { note: hap.value };
} */
if (typeof hap.value !== 'object') {
throw new Error(
`hap.value ${hap.value} is not supported by webaudio output. Hint: append .note() or .s() to the end`,
);
}
// calculate correct time (tone.js workaround)
let t = ac.currentTime + deadline;
// destructure value
let {
freq,
s,
sf,
clip = 0, // if 1, samples will be cut off when the hap ends
n = 0,
note,
gain = 0.8,
cutoff,
resonance = 1,
hcutoff,
hresonance = 1,
bandf,
bandq = 1,
coarse,
crush,
shape,
pan,
speed = 1, // sample playback speed
begin = 0,
end = 1,
vowel,
delay = 0,
delayfeedback = 0.5,
delaytime = 0.25,
unit,
nudge = 0, // TODO: is this in seconds?
cut,
loop,
orbit = 1,
room,
size = 2,
roomsize = size,
} = hap.value;
const { velocity = 1 } = hap.context;
gain *= velocity; // legacy fix for velocity
// the chain will hold all audio nodes that connect to each other
const chain = [];
if (typeof s === 'string') {
[s, n] = splitSN(s, n);
}
if (typeof note === 'string') {
[note, n] = splitSN(note, n);
}
if (!s || ['sine', 'square', 'triangle', 'sawtooth'].includes(s)) {
// destructure adsr here, because the default should be different for synths and samples
const { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = hap.value;
// with synths, n and note are the same thing
n = note || n || 36;
if (typeof n === 'string') {
n = toMidi(n); // e.g. c3 => 48
}
// get frequency
if (!freq && typeof n === 'number') {
freq = fromMidi(n); // + 48);
}
// make oscillator
const o = getOscillator({ t, s, freq, duration: hapDuration, release });
chain.push(o);
// level down oscillators as they are really loud compared to samples i've tested
chain.push(gainNode(0.3));
// TODO: make adsr work with samples without pops
// envelope
const adsr = getADSR(attack, decay, sustain, release, 1, t, t + hapDuration);
chain.push(adsr);
} else {
// destructure adsr here, because the default should be different for synths and samples
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = hap.value;
// load sample
if (speed === 0) {
// no playback
return;
}
if (!s) {
console.warn('no sample specified');
return;
}
const soundfont = getSoundfontKey(s);
let bufferSource;
try {
if (soundfont) {
// is soundfont
bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac);
} else {
// is sample from loaded samples(..)
bufferSource = await getSampleBufferSource(s, n, note, speed);
}
} catch (err) {
console.warn(err);
return;
}
// asny stuff above took too long?
if (ac.currentTime > t) {
console.warn('sample still loading:', s, n);
return;
}
if (!bufferSource) {
console.warn('no buffer source');
return;
}
bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value;
if (unit === 'c') {
// are there other units?
bufferSource.playbackRate.value = bufferSource.playbackRate.value * bufferSource.buffer.duration;
}
let duration = soundfont || clip ? hapDuration : bufferSource.buffer.duration / bufferSource.playbackRate.value;
// "The computation of the offset into the sound is performed using the sound buffer's natural sample rate,
// rather than the current playback rate, so even if the sound is playing at twice its normal speed,
// the midway point through a 10-second audio buffer is still 5."
const offset = begin * duration * bufferSource.playbackRate.value;
duration = (end - begin) * duration;
if (loop) {
bufferSource.loop = true;
bufferSource.loopStart = offset;
bufferSource.loopEnd = offset + duration;
duration = loop * duration;
}
t += nudge;
bufferSource.start(t, offset);
if (cut !== undefined) {
cutGroups[cut]?.stop(t); // fade out?
cutGroups[cut] = bufferSource;
}
chain.push(bufferSource);
bufferSource.stop(t + duration + release);
const adsr = getADSR(attack, decay, sustain, release, 1, t, t + duration);
chain.push(adsr);
}
// gain stage
chain.push(gainNode(gain));
// filters
cutoff !== undefined && chain.push(getFilter('lowpass', cutoff, resonance));
hcutoff !== undefined && chain.push(getFilter('highpass', hcutoff, hresonance));
bandf !== undefined && chain.push(getFilter('bandpass', bandf, bandq));
vowel !== undefined && chain.push(ac.createVowelFilter(vowel));
// effects
coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse }));
crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush }));
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape }));
// panning
if (pan !== undefined) {
const panner = ac.createStereoPanner();
panner.pan.value = 2 * pan - 1;
chain.push(panner);
}
// last gain
const post = gainNode(1);
chain.push(post);
post.connect(getDestination());
// delay
let delaySend;
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
const delyNode = getDelay(orbit, delaytime, delayfeedback, t);
delaySend = effectSend(post, delyNode, delay);
}
// reverb
let reverbSend;
if (room > 0 && roomsize > 0) {
const reverbNode = getReverb(orbit, roomsize);
reverbSend = effectSend(post, reverbNode, room);
}
// connect chain elements together
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
// disconnect all nodes when source node has ended:
chain[0].onended = () => chain.concat([delaySend, reverbSend]).forEach((n) => n?.disconnect());
} catch (e) {
console.warn('.out error:', e);
}
};
export const webaudioOutputTrigger = (t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps);
Pattern.prototype.out = function () {
// TODO: refactor (t, hap, ct, cps) to (hap, deadline, duration) ?
return this.onTrigger(webaudioOutputTrigger);
};
+96
View File
@@ -0,0 +1,96 @@
// LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE
// all the credit goes to dktr0's webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js
// <3
class CoarseProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [{ name: 'coarse', defaultValue: 1 }];
}
constructor() {
super();
this.notStarted = true;
}
process(inputs, outputs, parameters) {
const input = inputs[0];
const output = outputs[0];
const coarse = parameters.coarse;
const blockSize = 128;
const hasInput = !(input[0] === undefined);
if (hasInput) {
this.notStarted = false;
output[0][0] = input[0][0];
for (let n = 1; n < blockSize; n++) {
if (n % coarse == 0) output[0][n] = input[0][n];
else output[0][n] = output[0][n - 1];
}
}
return this.notStarted || hasInput;
}
}
registerProcessor('coarse-processor', CoarseProcessor);
class CrushProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [{ name: 'crush', defaultValue: 0 }];
}
constructor() {
super();
this.notStarted = true;
}
process(inputs, outputs, parameters) {
const input = inputs[0];
const output = outputs[0];
const crush = parameters.crush;
const blockSize = 128;
const hasInput = !(input[0] === undefined);
if (hasInput) {
this.notStarted = false;
if (crush.length === 1) {
const x = Math.pow(2, crush[0] - 1);
for (let n = 0; n < blockSize; n++) output[0][n] = Math.round(input[0][n] * x) / x;
} else {
for (let n = 0; n < blockSize; n++) {
let x = Math.pow(2, crush[n] - 1);
output[0][n] = Math.round(input[0][n] * x) / x;
}
}
}
return this.notStarted || hasInput;
}
}
registerProcessor('crush-processor', CrushProcessor);
class ShapeProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [{ name: 'shape', defaultValue: 0 }];
}
constructor() {
super();
this.notStarted = true;
}
process(inputs, outputs, parameters) {
const input = inputs[0];
const output = outputs[0];
const shape0 = parameters.shape[0];
const shape1 = shape0 < 1 ? shape0 : 1.0 - 4e-10;
const shape = (2.0 * shape1) / (1.0 - shape1);
const blockSize = 128;
const hasInput = !(input[0] === undefined);
if (hasInput) {
this.notStarted = false;
for (let n = 0; n < blockSize; n++) {
output[0][n] = ((1 + shape) * input[0][n]) / (1 + shape * Math.abs(input[0][n]));
}
}
return this.notStarted || hasInput;
}
}
registerProcessor('shape-processor', ShapeProcessor);
+449
View File
@@ -18,6 +18,7 @@
"@vitejs/plugin-react": "^1.3.0",
"autoprefixer": "^10.4.7",
"postcss": "^8.4.13",
"rollup-plugin-visualizer": "^5.8.1",
"tailwindcss": "^3.0.24",
"vite": "^2.9.9"
}
@@ -634,6 +635,15 @@
"node": ">=0.4.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -838,6 +848,17 @@
"node": ">= 6"
}
},
"node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -908,6 +929,15 @@
}
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/defined": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
@@ -949,6 +979,12 @@
"integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==",
"dev": true
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/es5-ext": {
"version": "0.10.61",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz",
@@ -1465,6 +1501,15 @@
"node": ">=6.9.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -1531,6 +1576,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -1540,6 +1600,15 @@
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -1566,6 +1635,18 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1753,6 +1834,23 @@
"node": ">= 6"
}
},
"node_modules/open": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz",
"integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==",
"dev": true,
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
"is-wsl": "^2.2.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -1977,6 +2075,15 @@
"node": ">=8.10.0"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -2019,6 +2126,44 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rollup-plugin-visualizer": {
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.8.1.tgz",
"integrity": "sha512-NBT/xN/LWCwDM2/j5vYmjzpEAKHyclo/8Cv8AfTCwgADAG+tLJDy1vzxMw6NO0dSDjmTeRELD9UU3FwknLv0GQ==",
"dev": true,
"dependencies": {
"nanoid": "^3.3.4",
"open": "^8.4.0",
"source-map": "^0.7.3",
"yargs": "^17.5.1"
},
"bin": {
"rollup-plugin-visualizer": "dist/bin/cli.js"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"rollup": "^2.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/rollup-plugin-visualizer/node_modules/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/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -2066,6 +2211,15 @@
"semver": "bin/semver.js"
}
},
"node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -2075,6 +2229,32 @@
"node": ">=0.10.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -2281,6 +2461,56 @@
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/wrap-ansi/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -2290,6 +2520,15 @@
"node": ">=0.4"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
@@ -2306,6 +2545,33 @@
"engines": {
"node": ">= 6"
}
},
"node_modules/yargs": {
"version": "17.5.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
"integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==",
"dev": true,
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
"node": ">=12"
}
}
},
"dependencies": {
@@ -2776,6 +3042,12 @@
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
"dev": true
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -2901,6 +3173,17 @@
}
}
},
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -2957,6 +3240,12 @@
"ms": "2.1.2"
}
},
"define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true
},
"defined": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
@@ -2992,6 +3281,12 @@
"integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==",
"dev": true
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"es5-ext": {
"version": "0.10.61",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz",
@@ -3289,6 +3584,12 @@
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -3337,12 +3638,24 @@
"has": "^1.0.3"
}
},
"is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -3363,6 +3676,15 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"requires": {
"is-docker": "^2.0.0"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -3492,6 +3814,17 @@
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true
},
"open": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz",
"integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==",
"dev": true,
"requires": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
"is-wsl": "^2.2.0"
}
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -3619,6 +3952,12 @@
"picomatch": "^2.2.1"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true
},
"resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -3645,6 +3984,26 @@
"fsevents": "~2.3.2"
}
},
"rollup-plugin-visualizer": {
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.8.1.tgz",
"integrity": "sha512-NBT/xN/LWCwDM2/j5vYmjzpEAKHyclo/8Cv8AfTCwgADAG+tLJDy1vzxMw6NO0dSDjmTeRELD9UU3FwknLv0GQ==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"open": "^8.4.0",
"source-map": "^0.7.3",
"yargs": "^17.5.1"
},
"dependencies": {
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
}
}
},
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -3675,12 +4034,38 @@
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true
},
"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
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -3835,12 +4220,55 @@
"webidl-conversions": "^3.0.0"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true
},
"yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
@@ -3851,6 +4279,27 @@
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
},
"yargs": {
"version": "17.5.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
"integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==",
"dev": true,
"requires": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.0.0"
}
},
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
}
}
}
+1
View File
@@ -25,6 +25,7 @@
"@vitejs/plugin-react": "^1.3.0",
"autoprefixer": "^10.4.7",
"postcss": "^8.4.13",
"rollup-plugin-visualizer": "^5.8.1",
"tailwindcss": "^3.0.24",
"vite": "^2.9.9"
}
+14 -10
View File
@@ -7,14 +7,13 @@ This program is free software: you can redistribute it and/or modify it under th
import controls from '@strudel.cycles/core/controls.mjs';
import { evalScope, evaluate } from '@strudel.cycles/eval';
import { CodeMirror, cx, flash, useHighlighting, useRepl, useWebMidi } from '@strudel.cycles/react';
import { getDefaultSynth, cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
import { cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import './App.css';
import logo from './logo.svg';
import * as tunes from './tunes.mjs';
import { prebake } from './prebake.mjs';
import * as WebDirt from 'WebDirt';
import { loadWebDirt } from '@strudel.cycles/webdirt';
import { resetLoadedSamples, getAudioContext } from '@strudel.cycles/webaudio';
import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
@@ -37,16 +36,10 @@ evalScope(
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
import('@strudel.cycles/webdirt'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
);
loadWebDirt({
sampleMapUrl: 'EmuSP12.json',
sampleFolder: 'EmuSP12',
});
prebake();
async function initCode() {
@@ -87,7 +80,6 @@ function getRandomTune() {
}
const randomTune = getRandomTune();
const defaultSynth = getDefaultSynth();
const isEmbedded = window.location !== window.parent.location;
function App() {
// const [editor, setEditor] = useState();
@@ -112,7 +104,6 @@ function App() {
hideConsole,
} = useRepl({
tune: '// LOADING...',
defaultSynth,
});
useEffect(() => {
initCode().then((decoded) => setCode(decoded || randomTune));
@@ -214,6 +205,19 @@ function App() {
<>loading...</>
)}
</button>
<button
onClick={() => {
dirty && activateCode();
pushLog('Code updated! Tip: You can also update the code by pressing ctrl+enter.');
}}
className={cx(
'hover:bg-gray-300',
!isEmbedded ? 'p-2' : 'px-2',
!dirty || !activeCode ? 'opacity-50' : '',
)}
>
🔄 update
</button>
{!isEmbedded && (
<button
className="hover:bg-gray-300 p-2"
+6 -4
View File
@@ -1,7 +1,7 @@
import { Pattern, toMidi } from '@strudel.cycles/core';
import { samples } from '@strudel.cycles/webaudio';
export async function prebake(isMock = false) {
export async function prebake({ isMock = false, baseDir = '.' } = {}) {
samples(
{
piano: {
@@ -39,12 +39,12 @@ export async function prebake(isMock = false) {
},
// https://archive.org/details/SalamanderGrandPianoV3
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
'./piano/',
`${baseDir}/piano/`,
);
if (!isMock) {
await fetch('EmuSP12.json')
.then((res) => res.json())
.then((json) => samples(json, './EmuSP12/'));
.then((json) => samples(json, `${baseDir}/EmuSP12/`));
}
}
@@ -54,9 +54,11 @@ const panwidth = (pan, width) => pan * width + (1 - width) / 2;
Pattern.prototype.piano = function () {
return this.clip(1)
.s('piano')
.release(0.1)
.fmap((value) => {
const midi = typeof value.note === 'string' ? toMidi(value.note) : value.note;
// pan by pitch
const pan = panwidth(Math.min(toMidi(value.note) / maxPan, 1), 0.5);
const pan = panwidth(Math.min(midi / maxPan, 1), 0.5);
return { ...value, pan: (value.pan || 1) * pan };
});
};
+1 -1
View File
@@ -123,7 +123,7 @@ const uiHelpersMocked = {
backgroundImage: id,
};
prebake(true);
prebake({ isMock: true });
// TODO: refactor to evalScope
evalScope(
+3 -15
View File
@@ -6,11 +6,8 @@ This program is free software: you can redistribute it and/or modify it under th
import { Tone } from '@strudel.cycles/tone';
import { State, TimeSpan } from '@strudel.cycles/core';
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
import { evaluate } from '@strudel.cycles/eval';
import { getDefaultSynth } from '@strudel.cycles/tone';
const defaultSynth = getDefaultSynth();
import { webaudioOutputTrigger } from '@strudel.cycles/webaudio';
// this is a test to play back events with as less runtime code as possible..
// the code asks for the number of seconds to prequery
@@ -47,17 +44,8 @@ async function playStatic(code) {
events.forEach((event) => {
Tone.getTransport().schedule((time) => {
try {
const { onTrigger, velocity } = event.context;
if (!onTrigger) {
if (defaultSynth) {
const note = getPlayableNoteValue(event);
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} else {
throw new Error('no defaultSynth passed to useRepl.');
}
} else {
onTrigger(time, event);
}
const { onTrigger = webaudioOutputTrigger } = event.context;
onTrigger(time, event);
} catch (err) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
File diff suppressed because it is too large Load Diff
+164
View File
@@ -0,0 +1,164 @@
export const timeCatMini = `stack(
"c3@3 [eb3, g3, [c4 d4]/2]",
"c2 g2",
"[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2".slow(8)
)`;
export const timeCat = `stack(
timeCat([3, c3], [1, stack(eb3, g3, seq(c4, d4).slow(2))]),
seq(c2, g2),
seq(
timeCat([5, eb4], [3, seq(f4, eb4, d4)]),
seq(eb4, c4).slow(2)
).slow(4)
)`;
export const shapeShifted = `stack(
seq(
e5, [b4, c5], d5, [c5, b4],
a4, [a4, c5], e5, [d5, c5],
b4, [r, c5], d5, e5,
c5, a4, a4, r,
[r, d5], [r, f5], a5, [g5, f5],
e5, [r, c5], e5, [d5, c5],
b4, [b4, c5], d5, e5,
c5, a4, a4, r,
).rev(),
seq(
e2, e3, e2, e3, e2, e3, e2, e3,
a2, a3, a2, a3, a2, a3, a2, a3,
gs2, gs3, gs2, gs3, e2, e3, e2, e3,
a2, a3, a2, a3, a2, a3, b1, c2,
d2, d3, d2, d3, d2, d3, d2, d3,
c2, c3, c2, c3, c2, c3, c2, c3,
b1, b2, b1, b2, e2, e3, e2, e3,
a1, a2, a1, a2, a1, a2, a1, a2,
).rev()
).slow(16)`;
/* export const tetrisWithFunctions = `stack(seq(
'e5', seq('b4', 'c5'), 'd5', seq('c5', 'b4'),
'a4', seq('a4', 'c5'), 'e5', seq('d5', 'c5'),
'b4', seq(r, 'c5'), 'd5', 'e5',
'c5', 'a4', 'a4', r,
seq(r, 'd5'), seq(r, 'f5'), 'a5', seq('g5', 'f5'),
'e5', seq(r, 'c5'), 'e5', seq('d5', 'c5'),
'b4', seq('b4', 'c5'), 'd5', 'e5',
'c5', 'a4', 'a4', r),
seq(
'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3',
'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3',
'g#2', 'g#3', 'g#2', 'g#3', 'e2', 'e3', 'e2', 'e3',
'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'b1', 'c2',
'd2', 'd3', 'd2', 'd3', 'd2', 'd3', 'd2', 'd3',
'c2', 'c3', 'c2', 'c3', 'c2', 'c3', 'c2', 'c3',
'b1', 'b2', 'b1', 'b2', 'e2', 'e3', 'e2', 'e3',
'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2',
)
).slow(16)`; */
/* export const tetris = `stack(
seq(
"e5 [b4 c5] d5 [c5 b4]",
"a4 [a4 c5] e5 [d5 c5]",
"b4 [~ c5] d5 e5",
"c5 a4 a4 ~",
"[~ d5] [~ f5] a5 [g5 f5]",
"e5 [~ c5] e5 [d5 c5]",
"b4 [b4 c5] d5 e5",
"c5 a4 a4 ~"
),
seq(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
)
).slow(16)`;
*/
export const whirlyStrudel = `seq(e4, [b2, b3], c4)
.every(4, fast(2))
.every(3, slow(1.5))
.fast(cat(1.25, 1, 1.5))
.every(2, _ => seq(e4, r, e3, d4, r))`;
export const transposedChordsHacked = `stack(
"c2 eb2 g2",
"Cm7".voicings(['g2','c4']).slow(2)
).transpose(
"<1 2 3 2>".slow(2)
).transpose(5)`;
export const scaleTranspose = `"f2,f3,c4,ab4"
.scale(seq('F minor', 'F harmonic minor').slow(4))
.scaleTranspose("<0 -1 -2 -3>")
.transpose("0 1".slow(16))`;
export const struct = `stack(
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
"[C^7 A7] [Dm7 G7]".struct("[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2")
.voicings(['G3','A4'])
).slow(4)`;
export const magicSofa = `stack(
"<C^7 F^7 ~> <Dm7 G7 A7 ~>"
.every(2, fast(2))
.voicings(),
"<c2 f2 g2> <d2 g2 a2 e2>"
).transpose("<0 2 3 4>")`;
// below doesn't work anymore due to constructor cleanup
// ).slow(1).transpose.cat(0, 2, 3, 4)`;
export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]"
.superimpose(
transpose(-12).late(0),
transpose(7).late(0.1),
transpose(10).late(0.2),
transpose(12).late(0.3),
transpose(24).late(0.4)
)
.scale(cat('C dorian', 'C mixolydian'))
.scaleTranspose("<0 1 2 1>")
.slow(2)`;
export const technoDrums = `stack(
"c1*2".tone(new MembraneSynth().toDestination()),
"~ x".tone(new NoiseSynth().toDestination()),
"[~ c4]*2".tone(new MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),getDestination()))
)`;
/*
export const caverave = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out());
const kick = new MembraneSynth().chain(vol(.8), out());
const snare = new NoiseSynth().chain(vol(.8), out());
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out());
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out());
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out());
const drums = stack(
"c1*2".tone(kick).mask("<x@7 ~>/8"),
"~ <x!7 [x@3 x]>".tone(snare).mask("<x@7 ~>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).struct("[~ x]*2")
.layer(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).apply(thru).tone(keys).mask("<~ x>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).apply(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().apply(thru).every(2, early(1/8)).tone(keys).mask("<x@7 ~>/8".early(1/4))
)
stack(
drums.fast(2),
synths
).slow(2)`; */
+125 -298
View File
@@ -4,88 +4,6 @@ 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/>.
*/
export const timeCatMini = `stack(
"c3@3 [eb3, g3, [c4 d4]/2]",
"c2 g2",
"[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2".slow(8)
)`;
export const timeCat = `stack(
timeCat([3, c3], [1, stack(eb3, g3, seq(c4, d4).slow(2))]),
seq(c2, g2),
seq(
timeCat([5, eb4], [3, seq(f4, eb4, d4)]),
seq(eb4, c4).slow(2)
).slow(4)
)`;
export const shapeShifted = `stack(
seq(
e5, [b4, c5], d5, [c5, b4],
a4, [a4, c5], e5, [d5, c5],
b4, [r, c5], d5, e5,
c5, a4, a4, r,
[r, d5], [r, f5], a5, [g5, f5],
e5, [r, c5], e5, [d5, c5],
b4, [b4, c5], d5, e5,
c5, a4, a4, r,
).rev(),
seq(
e2, e3, e2, e3, e2, e3, e2, e3,
a2, a3, a2, a3, a2, a3, a2, a3,
gs2, gs3, gs2, gs3, e2, e3, e2, e3,
a2, a3, a2, a3, a2, a3, b1, c2,
d2, d3, d2, d3, d2, d3, d2, d3,
c2, c3, c2, c3, c2, c3, c2, c3,
b1, b2, b1, b2, e2, e3, e2, e3,
a1, a2, a1, a2, a1, a2, a1, a2,
).rev()
).slow(16)`;
/* export const tetrisWithFunctions = `stack(seq(
'e5', seq('b4', 'c5'), 'd5', seq('c5', 'b4'),
'a4', seq('a4', 'c5'), 'e5', seq('d5', 'c5'),
'b4', seq(r, 'c5'), 'd5', 'e5',
'c5', 'a4', 'a4', r,
seq(r, 'd5'), seq(r, 'f5'), 'a5', seq('g5', 'f5'),
'e5', seq(r, 'c5'), 'e5', seq('d5', 'c5'),
'b4', seq('b4', 'c5'), 'd5', 'e5',
'c5', 'a4', 'a4', r),
seq(
'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3',
'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3',
'g#2', 'g#3', 'g#2', 'g#3', 'e2', 'e3', 'e2', 'e3',
'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'b1', 'c2',
'd2', 'd3', 'd2', 'd3', 'd2', 'd3', 'd2', 'd3',
'c2', 'c3', 'c2', 'c3', 'c2', 'c3', 'c2', 'c3',
'b1', 'b2', 'b1', 'b2', 'e2', 'e3', 'e2', 'e3',
'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2',
)
).slow(16)`; */
/* export const tetris = `stack(
seq(
"e5 [b4 c5] d5 [c5 b4]",
"a4 [a4 c5] e5 [d5 c5]",
"b4 [~ c5] d5 e5",
"c5 a4 a4 ~",
"[~ d5] [~ f5] a5 [g5 f5]",
"e5 [~ c5] e5 [d5 c5]",
"b4 [b4 c5] d5 e5",
"c5 a4 a4 ~"
),
seq(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
)
).slow(16)`;
*/
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
@@ -104,12 +22,6 @@ export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[[a1 a2]*4]\`.slow(16)
`;
export const whirlyStrudel = `seq(e4, [b2, b3], c4)
.every(4, fast(2))
.every(3, slow(1.5))
.fast(cat(1.25, 1, 1.5))
.every(2, _ => seq(e4, r, e3, d4, r))`;
export const swimming = `stack(
seq(
"~",
@@ -222,45 +134,6 @@ export const giantStepsReggae = `stack(
.struct("x ~".fast(4*8))
).slow(25)`;
export const transposedChordsHacked = `stack(
"c2 eb2 g2",
"Cm7".voicings(['g2','c4']).slow(2)
).transpose(
"<1 2 3 2>".slow(2)
).transpose(5)`;
export const scaleTranspose = `"f2,f3,c4,ab4"
.scale(seq('F minor', 'F harmonic minor').slow(4))
.scaleTranspose("<0 -1 -2 -3>")
.transpose("0 1".slow(16))`;
export const struct = `stack(
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
"[C^7 A7] [Dm7 G7]".struct("[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2")
.voicings(['G3','A4'])
).slow(4)`;
export const magicSofa = `stack(
"<C^7 F^7 ~> <Dm7 G7 A7 ~>"
.every(2, fast(2))
.voicings(),
"<c2 f2 g2> <d2 g2 a2 e2>"
).transpose("<0 2 3 4>")`;
// below doesn't work anymore due to constructor cleanup
// ).slow(1).transpose.cat(0, 2, 3, 4)`;
export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]"
.superimpose(
transpose(-12).late(0),
transpose(7).late(0.1),
transpose(10).late(0.2),
transpose(12).late(0.3),
transpose(24).late(0.4)
)
.scale(cat('C dorian', 'C mixolydian'))
.scaleTranspose("<0 1 2 1>")
.slow(2)`;
export const zeldasRescue = `stack(
// melody
\`[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
@@ -276,79 +149,46 @@ export const zeldasRescue = `stack(
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[D2 A2] C3@2] [[C2 G2] B2@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`
).transpose(12).slow(48).tone(
new PolySynth().chain(
new Gain(0.3),
new Chorus(2, 2.5, 0.5).start(),
new Freeverb(),
getDestination())
)`;
).transpose(12).slow(48)
.superimpose(x=>x.add(0.06)) // add slightly detuned voice
.note()
.gain(.1)
.s('triangle')
.room(1)
`;
export const technoDrums = `stack(
"c1*2".tone(new MembraneSynth().toDestination()),
"~ x".tone(new NoiseSynth().toDestination()),
"[~ c4]*2".tone(new MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),getDestination()))
)`;
export const caverave = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out());
const kick = new MembraneSynth().chain(vol(.8), out());
const snare = new NoiseSynth().chain(vol(.8), out());
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out());
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out());
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out());
export const caverave = `const keys = x => x.s('sawtooth').cutoff(1200).gain(.5).attack(0).decay(.16).sustain(.3).release(.1);
const drums = stack(
"c1*2".tone(kick).mask("<x@7 ~>/8"),
"~ <x!7 [x@3 x]>".tone(snare).mask("<x@7 ~>/4"),
"[~ c4]*2".tone(hihat)
s("bd*2").mask("<x@7 ~>/8").gain(.8),
s("~ <sd!7 [sd@3 ~]>").mask("<x@7 ~>/4").gain(.5),
s("[~ hh]*2").delay(.3).delayfeedback(.5).delaytime(.125).gain(.4)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).struct("[~ x]*2")
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor'])
.slow(8)).struct("[~ x]*2")
.layer(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).apply(thru).tone(keys).mask("<~ x>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).apply(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().apply(thru).every(2, early(1/8)).tone(keys).mask("<x@7 ~>/8".early(1/4))
).apply(thru).note().apply(keys).mask("<~ x>/16"),
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru))
.struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2))
.s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.2 ~]".fast(2)).voicings()
.apply(thru).every(2, early(1/8)).note().apply(keys).sustain(0)
.delay(.4).delaytime(.12)
.mask("<x@7 ~>/8".early(1/4))
)
stack(
drums.fast(2),
synths
).slow(2)`;
export const callcenterhero = `const bpm = 90;
const lead = polysynth().set({...osc('sine4'),...adsr(.004)}).chain(vol(0.15),out())
const bass = fmsynth({...osc('sawtooth6'),...adsr(0.05,.6,0.8,0.1)}).chain(vol(0.6), out());
const s = scale(cat('F3 minor', 'Ab3 major', 'Bb3 dorian', 'C4 phrygian dominant').slow(4));
stack(
"0 2".struct("<x ~> [x ~]").apply(s).scaleTranspose(stack(0,2)).tone(lead),
"<6 7 9 7>".struct("[~ [x ~]*2]*2").apply(s).scaleTranspose("[0,2] [2,4]".fast(2).every(4,rev)).tone(lead),
"-14".struct("[~ x@0.8]*2".early(0.01)).apply(s).tone(bass),
"c2*2".tone(membrane().chain(vol(0.6), out())),
"~ c2".tone(noise().chain(vol(0.2), out())),
"c4*4".tone(metal(adsr(0,.05,0)).chain(vol(0.03), out()))
)
.slow(120 / bpm)`;
export const primalEnemy = `const f = fast("<1 <2 [4 8]>>");
stack(
"c3,g3,c4".struct("[x ~]*2").apply(f).transpose("<0 <3 [5 [7 [9 [11 13]]]]>>"),
"c2 [c2 ~]*2".tone(synth(osc('sawtooth8')).chain(vol(0.8),out())),
"c1*2".tone(membrane().chain(vol(0.8),out()))
).slow(1)`;
export const synthDrums = `stack(
"c1*2".tone(membrane().chain(vol(0.8),out())),
"~ c3".tone(noise().chain(vol(0.8),out())),
"c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.015)).chain(vol(0.8),out()))
)
`;
export const sampleDrums = `const drums = await players({
export const sampleDrums = `samples({
bd: 'bd/BT0A0D0.wav',
sn: 'sn/ST0T0S3.wav',
hh: 'hh/000_hh3closedhh.wav'
@@ -358,9 +198,11 @@ stack(
"<bd!3 bd(3,4,2)>",
"hh*4",
"~ <sn!3 sn(3,4,1)>"
).tone(drums.chain(out()))
).s()
`;
// TODO:
/*
export const xylophoneCalling = `const t = x => x.scaleTranspose("<0 2 4 3>/4").transpose(-2)
const s = x => x.scale(cat('C3 minor pentatonic','G3 minor pentatonic').slow(4))
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.1), out());
@@ -397,7 +239,10 @@ stack(
"c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.02)).chain(vol(0.5).connect(delay),out()))
).slow(1)
// strudel disable-highlighting`;
*/
// TODO:
/*
export const sowhatelse = `// mixer
const mix = (key) => vol({
chords: .2,
@@ -434,6 +279,7 @@ stack(
"[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8")
).fast(6/8)
// strudel disable-highlighting`;
*/
export const barryHarris = `backgroundImage(
'https://media.npr.org/assets/img/2017/02/03/barryharris_600dpi_wide-7eb49998aa1af377d62bb098041624c0a0d1a454.jpg',
@@ -444,18 +290,14 @@ export const barryHarris = `backgroundImage(
.scale('C bebop major')
.transpose("<0 1 2 1>/8")
.slow(2)
.note().piano().out()
.note().piano()
`;
export const blippyRhodes = `const delay = new FeedbackDelay(1/12, .4).chain(vol(0.3), out());
const drums = await players({
export const blippyRhodes = `samples({
bd: 'samples/tidal/bd/BT0A0D0.wav',
sn: 'samples/tidal/sn/ST0T0S3.wav',
hh: 'samples/tidal/hh/000_hh3closedhh.wav'
}, 'https://loophole-letters.vercel.app/')
const rhodes = await sampler({
hh: 'samples/tidal/hh/000_hh3closedhh.wav',
rhodes: {
E1: 'samples/rhodes/MK2Md2000.mp3',
E2: 'samples/rhodes/MK2Md2012.mp3',
E3: 'samples/rhodes/MK2Md2024.mp3',
@@ -463,33 +305,37 @@ const rhodes = await sampler({
E5: 'samples/rhodes/MK2Md2048.mp3',
E6: 'samples/rhodes/MK2Md2060.mp3',
E7: 'samples/rhodes/MK2Md2072.mp3'
}
}, 'https://loophole-letters.vercel.app/')
const bass = synth(osc('sawtooth8')).chain(vol(.5),out())
const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', cat('Db major','Db mixolydian')])
stack(
"<bd sn> <hh hh*2 hh*3>"
.tone(drums.chain(out())),
s("<bd sn> <hh hh*2 hh*3>"),
"<g4 c5 a4 [ab4 <eb5 f5>]>"
.scale(scales)
.struct("x*8")
.scaleTranspose("0 [-5,-2] -7 [-9,-2]")
.legato(.3)
.slow(2)
.tone(rhodes.chain(vol(0.5).connect(delay), out())),
//"<C^7 C7 F^7 [Fm7 <Db^7 Db7>]>".slow(2).voicings().struct("~ x").legato(.25).tone(rhodes),
.note()
.s('rhodes')
.clip(1)
.room(.5)
.delay(.3)
.delayfeedback(.4)
.delaytime(1/12).gain(.5),
"<c2 c3 f2 [[F2 C2] db2]>"
.legato("<1@3 [.3 1]>")
.slow(2)
.tone(bass),
.slow(2).superimpose(x=>x.add(.02))
.note().gain(.3)
.s('sawtooth').cutoff(600),
).fast(3/2)`;
export const wavyKalimba = `const delay = new FeedbackDelay(1/3, .5).chain(vol(.2), out())
let kalimba = await sampler({
C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3'
export const wavyKalimba = `samples({
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
})
kalimba = kalimba.chain(vol(0.6).connect(delay),out());
const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major'])
stack(
@@ -498,8 +344,7 @@ stack(
.scale(scales)
.struct("x*8")
.velocity("<.8 .3 .6>*8")
.slow(2)
.tone(kalimba),
.slow(2),
"<c2 c2 f2 [[F2 C2] db2]>"
.scale(scales)
.scaleTranspose("[0 <2 4>]*2")
@@ -507,11 +352,17 @@ stack(
.velocity("<.8 .5>*4")
.velocity(0.8)
.slow(2)
.tone(kalimba)
)
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
.fast(1)`;
.fast(1)
.note()
.clip(1)
.s('kalimba')
.delay(.2)
`;
// TODO: rework tune to use freq
/*
export const jemblung = `const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out());
const snare = noise({type:'white',...adsr(0,0.2,0)}).chain(lowpass(5000),vol(1.8),out());
const s = polysynth().set({...osc('sawtooth4'),...adsr(0.01,.2,.6,0.2)}).chain(vol(.23).connect(delay),out());
@@ -538,20 +389,7 @@ stack(
// hihat
"c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())),
).slow(3)`;
export const risingEnemy = `stack(
"2,6"
.scale('F3 dorian')
.transpose(sine2.struct("x*64").slow(4).mul(2).round())
.fast(2)
.struct("x x*3")
.legato(".9 .3"),
"0@3 -3*3".legato(".95@3 .4").scale('F2 dorian')
)
.transpose("<0 1 2 1>/2".early(0.5))
.transpose(5)
.fast(2 / 3)
.note().piano().out()`;
*/
export const festivalOfFingers = `const chords = "<Cm7 Fm7 G7 F#7>";
stack(
@@ -563,21 +401,7 @@ stack(
.scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4"))
).slow(2)
.velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8))
.note().piano().out()`;
export const festivalOfFingers2 = `const chords = "<Cm7 Fm7 G7 F#7 >";
const scales = cat('C minor','F dorian','G dorian','F# mixolydian')
stack(
chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
chords.rootNotes(2).struct("x(4,8)"),
chords.rootNotes(4)
.scale(scales)
.struct("x(3,8,-2)".fast(2))
.scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/3"))
).slow(2).transpose(-1)
.legato(cosine.struct("x*8").add(4/5).mul(4/5).fast(8))
.velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8))
.note().piano().out().fast(3/4)`;
.note().piano()`;
// iter, echo, echoWith
export const undergroundPlumber = `backgroundImage('https://images.nintendolife.com/news/2016/08/video_exploring_the_funky_inspiration_for_the_super_mario_bros_underground_theme/large.jpg',{ className:'darken' })
@@ -601,7 +425,7 @@ stack(
.echoWith(4, 1/8, (x,n)=>x.transpose(n*12).velocity(Math.pow(.4,n)))
.legato(.1)
.layer(h).note()
).out()
)
.fast(2/3)
.pianoroll({})`;
@@ -620,7 +444,7 @@ stack(
).transpose(-1).note().piano(),
s("mad").slow(2)
).cpm(78).slow(4)
.out()
.pianoroll()
`;
@@ -639,7 +463,7 @@ stack(
.scale(scale)
.scaleTranspose("<0>".slow(4))
.transpose(5)
.note().piano().out()
.note().piano()
.velocity(.8)
.slow(2)
.pianoroll({maxMidi:100,minMidi:20})`;
@@ -651,7 +475,7 @@ export const echoPiano = `"<0 2 [4 6](3,4,1) 3*2>"
.off(1/2, x=>x.scaleTranspose(6).color('steelblue'))
.legato(.5)
.echo(4, 1/8, .5)
.note().piano().out()
.note().piano()
.pianoroll()`;
export const sml1 = `
@@ -687,8 +511,10 @@ stack(
f3!2 e3!2 ab3!2 ~!2
>\`
.legato(.5)
).fast(2) // .note().piano().out()`;
).fast(2) // .note().piano()`;
/*
// TODO: does not work on linux (at least for me..)
export const speakerman = `backgroundImage('https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.ytimg.com%2Fvi%2FXR0rKqW3VwY%2Fmaxresdefault.jpg&f=1&nofb=1',
{ className:'darken', style:'background-size:cover'})
stack(
@@ -708,48 +534,29 @@ stack(
'got to keep on running',
).speak("en zu en".slow(12), "<0 2 3 4 5 6>".slow(2)),
).slow(4)`;
*/
export const randomBells = `const delay = new FeedbackDelay(1/3, .8).chain(vol(.2), out());
let bell = await sampler({
C6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3'
export const randomBells = `samples({
bell: { c6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3' },
bass: { d2: 'https://freesound.org/data/previews/608/608286_13074022-lq.mp3' }
})
const bass = await sampler({
d2: 'https://freesound.org/data/previews/608/608286_13074022-lq.mp3'
});
bell = bell.chain(vol(0.6).connect(delay),out());
"0".euclidLegato(3,8)
stack(
// bells
"0".euclidLegato(3,8)
.echo(3, 1/16, .5)
.add(rand.range(0,12))
.velocity(rand.range(.5,1))
.legato(rand.range(.4,3))
.scale(cat('D minor pentatonic')).tone(bell)
.stack("<D2 A2 G2 F2>".euclidLegato(6,8,1).tone(bass.toDestination()))
.slow(6)
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`;
/* export const waa = `n("a4 [a3 c3] a3 c3")
.sub("<7 12>/2")
.off(1/8, add("12"))
.off(1/4, add("7"))
.legato(.5)
.slow(2)
.wave("sawtooth square")
.filter('lowpass', "<2000 1000 500>")
.out()`; */
export const waa = `n(
"a4 [a3 c3] a3 c3"
.sub("<7 12>/2")
.off(1/8, add("12"))
.off(1/4, add("7"))
.scale(cat('D minor pentatonic')).note()
.s('bell').gain(.6).delay(.2).delaytime(1/3).delayfeedback(.8),
// bass
"<D2 A2 G2 F2>".euclidLegato(6,8,1).note().s('bass').clip(1).gain(.8)
)
.legato(.5)
.slow(2)
.s("sawtooth square")
.cutoff("<2000 1000 500>")
.out()
`;
.slow(6)
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})
`;
export const waa2 = `n(
"a4 [a3 c3] a3 c3"
@@ -761,7 +568,9 @@ export const waa2 = `n(
.legato(sine.range(0.3, 2).slow(28))
.s("sawtooth square".fast(2))
.cutoff(cosine.range(500,4000).slow(16))
.out()`;
.gain(.5)
.room(.5)
`;
export const hyperpop = `const lfo = cosine.slow(15);
const lfo2 = sine.slow(16);
@@ -769,7 +578,7 @@ const filter1 = x=>x.cutoff(lfo2.range(300,3000));
const filter2 = x=>x.hcutoff(lfo.range(1000,6000)).cutoff(4000)
const scales = cat('D3 major', 'G3 major').slow(8)
const drums = await players({
samples({
bd: '344/344757_1676145-lq.mp3',
sn: '387/387186_7255534-lq.mp3',
hh: '561/561241_12517458-lq.mp3',
@@ -803,15 +612,13 @@ stack(
.gain(sine.range(.1,.4).slow(8))
.attack(.001).decay(.2).sustain(0)
.apply(filter2)
).out().stack(
).stack(
stack(
"bd <~@7 [~ bd]>".fast(2),
"~ sn",
"[~ hh3]*2"
).tone(drums.chain(vol(.18),out())).fast(2)
).s().fast(2).gain(.7)
).slow(2)
//.pianoroll({minMidi:20, maxMidi:160})
// strudel disable-highlighting`;
export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
@@ -827,7 +634,7 @@ export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
.scale(cat('D dorian','G mixolydian','C dorian','F mixolydian'))
.legato(1)
.slow(2)
.note().piano().out()
.note().piano()
//.pianoroll({maxMidi:160})`;
export const bossa = `
@@ -835,8 +642,8 @@ const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C
stack(
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
).note().piano().out().slow(2)`;
).note().piano().slow(2)`;
/*
export const customTrigger = `stack(
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)").layer(x=>x.mul("1.006,.995"))),
freq("440(5,8)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
@@ -854,17 +661,7 @@ export const customTrigger = `stack(
o.connect(master);
o.start(t);
o.stop(t + hap.duration);
}).stack(s("bd(3,8),hh*4,~ sd").webdirt())`;
export const bornagain = `stack(
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)")
.layer(x=>x.mul("1.006,.995"))), // detune
freq("440(5,8)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
).s("<sawtooth square>/2")
.cutoff(perlin.range(100,4000).slow(4))
.jux(rev)
.out()
.stack(s("bd(3,8),hh*4,~ sd").webdirt())`;
}).stack(s("bd(3,8),hh*4,~ sd").webdirt())`; */
export const meltingsubmarine = `samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
@@ -904,7 +701,7 @@ stack(
.echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes
//.hush()
)
.out()
.slow(3/2)`;
export const swimmingWithSoundfonts = `stack(
@@ -966,7 +763,7 @@ export const swimmingWithSoundfonts = `stack(
"[G2 C2 F2 F2]"
).s('Acoustic Bass: Bass')
).slow(51)
.out()`;
`;
export const outroMusic = `samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
@@ -993,7 +790,7 @@ export const outroMusic = `samples({
.n(3).color('gray')
).slow(3/2)
//.pianoroll({autorange:1,vertical:1,fold:0})
.out()`;
`;
export const bassFuge = `samples({ flbass: ['00_c2_finger_long_neck.wav','01_c2_finger_short_neck.wav','02_c2_finger_long_bridge.wav','03_c2_finger_short_bridge.wav','04_c2_pick_long.wav','05_c2_pick_short.wav','06_c2_palm_mute.wav'] },
'github:cleary/samples-flbass/main/')
@@ -1017,7 +814,7 @@ x=>x.add(7).color('steelblue')
//.hcutoff(400)
.clip(1)
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
.out()
.pianoroll({vertical:1})`;
export const bossaRandom = `const chords = "<Am7 Am7 Dm7 E7>"
@@ -1029,4 +826,34 @@ stack(
x? ~ ~ x@3 ~ x |
x? ~ ~ x ~ x@3\`),
roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7")
).slow(2).pianoroll().note().piano().out();`;
).slow(2).pianoroll().note().piano()`;
export const chop = `samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
s("p")
.loopAt(32,1)
.chop(128)
.jux(rev)
.shape(.4)
.decay(.1)
.sustain(.6)
`;
export const delay = `stack(
s("bd <sd cp>")
.delay("<0 .5>")
.delaytime(".16 | .33")
.delayfeedback(".6 | .8")
).sometimes(x=>x.speed("-1"))`;
export const orbit = `stack(
s("bd <sd cp>")
.delay(.5)
.delaytime(.33)
.delayfeedback(.6),
s("hh*2")
.delay(.8)
.delaytime(.08)
.delayfeedback(.7)
.orbit(2)
).sometimes(x=>x.speed("-1"))`;
+5
View File
@@ -1,10 +1,15 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
outDir: '../out',
sourcemap: false,
rollupOptions: {
plugins: [visualizer({ template: 'treemap' })],
},
},
});
+7 -16
View File
@@ -1,19 +1,14 @@
import { Tone } from '@strudel.cycles/tone';
import { evalScope } from '@strudel.cycles/eval';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import controls from '@strudel.cycles/core/controls.mjs';
import * as WebDirt from 'WebDirt';
import { loadWebDirt } from '@strudel.cycles/webdirt';
import { samples } from '@strudel.cycles/webaudio';
import { prebake } from '../repl/src/prebake.mjs';
export const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
oscillator: { type: 'triangle' },
envelope: {
release: 0.01,
},
});
fetch('https://strudel.tidalcycles.org/EmuSP12.json')
.then((res) => res.json())
.then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/'));
evalScope(
Tone,
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/tone'),
@@ -23,14 +18,10 @@ evalScope(
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
import('@strudel.cycles/webdirt'),
);
loadWebDirt({
sampleMapUrl: '../EmuSP12.json',
sampleFolder: '../EmuSP12',
});
prebake();
export function MiniRepl({ tune }) {
return <_MiniRepl tune={tune} defaultSynth={defaultSynth} hideOutsideView={true} />;
return <_MiniRepl tune={tune} hideOutsideView={true} />;
}
+5 -6
View File
@@ -13,9 +13,9 @@ import '@strudel.cycles/react/dist/style.css';
ReactDOM.render(
<React.StrictMode>
<div className="min-h-screen">
<header className="flex-none flex justify-start sticky top-0 z-[2] w-full h-16 px-2 items-center border-b border-gray-200 bg-white">
<div className="p-4 w-full flex justify-between">
<div className="min-h-screen bg-slate-900">
<header className="flex-none flex justify-start sticky top-0 z-[2] w-full h-16 px-2 items-center border-b border-slate-500 text-white bg-slate-900 z-[100]">
<div className="p-4 w-full flex justify-between items-center">
<div className="flex items-center space-x-2">
<img src={'https://tidalcycles.org/img/logo.svg'} className="Tidal-logo w-10 h-10" alt="logo" />
<h1 className="text-xl cursor-pointer" onClick={() => window.scrollTo(0, 0)}>
@@ -23,15 +23,14 @@ ReactDOM.render(
</h1>
</div>
{!window.location.href.includes('localhost') && (
<div className="flex space-x-4">
<div className="flex space-x-4 text-slate-200">
<a href="../">go to REPL</a>
</div>
)}
</div>
</header>
<main className="p-4 pl-6 max-w-3xl prose">
<main className="p-4 pl-6 max-w-3xl prose prose-invert">
<Tutorial />
{/* <ApiDoc /> */}
</main>
</div>
</React.StrictMode>,
+152
View File
@@ -0,0 +1,152 @@
# Old APIs
These APIs are outdated and might break in the future.
## Webdirt API (deprecated)
You can use the powerful sampling engine [Webdirt](https://github.com/dktr0/WebDirt) with Strudel.
{{ 'Pattern.webdirt' | jsdoc }}
<br />
<br />
## Tone API (deprecated)
The Tone API uses Tone.js instruments ands effects to create sounds.
<MiniRepl
tune={`stack(
"[c5 c5 bb4 c5] [~ g4 ~ g4] [c5 f5 e5 c5] ~"
.tone(synth(adsr(0,.1,0,0)).chain(out())),
"[c2 c3]*8"
.tone(synth({
...osc('sawtooth'),
...adsr(0,.1,0.4,0)
}).chain(lowpass(300), out()))
).slow(4)`}
/>
### tone(instrument)
To change the instrument of a pattern, you can pass any [Tone.js Source](https://tonejs.github.io/docs/14.7.77/index.html) to .tone:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(new FMSynth().toDestination())`}
/>
While this works, it is a little bit verbose. To simplify things, all Tone Synths have a shortcut:
```js
const amsynth = (options) => new AMSynth(options);
const duosynth = (options) => new DuoSynth(options);
const fmsynth = (options) => new FMSynth(options);
const membrane = (options) => new MembraneSynth(options);
const metal = (options) => new MetalSynth(options);
const monosynth = (options) => new MonoSynth(options);
const noise = (options) => new NoiseSynth(options);
const pluck = (options) => new PluckSynth(options);
const polysynth = (options) => new PolySynth(options);
const synth = (options) => new Synth(options);
const sampler = (options, baseUrl?) => new Sampler(options); // promisified, see below
const players = (options, baseUrl?) => new Sampler(options); // promisified, see below
```
### sampler
With sampler, you can create tonal instruments from samples:
<MiniRepl
tune={`sampler({
C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3'
}).then(kalimba =>
saw.struct("x*8").mul(16).round()
.legato(4).scale('D dorian').slow(2)
.tone(kalimba.toDestination())
)`}
/>
The sampler function promisifies [Tone.js Sampler](https://tonejs.github.io/docs/14.7.77/Sampler).
Note that this function currently only works with this promise notation, but in the future,
it will be possible to use async instruments in a synchronous fashion.
### players
With players, you can create sound banks:
<MiniRepl
tune={`players({
bd: 'samples/tidal/bd/BT0A0D0.wav',
sn: 'samples/tidal/sn/ST0T0S3.wav',
hh: 'samples/tidal/hh/000_hh3closedhh.wav'
}, 'https://loophole-letters.vercel.app/')
.then(drums=>
"bd hh sn hh".tone(drums.toDestination())
)
`}
/>
The sampler function promisifies [Tone.js Players](https://tonejs.github.io/docs/14.7.77/Players).
Note that this function currently only works with this promise notation, but in the future,
it will be possible to use async instruments in a synchronous fashion.
### out
Shortcut for Tone.Destination. Intended to be used with Tone's .chain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(membrane().chain(out()))`}
/>
This alone is not really useful, so read on..
### vol(volume)
Helper that returns a Gain Node with the given volume. Intended to be used with Tone's .chain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(noise().chain(vol(0.5), out()))`}
/>
### osc(type)
Helper to set the waveform of a synth, monosynth or polysynth:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(synth(osc('sawtooth4')).chain(out()))`}
/>
The base types are `sine`, `square`, `sawtooth`, `triangle`. You can also append a number between 1 and 32 to reduce the harmonic partials.
### lowpass(cutoff)
Helper that returns a Filter Node of type lowpass with the given cutoff. Intended to be used with Tone's .chain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(synth(osc('sawtooth')).chain(lowpass(800), out()))`}
/>
### highpass(cutoff)
Helper that returns a Filter Node of type highpass with the given cutoff. Intended to be used with Tone's .chain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(synth(osc('sawtooth')).chain(highpass(2000), out()))`}
/>
### adsr
Helper to set the envelope of a Tone.js instrument. Intended to be used with Tone's .set:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(synth(adsr(0,.1,0,0)).chain(out()))`}
/>
+1 -4
View File
@@ -23,8 +23,6 @@ ${item.description.replaceAll(/\{\@link ([a-zA-Z]+)?\#?([a-zA-Z]*)\}/g, (_, a, b
return `<a href="#${a}${b ? `-${b}` : ''}">${a}${b ? `#${b}` : ''}</a>`;
})}
${!!item.params?.length ? '**Parameters**' : ''}
${
item.params
?.map(
@@ -36,8 +34,7 @@ ${
${
item.examples?.length
? `**Examples**
? `
<div className="space-y-2">
${item.examples?.map((example, k) => `<MiniRepl tune={\`${example}\`} />`).join('\n\n')}
</div>`
+509 -571
View File
File diff suppressed because it is too large Load Diff