Compare commits

...

129 Commits

Author SHA1 Message Date
Felix Roos f46cf02819 poc: fraction shorthands 2023-11-07 08:40:21 +01:00
Felix Roos 682ac25111 Merge pull request #791 from tidalcycles/fix-ref-anchors
don't use anchor links for reference
2023-11-06 23:17:31 +01:00
Felix Roos 07da9a9dd2 don't use anchor links for reference 2023-11-06 23:13:10 +01:00
Felix Roos 395ae4d05a Merge pull request #788 from tidalcycles/github-shortcut
samples loading shortcuts:
2023-11-06 22:40:43 +01:00
Felix Roos 2ef35a8583 samples loading shortcuts:
- use main branch by default in github shortcut
- add bubo shortcut
2023-11-06 22:17:33 +01:00
Felix Roos 5807dbfdac Merge pull request #786 from tidalcycles/fix-scope-flip
Fix scope pos + document
2023-11-05 22:47:48 +01:00
Felix Roos c4f26c3cad snapshot 2023-11-05 22:46:16 +01:00
Felix Roos 26d87b691c document scope functions 2023-11-05 22:43:37 +01:00
Felix Roos 52e2b90eef fix: scope pos 2023-11-05 22:22:35 +01:00
Felix Roos 62eb9ce598 Merge pull request #783 from ilesinge/hover_tooltip
Implement optional hover tooltip with function documentation
2023-11-05 22:10:25 +01:00
Felix Roos a05d3a47c6 Merge pull request #785 from ilesinge/reference_parameters
Add function params in reference tab
2023-11-05 16:46:06 +01:00
Alexandre G.-Raymond e9265eff35 Add function params in reference tab 2023-11-05 16:40:46 +01:00
Alexandre G.-Raymond 353333653f Implement optional hover tooltip with function documentation 2023-11-05 13:00:54 +01:00
Felix Roos 83fec57400 Merge pull request #781 from tidalcycles/fix-styles
fix: style issues
2023-11-05 12:42:00 +01:00
Felix Roos 7ad0ec1600 fix: namespace mini-repl styles 2023-11-05 12:35:49 +01:00
Felix Roos ec611aac45 Merge pull request #780 from tidalcycles/xfade
add xfade
2023-11-05 12:21:28 +01:00
Felix Roos d7752961b8 snapshot 2023-11-05 12:12:25 +01:00
Felix Roos 8c5837e78a add xfade + docs 2023-11-05 12:11:17 +01:00
Felix Roos 0ecc6c196f Merge pull request #775 from tidalcycles/astro-3
Update to Astro 3
2023-11-02 09:30:26 +01:00
Felix Roos 77686547eb no double ! 2023-11-02 09:14:45 +01:00
Felix Roos 419cf6c12b update to astro 3 2023-11-02 09:13:29 +01:00
Felix Roos 326571b797 remove obsolete config 2023-11-02 09:01:53 +01:00
Felix Roos c25c2693f5 Merge branch 'main' into astro-3 2023-11-02 09:00:42 +01:00
Felix Roos 8d8a1f552d Merge pull request #773 from Dsm0/vscode-bindings
add vscode bindings
2023-11-02 08:57:14 +01:00
Felix Roos 638f5c7323 rename bindings key 2023-11-02 08:48:11 +01:00
Felix Roos 9ec5d33840 Merge pull request #774 from tidalcycles/tauri-clipboard
fix: share copy to clipboard + alert
2023-11-02 08:31:30 +01:00
Felix Roos 8d3ba2a8cd fix: share copy to clipboard + alert 2023-11-02 08:29:27 +01:00
Dsm0 0c2838cc90 fixed style issues 2023-11-01 17:55:40 -04:00
Felix Roos 1e5edfca26 Update CONTRIBUTING.md
fix prettier instructions
2023-11-01 22:40:23 +01:00
Dsm0 b141e1ee68 (hopefully) fixed broken lock file 2023-11-01 17:30:59 -04:00
Will 2fd4836228 Merge branch 'main' into vscode-bindings 2023-11-01 17:24:05 -04:00
Alex McLean 9ceab0f294 Fix chunk, add fastChunk and repeatCycles (#712)
* Match behaviour of chunk with tidal, add fastChunk for old strudel behaviour. Also adds repeatCycles. fixes #689

* documentation
2023-11-01 21:22:33 +00:00
Dsm0 d4c12dcef4 integrated vscode bindings 2023-11-01 17:14:22 -04:00
Felix Roos 16bb920011 Merge pull request #767 from ilesinge/document_adsr
Document adsr function
2023-11-01 22:12:48 +01:00
Felix Roos 158fa85c46 Merge pull request #771 from daslyfe/add_pick_and_squeeze_functions
Add pick and squeeze functions
2023-11-01 22:11:48 +01:00
Felix Roos 34bd0e11e4 add doc chapters 2023-11-01 22:09:40 +01:00
Felix Roos 9e649a18c4 snapshots 2023-11-01 22:07:19 +01:00
Felix Roos 2bb45afe5f Merge pull request #772 from tidalcycles/update-vite-pwa
Update vite pwa
2023-11-01 22:04:00 +01:00
Felix Roos 41a9098c80 use new flag 2023-11-01 21:59:53 +01:00
Felix Roos 36ef03a406 use 0.1.4 @vite-pwa/astro 2023-11-01 21:57:14 +01:00
Jade Rowland fd6c713119 move to signal file with other choose functions 2023-10-31 12:18:19 -04:00
Jade Rowland 4a57d3ada1 functions 2023-10-31 11:58:11 -04:00
Felix Roos f16cb8f7d2 Merge pull request #768 from tidalcycles/strudel-cc
replace strudel.tidalcycles.org with strudel.cc
2023-10-28 23:55:07 +02:00
Felix Roos af915be142 replace strudel.tidalcycles.org with strudel.cc 2023-10-28 23:49:30 +02:00
Alexandre G.-Raymond 2633f4fcef Document adsr function 2023-10-28 19:50:41 +02:00
Felix Roos 6c2330984e Merge pull request #766 from ilesinge/document_striate
Document striate function
2023-10-28 12:52:36 +02:00
Alexandre G.-Raymond 1e1d5ef442 Document striate function 2023-10-28 11:47:37 +02:00
Felix Roos 248757ac3f Merge pull request #760 from tidalcycles/zen-fix
fix zen mode logo overlap
2023-10-27 23:06:20 +02:00
Felix Roos cdbfb2c08c Merge pull request #764 from tidalcycles/fix-scale-offset2
fix: scale offset
2023-10-27 23:01:18 +02:00
Felix Roos d84c917f19 fix: scale offset 2023-10-27 22:50:21 +02:00
Felix Roos 1c7c82358d Merge pull request #762 from tidalcycles/fix-addivite-synthesis-phases
Fix addivite synthesis phases
2023-10-27 21:59:35 +02:00
Felix Roos c085694a8b fix: additive synthesis 2023-10-27 21:55:49 +02:00
Felix Roos a57c2b9fe5 fix: flip scope in y direction 2023-10-27 21:55:25 +02:00
Felix Roos 9fc87fe20f add px in zen mode to fix logo overlap 2023-10-27 21:29:31 +02:00
Felix Roos 4761e25fe1 Merge pull request #759 from tidalcycles/hydra-integration
Hydra integration
2023-10-26 16:28:16 +02:00
Felix Roos 3ce50d80de ignore hydra.mjs in linter 2023-10-26 16:20:19 +02:00
Felix Roos 8754b023d5 document H function 2023-10-26 16:11:22 +02:00
Felix Roos 22c9829fea add H function to plug patterns into Hydra 2023-10-26 16:05:10 +02:00
Felix Roos 0f7b3e9f84 add docs + fix hydra for mini repl 2023-10-26 16:01:51 +02:00
Felix Roos 1a456828df Merge branch 'main' into hydra-integration 2023-10-26 14:14:49 +02:00
Felix Roos 5958c69d64 Merge pull request #758 from tidalcycles/play-function
add play function
2023-10-26 14:14:26 +02:00
Felix Roos b14aa901ba also return pat 2023-10-26 13:16:15 +02:00
Felix Roos 535dfb5099 Merge pull request #740 from ilesinge/shabda_shortcut
Add shabda shortcut
2023-10-26 13:11:00 +02:00
Felix Roos 19f1aad88c Merge pull request #751 from ilesinge/alphabet_samples
mini notation: international alphabets support
2023-10-26 13:07:23 +02:00
Felix Roos 9009eccc8e add play function 2023-10-26 13:06:58 +02:00
Felix Roos 7f3cdedef9 basic hydra integration 2023-10-26 13:05:54 +02:00
Felix Roos 05095805f1 Merge pull request #753 from tidalcycles/fix-trailing-slash-bug
hopefully fix trainling slashes bug
2023-10-22 23:00:52 +02:00
Felix Roos 5430147572 use new vite-pwa-astro version from file 2023-10-22 22:53:50 +02:00
Alexandre G.-Raymond 9b9f031fe1 Revert error message length limitation 2023-10-21 23:41:56 +02:00
Felix Roos 2ad63247c2 Merge pull request #748 from ilesinge/fix_krill_readme
Fix krill build command in README
2023-10-21 23:38:50 +02:00
Alexandre G.-Raymond 87f37e3d87 Add a human-readable error message for invalid char in step 2023-10-21 23:20:04 +02:00
Alexandre G.-Raymond cb8edd9b8f Support international alphabets in mininotation 2023-10-21 22:51:20 +02:00
Alexandre G.-Raymond 072ae7280e Fix krill build command in README 2023-10-21 22:26:12 +02:00
Felix Roos e99c299c6f Merge pull request #745 from tidalcycles/fix-slash-confusion
completely revert config mess
2023-10-21 00:20:49 +02:00
Felix Roos 46cdbd514f completely revert config mess 2023-10-21 00:18:41 +02:00
Felix Roos a3e0189f0b Merge pull request #744 from tidalcycles/fix-slash-confusion
fix: try different trailing slash behavior
2023-10-20 22:41:39 +02:00
Felix Roos 8f4344f413 fix: try different trailing slash behavior 2023-10-20 22:38:46 +02:00
Felix Roos 7e28de1d24 Merge pull request #743 from tidalcycles/fix-slash-confusion
fix: trailing slash confusion
2023-10-20 22:34:03 +02:00
Felix Roos 624e685212 fix: trailing slash confusion 2023-10-20 22:32:12 +02:00
Felix Roos a23765a1cb fix: trailing slash confusion 2023-10-20 22:30:54 +02:00
Felix Roos 048502ecd1 Merge branch 'main' into astro-3 2023-10-20 22:08:21 +02:00
Alexandre G.-Raymond 1b072448c4 Add test snapshots for shabda shortcut 2023-10-20 21:59:54 +02:00
Felix Roos 86997488fb Merge pull request #741 from daslyfe/bugfix_choose_with
[Bug Fix] chooseWith: prevent pattern from stopping audio when selection is >= 1 or < 0
2023-10-20 12:07:03 +02:00
Felix Roos db6efec417 Merge pull request #742 from tidalcycles/recipes
Recipes
2023-10-20 11:34:26 +02:00
Felix Roos 2cc60c6ee2 format 2023-10-20 11:28:35 +02:00
Felix Roos 80c98b6a35 fix: loud example 2023-10-20 11:23:38 +02:00
Felix Roos f51c01dc88 move stuff around 2023-10-20 11:18:54 +02:00
Felix Roos 7b8a35a311 add understand cycles 2023-10-20 11:18:47 +02:00
Felix Roos d9db5263e8 add recipes 2023-10-20 11:18:40 +02:00
Jade Rowland 912a661c04 fixed example test failure 2023-10-17 23:03:14 -04:00
Jade Rowland d3edc62fc7 document function 2023-10-17 21:31:11 -04:00
Jade Rowland 6b5de5fec8 fixed 2023-10-17 01:33:51 -04:00
Alexandre G.-Raymond fed4c51aa4 Prettier doc file 2023-10-14 16:30:29 +02:00
Alexandre G.-Raymond 1721209f85 Update samples/shabda documentation 2023-10-14 16:20:53 +02:00
Alexandre G.-Raymond 289820188e Add shabda shortcut (#406) 2023-10-14 16:20:17 +02:00
Felix Roos 4fa0a9064b Merge branch 'main' of https://github.com/tidalcycles/strudel 2023-10-14 10:08:53 +02:00
Felix Roos f751d97c29 Merge pull request #737 from tidalcycles/readme-fix
vite-vanilla-repl readme fix
2023-10-13 12:57:24 +02:00
Felix Roos d7028252c2 vite-vanilla-repl readme fix 2023-10-13 12:55:31 +02:00
Felix Roos a2ee70a964 Merge pull request #717 from vasilymilovidov/reverb
Add support for using samples as impulse response buffers for the reverb
2023-10-10 00:17:59 +02:00
Felix Roos 57b166d13a fix: pitched sample as ir 2023-10-09 22:49:38 +02:00
Felix Roos 5c76f2b9a2 bump superdough to 0.9.10 2023-10-09 22:30:34 +02:00
Felix Roos e59f06ff92 Merge branch 'main' into reverb 2023-10-09 22:15:25 +02:00
Felix Roos d3b96dd048 Merge pull request #732 from tidalcycles/reverb-fix-sr
fix: reverb sampleRate
2023-10-09 21:43:42 +02:00
Felix Roos 6544e8135c fix: reverb sampleRate 2023-10-09 21:40:38 +02:00
Felix Roos 54ed415d75 Merge pull request #731 from tidalcycles/reverb-fix
fix: reverb roomsize not required
2023-10-09 21:34:33 +02:00
Felix Roos f34937d4ae fix: roomsize not required 2023-10-09 21:31:49 +02:00
Felix Roos a4fc40875b Merge pull request #729 from tidalcycles/compressor
Compressor
2023-10-08 13:54:51 +02:00
Felix Roos 56c1441494 snapshot 2023-10-08 13:38:18 +02:00
Felix Roos 08ce8213fc format 2023-10-08 13:37:56 +02:00
Felix Roos 0a3692e459 compressor docs 2023-10-08 13:37:39 +02:00
Felix Roos 341546508f Merge pull request #728 from tidalcycles/fix-sample-url-special-chars
fix: hashes in urls
2023-10-08 13:25:57 +02:00
Felix Roos 444d583a49 fix: hashes in urls 2023-10-08 13:23:13 +02:00
Felix Roos 130ad4451b add compressor + postgain 2023-10-08 00:30:12 +02:00
vmilovidov d24cf8e7ec Merge branch 'main' into reverb 2023-10-07 19:50:05 +04:00
Vasilii Milovidov 2d750a14b9 fix: conflicts 2023-10-07 19:48:07 +04:00
Vasilii Milovidov b25b9d815b fix: conflicts 2023-10-07 19:30:07 +04:00
Felix Roos daf965f119 Merge pull request #727 from tidalcycles/fix-n-scale
consume n with scale
2023-10-07 15:48:06 +02:00
Felix Roos 296cd83a55 snapshot 2023-10-07 15:45:44 +02:00
Felix Roos b297888eb4 consume n with scale 2023-10-07 15:40:08 +02:00
Felix Roos e2569ed6dd Merge pull request #726 from tidalcycles/reverb-perf-pitfall
fix: reverb regenerate loophole
2023-10-07 00:27:21 +02:00
Felix Roos 4718bfac25 fix: reverb regenerate loophole 2023-10-07 00:22:55 +02:00
Vasilii Milovidov 2f0798de32 fix: formatting 2023-10-05 13:17:17 +04:00
vmilovidov 11b0a00593 Merge branch 'main' into reverb 2023-10-05 12:16:55 +04:00
Vasilii Milovidov 1d4eb59e94 fix: reverbGen.mjs 2023-10-05 04:10:35 +04:00
Vasilii Milovidov d6c8838fe2 Integrate with the new impulse generation functionality 2023-10-05 03:49:50 +04:00
Vasilii Milovidov 13cb32903a Prepare to merge with PR #718 2023-10-02 12:13:44 +04:00
Vasilii Milovidov ac9c629c0b cleanup 2023-10-01 15:36:52 +04:00
Vasilii Milovidov 755e24315e use sample as an impulse response for the reverb 2023-10-01 15:31:10 +04:00
Vasilii Milovidov fd18508266 wip: use sample as an impulse response for the reverb 2023-10-01 05:15:47 +04:00
89 changed files with 5768 additions and 3454 deletions
+2 -1
View File
@@ -19,4 +19,5 @@ vite.config.js
**/dev-dist
**/dist
/src-tauri/target/**/*
reverbGen.mjs
reverbGen.mjs
hydra.mjs
+3 -3
View File
@@ -13,7 +13,7 @@ To get in touch with the contributors, either
## Ask a Question
If you have any questions about strudel, make sure you've glanced through the
[docs](https://strudel.tidalcycles.org/learn/) to find out if it answers your question.
[docs](https://strudel.cc/learn/) to find out if it answers your question.
If not, use one of the Communication Channels above!
Don't be afraid to ask! Your question might be of great value for other people too.
@@ -31,7 +31,7 @@ Use one of the Communication Channels listed above.
## Improve the Docs
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/workshop/getting-started/),
If you find some weak spots in the [docs](https://strudel.cc/workshop/getting-started/),
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
## Propose a Feature
@@ -83,7 +83,7 @@ Please report any problems you've had with the setup instructions!
To make sure the code changes only where it should, we are using prettier to unify the code style.
- You can format all files at once by running `pnpm prettier` from the project root
- You can format all files at once by running `pnpm codeformat` from the project root
- Run `pnpm format-check` from the project root to check if all files are well formatted
If you use VSCode, you can
+2 -2
View File
@@ -4,8 +4,8 @@
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is slowly stabilising, but please continue to tread carefully.
- Try it here: <https://strudel.tidalcycles.org/>
- Docs: <https://strudel.tidalcycles.org/learn/>
- Try it here: <https://strudel.cc>
- Docs: <https://strudel.cc/learn>
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
+1 -1
View File
@@ -47,7 +47,7 @@ If you want to automatically deploy your site on push, go to `deploy.yml` and ch
## running locally
- install dependencies with `npm run setup`
- run dev server with `npm run repl` and open `http://localhost:3000/strudel/swatch/`
- run dev server with `npm run repl` and open `http://localhost:4321/strudel/swatch/`
## tests fail?
+1 -1
View File
@@ -43,7 +43,7 @@
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://strudel.tidalcycles.org",
"homepage": "https://strudel.cc",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
+45
View File
@@ -86,6 +86,16 @@ const generic_params = [
*
*/
['gain'],
/**
* Gain applied after all effects have been processed.
*
* @name postgain
* @example
* s("bd sd,hh*4")
* .compressor("-20:20:10:.002:.02").postgain(1.5)
*
*/
['postgain'],
/**
* Like {@link gain}, but linear.
*
@@ -1020,6 +1030,15 @@ const generic_params = [
*
*/
['roomfade', 'rfade'],
/**
* Sets the sample to use as an impulse response for the reverb. * * @name iresponse
* @param {string | Pattern} sample to use as an impulse response
* @synonyms ir
* @example
* s("bd sd").room(.8).ir("<shaker_large:0 shaker_large:2>")
*
*/
[['ir', 'i'], 'iresponse'],
/**
* Sets the room size of the reverb, see {@link room}.
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
@@ -1051,6 +1070,21 @@ const generic_params = [
*
*/
['shape'],
/**
* Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")`
* More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties)
*
* @name compressor
* @example
* s("bd sd,hh*4")
* .compressor("-20:20:10:.002:.02")
*
*/
[['compressor', 'compressorRatio', 'compressorKnee', 'compressorAttack', 'compressorRelease']],
['compressorKnee'],
['compressorRatio'],
['compressorAttack'],
['compressorRelease'],
/**
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
*
@@ -1262,6 +1296,17 @@ generic_params.forEach(([names, ...aliases]) => {
controls.createParams = (...names) =>
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
/**
* ADSR envelope: Combination of Attack, Decay, Sustain, and Release.
*
* @name adsr
* @param {number | Pattern} time attack time in seconds
* @param {number | Pattern} time decay time in seconds
* @param {number | Pattern} gain sustain level (0 to 1)
* @param {number | Pattern} time release time in seconds
* @example
* note("<c3 bb2 f3 eb3>").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2")
*/
controls.adsr = register('adsr', (adsr, pat) => {
adsr = !Array.isArray(adsr) ? [adsr] : adsr;
const [attack, decay, sustain, release] = adsr;
@@ -3,6 +3,6 @@
This folder demonstrates how to set up a strudel repl using vite and vanilla JS + codemirror. Run it using:
```sh
npm i
npm run dev
pnpm i
pnpm dev
```
@@ -1,6 +1,6 @@
export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
await samples('github:felixroos/samples/main')
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
"<[0,<6 7 9>,13,<17 20 22 26>]!2>/2"
// make it 22 edo
@@ -34,7 +34,7 @@ await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'githu
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
await samples('github:felixroos/samples/main')
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
addVoicings('hip', {
m11: ['2M 3m 4P 7m'],
@@ -70,7 +70,7 @@ export const funk42 = `// froos - how to funk in 42 lines of code
// thanks to peach for the transcription: https://www.youtube.com/watch?v=8eiPXvIgda4
await samples('github:felixroos/samples/main')
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
setcps(.5)
+1 -1
View File
@@ -29,7 +29,7 @@
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://strudel.tidalcycles.org",
"homepage": "https://strudel.cc",
"dependencies": {
"fraction.js": "^4.2.0"
},
+75 -5
View File
@@ -9,7 +9,7 @@ import Fraction from './fraction.mjs';
import Hap from './hap.mjs';
import State from './state.mjs';
import { unionWithObj } from './value.mjs';
import { char2fraction } from './util.mjs';
import { compose, removeUndefineds, flatten, id, listRange, curry, _mod, numeralArgs, parseNumeral } from './util.mjs';
import drawLine from './drawLine.mjs';
import { logger } from './logger.mjs';
@@ -1903,6 +1903,7 @@ export const when = register('when', function (on, func, pat) {
* "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
*/
export const off = register('off', function (time_pat, func, pat) {
time_pat = char2fraction(time_pat);
return stack(pat, func(pat.late(time_pat)));
});
@@ -2100,23 +2101,43 @@ export const { iterBack, iterback } = register(['iterBack', 'iterback'], functio
return _iter(times, pat, true);
});
/**
* Repeats each cycle the given number of times.
* @name repeatCycles
* @memberof Pattern
* @returns Pattern
* @example
* note(irand(12).add(34)).segment(4).repeatCycles(2).s("gm_acoustic_guitar_nylon")
*/
const _repeatCycles = function (n, pat) {
return slowcat(...Array(n).fill(pat));
};
const { repeatCycles } = register('repeatCycles', _repeatCycles);
/**
* 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
* @synonyms slowChunk, slowchunk
* @memberof Pattern
* @returns Pattern
* @example
* "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note()
*/
const _chunk = function (n, func, pat, back = false) {
const _chunk = function (n, func, pat, back = false, fast = false) {
const binary = Array(n - 1).fill(false);
binary.unshift(true);
const binary_pat = _iter(n, sequence(...binary), back);
// Invert the 'back' because we want to shift the pattern forwards,
// and so time backwards
const binary_pat = _iter(n, sequence(...binary), !back);
if (!fast) {
pat = pat.repeatCycles(n);
}
return pat.when(binary_pat, func);
};
export const chunk = register('chunk', function (n, func, pat) {
return _chunk(n, func, pat, false);
const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, false);
});
/**
@@ -2132,6 +2153,21 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
return _chunk(n, func, pat, true);
});
/**
* Like `chunk`, but the cycles of the source pattern aren't repeated
* for each set of chunks.
* @name fastChunk
* @synonyms fastchunk
* @memberof Pattern
* @returns Pattern
* @example
* "<0 8> 1 2 3 4 5 6 7".fastChunk(4, x => x.color('red')).slow(4).scale("C2:major").note()
.s("folkharp")
*/
const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, true);
});
// TODO - redefine elsewhere in terms of mask
export const bypass = register('bypass', function (on, pat) {
on = Boolean(parseInt(on));
@@ -2217,6 +2253,14 @@ export const chop = register('chop', function (n, pat) {
return pat.squeezeBind(func);
});
/**
* Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop.
* @name striate
* @memberof Pattern
* @returns Pattern
* @example
* s("numbers:0 numbers:1 numbers:2").striate(6).slow(6)
*/
export const striate = register('striate', function (n, pat) {
const slices = Array.from({ length: n }, (x, i) => i);
const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
@@ -2349,3 +2393,29 @@ export const ref = (accessor) =>
pure(1)
.withValue(() => reify(accessor()))
.innerJoin();
let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5);
/**
* Cross-fades between left and right from 0 to 1:
* - 0 = (full left, no right)
* - .5 = (both equal)
* - 1 = (no left, full right)
*
* @name xfade
* @example
* xfade(s("bd*2"), "<0 .25 .5 .75 1>", s("hh*8"))
*/
export let xfade = (a, pos, b) => {
pos = reify(pos);
a = reify(a);
b = reify(b);
let gaina = pos.fmap((v) => ({ gain: fadeGain(v) }));
let gainb = pos.fmap((v) => ({ gain: fadeGain(1 - v) }));
return stack(a.mul(gaina), b.mul(gainb));
};
// the prototype version is actually flipped so left/right makes sense
Pattern.prototype.xfade = function (pos, b) {
return xfade(this, pos, b);
};
+11
View File
@@ -24,6 +24,7 @@ export function repl({
getTime,
onToggle,
});
let playPatterns = [];
const setPattern = (pattern, autostart = true) => {
pattern = editPattern?.(pattern) || pattern;
scheduler.setPattern(pattern, autostart);
@@ -35,7 +36,11 @@ export function repl({
}
try {
await beforeEval?.({ code });
playPatterns = [];
let { pattern, meta } = await _evaluate(code, transpiler);
if (playPatterns.length) {
pattern = pattern.stack(...playPatterns);
}
logger(`[eval] code updated`);
setPattern(pattern, autostart);
afterEval?.({ code, pattern, meta });
@@ -57,6 +62,11 @@ export function repl({
return pat.loopAtCps(cycles, scheduler.cps);
});
const play = register('play', (pat) => {
playPatterns.push(pat);
return pat;
});
const fit = register('fit', (pat) =>
pat.withHap((hap) =>
hap.withValue((v) => ({
@@ -70,6 +80,7 @@ export function repl({
evalScope({
loopAt,
fit,
play,
setCps,
setcps: setCps,
setCpm,
+54 -2
View File
@@ -7,7 +7,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, register } from './pattern.mjs';
import Fraction from './fraction.mjs';
import { id } from './util.mjs';
import { id, _mod, clamp } from './util.mjs';
export function steady(value) {
// A continuous value
@@ -155,12 +155,62 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
*/
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
/**
* pick from the list of values (or patterns of values) via the index using the given
* pattern of integers
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note(pick("<0 1 [2!2] 3>", ["g a", "e f", "f g f g" , "g a c d"]))
*/
export const pick = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
}
return pat
.fmap((i) => {
const key = clamp(Math.round(i), 0, xs.length - 1);
return xs[key];
})
.innerJoin();
};
/**
* pick from the list of values (or patterns of values) via the index using the given
* pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g" , "g a c d"]))
*/
export const squeeze = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
}
return pat
.fmap((i) => {
const key = _mod(Math.round(i), xs.length);
return xs[key];
})
.squeezeJoin();
};
export const __chooseWith = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
}
return pat.range(0, xs.length).fmap((i) => xs[Math.floor(i)]);
return pat.range(0, xs.length).fmap((i) => {
const key = Math.min(Math.max(Math.floor(i), 0), xs.length - 1);
return xs[key];
});
};
/**
* Choose from the list of values (or patterns of values) using the given
@@ -168,6 +218,8 @@ export const __chooseWith = (pat, xs) => {
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note("c2 g2!2 d2 f1").s(chooseWith(sine.fast(2), ["sawtooth", "triangle", "bd:6"]))
*/
export const chooseWith = (pat, xs) => {
return __chooseWith(pat, xs).outerJoin();
+19
View File
@@ -1003,4 +1003,23 @@ describe('Pattern', () => {
);
});
});
describe('chunk', () => {
it('Processes each cycle of the source pattern multiple times, once for each chunk', () => {
expect(sequence(0, 1, 2, 3).slow(2).chunk(2, add(10)).fast(4).firstCycleValues).toStrictEqual([
10, 1, 0, 11, 12, 3, 2, 13,
]);
});
});
describe('fastChunk', () => {
it('Unlike chunk, cycles of the source pattern proceed cycle-by-cycle', () => {
expect(sequence(0, 1, 2, 3).slow(2).fastChunk(2, add(10)).fast(4).firstCycleValues).toStrictEqual([
10, 1, 2, 13, 10, 1, 2, 13,
]);
});
});
describe('repeatCycles', () => {
it('Repeats each cycle of the source pattern the given number of times', () => {
expect(slowcat(0, 1).repeatCycles(2).fast(6).firstCycleValues).toStrictEqual([0, 0, 1, 1, 0, 0]);
});
});
});
-2
View File
@@ -46,7 +46,5 @@ export const cleanupUi = () => {
const container = document.getElementById('code');
if (container) {
container.style = '';
// TODO: find a way to remove that duplication..
container.className = 'grow flex text-gray-100 relative overflow-auto cursor-text pb-0'; // has to match App.tsx
}
};
+20
View File
@@ -1,3 +1,5 @@
import Fraction from './fraction.mjs';
/*
util.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/util.mjs>
@@ -274,3 +276,21 @@ export const sol2note = (n, notation = 'letters') => {
const oct = Math.floor(n / 12) - 1;
return note + oct;
};
const fractionCharacters = {
w: Fraction(1),
h: Fraction(0.5),
q: Fraction(0.25),
e: Fraction(0.125),
s: Fraction(0.0625),
t: Fraction(1 / 3),
f: Fraction(0.2),
x: Fraction(1 / 6),
};
const validFractionCharacters = Object.keys(fractionCharacters);
export const char2fraction = (maybeChar) => {
if (typeof maybeChar === 'string' && validFractionCharacters.includes(maybeChar)) {
return fractionCharacters[maybeChar];
}
return maybeChar;
};
+1 -1
View File
@@ -6,7 +6,7 @@ class Strudel extends HTMLElement {
setTimeout(() => {
const code = (this.innerHTML + '').replace('<!--', '').replace('-->', '').trim();
const iframe = document.createElement('iframe');
const src = `https://strudel.tidalcycles.org/#${encodeURIComponent(btoa(code))}`;
const src = `https://strudel.cc/#${encodeURIComponent(btoa(code))}`;
// const src = `http://localhost:3000/#${encodeURIComponent(btoa(code))}`;
iframe.setAttribute('src', src);
iframe.setAttribute('width', '600');
+29
View File
@@ -0,0 +1,29 @@
# @strudel/hydra
This package integrates [hydra-synth](https://www.npmjs.com/package/hydra-synth) into strudel.
## Usage in Strudel
This package is imported into strudel by default. To activate Hydra, place this code at the top of your code:
```js
await initHydra();
```
Then you can use hydra below!
## Usage via npm
```sh
npm i @strudel/hydra
```
Then add the import to your evalScope:
```js
import { evalScope } from '@strudel.cycles/core';
evalScope(
import('@strudel/hydra')
)
```
+15
View File
@@ -0,0 +1,15 @@
import { getDrawContext } from '@strudel.cycles/core';
export async function initHydra() {
if (!document.getElementById('hydra-canvas')) {
const { canvas: testCanvas } = getDrawContext();
await import('https://unpkg.com/hydra-synth');
const hydraCanvas = testCanvas.cloneNode(true);
hydraCanvas.id = 'hydra-canvas';
testCanvas.after(hydraCanvas);
new Hydra({ canvas: hydraCanvas, detectAudio: false });
s0.init({ src: testCanvas });
}
}
export const H = (p) => () => p.queryArc(getTime(), getTime())[0].value;
+43
View File
@@ -0,0 +1,43 @@
{
"name": "@strudel/hydra",
"version": "0.9.0",
"description": "Hydra integration for strudel",
"main": "hydra.mjs",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": {
"server": "node server.js",
"tidal-sniffer": "node tidal-sniffer.js",
"client": "npx serve -p 4321",
"build-bin": "npx pkg server.js --targets node16-macos-x64,node16-win-x64,node16-linux-x64 --out-path bin",
"build": "vite build",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"hydra-synth": "^1.3.29"
},
"devDependencies": {
"pkg": "^5.8.1",
"vite": "^4.3.3"
}
}
+19
View File
@@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: resolve(__dirname, 'hydra.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
+2 -2
View File
@@ -32,7 +32,7 @@ yields:
## Mini Notation API
See "Mini Notation" in the [Strudel Tutorial](https://strudel.tidalcycles.org/learn/mini-notation)
See "Mini Notation" in the [Strudel Tutorial](https://strudel.cc/learn/mini-notation)
## Building the Parser
@@ -40,5 +40,5 @@ The parser [krill-parser.js] is generated from [krill.pegjs](./krill.pegjs) usin
To generate the parser, run
```js
npm build:parser
npm run build:parser
```
File diff suppressed because one or more lines are too long
+24 -1
View File
@@ -103,7 +103,8 @@ quote = '"' / "'"
// ------------------ steps and cycles ---------------------------
// single step definition (e.g bd)
step_char = [0-9a-zA-Z~] / "-" / "#" / "." / "^" / "_"
step_char "a letter, a number, \"-\", \"#\", \".\", \"^\", \"_\"" =
unicode_letter / [0-9~] / "-" / "#" / "." / "^" / "_"
step = ws chars:step_char+ ws { return new AtomStub(chars.join("")) }
// define a sub cycle e.g. [1 2, 3 [4]]
@@ -265,3 +266,25 @@ hush = "hush"
// ---------------------- statements ----------------------------
statement = mini_definition / command
// ---------------------- unicode ----------------------------
unicode_letter = Lu / Ll / Lt / Lm / Lo / Nl
// Letter, Lowercase
Ll = [\u0061-\u007A\u00B5\u00DF-\u00F6\u00F8-\u00FF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]
// Letter, Modifier
Lm = [\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5-\u06E6\u07F4-\u07F5\u07FA\u081A\u0824\u0828\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C-\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D-\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C-\uA69D\uA717-\uA71F\uA770\uA788\uA7F8-\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3-\uAAF4\uAB5C-\uAB5F\uFF70\uFF9E-\uFF9F]
// Letter, Other
Lo = [\u00AA\u00BA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A-\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]
// Letter, Titlecase
Lt = [\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC]
// Letter, Uppercase
Lu = [\u0041-\u005A\u00C0-\u00D6\u00D8-\u00DE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178-\u0179\u017B\u017D\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018B\u018E-\u0191\u0193-\u0194\u0196-\u0198\u019C-\u019D\u019F-\u01A0\u01A2\u01A4\u01A6-\u01A7\u01A9\u01AC\u01AE-\u01AF\u01B1-\u01B3\u01B5\u01B7-\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A-\u023B\u023D-\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9-\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0-\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E-\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D-\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uFF21-\uFF3A]
// Number, Letter
Nl = [\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF]
+2 -2
View File
@@ -34,6 +34,6 @@ Now open the REPL and type:
s("<bd sd> hh").osc()
```
or just [click here](https://strudel.tidalcycles.org/#cygiPGJkIHNkPiBoaCIpLm9zYygp)...
or just [click here](https://strudel.cc/#cygiPGJkIHNkPiBoaCIpLm9zYygp)...
You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.tidalcycles.org/learn/input-output/#superdirt-api)
You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.cc/learn/input-output/#superdirt-api)
+1 -1
View File
@@ -37,7 +37,7 @@ function connect() {
/**
*
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
* For more info, read [MIDI & OSC in the docs](https://strudel.tidalcycles.org/learn/input-output)
* For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output)
*
* @name osc
* @memberof Pattern
+3 -3
View File
@@ -21,12 +21,12 @@ import { samples, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
async function prebake() {
await samples(
'https://strudel.tidalcycles.org/tidal-drum-machines.json',
'https://strudel.cc/tidal-drum-machines.json',
'github:ritchse/tidal-drum-machines/main/machines/'
);
await samples(
'https://strudel.tidalcycles.org/EmuSP12.json',
'https://strudel.tidalcycles.org/EmuSP12/'
'https://strudel.cc/EmuSP12.json',
'https://strudel.cc/EmuSP12/'
);
}
+5
View File
@@ -33,12 +33,17 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@codemirror/autocomplete": "^6.6.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/lang-javascript": "^6.1.7",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.10.0",
"@lezer/highlight": "^1.1.4",
"@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.0.14",
"@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
@@ -26,7 +26,6 @@ export function Autocomplete({ doc }) {
<pre
className="cursor-pointer"
onMouseDown={(e) => {
console.log('ola!');
navigator.clipboard.writeText(example);
e.stopPropagation();
}}
+26 -2
View File
@@ -1,12 +1,15 @@
import { autocompletion } from '@codemirror/autocomplete';
import { Prec } from '@codemirror/state';
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
import { EditorView } from '@codemirror/view';
import { ViewPlugin, EditorView, keymap } from '@codemirror/view';
import { emacs } from '@replit/codemirror-emacs';
import { vim } from '@replit/codemirror-vim';
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
import _CodeMirror from '@uiw/react-codemirror';
import React, { useCallback, useMemo } from 'react';
import strudelTheme from '../themes/strudel-theme';
import { strudelAutocomplete } from './Autocomplete';
import { strudelTooltip } from './Tooltip';
import {
highlightExtension,
flashField,
@@ -31,6 +34,7 @@ export default function CodeMirror({
keybindings,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isTooltipEnabled,
isLineWrappingEnabled,
fontSize = 18,
fontFamily = 'monospace',
@@ -61,11 +65,25 @@ export default function CodeMirror({
[onSelectionChange],
);
const vscodePlugin = ViewPlugin.fromClass(
class {
constructor(view) {}
},
{
provide: (plugin) => {
return Prec.highest(keymap.of([...vscodeKeymap]));
},
},
);
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
const extensions = useMemo(() => {
let _extensions = [...staticExtensions];
let bindings = {
vim,
emacs,
vscode: vscodeExtension,
};
if (bindings[keybindings]) {
@@ -78,12 +96,18 @@ export default function CodeMirror({
_extensions.push(autocompletion({ override: [] }));
}
if (isTooltipEnabled) {
_extensions.push(strudelTooltip);
}
_extensions.push([keymap.of({})]);
if (isLineWrappingEnabled) {
_extensions.push(EditorView.lineWrapping);
}
return _extensions;
}, [keybindings, isAutoCompletionEnabled, isLineWrappingEnabled]);
}, [keybindings, isAutoCompletionEnabled, isTooltipEnabled, isLineWrappingEnabled]);
const basicSetup = useMemo(() => ({ lineNumbers: isLineNumbersDisplayed }), [isLineNumbersDisplayed]);
+69
View File
@@ -0,0 +1,69 @@
import { createRoot } from 'react-dom/client';
import { hoverTooltip } from '@codemirror/view';
import jsdoc from '../../../../doc.json';
import { Autocomplete } from './Autocomplete';
const getDocLabel = (doc) => doc.name || doc.longname;
let ctrlDown = false;
// Record Control key event to trigger or block the tooltip depending on the state
window.addEventListener(
'keyup',
function (e) {
if (e.key == 'Control') {
ctrlDown = false;
}
},
true,
);
window.addEventListener(
'keydown',
function (e) {
if (e.key == 'Control') {
ctrlDown = true;
}
},
true,
);
export const strudelTooltip = hoverTooltip(
(view, pos, side) => {
// Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips
let { from, to, text } = view.state.doc.lineAt(pos);
let start = pos,
end = pos;
while (start > from && /\w/.test(text[start - from - 1])) {
start--;
}
while (end < to && /\w/.test(text[end - from])) {
end++;
}
if ((start == pos && side < 0) || (end == pos && side > 0)) {
return null;
}
let word = text.slice(start - from, end - from);
// Get entry from Strudel documentation
let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0];
if (!entry) {
return null;
}
if (!ctrlDown) {
return null;
}
return {
pos: start,
end,
above: false,
arrow: true,
create(view) {
let dom = document.createElement('div');
dom.className = 'strudel-tooltip';
createRoot(dom).render(<Autocomplete doc={entry} />);
return { dom };
},
};
},
{ hoverTime: 10 },
);
+4
View File
@@ -28,3 +28,7 @@
footer {
z-index: 0 !important;
}
.strudel-tooltip {
padding: 5px;
}
+1 -1
View File
@@ -1,7 +1,7 @@
# superdough
superdough is a simple web audio sampler and synth, intended for live coding.
It is the default output of [strudel](https://strudel.tidalcycles.org/).
It is the default output of [strudel](https://strudel.cc/).
This package has no ties to strudel and can be used to quickly bake your own music system on the web.
## Install
+11
View File
@@ -78,6 +78,17 @@ export const getParamADSR = (param, attack, decay, sustain, release, min, max, b
param.linearRampToValueAtTime(min, end + Math.max(release, 0.1));
};
export function getCompressor(ac, threshold, ratio, knee, attack, release) {
const options = {
threshold: threshold ?? -3,
ratio: ratio ?? 10,
knee: knee ?? 10,
attack: attack ?? 0.005,
release: release ?? 0.05,
};
return new DynamicsCompressorNode(ac, options);
}
export function createFilter(
context,
type,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "superdough",
"version": "0.9.9",
"version": "0.9.10",
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
"main": "index.mjs",
"type": "module",
+35 -18
View File
@@ -1,30 +1,47 @@
import reverbGen from './reverbGen.mjs';
if (typeof AudioContext !== 'undefined') {
AudioContext.prototype.generateReverb = reverbGen.generateReverb;
AudioContext.prototype.createReverb = function (duration, fade, lp, dim) {
AudioContext.prototype.adjustLength = function (duration, buffer) {
const newLength = buffer.sampleRate * duration;
const newBuffer = this.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
let oldData = buffer.getChannelData(channel);
let newData = newBuffer.getChannelData(channel);
for (let i = 0; i < newLength; i++) {
newData[i] = oldData[i] || 0;
}
}
return newBuffer;
};
AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir) {
const convolver = this.createConvolver();
convolver.generate = (d, fade, lp, dim) => {
this.generateReverb(
{
audioContext: this,
sampleRate: 44100,
numChannels: 2,
decayTime: d,
fadeInTime: fade,
lpFreqStart: lp,
lpFreqEnd: dim,
},
(buffer) => {
convolver.buffer = buffer;
},
);
convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000, ir) => {
convolver.duration = d;
convolver.fade = fade;
convolver.lp = lp;
convolver.dim = dim;
convolver.ir = ir;
if (ir) {
convolver.buffer = this.adjustLength(d, ir);
} else {
reverbGen.generateReverb(
{
audioContext: this,
numChannels: 2,
decayTime: d,
fadeInTime: fade,
lpFreqStart: lp,
lpFreqEnd: dim,
},
(buffer) => {
convolver.buffer = buffer;
},
);
}
};
convolver.generate(duration, fade, lp, dim);
convolver.generate(duration, fade, lp, dim, ir);
return convolver;
};
}
+23 -22
View File
@@ -16,14 +16,14 @@ var reverbGen = {};
/** Generates a reverb impulse response.
@param {!Object} params TODO: Document the properties.
@param {!function(!AudioBuffer)} callback Function to call when
the impulse response has been generated. The impulse response
is passed to this function as its parameter. May be called
immediately within the current execution context, or later. */
@param {!Object} params TODO: Document the properties.
@param {!function(!AudioBuffer)} callback Function to call when
the impulse response has been generated. The impulse response
is passed to this function as its parameter. May be called
immediately within the current execution context, or later. */
reverbGen.generateReverb = function (params, callback) {
var audioContext = params.audioContext || new AudioContext();
var sampleRate = params.sampleRate || 44100;
var sampleRate = audioContext.sampleRate;
var numChannels = params.numChannels || 2;
// params.decayTime is the -60dB fade time. We let it go 50% longer to get to -90dB.
var totalTime = params.decayTime * 1.5;
@@ -48,12 +48,13 @@ reverbGen.generateReverb = function (params, callback) {
/** Creates a canvas element showing a graph of the given data.
@param {!Float32Array} data An array of numbers, or a Float32Array.
@param {number} width Width in pixels of the canvas.
@param {number} height Height in pixels of the canvas.
@param {number} min Minimum value of data for the graph (lower edge).
@param {number} max Maximum value of data in the graph (upper edge).
@return {!CanvasElement} The generated canvas element. */
@param {!Float32Array} data An array of numbers, or a Float32Array.
@param {number} width Width in pixels of the canvas.
@param {number} height Height in pixels of the canvas.
@param {number} min Minimum value of data for the graph (lower edge).
@param {number} max Maximum value of data in the graph (upper edge).
@return {!CanvasElement} The generated canvas element. */
reverbGen.generateGraph = function (data, width, height, min, max) {
var canvas = document.createElement('canvas');
canvas.width = width;
@@ -72,13 +73,13 @@ reverbGen.generateGraph = function (data, width, height, min, max) {
/** Applies a constantly changing lowpass filter to the given sound.
@private
@param {!AudioBuffer} input
@param {number} lpFreqStart
@param {number} lpFreqEnd
@param {number} lpFreqEndAt
@param {!function(!AudioBuffer)} callback May be called
immediately within the current execution context, or later.*/
@private
@param {!AudioBuffer} input
@param {number} lpFreqStart
@param {number} lpFreqEnd
@param {number} lpFreqEndAt
@param {!function(!AudioBuffer)} callback May be called
immediately within the current execution context, or later.*/
var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) {
if (lpFreqStart == 0) {
callback(input);
@@ -110,8 +111,8 @@ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt,
};
/** @private
@param {!AudioBuffer} buffer
@return {!Array.<!Float32Array>} An array containing the Float32Array of each channel's samples. */
@param {!AudioBuffer} buffer
@return {!Array.<!Float32Array>} An array containing the Float32Array of each channel's samples. */
var getAllChannelData = function (buffer) {
var channels = [];
for (var i = 0; i < buffer.numberOfChannels; i++) {
@@ -121,7 +122,7 @@ var getAllChannelData = function (buffer) {
};
/** @private
@return {number} A random number from -1 to 1. */
@return {number} A random number from -1 to 1. */
var randomSample = function () {
return Math.random() * 2 - 1;
};
+30 -1
View File
@@ -64,6 +64,7 @@ export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resol
export const loadBuffer = (url, ac, s, n = 0) => {
const label = s ? `sound "${s}:${n}"` : 'sample';
url = url.replace('#', '%23');
if (!loadCache[url]) {
logger(`[sampler] load ${label}..`, 'load-sample', { url });
const timestamp = Date.now();
@@ -146,7 +147,12 @@ function getSamplesPrefixHandler(url) {
* sd: '808sd/SD0010.WAV'
* }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
* s("[bd ~]*2, [~ hh]*2, ~ sd")
*
* @example
* samples('shabda:noise,chimp:2')
* s("noise <chimp:0*2 chimp:1>")
* @example
* samples('shabda/speech/fr-FR/f:chocolat')
* s("chocolat*4")
*/
export const samples = async (sampleMap, baseUrl = sampleMap._base || '', options = {}) => {
@@ -156,11 +162,34 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
if (handler) {
return handler(sampleMap);
}
if (sampleMap.startsWith('bubo:')) {
const [_, repo] = sampleMap.split(':');
sampleMap = `github:Bubobubobubobubo/dough-${repo}`;
}
if (sampleMap.startsWith('github:')) {
let [_, path] = sampleMap.split('github:');
path = path.endsWith('/') ? path.slice(0, -1) : path;
if (path.split('/').length === 2) {
// assume main as default branch if none set
path += '/main';
}
sampleMap = `https://raw.githubusercontent.com/${path}/strudel.json`;
}
if (sampleMap.startsWith('shabda:')) {
let [_, path] = sampleMap.split('shabda:');
sampleMap = `https://shabda.ndre.gr/${path}.json?strudel=1`;
}
if (sampleMap.startsWith('shabda/speech')) {
let [_, path] = sampleMap.split('shabda/speech');
path = path.startsWith('/') ? path.substring(1) : path;
let [params, words] = path.split(':');
let gender = 'f';
let language = 'en-GB';
if (params) {
[language, gender] = params.split('/');
}
sampleMap = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'`;
}
if (typeof fetch !== 'function') {
// not a browser
return;
+55 -17
View File
@@ -9,17 +9,21 @@ import './reverb.mjs';
import './vowel.mjs';
import { clamp } from './util.mjs';
import workletsUrl from './worklets.mjs?url';
import { createFilter, gainNode } from './helpers.mjs';
import { createFilter, gainNode, getCompressor } from './helpers.mjs';
import { map } from 'nanostores';
import { logger } from './logger.mjs';
import { loadBuffer } from './sampler.mjs';
export const soundMap = map();
export function registerSound(key, onTrigger, data = {}) {
soundMap.setKey(key, { onTrigger, data });
}
export function getSound(s) {
return soundMap.get()[s];
}
export const resetLoadedSounds = () => soundMap.set({});
let audioContext;
@@ -46,6 +50,7 @@ export const panic = () => {
};
let workletsLoading;
function loadWorklets() {
if (workletsLoading) {
return workletsLoading;
@@ -89,6 +94,7 @@ export async function initAudioOnFirstClick(options) {
let delays = {};
const maxfeedback = 0.98;
function getDelay(orbit, delaytime, delayfeedback, t) {
if (delayfeedback > maxfeedback) {
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
@@ -108,28 +114,35 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
let reverbs = {};
function getReverb(orbit, duration = 2, fade, lp, dim) {
let hasChanged = (now, before) => now !== undefined && now !== before;
function getReverb(orbit, duration, fade, lp, dim, ir) {
// If no reverb has been created for a given orbit, create one
if (!reverbs[orbit]) {
const ac = getAudioContext();
const reverb = ac.createReverb(duration, fade, lp, dim);
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
reverb.connect(getDestination());
reverbs[orbit] = reverb;
}
if (
reverbs[orbit].duration !== duration ||
reverbs[orbit].fade !== fade ||
reverbs[orbit].lp !== lp ||
reverbs[orbit].dim !== dim
hasChanged(duration, reverbs[orbit].duration) ||
hasChanged(fade, reverbs[orbit].fade) ||
hasChanged(lp, reverbs[orbit].lp) ||
hasChanged(dim, reverbs[orbit].dim) ||
reverbs[orbit].ir !== ir
) {
reverbs[orbit].generate(duration, fade, lp, dim);
// only regenerate when something has changed
// avoids endless regeneration on things like
// stack(s("a"), s("b").rsize(8)).room(.5)
// this only works when args may stay undefined until here
// setting default values breaks this
reverbs[orbit].generate(duration, fade, lp, dim, ir);
}
return reverbs[orbit];
}
export let analyser, analyserData /* s = {} */;
export function getAnalyser(/* orbit, */ fftSize = 2048) {
if (!analyser /*s [orbit] */) {
const analyserNode = getAudioContext().createAnalyser();
@@ -185,6 +198,7 @@ export const superdough = async (value, deadline, hapDuration) => {
bank,
source,
gain = 0.8,
postgain = 1,
// filters
ftype = '12db',
fanchor = 0.5,
@@ -223,13 +237,20 @@ export const superdough = async (value, deadline, hapDuration) => {
delaytime = 0.25,
orbit = 1,
room,
roomfade = 0.1,
roomlp = 15000,
roomdim = 1000,
roomsize = 2,
roomfade,
roomlp,
roomdim,
roomsize,
ir,
i = 0,
velocity = 1,
analyze, // analyser wet
fft = 8, // fftSize 0 - 10
compressor: compressorThreshold,
compressorRatio,
compressorKnee,
compressorAttack,
compressorRelease,
} = value;
gain *= velocity; // legacy fix for velocity
let toDisconnect = []; // audio nodes that will be disconnected when the source has ended
@@ -258,6 +279,7 @@ export const superdough = async (value, deadline, hapDuration) => {
// this can be used for things like speed(0) in the sampler
return;
}
if (ac.currentTime > t) {
logger('[webaudio] skip hap: still loading', ac.currentTime - t);
return;
@@ -344,6 +366,11 @@ export const superdough = async (value, deadline, hapDuration) => {
crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush }));
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape }));
compressorThreshold !== undefined &&
chain.push(
getCompressor(ac, compressorThreshold, compressorRatio, compressorKnee, compressorAttack, compressorRelease),
);
// panning
if (pan !== undefined) {
const panner = ac.createStereoPanner();
@@ -352,7 +379,7 @@ export const superdough = async (value, deadline, hapDuration) => {
}
// last gain
const post = gainNode(1);
const post = gainNode(postgain);
chain.push(post);
post.connect(getDestination());
@@ -364,8 +391,19 @@ export const superdough = async (value, deadline, hapDuration) => {
}
// reverb
let reverbSend;
if (room > 0 && roomsize > 0) {
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim);
if (room > 0) {
let roomIR;
if (ir !== undefined) {
let url;
let sample = getSound(ir);
if (Array.isArray(sample)) {
url = sample.data.samples[i % sample.data.samples.length];
} else if (typeof sample === 'object') {
url = Object.values(sample.data.samples).flat()[i % Object.values(sample.data.samples).length];
}
roomIR = await loadBuffer(url, ac, ir, 0);
}
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR);
reverbSend = effectSend(post, reverbNode, room);
}
+8 -7
View File
@@ -73,13 +73,13 @@ export function waveformN(partials, type) {
const ac = getAudioContext();
const osc = ac.createOscillator();
const amplitudes = {
sawtooth: (n) => 1 / n,
square: (n) => (n % 2 === 0 ? 0 : 1 / n),
triangle: (n) => (n % 2 === 0 ? 0 : 1 / (n * n)),
const terms = {
sawtooth: (n) => [0, -1 / n],
square: (n) => [0, n % 2 === 0 ? 0 : 1 / n],
triangle: (n) => [n % 2 === 0 ? 0 : 1 / (n * n), 0],
};
if (!amplitudes[type]) {
if (!terms[type]) {
throw new Error(`unknown wave type ${type}`);
}
@@ -87,8 +87,9 @@ export function waveformN(partials, type) {
imag[0] = 0;
let n = 1;
while (n <= partials) {
real[n] = amplitudes[type](n);
imag[n] = 0;
const [r, i] = terms[type](n);
real[n] = r;
imag[n] = i;
n++;
}
+1 -1
View File
@@ -31,4 +31,4 @@ yields:
## Tonal API
See "Tonal API" in the [Strudel Tutorial](https://strudel.tidalcycles.org/learn/tonal)
See "Tonal API" in the [Strudel Tutorial](https://strudel.cc/learn/tonal)
+17 -5
View File
@@ -7,6 +7,19 @@ This program is free software: you can redistribute it and/or modify it under th
import { Note, Interval, Scale } from '@tonaljs/tonal';
import { register, _mod } from '@strudel.cycles/core';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
function scaleStep(step, scale) {
scale = scale.replaceAll(':', ' ');
step = Math.ceil(step);
const { intervals, tonic } = Scale.get(scale);
const { pc, oct = 3 } = Note.get(tonic);
const octaveOffset = Math.floor(step / intervals.length);
const scaleStep = _mod(step, intervals.length);
const interval = Interval.add(intervals[scaleStep], octavesInterval(octaveOffset));
return Note.transpose(pc + oct, interval);
}
// transpose note inside scale by offset steps
// function scaleOffset(scale: string, offset: number, note: string) {
function scaleOffset(scale, offset, note) {
@@ -150,13 +163,12 @@ export const scale = register('scale', function (scale, pat) {
return pat.withHap((hap) => {
const isObject = typeof hap.value === 'object';
let note = isObject ? hap.value.n : hap.value;
if (isObject) {
delete hap.value.n; // remove n so it won't cause trouble
}
const asNumber = Number(note);
if (!isNaN(asNumber)) {
// TODO: worth keeping for supporting ':' in (non-mininotation) strings?
scale = scale.replaceAll(':', ' ');
let [tonic, scaleName] = Scale.tokenize(scale);
const { pc, oct = 3 } = Note.get(tonic);
note = scaleOffset(pc + ' ' + scaleName, asNumber, pc + oct);
note = scaleStep(asNumber, scale);
}
return hap.withValue(() => (isObject ? { ...hap.value, note } : note)).setContext({ ...hap.context, scale });
});
+2 -2
View File
@@ -51,7 +51,7 @@ document.getElementById('play').addEventListener('click',
)
```
You can learn [more about the `samples` function here](https://strudel.tidalcycles.org/learn/samples#loading-custom-samples).
You can learn [more about the `samples` function here](https://strudel.cc/learn/samples#loading-custom-samples).
### Evaluating Code
@@ -72,7 +72,7 @@ document.getElementById('play').addEventListener('stop',
### Double vs Single Quotes
There is a tiny difference between the [Strudel REPL](https://strudel.tidalcycles.org/) and `@strudel/web`.
There is a tiny difference between the [Strudel REPL](https://strudel.cc/) and `@strudel/web`.
In the REPL you can use 'single quotes' for regular JS strings and "double quotes" for mini notation patterns.
In `@strudel/web`, it does not matter which types of quotes you're using.
There will probably be an escapte hatch for that in the future.
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="https://strudel.tidalcycles.org/favicon.ico" />
<link rel="icon" type="image/svg+xml" href="https://strudel.cc/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@strudel/web REPL Example</title>
</head>
+1 -1
View File
@@ -33,4 +33,4 @@ document.getElementById("stop").addEventListener("click", () => scheduler.stop()
[Play with the example codesandbox](https://codesandbox.io/s/amazing-dawn-gclfwg?file=/src/index.js).
Read more in the docs about [samples](https://strudel.tidalcycles.org/learn/samples/), [synths](https://strudel.tidalcycles.org/learn/synths/) and [effects](https://strudel.tidalcycles.org/learn/effects/).
Read more in the docs about [samples](https://strudel.cc/learn/samples/), [synths](https://strudel.cc/learn/synths/) and [effects](https://strudel.cc/learn/effects/).
+28 -3
View File
@@ -3,7 +3,7 @@ import { analyser, getAnalyzerData } from 'superdough';
export function drawTimeScope(
analyser,
{ align = true, color = 'white', thickness = 3, scale = 0.25, pos = 0.75, next = 1, trigger = 0 } = {},
{ align = true, color = 'white', thickness = 3, scale = 0.25, pos = 0.75, trigger = 0 } = {},
) {
const ctx = getDrawContext();
const dataArray = getAnalyzerData('time');
@@ -22,10 +22,9 @@ export function drawTimeScope(
const sliceWidth = (canvas.width * 1.0) / bufferSize;
let x = 0;
for (let i = triggerIndex; i < bufferSize; i++) {
const v = dataArray[i] + 1;
const y = (scale * (v - 1) + pos) * canvas.height;
const y = (pos - scale * (v - 1)) * canvas.height;
if (i === 0) {
ctx.moveTo(x, y);
@@ -71,6 +70,18 @@ function clearScreen(smear = 0, smearRGB = `0,0,0`) {
}
}
/**
* Renders an oscilloscope for the frequency domain of the audio signal.
* @name fscope
* @param {string} color line color as hex or color name. defaults to white.
* @param {number} scale scales the y-axis. Defaults to 0.25
* @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen
* @param {number} lean y-axis alignment where 0 = top and 1 = bottom
* @param {number} min min value
* @param {number} max max value
* @example
* s("sawtooth").fscope()
*/
Pattern.prototype.fscope = function (config = {}) {
return this.analyze(1).draw(() => {
clearScreen(config.smear);
@@ -78,6 +89,20 @@ Pattern.prototype.fscope = function (config = {}) {
});
};
/**
* Renders an oscilloscope for the time domain of the audio signal.
* @name scope
* @synonyms tscope
* @param {object} config optional config with options:
* @param {boolean} align if 1, the scope will be aligned to the first zero crossing. defaults to 1
* @param {string} color line color as hex or color name. defaults to white.
* @param {number} thickness line thickness. defaults to 3
* @param {number} scale scales the y-axis. Defaults to 0.25
* @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen
* @param {number} trigger amplitude value that is used to align the scope. defaults to 0.
* @example
* s("sawtooth").scope()
*/
Pattern.prototype.tscope = function (config = {}) {
return this.analyze(1).draw(() => {
clearScreen(config.smear);
+1 -1
View File
@@ -199,7 +199,7 @@ interfaces.
# Links
The Strudel REPL is available at <https://strudel.tidalcycles.org>,
The Strudel REPL is available at <https://strudel.cc>,
including an interactive tutorial. The repository is at
<https://github.com/tidalcycles/strudel>, all the code is open source
under the GPL-3.0 License.
+1 -1
View File
@@ -127,7 +127,7 @@ For the future, it is planned to integrate alternative sound engines such as Gli
# Links
The Strudel REPL is available at <https://strudel.tidalcycles.org>, including an interactive tutorial.
The Strudel REPL is available at <https://strudel.cc>, including an interactive tutorial.
The repository is at <https://github.com/tidalcycles/strudel>, all the code is open source under the GPL-3.0 License.
# Acknowledgments
+2 -2
View File
@@ -717,8 +717,8 @@ fun ahead.</p>
<h1 data-number="11" id="links"><span
class="header-section-number">11</span> Links</h1>
<p>The Strudel REPL is available at <a
href="https://strudel.tidalcycles.org"
class="uri">https://strudel.tidalcycles.org</a>, including an
href="https://strudel.cc"
class="uri">https://strudel.cc</a>, including an
interactive tutorial. The repository is at <a
href="https://github.com/tidalcycles/strudel"
class="uri">https://github.com/tidalcycles/strudel</a>, all the code is
+1 -1
View File
@@ -450,7 +450,7 @@ While Haskell's type system makes it a great language for the ongoing developmen
# Links
The Strudel REPL is available at <https://strudel.tidalcycles.org>, including an interactive tutorial.
The Strudel REPL is available at <https://strudel.cc>, including an interactive tutorial.
The repository is at <https://github.com/tidalcycles/strudel>, all the code is open source under the AGPL-3.0 License.
# Acknowledgments
+1874 -1230
View File
File diff suppressed because it is too large Load Diff
+91
View File
@@ -1727,6 +1727,17 @@ dependencies = [
"objc_exception",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc_exception"
version = "0.1.2"
@@ -2190,6 +2201,30 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]]
name = "rfd"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea"
dependencies = [
"block",
"dispatch",
"glib-sys",
"gobject-sys",
"gtk-sys",
"js-sys",
"lazy_static",
"log",
"objc",
"objc-foundation",
"objc_id",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows 0.37.0",
]
[[package]]
name = "rosc"
version = "0.10.1"
@@ -2696,6 +2731,7 @@ dependencies = [
"percent-encoding",
"rand 0.8.5",
"raw-window-handle",
"rfd",
"semver",
"serde",
"serde_json",
@@ -3251,6 +3287,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.87"
@@ -3406,6 +3454,19 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647"
dependencies = [
"windows_aarch64_msvc 0.37.0",
"windows_i686_gnu 0.37.0",
"windows_i686_msvc 0.37.0",
"windows_x86_64_gnu 0.37.0",
"windows_x86_64_msvc 0.37.0",
]
[[package]]
name = "windows"
version = "0.39.0"
@@ -3512,6 +3573,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
[[package]]
name = "windows_aarch64_msvc"
version = "0.39.0"
@@ -3530,6 +3597,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
[[package]]
name = "windows_i686_gnu"
version = "0.39.0"
@@ -3548,6 +3621,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
[[package]]
name = "windows_i686_msvc"
version = "0.39.0"
@@ -3566,6 +3645,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.39.0"
@@ -3596,6 +3681,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.39.0"
+1 -1
View File
@@ -17,7 +17,7 @@ tauri-build = { version = "1.4.0", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.4.0", features = ["fs-all"] }
tauri = { version = "1.4.0", features = [ "dialog-all", "clipboard-write-text", "fs-all"] }
midir = "0.9.1"
tokio = { version = "1.29.0", features = ["full"] }
rosc = "0.10.1"
+7 -1
View File
@@ -3,7 +3,7 @@
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:3000",
"devPath": "http://localhost:4321",
"distDir": "../website/dist",
"withGlobalTauri": true
},
@@ -13,6 +13,12 @@
},
"tauri": {
"allowlist": {
"dialog": {
"all": true
},
"clipboard": {
"writeText": true
},
"all": false,
"fs": {
"all": true,
+379 -101
View File
@@ -633,6 +633,15 @@ exports[`runs examples > example "addVoicings" example index 0 1`] = `
]
`;
exports[`runs examples > example "adsr" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c3 s:sawtooth cutoff:600 ]",
"[ 1/1 → 2/1 | note:bb2 s:sawtooth cutoff:600 ]",
"[ 2/1 → 3/1 | note:f3 s:sawtooth cutoff:600 ]",
"[ 3/1 → 4/1 | note:eb3 s:sawtooth cutoff:600 ]",
]
`;
exports[`runs examples > example "almostAlways" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:hh speed:0.5 ]",
@@ -1071,6 +1080,31 @@ exports[`runs examples > example "chooseCycles" example index 1 1`] = `
]
`;
exports[`runs examples > example "chooseWith" example index 0 1`] = `
[
"[ 0/1 → 1/5 | note:c2 s:bd n:6 ]",
"[ 1/5 → 2/5 | note:g2 s:sawtooth ]",
"[ 2/5 → 3/5 | note:g2 s:triangle ]",
"[ 3/5 → 4/5 | note:d2 s:bd n:6 ]",
"[ 4/5 → 1/1 | note:f1 s:sawtooth ]",
"[ 1/1 → 6/5 | note:c2 s:bd n:6 ]",
"[ 6/5 → 7/5 | note:g2 s:sawtooth ]",
"[ 7/5 → 8/5 | note:g2 s:triangle ]",
"[ 8/5 → 9/5 | note:d2 s:bd n:6 ]",
"[ 9/5 → 2/1 | note:f1 s:sawtooth ]",
"[ 2/1 → 11/5 | note:c2 s:bd n:6 ]",
"[ 11/5 → 12/5 | note:g2 s:sawtooth ]",
"[ 12/5 → 13/5 | note:g2 s:triangle ]",
"[ 13/5 → 14/5 | note:d2 s:bd n:6 ]",
"[ 14/5 → 3/1 | note:f1 s:sawtooth ]",
"[ 3/1 → 16/5 | note:c2 s:bd n:6 ]",
"[ 16/5 → 17/5 | note:g2 s:sawtooth ]",
"[ 17/5 → 18/5 | note:g2 s:triangle ]",
"[ 18/5 → 19/5 | note:d2 s:bd n:6 ]",
"[ 19/5 → 4/1 | note:f1 s:sawtooth ]",
]
`;
exports[`runs examples > example "chop" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:rhodes begin:0.75 end:1 speed:0.25 unit:c ]",
@@ -1081,27 +1115,6 @@ exports[`runs examples > example "chop" example index 0 1`] = `
`;
exports[`runs examples > example "chunk" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:A4 ]",
"[ 1/4 → 1/2 | note:B3 ]",
"[ 1/2 → 3/4 | note:C4 ]",
"[ 3/4 → 1/1 | note:D4 ]",
"[ 1/1 → 5/4 | note:A3 ]",
"[ 5/4 → 3/2 | note:B3 ]",
"[ 3/2 → 7/4 | note:C4 ]",
"[ 7/4 → 2/1 | note:D5 ]",
"[ 2/1 → 9/4 | note:A3 ]",
"[ 9/4 → 5/2 | note:B3 ]",
"[ 5/2 → 11/4 | note:C5 ]",
"[ 11/4 → 3/1 | note:D4 ]",
"[ 3/1 → 13/4 | note:A3 ]",
"[ 13/4 → 7/2 | note:B4 ]",
"[ 7/2 → 15/4 | note:C4 ]",
"[ 15/4 → 4/1 | note:D4 ]",
]
`;
exports[`runs examples > example "chunkBack" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:A4 ]",
"[ 1/4 → 1/2 | note:B3 ]",
@@ -1122,6 +1135,27 @@ exports[`runs examples > example "chunkBack" example index 0 1`] = `
]
`;
exports[`runs examples > example "chunkBack" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:A4 ]",
"[ 1/4 → 1/2 | note:B3 ]",
"[ 1/2 → 3/4 | note:C4 ]",
"[ 3/4 → 1/1 | note:D4 ]",
"[ 1/1 → 5/4 | note:A3 ]",
"[ 5/4 → 3/2 | note:B3 ]",
"[ 3/2 → 7/4 | note:C4 ]",
"[ 7/4 → 2/1 | note:D5 ]",
"[ 2/1 → 9/4 | note:A3 ]",
"[ 9/4 → 5/2 | note:B3 ]",
"[ 5/2 → 11/4 | note:C5 ]",
"[ 11/4 → 3/1 | note:D4 ]",
"[ 3/1 → 13/4 | note:A3 ]",
"[ 13/4 → 7/2 | note:B4 ]",
"[ 7/2 → 15/4 | note:C4 ]",
"[ 15/4 → 4/1 | note:D4 ]",
]
`;
exports[`runs examples > example "clip" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano clip:0.5 ]",
@@ -1185,6 +1219,35 @@ exports[`runs examples > example "compress" example index 0 1`] = `
]
`;
exports[`runs examples > example "compressor" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 0/1 → 1/2 | s:bd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 1/4 → 1/2 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 1/2 → 3/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 1/2 → 1/1 | s:sd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 3/4 → 1/1 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 1/1 → 5/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 1/1 → 3/2 | s:bd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 5/4 → 3/2 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 3/2 → 7/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 3/2 → 2/1 | s:sd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 7/4 → 2/1 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 2/1 → 9/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 2/1 → 5/2 | s:bd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 9/4 → 5/2 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 5/2 → 11/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 5/2 → 3/1 | s:sd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 11/4 → 3/1 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 3/1 → 13/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 3/1 → 7/2 | s:bd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 13/4 → 7/2 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 7/2 → 15/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 7/2 → 4/1 | s:sd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
"[ 15/4 → 4/1 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 ]",
]
`;
exports[`runs examples > example "cosine" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:Eb4 ]",
@@ -1794,6 +1857,19 @@ exports[`runs examples > example "fast" example index 0 1`] = `
]
`;
exports[`runs examples > example "fastChunk" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:C2 s:folkharp ]",
"[ 1/2 → 1/1 | note:D2 s:folkharp ]",
"[ 1/1 → 3/2 | note:E2 s:folkharp ]",
"[ 3/2 → 2/1 | note:F2 s:folkharp ]",
"[ 2/1 → 5/2 | note:G2 s:folkharp ]",
"[ 5/2 → 3/1 | note:A2 s:folkharp ]",
"[ 3/1 → 7/2 | note:B2 s:folkharp ]",
"[ 7/2 → 4/1 | note:C3 s:folkharp ]",
]
`;
exports[`runs examples > example "fastGap" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd ]",
@@ -2067,6 +2143,15 @@ exports[`runs examples > example "freq" example index 1 1`] = `
]
`;
exports[`runs examples > example "fscope" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:sawtooth analyze:1 ]",
"[ 1/1 → 2/1 | s:sawtooth analyze:1 ]",
"[ 2/1 → 3/1 | s:sawtooth analyze:1 ]",
"[ 3/1 → 4/1 | s:sawtooth analyze:1 ]",
]
`;
exports[`runs examples > example "ftype" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c2 s:sawtooth cutoff:500 bpenv:4 ftype:12db ]",
@@ -3221,6 +3306,23 @@ exports[`runs examples > example "perlin" example index 0 1`] = `
]
`;
exports[`runs examples > example "pick" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:g ]",
"[ 1/2 → 1/1 | note:a ]",
"[ 1/1 → 3/2 | note:e ]",
"[ 3/2 → 2/1 | note:f ]",
"[ 2/1 → 9/4 | note:f ]",
"[ 9/4 → 5/2 | note:g ]",
"[ 5/2 → 11/4 | note:f ]",
"[ 11/4 → 3/1 | note:g ]",
"[ 3/1 → 13/4 | note:g ]",
"[ 13/4 → 7/2 | note:a ]",
"[ 7/2 → 15/4 | note:c ]",
"[ 15/4 → 4/1 | note:d ]",
]
`;
exports[`runs examples > example "ply" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd ]",
@@ -3293,6 +3395,35 @@ exports[`runs examples > example "polymeterSteps" example index 0 1`] = `
]
`;
exports[`runs examples > example "postgain" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 0/1 → 1/2 | s:bd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 1/4 → 1/2 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 1/2 → 3/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 1/2 → 1/1 | s:sd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 3/4 → 1/1 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 1/1 → 5/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 1/1 → 3/2 | s:bd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 5/4 → 3/2 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 3/2 → 7/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 3/2 → 2/1 | s:sd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 7/4 → 2/1 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 2/1 → 9/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 2/1 → 5/2 | s:bd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 9/4 → 5/2 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 5/2 → 11/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 5/2 → 3/1 | s:sd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 11/4 → 3/1 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 3/1 → 13/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 3/1 → 7/2 | s:bd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 13/4 → 7/2 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 7/2 → 15/4 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 7/2 → 4/1 | s:sd compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
"[ 15/4 → 4/1 | s:hh compressor:-20 compressorRatio:20 compressorKnee:10 compressorAttack:0.002 compressorRelease:0.02 postgain:1.5 ]",
]
`;
exports[`runs examples > example "press" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:hh ]",
@@ -3537,6 +3668,27 @@ exports[`runs examples > example "release" example index 0 1`] = `
]
`;
exports[`runs examples > example "repeatCycles" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:42 s:gm_acoustic_guitar_nylon ]",
"[ 1/4 → 1/2 | note:38 s:gm_acoustic_guitar_nylon ]",
"[ 1/2 → 3/4 | note:35 s:gm_acoustic_guitar_nylon ]",
"[ 3/4 → 1/1 | note:38 s:gm_acoustic_guitar_nylon ]",
"[ 1/1 → 5/4 | note:42 s:gm_acoustic_guitar_nylon ]",
"[ 5/4 → 3/2 | note:38 s:gm_acoustic_guitar_nylon ]",
"[ 3/2 → 7/4 | note:35 s:gm_acoustic_guitar_nylon ]",
"[ 7/4 → 2/1 | note:38 s:gm_acoustic_guitar_nylon ]",
"[ 2/1 → 9/4 | note:42 s:gm_acoustic_guitar_nylon ]",
"[ 9/4 → 5/2 | note:36 s:gm_acoustic_guitar_nylon ]",
"[ 5/2 → 11/4 | note:39 s:gm_acoustic_guitar_nylon ]",
"[ 11/4 → 3/1 | note:41 s:gm_acoustic_guitar_nylon ]",
"[ 3/1 → 13/4 | note:42 s:gm_acoustic_guitar_nylon ]",
"[ 13/4 → 7/2 | note:36 s:gm_acoustic_guitar_nylon ]",
"[ 7/2 → 15/4 | note:39 s:gm_acoustic_guitar_nylon ]",
"[ 15/4 → 4/1 | note:41 s:gm_acoustic_guitar_nylon ]",
]
`;
exports[`runs examples > example "reset" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:hh ]",
@@ -3898,6 +4050,42 @@ exports[`runs examples > example "samples" example index 1 1`] = `
]
`;
exports[`runs examples > example "samples" example index 2 1`] = `
[
"[ 0/1 → 1/2 | s:noise ]",
"[ 1/2 → 3/4 | s:chimp n:0 ]",
"[ 3/4 → 1/1 | s:chimp n:0 ]",
"[ 1/1 → 3/2 | s:noise ]",
"[ 3/2 → 2/1 | s:chimp n:1 ]",
"[ 2/1 → 5/2 | s:noise ]",
"[ 5/2 → 11/4 | s:chimp n:0 ]",
"[ 11/4 → 3/1 | s:chimp n:0 ]",
"[ 3/1 → 7/2 | s:noise ]",
"[ 7/2 → 4/1 | s:chimp n:1 ]",
]
`;
exports[`runs examples > example "samples" example index 3 1`] = `
[
"[ 0/1 → 1/4 | s:chocolat ]",
"[ 1/4 → 1/2 | s:chocolat ]",
"[ 1/2 → 3/4 | s:chocolat ]",
"[ 3/4 → 1/1 | s:chocolat ]",
"[ 1/1 → 5/4 | s:chocolat ]",
"[ 5/4 → 3/2 | s:chocolat ]",
"[ 3/2 → 7/4 | s:chocolat ]",
"[ 7/4 → 2/1 | s:chocolat ]",
"[ 2/1 → 9/4 | s:chocolat ]",
"[ 9/4 → 5/2 | s:chocolat ]",
"[ 5/2 → 11/4 | s:chocolat ]",
"[ 11/4 → 3/1 | s:chocolat ]",
"[ 3/1 → 13/4 | s:chocolat ]",
"[ 13/4 → 7/2 | s:chocolat ]",
"[ 7/2 → 15/4 | s:chocolat ]",
"[ 15/4 → 4/1 | s:chocolat ]",
]
`;
exports[`runs examples > example "saw" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c3 clip:0.03125 ]",
@@ -3942,96 +4130,96 @@ exports[`runs examples > example "saw" example index 1 1`] = `
exports[`runs examples > example "scale" example index 0 1`] = `
[
"[ 0/1 → 1/6 | n:0 note:C3 ]",
"[ 1/6 → 1/3 | n:2 note:E3 ]",
"[ 1/3 → 1/2 | n:4 note:G3 ]",
"[ 1/2 → 2/3 | n:6 note:B3 ]",
"[ 2/3 → 5/6 | n:4 note:G3 ]",
"[ 5/6 → 1/1 | n:2 note:E3 ]",
"[ 1/1 → 7/6 | n:0 note:C3 ]",
"[ 7/6 → 4/3 | n:2 note:E3 ]",
"[ 4/3 → 3/2 | n:4 note:G3 ]",
"[ 3/2 → 5/3 | n:6 note:B3 ]",
"[ 5/3 → 11/6 | n:4 note:G3 ]",
"[ 11/6 → 2/1 | n:2 note:E3 ]",
"[ 2/1 → 13/6 | n:0 note:C3 ]",
"[ 13/6 → 7/3 | n:2 note:E3 ]",
"[ 7/3 → 5/2 | n:4 note:G3 ]",
"[ 5/2 → 8/3 | n:6 note:B3 ]",
"[ 8/3 → 17/6 | n:4 note:G3 ]",
"[ 17/6 → 3/1 | n:2 note:E3 ]",
"[ 3/1 → 19/6 | n:0 note:C3 ]",
"[ 19/6 → 10/3 | n:2 note:E3 ]",
"[ 10/3 → 7/2 | n:4 note:G3 ]",
"[ 7/2 → 11/3 | n:6 note:B3 ]",
"[ 11/3 → 23/6 | n:4 note:G3 ]",
"[ 23/6 → 4/1 | n:2 note:E3 ]",
"[ 0/1 → 1/6 | note:C3 ]",
"[ 1/6 → 1/3 | note:E3 ]",
"[ 1/3 → 1/2 | note:G3 ]",
"[ 1/2 → 2/3 | note:B3 ]",
"[ 2/3 → 5/6 | note:G3 ]",
"[ 5/6 → 1/1 | note:E3 ]",
"[ 1/1 → 7/6 | note:C3 ]",
"[ 7/6 → 4/3 | note:E3 ]",
"[ 4/3 → 3/2 | note:G3 ]",
"[ 3/2 → 5/3 | note:B3 ]",
"[ 5/3 → 11/6 | note:G3 ]",
"[ 11/6 → 2/1 | note:E3 ]",
"[ 2/1 → 13/6 | note:C3 ]",
"[ 13/6 → 7/3 | note:E3 ]",
"[ 7/3 → 5/2 | note:G3 ]",
"[ 5/2 → 8/3 | note:B3 ]",
"[ 8/3 → 17/6 | note:G3 ]",
"[ 17/6 → 3/1 | note:E3 ]",
"[ 3/1 → 19/6 | note:C3 ]",
"[ 19/6 → 10/3 | note:E3 ]",
"[ 10/3 → 7/2 | note:G3 ]",
"[ 7/2 → 11/3 | note:B3 ]",
"[ 11/3 → 23/6 | note:G3 ]",
"[ 23/6 → 4/1 | note:E3 ]",
]
`;
exports[`runs examples > example "scale" example index 1 1`] = `
[
"[ 0/1 → 1/4 | n:0 note:C3 s:piano ]",
"[ 0/1 → 1/4 | n:7 note:C4 s:piano ]",
"[ 1/4 → 1/2 | n:4 note:G3 s:piano ]",
"[ 1/2 → 3/4 | n:2 note:E3 s:piano ]",
"[ 1/2 → 3/4 | n:7 note:C4 s:piano ]",
"[ 3/4 → 1/1 | n:4 note:G3 s:piano ]",
"[ 1/1 → 5/4 | n:0 note:C3 s:piano ]",
"[ 1/1 → 5/4 | n:7 note:C4 s:piano ]",
"[ 5/4 → 3/2 | n:4 note:G3 s:piano ]",
"[ 3/2 → 7/4 | n:2 note:E3 s:piano ]",
"[ 3/2 → 7/4 | n:7 note:C4 s:piano ]",
"[ 7/4 → 2/1 | n:4 note:G3 s:piano ]",
"[ 2/1 → 9/4 | n:0 note:C3 s:piano ]",
"[ 2/1 → 9/4 | n:7 note:C4 s:piano ]",
"[ 9/4 → 5/2 | n:4 note:G3 s:piano ]",
"[ 5/2 → 11/4 | n:2 note:Eb3 s:piano ]",
"[ 5/2 → 11/4 | n:7 note:C4 s:piano ]",
"[ 11/4 → 3/1 | n:4 note:G3 s:piano ]",
"[ 3/1 → 13/4 | n:0 note:C3 s:piano ]",
"[ 3/1 → 13/4 | n:7 note:C4 s:piano ]",
"[ 13/4 → 7/2 | n:4 note:G3 s:piano ]",
"[ 7/2 → 15/4 | n:2 note:Eb3 s:piano ]",
"[ 7/2 → 15/4 | n:7 note:C4 s:piano ]",
"[ 15/4 → 4/1 | n:4 note:G3 s:piano ]",
"[ 0/1 → 1/4 | note:C3 s:piano ]",
"[ 0/1 → 1/4 | note:C4 s:piano ]",
"[ 1/4 → 1/2 | note:G3 s:piano ]",
"[ 1/2 → 3/4 | note:E3 s:piano ]",
"[ 1/2 → 3/4 | note:C4 s:piano ]",
"[ 3/4 → 1/1 | note:G3 s:piano ]",
"[ 1/1 → 5/4 | note:C3 s:piano ]",
"[ 1/1 → 5/4 | note:C4 s:piano ]",
"[ 5/4 → 3/2 | note:G3 s:piano ]",
"[ 3/2 → 7/4 | note:E3 s:piano ]",
"[ 3/2 → 7/4 | note:C4 s:piano ]",
"[ 7/4 → 2/1 | note:G3 s:piano ]",
"[ 2/1 → 9/4 | note:C3 s:piano ]",
"[ 2/1 → 9/4 | note:C4 s:piano ]",
"[ 9/4 → 5/2 | note:G3 s:piano ]",
"[ 5/2 → 11/4 | note:Eb3 s:piano ]",
"[ 5/2 → 11/4 | note:C4 s:piano ]",
"[ 11/4 → 3/1 | note:G3 s:piano ]",
"[ 3/1 → 13/4 | note:C3 s:piano ]",
"[ 3/1 → 13/4 | note:C4 s:piano ]",
"[ 13/4 → 7/2 | note:G3 s:piano ]",
"[ 7/2 → 15/4 | note:Eb3 s:piano ]",
"[ 7/2 → 15/4 | note:C4 s:piano ]",
"[ 15/4 → 4/1 | note:G3 s:piano ]",
]
`;
exports[`runs examples > example "scale" example index 2 1`] = `
[
"[ 0/1 → 1/8 | n:10 note:C5 s:folkharp ]",
"[ 1/8 → 1/4 | n:2 note:F3 s:folkharp ]",
"[ 1/4 → 3/8 | n:7 note:F4 s:folkharp ]",
"[ 3/8 → 1/2 | n:4 note:A3 s:folkharp ]",
"[ 1/2 → 5/8 | n:2 note:F3 s:folkharp ]",
"[ 5/8 → 3/4 | n:5 note:C4 s:folkharp ]",
"[ 3/4 → 7/8 | n:9 note:A4 s:folkharp ]",
"[ 7/8 → 1/1 | n:8 note:G4 s:folkharp ]",
"[ 1/1 → 9/8 | n:7 note:F4 s:folkharp ]",
"[ 9/8 → 5/4 | n:1 note:D3 s:folkharp ]",
"[ 5/4 → 11/8 | n:1 note:D3 s:folkharp ]",
"[ 11/8 → 3/2 | n:6 note:D4 s:folkharp ]",
"[ 3/2 → 13/8 | n:2 note:F3 s:folkharp ]",
"[ 13/8 → 7/4 | n:4 note:A3 s:folkharp ]",
"[ 7/4 → 15/8 | n:6 note:D4 s:folkharp ]",
"[ 15/8 → 2/1 | n:10 note:C5 s:folkharp ]",
"[ 2/1 → 17/8 | n:4 note:A3 s:folkharp ]",
"[ 17/8 → 9/4 | n:0 note:C3 s:folkharp ]",
"[ 9/4 → 19/8 | n:8 note:G4 s:folkharp ]",
"[ 19/8 → 5/2 | n:2 note:F3 s:folkharp ]",
"[ 5/2 → 21/8 | n:7 note:F4 s:folkharp ]",
"[ 21/8 → 11/4 | n:6 note:D4 s:folkharp ]",
"[ 11/4 → 23/8 | n:11 note:D5 s:folkharp ]",
"[ 23/8 → 3/1 | n:3 note:G3 s:folkharp ]",
"[ 3/1 → 25/8 | n:0 note:C3 s:folkharp ]",
"[ 25/8 → 13/4 | n:11 note:D5 s:folkharp ]",
"[ 13/4 → 27/8 | n:4 note:A3 s:folkharp ]",
"[ 27/8 → 7/2 | n:9 note:A4 s:folkharp ]",
"[ 7/2 → 29/8 | n:10 note:C5 s:folkharp ]",
"[ 29/8 → 15/4 | n:12 note:F5 s:folkharp ]",
"[ 15/4 → 31/8 | n:1 note:D3 s:folkharp ]",
"[ 31/8 → 4/1 | n:4 note:A3 s:folkharp ]",
"[ 0/1 → 1/8 | note:C5 s:folkharp ]",
"[ 1/8 → 1/4 | note:F3 s:folkharp ]",
"[ 1/4 → 3/8 | note:F4 s:folkharp ]",
"[ 3/8 → 1/2 | note:A3 s:folkharp ]",
"[ 1/2 → 5/8 | note:F3 s:folkharp ]",
"[ 5/8 → 3/4 | note:C4 s:folkharp ]",
"[ 3/4 → 7/8 | note:A4 s:folkharp ]",
"[ 7/8 → 1/1 | note:G4 s:folkharp ]",
"[ 1/1 → 9/8 | note:F4 s:folkharp ]",
"[ 9/8 → 5/4 | note:D3 s:folkharp ]",
"[ 5/4 → 11/8 | note:D3 s:folkharp ]",
"[ 11/8 → 3/2 | note:D4 s:folkharp ]",
"[ 3/2 → 13/8 | note:F3 s:folkharp ]",
"[ 13/8 → 7/4 | note:A3 s:folkharp ]",
"[ 7/4 → 15/8 | note:D4 s:folkharp ]",
"[ 15/8 → 2/1 | note:C5 s:folkharp ]",
"[ 2/1 → 17/8 | note:A3 s:folkharp ]",
"[ 17/8 → 9/4 | note:C3 s:folkharp ]",
"[ 9/4 → 19/8 | note:G4 s:folkharp ]",
"[ 19/8 → 5/2 | note:F3 s:folkharp ]",
"[ 5/2 → 21/8 | note:F4 s:folkharp ]",
"[ 21/8 → 11/4 | note:D4 s:folkharp ]",
"[ 11/4 → 23/8 | note:D5 s:folkharp ]",
"[ 23/8 → 3/1 | note:G3 s:folkharp ]",
"[ 3/1 → 25/8 | note:C3 s:folkharp ]",
"[ 25/8 → 13/4 | note:D5 s:folkharp ]",
"[ 13/4 → 27/8 | note:A3 s:folkharp ]",
"[ 27/8 → 7/2 | note:A4 s:folkharp ]",
"[ 7/2 → 29/8 | note:C5 s:folkharp ]",
"[ 29/8 → 15/4 | note:F5 s:folkharp ]",
"[ 15/4 → 31/8 | note:D3 s:folkharp ]",
"[ 31/8 → 4/1 | note:A3 s:folkharp ]",
]
`;
@@ -4056,6 +4244,15 @@ exports[`runs examples > example "scaleTranspose" example index 0 1`] = `
]
`;
exports[`runs examples > example "scope" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:sawtooth analyze:1 ]",
"[ 1/1 → 2/1 | s:sawtooth analyze:1 ]",
"[ 2/1 → 3/1 | s:sawtooth analyze:1 ]",
"[ 3/1 → 4/1 | s:sawtooth analyze:1 ]",
]
`;
exports[`runs examples > example "segment" example index 0 1`] = `
[
"[ 0/1 → 1/24 | note:40.25 ]",
@@ -4493,6 +4690,25 @@ exports[`runs examples > example "square" example index 0 1`] = `
]
`;
exports[`runs examples > example "squeeze" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:g ]",
"[ 1/1 → 2/1 | note:a ]",
"[ 2/1 → 17/8 | note:f ]",
"[ 17/8 → 9/4 | note:g ]",
"[ 9/4 → 19/8 | note:f ]",
"[ 19/8 → 5/2 | note:g ]",
"[ 5/2 → 21/8 | note:f ]",
"[ 21/8 → 11/4 | note:g ]",
"[ 11/4 → 23/8 | note:f ]",
"[ 23/8 → 3/1 | note:g ]",
"[ 3/1 → 13/4 | note:g ]",
"[ 13/4 → 7/2 | note:a ]",
"[ 7/2 → 15/4 | note:c ]",
"[ 15/4 → 4/1 | note:d ]",
]
`;
exports[`runs examples > example "squiz" example index 0 1`] = `
[
"[ 0/1 → 1/4 | squiz:2 s:bd ]",
@@ -4564,6 +4780,23 @@ exports[`runs examples > example "stack" example index 0 2`] = `
]
`;
exports[`runs examples > example "striate" example index 0 1`] = `
[
"[ 0/1 → 1/3 | s:numbers n:0 begin:0 end:0.16666666666666666 ]",
"[ 1/3 → 2/3 | s:numbers n:1 begin:0 end:0.16666666666666666 ]",
"[ 2/3 → 1/1 | s:numbers n:2 begin:0 end:0.16666666666666666 ]",
"[ 1/1 → 4/3 | s:numbers n:0 begin:0.16666666666666666 end:0.3333333333333333 ]",
"[ 4/3 → 5/3 | s:numbers n:1 begin:0.16666666666666666 end:0.3333333333333333 ]",
"[ 5/3 → 2/1 | s:numbers n:2 begin:0.16666666666666666 end:0.3333333333333333 ]",
"[ 2/1 → 7/3 | s:numbers n:0 begin:0.3333333333333333 end:0.5 ]",
"[ 7/3 → 8/3 | s:numbers n:1 begin:0.3333333333333333 end:0.5 ]",
"[ 8/3 → 3/1 | s:numbers n:2 begin:0.3333333333333333 end:0.5 ]",
"[ 3/1 → 10/3 | s:numbers n:0 begin:0.5 end:0.6666666666666666 ]",
"[ 10/3 → 11/3 | s:numbers n:1 begin:0.5 end:0.6666666666666666 ]",
"[ 11/3 → 4/1 | s:numbers n:2 begin:0.5 end:0.6666666666666666 ]",
]
`;
exports[`runs examples > example "struct" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c3 ]",
@@ -5020,6 +5253,51 @@ exports[`runs examples > example "withValue" example index 0 1`] = `
]
`;
exports[`runs examples > example "xfade" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:hh gain:0 ]",
"[ 0/1 → 1/2 | s:bd gain:1 ]",
"[ 1/8 → 1/4 | s:hh gain:0 ]",
"[ 1/4 → 3/8 | s:hh gain:0 ]",
"[ 3/8 → 1/2 | s:hh gain:0 ]",
"[ 1/2 → 5/8 | s:hh gain:0 ]",
"[ 1/2 → 1/1 | s:bd gain:1 ]",
"[ 5/8 → 3/4 | s:hh gain:0 ]",
"[ 3/4 → 7/8 | s:hh gain:0 ]",
"[ 7/8 → 1/1 | s:hh gain:0 ]",
"[ 1/1 → 9/8 | s:hh gain:0.5 ]",
"[ 1/1 → 3/2 | s:bd gain:1 ]",
"[ 9/8 → 5/4 | s:hh gain:0.5 ]",
"[ 5/4 → 11/8 | s:hh gain:0.5 ]",
"[ 11/8 → 3/2 | s:hh gain:0.5 ]",
"[ 3/2 → 13/8 | s:hh gain:0.5 ]",
"[ 3/2 → 2/1 | s:bd gain:1 ]",
"[ 13/8 → 7/4 | s:hh gain:0.5 ]",
"[ 7/4 → 15/8 | s:hh gain:0.5 ]",
"[ 15/8 → 2/1 | s:hh gain:0.5 ]",
"[ 2/1 → 17/8 | s:hh gain:1 ]",
"[ 2/1 → 5/2 | s:bd gain:1 ]",
"[ 17/8 → 9/4 | s:hh gain:1 ]",
"[ 9/4 → 19/8 | s:hh gain:1 ]",
"[ 19/8 → 5/2 | s:hh gain:1 ]",
"[ 5/2 → 21/8 | s:hh gain:1 ]",
"[ 5/2 → 3/1 | s:bd gain:1 ]",
"[ 21/8 → 11/4 | s:hh gain:1 ]",
"[ 11/4 → 23/8 | s:hh gain:1 ]",
"[ 23/8 → 3/1 | s:hh gain:1 ]",
"[ 3/1 → 25/8 | s:hh gain:1 ]",
"[ 3/1 → 7/2 | s:bd gain:0.5 ]",
"[ 25/8 → 13/4 | s:hh gain:1 ]",
"[ 13/4 → 27/8 | s:hh gain:1 ]",
"[ 27/8 → 7/2 | s:hh gain:1 ]",
"[ 7/2 → 29/8 | s:hh gain:1 ]",
"[ 7/2 → 4/1 | s:bd gain:0.5 ]",
"[ 29/8 → 15/4 | s:hh gain:1 ]",
"[ 15/4 → 31/8 | s:hh gain:1 ]",
"[ 31/8 → 4/1 | s:hh gain:1 ]",
]
`;
exports[`runs examples > example "zoom" example index 0 1`] = `
[
"[ 0/1 → 1/6 | s:hh ]",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -228,5 +228,5 @@ export const testCycles = {
festivalOfFingers3: 16,
};
// fixed: https://strudel.tidalcycles.org/?DBp75NUfSxIn (missing .note())
// bug: https://strudel.tidalcycles.org/?xHaKTd1kTpCn + https://strudel.tidalcycles.org/?o5LLePbx8kiQ
// fixed: https://strudel.cc/?DBp75NUfSxIn (missing .note())
// bug: https://strudel.cc/?xHaKTd1kTpCn + https://strudel.cc/?o5LLePbx8kiQ
+1 -1
View File
@@ -4,7 +4,7 @@ import data from './dbdump.json';
describe('renders shared tunes', async () => {
data.forEach(({ id, code, hash }) => {
const url = `https://strudel.tidalcycles.org/?${hash}`;
const url = `https://strudel.cc/?${hash}`;
it(`shared tune ${id} ${url}`, async ({ expect }) => {
if (code.includes('import(')) {
console.log('skip', url);
+2 -2
View File
@@ -1,6 +1,6 @@
# Strudel Website
This is the website for Strudel, deployed at [strudel.tidalcycles.org](https://strudel.tidalcycles.org/).
This is the website for Strudel, deployed at [strudel.cc](https://strudel.cc).
It includes the REPL live coding editor and the documentation site.
## Run locally
@@ -56,7 +56,7 @@ All commands are run from the root of the project, from a terminal:
| Command | Action |
| :--------------------- | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
+1 -1
View File
@@ -1,7 +1,7 @@
/*
Strudel - javascript-based environment for live coding algorithmic (musical) patterns
https://strudel.tidalcycles.org / https://github.com/tidalcycles/strudel/
https://strudel.cc / https://github.com/tidalcycles/strudel/
Copyright (C) Strudel contributors
https://github.com/tidalcycles/strudel/graphs/contributors
+2 -1
View File
@@ -11,7 +11,7 @@ import tailwind from '@astrojs/tailwind';
import AstroPWA from '@vite-pwa/astro';
// import { visualizer } from 'rollup-plugin-visualizer';
const site = `https://strudel.tidalcycles.org/`; // root url without a path
const site = `https://strudel.cc/`; // root url without a path
const base = '/'; // base path of the strudel site
// this rehype plugin converts relative anchor links to absolute ones
@@ -50,6 +50,7 @@ export default defineConfig({
mdx(options),
tailwind(),
AstroPWA({
experimental: { directoryAndTrailingSlashHandler: true },
registerType: 'autoUpdate',
injectRegister: 'auto',
workbox: {
+9 -8
View File
@@ -13,9 +13,9 @@
},
"dependencies": {
"@algolia/client-search": "^4.17.0",
"@astrojs/mdx": "^0.19.0",
"@astrojs/react": "^2.1.1",
"@astrojs/tailwind": "^3.1.1",
"@astrojs/mdx": "^1.1.3",
"@astrojs/react": "^3.0.4",
"@astrojs/tailwind": "^5.0.2",
"@docsearch/css": "^3.3.4",
"@docsearch/react": "^3.3.4",
"@headlessui/react": "^1.7.14",
@@ -28,13 +28,14 @@
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/osc": "workspace:*",
"@strudel.cycles/react": "workspace:*",
"@strudel/codemirror": "workspace:*",
"@strudel.cycles/serial": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*",
"@strudel/hydra": "workspace:*",
"@strudel/codemirror": "workspace:*",
"@strudel/desktopbridge": "workspace:*",
"@supabase/supabase-js": "^2.21.0",
"@tailwindcss/forms": "^0.5.3",
@@ -44,7 +45,7 @@
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@uiw/codemirror-themes-all": "^4.19.16",
"astro": "^2.3.2",
"astro": "^3.4.2",
"canvas": "^2.11.2",
"claviature": "^0.1.0",
"fraction.js": "^4.2.0",
@@ -59,9 +60,9 @@
"tailwindcss": "^3.3.2"
},
"devDependencies": {
"@vite-pwa/astro": "^0.0.5",
"@vite-pwa/astro": "^0.1.4",
"html-escaper": "^3.0.3",
"vite-plugin-pwa": "^0.14.7",
"workbox-window": "^6.5.4"
"vite-plugin-pwa": "^0.16.5",
"workbox-window": "^7.0.0"
}
}
+1 -1
View File
@@ -1 +1 @@
strudel.tidalcycles.org
strudel.cc
+10 -5
View File
@@ -6,7 +6,7 @@ export const SITE = {
export const OPEN_GRAPH = {
image: {
src: 'https://strudel.tidalcycles.org/icon.png',
src: 'https://strudel.cc/icon.png',
alt: 'Strudel Logo',
},
};
@@ -70,14 +70,13 @@ export const SIDEBAR: Sidebar = {
{ text: 'MIDI & OSC', link: 'learn/input-output' },
],
More: [
{ text: 'Recipes', link: 'recipes/recipes' },
{ text: 'Mini-Notation', link: 'learn/mini-notation' },
{ text: 'Coding syntax', link: 'learn/code' },
{ text: 'Offline', link: 'learn/pwa' },
{ text: 'Patterns', link: 'technical-manual/patterns' },
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
{ text: 'Music metadata', link: 'learn/metadata' },
{ text: 'CSound', link: 'learn/csound' },
{ text: 'Hydra', link: 'learn/hydra' },
],
'Pattern Functions': [
{ text: 'Introduction', link: 'functions/intro' },
@@ -89,7 +88,13 @@ export const SIDEBAR: Sidebar = {
{ text: 'Accumulation', link: 'learn/accumulation' },
{ text: 'Tonal Functions', link: 'learn/tonal' },
],
Understand: [{ text: 'Pitch', link: 'understand/pitch' }],
Understand: [
{ text: 'Coding syntax', link: 'learn/code' },
{ text: 'Pitch', link: 'understand/pitch' },
{ text: 'Cycles', link: 'understand/cycles' },
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
],
Development: [
{ text: 'REPL', link: 'technical-manual/repl' },
{ text: 'Sounds', link: 'technical-manual/sounds' },
+7 -7
View File
@@ -1,26 +1,26 @@
.cm-activeLine,
.cm-activeLineGutter {
.mini-repl .cm-activeLine,
.mini-repl .cm-activeLineGutter {
background-color: transparent !important;
}
.cm-theme {
.mini-repl .cm-theme {
background-color: var(--background);
border: 1px solid var(--lineHighlight);
padding: 2px;
}
.cm-scroller {
.mini-repl .cm-scroller {
font-family: inherit !important;
}
.cm-gutters {
.mini-repl .cm-gutters {
display: none !important;
}
.cm-cursorLayer {
.mini-repl .cm-cursorLayer {
animation-name: inherit !important;
}
.cm-cursor {
.mini-repl .cm-cursor {
border-left: 2px solid currentcolor !important;
}
+2 -1
View File
@@ -20,6 +20,7 @@ if (typeof window !== 'undefined') {
import('@strudel.cycles/osc'),
import('@strudel.cycles/csound'),
import('@strudel.cycles/soundfonts'),
import('@strudel/hydra'),
);
}
@@ -50,7 +51,7 @@ export function MiniRepl({
.catch((err) => console.error(err));
}, []);
return Repl ? (
<div className="mb-4">
<div className="mb-4 mini-repl">
<Repl
tune={tune}
hideOutsideView={true}
@@ -22,7 +22,7 @@ in der Muster eine Rolle spielen.
Du brauchst keine Erfahrung in JavaScript oder Tidal Cycles um mit Strudel Musik zu machen.
Dieser interaktive Workshop leitet dich spielerisch durch die Grundlagen von Strudel.
Der beste Ort um mit Strudel Musik zu machen ist das [Strudel REPL](https://strudel.tidalcycles.org/).
Der beste Ort um mit Strudel Musik zu machen ist das [Strudel REPL](https://strudel.cc/).
## Was kann man mit Strudel machen?
@@ -66,7 +66,7 @@ Hier ist ein Beispiel wie Strudel klingen kann:
Mehr Beispiele gibt es [hier](/examples).
Du kannst auch im [Strudel REPL](https://strudel.tidalcycles.org/) auf `shuffle` klicken um ein zufälliges Beispiel zu hören.
Du kannst auch im [Strudel REPL](https://strudel.cc/) auf `shuffle` klicken um ein zufälliges Beispiel zu hören.
## Workshop
@@ -60,4 +60,12 @@ import { JsDoc } from '../../docs/JsDoc';
<JsDoc client:idle name="invert" h={0} />
## pick
<JsDoc client:idle name="pick" h={0} />
## squeeze
<JsDoc client:idle name="squeeze" h={0} />
After Conditional Modifiers, let's see what [Accumulation Modifiers](/learn/accumulation) have to offer.
+20
View File
@@ -82,6 +82,10 @@ Strudel uses ADSR envelopes, which are probably the most common way to describe
<JsDoc client:idle name="release" h={0} />
## adsr
<JsDoc client:idle name="adsr" h={0} />
# Filter Envelope
Each filter can receive an additional filter envelope controlling the cutoff value dynamically. It uses an ADSR envelope similar to the one used for amplitude. There is an additional parameter to control the depth of the filter modulation: `lpenv`|`hpenv`|`bpenv`. This allows you to play subtle or huge filter modulations just the same by only increasing or decreasing the depth.
@@ -144,6 +148,18 @@ There is one filter envelope for each filter type and thus one set of envelope f
<JsDoc client:idle name="velocity" h={0} />
## compressor
<JsDoc client:idle name="compressor" h={0} />
## postgain
<JsDoc client:idle name="postgain" h={0} />
## xfade
<JsDoc client:idle name="xfade" h={0} />
# Panning
## jux
@@ -219,4 +235,8 @@ global effects use the same chain for all events of the same orbit:
<JsDoc client:idle name="roomdim" h={0} />
### iresponse
<JsDoc client:idle name="iresponse" h={0} />
Next, we'll look at strudel's support for [Csound](/learn/csound).
+4 -4
View File
@@ -10,11 +10,11 @@ import { JsDoc } from '../../docs/JsDoc';
Welcome to the Strudel documentation pages!
These pages will introduce you to [Strudel](https://strudel.tidalcycles.org/), a web-based [live coding](https://github.com/toplap/awesome-livecoding/) environment that implements the [Tidal Cycles](https://tidalcycles.org) algorithmic pattern language.
These pages will introduce you to [Strudel](https://strudel.cc/), a web-based [live coding](https://github.com/toplap/awesome-livecoding/) environment that implements the [Tidal Cycles](https://tidalcycles.org) algorithmic pattern language.
# What is Strudel?
[Strudel](https://strudel.tidalcycles.org/) is a version of [Tidal Cycles](https://tidalcycles.org) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), initiated by [Alex McLean](https://slab.org) and [Felix Roos](https://github.com/felixroos) in 2022.
[Strudel](https://strudel.cc/) is a version of [Tidal Cycles](https://tidalcycles.org) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), initiated by [Alex McLean](https://slab.org) and [Felix Roos](https://github.com/felixroos) in 2022.
Tidal Cycles, also known as Tidal, is a language for [algorithmic pattern](https://algorithmicpattern.org), and though it is most commonly used for [making music](https://tidalcycles.org/docs/showcase), it can be used for any kind of pattern making activity, including [weaving](https://www.youtube.com/watch?v=TfEmEsusXjU).
Tidal was first implemented as a library written in the [Haskell](https://www.haskell.org/) functional programming language, and by itself it does not make any sound.
@@ -24,7 +24,7 @@ Strudel however runs directly in your web browser, does not require any custom s
# Strudel REPL and MiniREPL
The main place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/) ([what is a REPL?](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)), but in these pages you will also encounter interactive "MiniREPLs" where you can listen to and edit Strudel patterns.
The main place to actually make music with Strudel is the [Strudel REPL](https://strudel.cc/) ([what is a REPL?](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)), but in these pages you will also encounter interactive "MiniREPLs" where you can listen to and edit Strudel patterns.
Try clicking the play icon below:
<MiniRepl client:idle tune={`s("bd sd")`} punchcard />
@@ -38,7 +38,7 @@ This interactive tutorial will guide you through the basics of Strudel.
# Show me some demos!
To see and hear what Strudel can do, visit the [Strudel REPL](https://strudel.tidalcycles.org/) and click the Shuffle icon in the top menu bar.
To see and hear what Strudel can do, visit the [Strudel REPL](https://strudel.cc/) and click the Shuffle icon in the top menu bar.
You can get a feel for Strudel by browsing and editing these examples and clicking the Refresh icon to update.
You can also browse through the examples [here](./examples).
+55
View File
@@ -0,0 +1,55 @@
---
title: Hydra
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
# Using Hydra inside Strudel
You can write [hydra](https://hydra.ojack.xyz/) code in strudel! All you have to do is to call `await initHydra()` at the top:
<MiniRepl
client:only="react"
tune={`await initHydra()
// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Zach Krall
// http://zachkrall.online/
osc(10, 0.9, 300)
.color(0.9, 0.7, 0.8)
.diff(
osc(45, 0.3, 100)
.color(0.9, 0.9, 0.9)
.rotate(0.18)
.pixelate(12)
.kaleid()
)
.scrollX(10)
.colorama()
.luma()
.repeatX(4)
.repeatY(4)
.modulate(
osc(1, -0.9, 300)
)
.scale(2)
.out()
note("[a,c,e,<a4 ab4 g4 gb4>,b4]/4").s("sawtooth").vib(2)
.lpf(600).lpa(2).lpenv(6)
`}
/>
There is a special function `H` that allows you to use a pattern as an input to hydra:
<MiniRepl
client:only="react"
tune={`await initHydra()
let pattern = "3 4 5 [6 7]*2"
shape(H(pattern)).out(o0)
n(pattern).scale("A:minor").piano().room(1)
`}
/>
You might now be able to see this properly here: [open in REPL](/#YXdhaXQgaW5pdEh5ZHJhKCkKbGV0IHBhdHRlcm4gPSAiMyA0IDUgWzYgN10qMiIKc2hhcGUoSChwYXR0ZXJuKSkub3V0KG8wKQpuKHBhdHRlcm4pLnNjYWxlKCJBOm1pbm9yIikucGlhbm8oKS5yb29tKDEpIA%3D%3D)
+1 -1
View File
@@ -71,7 +71,7 @@ Now you're all set!
## Usage
1. Start SuperCollider, either using SuperCollider IDE or by running `sclang` in a terminal
2. Open the [Strudel REPL](https://strudel.tidalcycles.org/#cygiYmQgc2QiKS5vc2MoKQ%3D%3D)
2. Open the [Strudel REPL](https://strudel.cc/#cygiYmQgc2QiKS5vc2MoKQ%3D%3D)
...or test it here:
+1 -1
View File
@@ -18,7 +18,7 @@ You can optionally add some music metadata in your Strudel code, by using tags i
Like other comments, those are ignored by Strudel, but it can be used by other tools to retrieve some information about the music.
It is for instance used by the [swatch tool](https://github.com/tidalcycles/strudel/tree/main/my-patterns) to display pattern titles in the [examples page](https://strudel.tidalcycles.org/examples/).
It is for instance used by the [swatch tool](https://github.com/tidalcycles/strudel/tree/main/my-patterns) to display pattern titles in the [examples page](https://strudel.cc/examples/).
## Alternative syntax
+1 -1
View File
@@ -13,7 +13,7 @@ Just like [Tidal Cycles](https://tidalcycles.org/), Strudel uses a so called "Mi
## Note
This page just explains the entirety of the Mini-Notation syntax.
If you are just getting started with Strudel, you can learn the basics of the Mini-Notation in a more practical manner in the [workshop](http://localhost:3000/workshop/first-sounds).
If you are just getting started with Strudel, you can learn the basics of the Mini-Notation in a more practical manner in the [workshop](/workshop/first-sounds).
After that, you can come back here if you want to understand every little detail.
## Example
+6 -6
View File
@@ -5,7 +5,7 @@ layout: ../../layouts/MainLayout.astro
# Using Strudel Offline
You can use Strudel even without a network! When you first visit the [Strudel REPL](strudel.tidalcycles.org/),
You can use Strudel even without a network! When you first visit the [Strudel REPL](https://strudel.cc/),
your browser will download the whole web app including documentation.
When the download is finished (&lt;1MB), you can visit the website even when offline,
getting the downloaded website instead of the online one.
@@ -32,7 +32,7 @@ You can view all cached files in your browser.
### Firefox
- Open the Developer Tools (`Tools > Web Developer > Web Developer Tools`)
- go to `Storage` tab and expand `Cache Storage > https://strudel.tidalcycles.org`.
- go to `Storage` tab and expand `Cache Storage > https://strudel.cc`.
- or go to the `Application` tab and view the latest updates in `Service Workers`
### Chromium based Browsers
@@ -57,14 +57,14 @@ without the browser ui.
With a chromium based browser:
1. go to the [Strudel REPL](strudel.tidalcycles.org/).
1. go to the [Strudel REPL](https://strudel.cc).
2. on the right of the adress bar, click `install Strudel REPL`
3. the REPL should now run as a standalone chromium app
Without a chromium based browser, you can use [nativefier](https://github.com/nativefier/nativefier) to generate a desktop app:
1. make sure you have NodeJS installed
2. run `npx nativefier strudel.tidalcycles.org`
2. run `npx nativefier strudel.cc`
<figure>
<img src="./pwa/strudel-linux.png" alt="Strudel on Linux" />
@@ -73,13 +73,13 @@ Without a chromium based browser, you can use [nativefier](https://github.com/na
### iOS
1. open to the [Strudel REPL](strudel.tidalcycles.org/) in safari
1. open to the [Strudel REPL](https://strudel.cc/) in safari
2. press the share icon and tab `Add to homescreen`
3. You should now have a strudel app icon that opens the repl in full screen
### Android
1. open to the [Strudel REPL](strudel.tidalcycles.org/)
1. open to the [Strudel REPL](https://strudel.cc/)
2. Tab the install button at the bottom
Ok, what are [Patterns](/technical-manual/patterns) all about?
+19 -2
View File
@@ -46,7 +46,7 @@ For drum sounds, strudel uses the comprehensive [tidal-drum-machines](https://gi
Furthermore, strudel also loads instrument samples from [VCSL](https://github.com/sgossner/VCSL) by default.
To see which sample names are available, open the `sounds` tab in the [REPL](https://strudel.tidalcycles.org/).
To see which sample names are available, open the `sounds` tab in the [REPL](https://strudel.cc/).
Note that only the sample maps (mapping names to URLs) are loaded initially, while the audio samples themselves are not loaded until they are actually played.
This behaviour of loading things only when they are needed is also called `lazy loading`.
@@ -283,7 +283,7 @@ With it, you can enter any sample name(s) to query from [freesound.org](https://
<MiniRepl
client:idle
tune={`await samples('https://shabda.ndre.gr/bass:4,hihat:4,rimshot:2.json?strudel=1')
tune={`await samples('shabda:bass:4,hihat:4,rimshot:2')
stack(
n("0 1 2 3").s('bass').slow(2),
n("0 1*2 2 3*2").s('hihat'),
@@ -291,6 +291,19 @@ stack(
).clip(1)`}
/>
You can also generate artificial voice samples with any text, in multiple languages.
Note that the language code and the gender parameters are optional and default to `en-GB` and `f`
<MiniRepl
client:idle
tune={`await samples('shabda/speech:the_drum,forever')
await samples('shabda/speech/fr-FR/m:magnifique')
stack(
s("the_drum").chop(16).speed(rand.range(0.85,1.1)),
s("forever magnifique").slow(8).late(0.25)
)`}
/>
# Sampler Effects
Sampler effects are functions that can be used to change the behaviour of sample playback.
@@ -335,6 +348,10 @@ Sampler effects are functions that can be used to change the behaviour of sample
<JsDoc client:idle name="Pattern.chop" h={0} />
### striate
<JsDoc client:idle name="Pattern.striate" h={0} />
### slice
<JsDoc client:idle name="Pattern.slice" h={0} />
+1 -1
View File
@@ -106,7 +106,7 @@ You can find a [list of available effects here](./learn/effects).
### Sampler
Strudel's sampler supports [a subset](http://127.0.0.1:3000/learn/samples) of Superdirt's sampler.
Strudel's sampler supports [a subset](/learn/samples) of Superdirt's sampler.
Also, samples are always loaded from a URL rather than from the disk, although [that might be possible in the future](https://github.com/tidalcycles/strudel/issues/118).
## Evaluation
+2 -2
View File
@@ -7,7 +7,7 @@ import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
import { samples } from '@strudel.cycles/webaudio';
see https://strudel.tidalcycles.org?zMEo5kowGrFc
see https://strudel.cc/?zMEo5kowGrFc
# Microrhythms
@@ -73,4 +73,4 @@ This is the second example of the video:
s('hh').micro(0, 1/6, 2/5, 2/3, 3/4)`}
/>
with bass: https://strudel.tidalcycles.org?sTglgJJCPIeY
with bass: https://strudel.cc/?sTglgJJCPIeY
+312
View File
@@ -0,0 +1,312 @@
---
title: Recipes
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
# Recipes
This page shows possible ways to achieve common (or not so common) musical goals.
There are often many ways to do a thing and there is no right or wrong.
The fun part is that each representation will give you different impulses when improvising.
## Arpeggios
An arpeggio is when the notes of a chord are played in sequence.
We can either write the notes by hand:
<MiniRepl
client:visible
tune={`note("c eb g c4")
.clip(2).s("gm_electric_guitar_clean")`}
punchcard
/>
...or use scales:
<MiniRepl
client:visible
tune={`n("0 2 4 7").scale("C:minor")
.clip(2).s("gm_electric_guitar_clean")`}
punchcard
/>
...or chord symbols:
<MiniRepl
client:visible
tune={`n("0 1 2 3").chord("Cm").mode("above:c3").voicing()
.clip(2).s("gm_electric_guitar_clean")`}
punchcard
/>
...using off:
<MiniRepl
client:visible
tune={`"0"
.off(1/3, add(2))
.off(1/2, add(4))
.n()
.scale("C:minor")
.s("gm_electric_guitar_clean")`}
punchcard
/>
## Chopping Breaks
A sample can be looped and chopped like this:
<MiniRepl
client:visible
tune={`await samples('github:yaxu/clean-breaks/main')
s("amen/8").fit().chop(16)`}
punchcard
/>
This fits the break into 8 cycles + chops it in 16 pieces.
The chops are not audible yet, because we're not doing any manipulation.
Let's add randmized doubling + reversing:
<MiniRepl
client:visible
tune={`await samples('github:yaxu/clean-breaks/main')
s("amen/8").fit().chop(16).cut(1)
.sometimesBy(.5, ply(2))
.sometimesBy(.25, mul(speed(-1)))`}
punchcard
/>
If we want to specify the order of samples, we can replace `chop` with `slice`:
<MiniRepl
client:visible
tune={`await samples('github:yaxu/clean-breaks/main')
s("amen/8").fit()
.slice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
.cut(1).rarely(ply(2))`}
punchcard
/>
If we use `splice` instead of `slice`, the speed adjusts to the duration of the event:
<MiniRepl
client:visible
tune={`await samples('github:yaxu/clean-breaks/main')
s("amen")
.splice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
.cut(1).rarely(ply(2))`}
punchcard
/>
Note that we don't need `fit`, because `splice` will do that by itself.
## Filter Envelopes
A minimal filter envelope looks like this:
<MiniRepl
client:visible
tune={`note("g1 bb1 <c2 eb2> d2")
.s("sawtooth")
.lpf(400).lpa(.2).lpenv(4)
.scope()`}
/>
We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`:
<MiniRepl
client:visible
tune={`note("g1 bb1 <c2 eb2> d2")
.s("sawtooth").lpq(8)
.lpf(400).lpa(.2).lpenv(-4)
.scope()`}
/>
## Layering Sounds
We can layer sounds by separating them with ",":
<MiniRepl
client:visible
tune={`note("<g1 bb1 d2 f1>")
.s("sawtooth, square") // <------
.scope()`}
/>
We can control the gain of individual sounds like this:
<MiniRepl
client:visible
tune={`note("<g1 bb1 d2 f1>")
.s("sawtooth, square:0:.5") // <--- "name:number:gain"
.scope()`}
/>
For more control over each voice, we can use `layer`:
<MiniRepl
client:visible
tune={`note("<g1 bb1 d2 f1>").layer(
x=>x.s("sawtooth").vib(4),
x=>x.s("square").add(note(12))
).scope()`}
/>
Here, we give the sawtooth a vibrato and the square is moved an octave up.
With `layer`, you can use any pattern method available on each voice, so sky is the limit..
## Oscillator Detune
We can fatten a sound by adding a detuned version to itself:
<MiniRepl
client:visible
tune={`note("<g1 bb1 d2 f1>")
.add(note("0,.1")) // <------ chorus
.s("sawtooth").scope()`}
punchcard
/>
Try out different values, or add another voice!
## Polyrhythms
Here is a simple example of a polyrhythm:
<MiniRepl client:visible tune={`s("bd*2,hh*3")`} punchcard />
A polyrhythm is when 2 different tempos happen at the same time.
## Polymeter
This is a polymeter:
<MiniRepl client:visible tune={`s("<bd rim>,<hh hh oh>").fast(2)`} punchcard />
A polymeter is when 2 different bar lengths play at the same tempo.
## Phasing
This is a phasing:
<MiniRepl client:visible tune={`note("<C D G A Bb D C A G D Bb A>*[6,6.1]").piano()`} punchcard />
Phasing happens when the same sequence plays at slightly different tempos.
## Running through samples
Using `run` with `n`, we can rush through a sample bank:
<MiniRepl
client:visible
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
n(run(8)).s("ftabla")`}
punchcard
/>
This works great with sample banks that contain similar sounds, like in this case different recordings of a tabla.
Often times, you'll hear the beginning of the phrase not where the pattern begins.
In this case, I hear the beginning at the third sample, which can be accounted for with `early`.
<MiniRepl
client:visible
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
n(run(8)).s("ftabla").early(2/8)`}
/>
Let's add some randomness:
<MiniRepl
client:visible
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
n(run(8)).s("ftabla").early(2/8)
.sometimes(mul(speed(1.5)))`}
/>
## Tape Warble
We can emulate a pitch warbling effect like this:
<MiniRepl
client:visible
tune={`note("c4 bb f eb")
.add(note(perlin.range(0,.5))) // <------ warble
.clip(2).s("gm_electric_guitar_clean")`}
/>
## Sound Duration
There are a number of ways to change the sound duration. Using clip:
<MiniRepl
client:visible
tune={`note("f ab bb c")
.clip("<2 1 .5 .25>/2")`}
/>
The value of clip is relative to the duration of each event.
We can also create overlaps using release:
<MiniRepl
client:visible
tune={`note("f ab bb c")
.release("<2 1 .5 .002>/2")`}
/>
This will smoothly fade out each sound for the given number of seconds.
We could also make the notes shorter with decay / sustain:
<MiniRepl
client:visible
tune={`note("f ab bb c")
.decay("<.2 .1 .02>/2").sustain(0)`}
/>
For now, there is a limitation where decay values that exceed the event duration may cause little cracks, so use higher numbers with caution..
When using samples, we also have `.end` to cut relative to the sample length:
<MiniRepl client:visible tune={`s("oh*4").end("<1 .5 .25 .1>")`} />
Compare that to clip:
<MiniRepl client:visible tune={`s("oh*4").clip("<1 .5 .25 .1>")`} />
or decay / sustain
<MiniRepl client:visible tune={`s("oh*4").decay("<.2 .12 .06 .01>").sustain(0)`} />
## Wavetable Synthesis
You can loop a sample with `loop` / `loopEnd`:
<MiniRepl client:visible tune={`note("<c eb g f>").s("bd").loop(1).loopEnd(.05).gain(.2)`} />
This allows us to play the first 5% of the bass drum as a synth!
To simplify loading wavetables, any sample that starts with `wt_` will be looped automatically:
<MiniRepl
client:visible
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
note("c eb g bb").s("wt_dbass").clip(2)`}
/>
Running through different wavetables can also give interesting variations:
<MiniRepl
client:visible
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
note("c2*8").s("wt_dbass").n(run(8))`}
/>
...adding a filter envelope + reverb:
<MiniRepl
client:visible
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
note("c2*8").s("wt_dbass").n(run(8))
.lpf(perlin.range(200,2000).slow(8))
.lpenv(-3).lpa(.1).room(.5)`}
/>
+1 -1
View File
@@ -9,7 +9,7 @@ The docs page is built ontop of astro's [docs site](https://github.com/withastro
## Adding a new Docs Page
1. add a `.mdx` file in a path under `website/src/pages/`, e.g. [website/src/pages/learn/code.mdx](https://raw.githubusercontent.com/tidalcycles/strudel/main/website/src/pages/learn/code.mdx) will be available under https://strudel.tidalcycles.org/learn/code (or locally under `http://localhost:3000/learn/code`)
1. add a `.mdx` file in a path under `website/src/pages/`, e.g. [website/src/pages/learn/code.mdx](https://raw.githubusercontent.com/tidalcycles/strudel/main/website/src/pages/learn/code.mdx) will be available under https://strudel.cc/learn/code (or locally under `http://localhost:4321/learn/code`)
2. make sure to copy the top part of another existing docs page. Adjust the title accordingly
3. To add a link to the sidebar, add a new entry to `SIDEBAR` to [`config.ts`](https://github.com/tidalcycles/strudel/blob/main/website/src/config.ts)
+1 -1
View File
@@ -7,7 +7,7 @@ import { MiniRepl } from '../../docs/MiniRepl';
# REPL
{/* The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library. */}
{/* The [REPL](https://strudel.cc/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library. */}
While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the Strudel REPL^[REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive programming interface from computing heritage, usually for a commandline interface but also applied to live coding editors.], which is a browser-based live coding environment. This live code editor is dedicated to manipulating Strudel patterns while they play. The REPL features built-in visual feedback, highlighting which elements in the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is designed to support both learning and live use of Strudel.
@@ -64,7 +64,7 @@ registerSound(
freq(220, 440, 330).s('mysaw');
```
You can actually use this code in the [REPL](https://strudel.tidalcycles.org/) and it'll work.
You can actually use this code in the [REPL](https://strudel.cc/) and it'll work.
After evaluating the code, you should see `mysaw` in listed in the sounds tab.
## Playing sounds
+130
View File
@@ -0,0 +1,130 @@
---
title: Understanding Cycles
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { PitchSlider } from '../../components/PitchSlider';
import Box from '@components/Box.astro';
# Understanding Cycles
The concept of cycles is very central to be able to understand how Strudel works.
Strudel's mother language, TidalCycles, even has it in its name.
## Cycles and BPM
In most music software, the unit BPM (beats per minute) is used to set the tempo.
Strudel expresses tempo as CPS (cycles per second), with a default of 1CPS:
<MiniRepl client:visible tune={`s("bd")`} />
Here we can hear the 1CPS in action: The kick repeats once per second like a clock.
We could say 1CPS = 1BPS (beats per second) = 60BPM. Let's add another kick:
<MiniRepl client:visible tune={`s("bd bd")`} />
Now we have 2 kicks per second, but the whole pattern still plays at 1CPS.
In terms of BPM, most musicians would tell you this is playing at 120bpm.
What about this one:
<MiniRepl client:visible tune={`s("bd hh")`} />
Because the second sound is now a hihat, the tempo feels slower again.
This brings us to an important realization:
<Box>
Tempo is based on perception.
The choice of sounds also has an impact on the tempo feel.
This is why the same CPS can produce different perceived tempos.
</Box>
## Setting CPM
If you're familiar with BPM, you can use the `cpm` method to set the tempo in cycles per minute:
<MiniRepl client:visible tune={`s("bd hh").cpm(110)`} />
If you want to add more beats per cycle, you might want to divide the cpm:
<MiniRepl client:visible tune={`s("bd sd bd rim, hh*8").cpm(110/4)`} />
Or using 2 beats per cycle:
<MiniRepl client:visible tune={`s("bd sd, hh*4").cpm(110/2)`} />
<Box>
To set a specific bpm, use `.cpm(bpm/bpc)`
- bpm: the target beats per minute
- bpc: the number of perceived beats per cycle
</Box>
## Cycles and Bars
Also in most music software, multiple beats form a bar (or measure).
The so called time signature specifies how many beats are in each bar.
In many types of music, it is common to use 4 beats per bar, also known as 4/4 time.
Many music programs use it as a default.
Strudel does not a have concept of bars or measures, there are only cycles.
How you use them is up to you. Above, we've had this example:
<MiniRepl client:visible tune={`s("bd sd bd rim, hh*8").cpm(110/4)`} />
This could be interpreted as 4/4 time with a tempo of 110bpm.
We could write out multiple bars like this:
<MiniRepl
client:visible
tune={`s(\`<
[bd sd bd rim, hh*8]
[bd sd bd rim*2, hh*8]
>\`).cpm(110/4)`}
/>
Instead of writing out each bar separately, we could express this much shorter:
<MiniRepl client:visible tune={`s("bd <sd rim*<1 2>>,hh*4").cpm(110/2)`} />
Here we can see that thinking in cycles rather than bars simplifies things a lot!
These types of simplifications work because of the repetitive nature of rhythm.
In computational terms, you could say the former notation has a lot of redundancy.
## Time Signatures
To get a time signature, just change the number of elements per bar. Here is a rhythm with 7 beats:
<MiniRepl client:visible tune={`s("bd ~ rim bd bd rim ~")`} />
or with 5:
<MiniRepl client:visible tune={`s("<bd hh hh bd hh hh bd rim bd hh>*5")`} />
We could also write multiple bars with different time signatures:
<MiniRepl
client:visible
tune={`s(\`<
[bd hh rim]@3
[bd hh rim sd]@4
>\`).cpm(110*2)`}
/>
Here we switch between 3/4 and 4/4, keeping the same tempo.
If we don't specify the length, we get what's called a metric modulation:
<MiniRepl
client:visible
tune={`s(\`<
[bd hh rim]
[bd hh rim sd]
>\`).cpm(110/2)`}
/>
Now the 3 elements get the same time as the 4 elements, which is why the tempo changes.
@@ -18,7 +18,7 @@ With Strudel, you can expressively write dynamic music pieces.<br/>
It is an official port of the [Tidal Cycles](https://tidalcycles.org/) pattern language to JavaScript.<br/>
You don't need to know JavaScript or Tidal Cycles to make music with Strudel.
This interactive tutorial will guide you through the basics of Strudel.<br/>
The best place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/)
The best place to actually make music with Strudel is the [Strudel REPL](https://strudel.cc/)
<div className="clear-both" />
@@ -62,7 +62,7 @@ Here is an example of how strudel can sound:
.slow(3/2)`}
/>
To hear more, go to the [Strudel REPL](https://strudel.tidalcycles.org/) and press shuffle to hear a random example pattern.
To hear more, go to the [Strudel REPL](https://strudel.cc/) and press shuffle to hear a random example pattern.
## Getting Started
+7 -1
View File
@@ -385,6 +385,7 @@ function SettingsTab({ scheduler }) {
keybindings,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isTooltipEnabled,
isLineWrappingEnabled,
fontSize,
fontFamily,
@@ -436,7 +437,7 @@ function SettingsTab({ scheduler }) {
<ButtonGroup
value={keybindings}
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }}
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs', vscode: 'VSCode' }}
></ButtonGroup>
</FormItem>
<FormItem label="Panel Position">
@@ -457,6 +458,11 @@ function SettingsTab({ scheduler }) {
onChange={(cbEvent) => settingsMap.setKey('isAutoCompletionEnabled', cbEvent.target.checked)}
value={isAutoCompletionEnabled}
/>
<Checkbox
label="Enable tooltips on Ctrl and hover"
onChange={(cbEvent) => settingsMap.setKey('isTooltipEnabled', cbEvent.target.checked)}
value={isTooltipEnabled}
/>
<Checkbox
label="Enable line wrapping"
onChange={(cbEvent) => settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)}
+23 -3
View File
@@ -3,17 +3,31 @@ const visibleFunctions = jsdocJson.docs
.filter(({ name, description }) => name && !name.startsWith('_') && !!description)
.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
const getInnerText = (html) => {
var div = document.createElement('div');
div.innerHTML = html;
return div.textContent || div.innerText || '';
};
export function Reference() {
return (
<div className="flex h-full w-full pt-2 text-foreground overflow-hidden">
<div className="w-42 flex-none h-full overflow-y-auto overflow-x-hidden pr-4">
{visibleFunctions.map((entry, i) => (
<a key={i} className="cursor-pointer block hover:bg-lineHighlight py-1 px-4" href={`#doc-${i}`}>
<a
key={i}
className="cursor-pointer block hover:bg-lineHighlight py-1 px-4"
onClick={() => {
const el = document.getElementById(`doc-${i}`);
const container = document.getElementById('reference-container');
container.scrollTo(0, el.offsetTop);
}}
>
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
</a>
))}
</div>
<div className="break-normal w-full h-full overflow-auto pl-4 flex relative">
<div className="break-normal w-full h-full overflow-auto pl-4 flex relative" id="reference-container">
<div className="prose dark:prose-invert max-w-full pr-4">
<h2>API Reference</h2>
<p>
@@ -24,8 +38,14 @@ export function Reference() {
<section key={i}>
<h3 id={`doc-${i}`}>{entry.name}</h3>
{/* <small>{entry.meta.filename}</small> */}
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
<ul>
{entry.params?.map(({ name, type, description }, i) => (
<li key={i}>
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
</li>
))}
</ul>
{entry.examples?.map((example, j) => (
<pre key={j}>{example}</pre>
))}
+13 -5
View File
@@ -23,6 +23,7 @@ import { settingPatterns } from '../settings.mjs';
import { code2hash, hash2code } from './helpers.mjs';
import { isTauri } from '../tauri.mjs';
import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs';
import { writeText } from '@tauri-apps/api/clipboard';
const { latestCode } = settingsMap.get();
@@ -41,7 +42,7 @@ let modules = [
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel/codemirror'),
import('@strudel/hydra'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/csound'),
@@ -78,9 +79,9 @@ async function initCode() {
const initialUrl = window.location.href;
const hash = initialUrl.split('?')[1]?.split('#')?.[0];
const codeParam = window.location.href.split('#')[1] || '';
// looking like https://strudel.tidalcycles.org/?J01s5i1J0200 (fixed hash length)
// looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length)
if (codeParam) {
// looking like https://strudel.tidalcycles.org/#ImMzIGUzIg%3D%3D (hash length depends on code length)
// looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length)
return hash2code(codeParam);
} else if (hash) {
return supabase
@@ -125,8 +126,10 @@ export function Repl({ embedded = false }) {
fontFamily,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isTooltipEnabled,
isLineWrappingEnabled,
panelPosition,
isZen,
} = useSettings();
const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]);
@@ -269,7 +272,11 @@ export function Repl({ embedded = false }) {
if (!error) {
setLastShared(activeCode || code);
// copy shareUrl to clipboard
await navigator.clipboard.writeText(shareUrl);
if (isTauri()) {
await writeText(shareUrl);
} else {
await navigator.clipboard.writeText(shareUrl);
}
const message = `Link copied to clipboard: ${shareUrl}`;
alert(message);
// alert(message);
@@ -322,13 +329,14 @@ export function Repl({ embedded = false }) {
</button>
)}
<div className="grow flex relative overflow-hidden">
<section className="text-gray-100 cursor-text pb-0 overflow-auto grow" id="code">
<section className={'text-gray-100 cursor-text pb-0 overflow-auto grow' + (isZen ? ' px-10' : '')} id="code">
<CodeMirror
theme={currentTheme}
value={code}
keybindings={keybindings}
isLineNumbersDisplayed={isLineNumbersDisplayed}
isAutoCompletionEnabled={isAutoCompletionEnabled}
isTooltipEnabled={isTooltipEnabled}
isLineWrappingEnabled={isLineWrappingEnabled}
fontSize={fontSize}
fontFamily={fontFamily}
+1 -1
View File
@@ -23,7 +23,7 @@ angle(saw)
.animate({smear:0})
`;
// https://strudel.tidalcycles.org/?C31_NrcMfZEO
// https://strudel.cc/?C31_NrcMfZEO
export const spiralflower = `const {innerWidth:ww,innerHeight:wh} = window;
const ctx = getDrawContext()
const piDiv180 = Math.PI / 180;
+2
View File
@@ -7,6 +7,7 @@ export const defaultSettings = {
keybindings: 'codemirror',
isLineNumbersDisplayed: true,
isAutoCompletionEnabled: false,
isTooltipEnabled: false,
isLineWrappingEnabled: false,
theme: 'strudelTheme',
fontFamily: 'monospace',
@@ -26,6 +27,7 @@ export function useSettings() {
isZen: [true, 'true'].includes(state.isZen) ? true : false,
isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false,
isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false,
isTooltipEnabled: [true, 'true'].includes(state.isTooltipEnabled) ? true : false,
isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false,
fontSize: Number(state.fontSize),
panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom',