Compare commits

..

781 Commits

Author SHA1 Message Date
Felix Roos 2aa1acfd95 Publish
- @strudel/codemirror@1.1.0
 - @strudel/core@1.1.0
 - @strudel/csound@1.1.0
 - @strudel/draw@1.1.0
 - @strudel/embed@1.1.0
 - hs2js@0.1.0
 - @strudel/hydra@1.1.0
 - @strudel/midi@1.1.0
 - @strudel/mini@1.1.0
 - @strudel/osc@1.1.0
 - @strudel/repl@1.1.0
 - @strudel/sampler@0.1.0
 - @strudel/serial@1.1.0
 - @strudel/soundfonts@1.1.0
 - superdough@1.1.0
 - @strudel/tidal@0.1.0
 - @strudel/tonal@1.1.0
 - @strudel/transpiler@1.1.0
 - @strudel/web@1.1.0
 - @strudel/webaudio@1.1.0
 - @strudel/xen@1.1.0
2024-05-31 23:07:26 +02:00
Felix Roos 2febbaf40b hotfix: rename tidal-repl example 2024-05-31 23:03:44 +02:00
Felix Roos 3ce32438d6 Merge pull request #1122 from tidalcycles/migrate-tutorial-fanchor
Migrate tutorial fanchor
2024-05-31 12:17:34 +02:00
Felix Roos a17351e11e snapshots 2024-05-31 11:18:32 +02:00
Felix Roos 94f5886466 update some examples 2024-05-31 11:17:43 +02:00
Felix Roos 1a72b9e526 wording 2024-05-31 11:07:56 +02:00
Felix Roos 1eef2c2511 versioning 2024-05-31 11:07:52 +02:00
Felix Roos 05289b171b also add versioning in mini repl 2024-05-31 11:01:09 +02:00
Felix Roos 33322be948 Merge remote-tracking branch 'origin/main' into migrate-tutorial-fanchor 2024-05-31 10:47:25 +02:00
Felix Roos 6f7f415747 Merge pull request #1121 from daslyfe/worklet_race_condition_fix
[BUG FIX] Audio worklets sometimes dont load
2024-05-31 10:46:47 +02:00
Felix Roos 7a4902862d tweaks + logs 2024-05-31 10:45:06 +02:00
Felix Roos c20e84773b immediately remove click listener 2024-05-31 10:40:30 +02:00
Felix Roos 5c9a16bc88 simplify initAudio 2024-05-31 10:40:15 +02:00
Felix Roos 07f58c023f bring back promise cache
just in case loadWorklets is called more than once
2024-05-31 10:36:49 +02:00
Felix Roos 08f0825561 fix example 2024-05-31 10:34:45 +02:00
Felix Roos 2cda78d2db Merge pull request #1107 from daslyfe/fanchor
change fanchor to 0
2024-05-31 10:25:24 +02:00
Jade (Rose) Rowland ff85f2307f remove unessecary functions 2024-05-30 18:58:41 -04:00
Jade (Rose) Rowland af3d32a493 updated to return promise on first click 2024-05-30 18:52:28 -04:00
Jade (Rose) Rowland c0c54c21e3 remove unessecary export 2024-05-30 16:30:49 -04:00
Jade (Rose) Rowland 8be5e9d2eb fixed 2024-05-30 16:25:15 -04:00
Felix Roos 589dfaf8cc Merge pull request #1119 from tidalcycles/web-package-label-statements
fix: use full repl in web package
2024-05-30 14:39:30 +02:00
Felix Roos 3cd31b2290 fix: use full repl in web package 2024-05-30 14:36:28 +02:00
Felix Roos 65856409b6 Merge pull request #1117 from tidalcycles/strudelmirror-in-scope
add strudelMirror to scope
2024-05-30 09:36:32 +02:00
Felix Roos e408fedaf6 format 2024-05-30 09:34:46 +02:00
Felix Roos d424b118ff add a few helpers 2024-05-30 09:34:31 +02:00
Felix Roos 492878754a add strudelMirror to scope 2024-05-30 09:22:17 +02:00
Felix Roos d86a183194 Merge pull request #1112 from Enelg52/mouseXY
Add the mousex and mousey signal
2024-05-29 13:06:34 +02:00
Felix Roos 6325d726a5 Merge pull request #1116 from giohappy/fix_codemirror_example
Fixes drawPianoroll import in codemirror example
2024-05-29 13:02:00 +02:00
Felix Roos 553045e666 lockfile 2024-05-29 12:59:01 +02:00
Felix Roos 4733264fb3 Revert "updated lock file"
This reverts commit 61d795af31.
2024-05-29 12:58:09 +02:00
Enelg52 0efac42d92 Add mousex and mousey the Docs 2024-05-29 10:42:31 +02:00
Enelg52 be6634f919 Should fix the tests 2024-05-29 10:26:29 +02:00
Enelg52 2901369471 Added alias and jsdoc 2024-05-29 10:21:40 +02:00
giohappy 61d795af31 updated lock file 2024-05-29 10:15:50 +02:00
Felix Roos 2bae6e06c0 add simple default value versioning 2024-05-28 23:08:41 +02:00
Felix Roos d9214b91b6 Merge branch 'main' into fanchor 2024-05-28 22:48:58 +02:00
Felix Roos 5348a6b2d8 Merge pull request #1074 from cleary/patch-6
clarify `off` in pattern-effects.mdx
2024-05-28 22:43:46 +02:00
Felix Roos 35e9385f59 use dec instead of ds 2024-05-28 22:41:37 +02:00
Felix Roos bf5e66d69a fix: lint 2024-05-28 22:39:49 +02:00
Giovanni Allegri 3cacafff90 Fix drawPianoroll import in codemirror example 2024-05-28 09:40:49 +02:00
Felix Roos e2ce5ea001 Merge branch 'main' of https://github.com/tidalcycles/strudel 2024-05-27 08:24:30 +02:00
Jade (Rose) Rowland 8f7194144d Merge pull request #1114 from daslyfe/fixworklets
Fix audio worklets
2024-05-26 11:33:05 -04:00
Jade (Rose) Rowland 12cb5b0939 remove imports 2024-05-26 11:31:18 -04:00
Jade (Rose) Rowland 1ad118d877 rollback phaser 2024-05-26 11:22:27 -04:00
Jade (Rose) Rowland bf73610b0a Merge pull request #1110 from daslyfe/phasermodulation
Calculate phaser modulation phase based on time
2024-05-26 10:53:56 -04:00
Enelg52 8f1496cebe added condition to pass the tests 2024-05-26 11:18:43 +02:00
Enelg52 7efbee0646 Runned Prettier 2024-05-25 20:54:31 +02:00
Enelg52 aec7417c14 Add the mousex and mousey signal 2024-05-25 20:47:20 +02:00
Jade (Rose) Rowland 026cbe050c Merge branch 'main' into phasermodulation 2024-05-24 17:30:09 -04:00
Felix Roos 8710cd446b Merge branch 'main' of https://github.com/tidalcycles/strudel 2024-05-23 19:43:34 +02:00
Jade (Rose) Rowland cad27301db Merge pull request #1103 from daslyfe/ladder_filter
Add analog-style ladder filter
2024-05-23 09:06:01 -04:00
Jade (Rose) Rowland 98cb128fa2 remove unessecary comment 2024-05-23 00:01:22 -04:00
Jade (Rose) Rowland 9bb1039bbd works great 2024-05-22 23:57:04 -04:00
Felix Roos 7952dfa5e9 bump sampler 2024-05-22 12:53:59 +02:00
Felix Roos 1666fc5649 Merge pull request #1109 from geikha/fix-windows-sampler-2
fix sampler on windows
2024-05-22 12:53:20 +02:00
Felix Roos b19c737c82 spare some cycles 2024-05-22 12:52:15 +02:00
Renzo Torr- 2d3a9b800f fix sampler on windows 2024-05-21 17:54:41 -03:00
Jade (Rose) Rowland e60e9b2fdd fix default res 2024-05-21 13:53:08 -04:00
Jade (Rose) Rowland 94529549a9 revert default res 2024-05-21 00:51:49 -04:00
Jade (Rose) Rowland 90dfa5170c Merge branch 'ladder_filter' of github.com:daslyfe/strudel into ladder_filter 2024-05-21 00:39:16 -04:00
Jade (Rose) Rowland c797fea1f7 update resonance 2024-05-21 00:38:00 -04:00
Felix Roos 841d897d0a Merge pull request #1044 from tidalcycles/fix-import
web package fixes
2024-05-20 23:26:19 +02:00
Felix Roos 9774e9627b Merge branch 'main' into fix-import 2024-05-20 23:23:31 +02:00
Felix Roos 888e0d4751 Merge remote-tracking branch 'origin/main' into ladder_filter 2024-05-20 22:42:52 +02:00
Felix Roos 1595a4ece3 Merge pull request #1108 from tidalcycles/fix-sampler-windows
Fix sampler windows
2024-05-20 22:41:28 +02:00
Felix Roos 1cebfd1835 bump 2024-05-20 22:32:54 +02:00
Felix Roos 8ea50fbe7d Merge branch 'aif-support' into fix-sampler-windows 2024-05-20 22:32:46 +02:00
Felix Roos 349f902dc0 bump 2024-05-20 22:31:01 +02:00
Felix Roos 8b1df7ce85 fix: add node to eslint 2024-05-20 22:30:05 +02:00
Felix Roos 892a0abe78 probably fixes sampler on windows 2024-05-20 22:27:28 +02:00
Jade (Rose) Rowland a214b1b224 fill in remaining defaults
git push
2024-05-19 23:16:27 -04:00
Jade (Rose) Rowland eed4a83b71 add defaults and fanchor docs 2024-05-19 21:13:46 -04:00
Jade (Rose) Rowland 17e2ee4a3b fix test 2024-05-19 13:32:08 -04:00
Jade (Rose) Rowland b8085e9dc3 change fanchor to 0 2024-05-19 13:25:56 -04:00
Jade (Rose) Rowland 1952e4d49a add example 2024-05-19 13:25:16 -04:00
Jade (Rose) Rowland be65a10925 update comment 2024-05-19 13:23:08 -04:00
Jade (Rose) Rowland 255c283ae4 organize code 2024-05-19 13:19:28 -04:00
Jade (Rose) Rowland 84a8ca9d19 filter type can take in number also 2024-05-19 13:04:31 -04:00
Felix Roos 25064b427d Merge pull request #870 from tidalcycles/haskell-parser
hs2js package / tidal parser
2024-05-19 12:07:26 +02:00
Felix Roos 1761fa0346 total confusion 2024-05-19 11:55:36 +02:00
Felix Roos dd29c5b09e Merge branch 'haskell-parser' of https://github.com/tidalcycles/strudel into haskell-parser 2024-05-19 11:51:48 +02:00
Felix Roos ef331f25e0 reset?! 2024-05-19 11:51:35 +02:00
Felix Roos 1bc1346f54 try static dep 2024-05-19 11:50:15 +02:00
Felix Roos 373b63c700 fix: add back hs2js as workspace dep 2024-05-19 11:48:48 +02:00
Felix Roos 41444ccd58 Revert "remove hs2js dep from website package"
This reverts commit fe1e10297c.
2024-05-19 11:47:16 +02:00
Felix Roos fe1e10297c remove hs2js dep from website package 2024-05-19 11:45:15 +02:00
Felix Roos e483f4c3d3 remove aif support as it doesn't work 2024-05-19 00:24:42 +02:00
Felix Roos 574c5285b9 bump 2024-05-19 00:18:29 +02:00
Felix Roos 5e4eefa553 add aif to list of supported audio file endings 2024-05-19 00:17:51 +02:00
Felix Roos 8ac409f03c simplify fn 2024-05-19 00:08:17 +02:00
Felix Roos c4b3d48498 remove old comment 2024-05-19 00:06:41 +02:00
Felix Roos 8f86bbc6ba bump 2024-05-19 00:02:16 +02:00
Felix Roos 97568300cb fix: links 2024-05-19 00:01:58 +02:00
Jade (Rose) Rowland bce24980cb fix tests 2024-05-18 11:12:41 -04:00
Felix Roos 33ca43a234 Merge pull request #1104 from lukad/patch-1
fix little dub tune example
2024-05-18 10:49:08 +02:00
Luka Dornhecker d0ecf15f77 fix little dub tune example 2024-05-18 10:33:22 +02:00
Jade (Rose) Rowland e87055d583 fix import 2024-05-18 01:33:40 -04:00
Jade (Rose) Rowland 9b52807c7e documentation 2024-05-18 01:01:19 -04:00
Jade (Rose) Rowland a8eafbc309 documentation 2024-05-18 00:51:56 -04:00
Felix Roos b0edf74aee fix all the problems 2024-05-18 01:39:38 +02:00
Felix Roos a8dce19902 fix: dep 2024-05-18 01:25:19 +02:00
Felix Roos dcd828a117 fix: import 2024-05-18 01:24:20 +02:00
Felix Roos aa2466b2cc fix: lint 2024-05-18 00:49:46 +02:00
Felix Roos a94ddc7947 tidal highlighting (single line only for now) 2024-05-18 00:44:47 +02:00
Jade (Rose) Rowland e6f272dd0c stereo 2024-05-17 18:20:08 -04:00
Jade (Rose) Rowland 4160639d88 integrated with envelopes 2024-05-17 17:38:44 -04:00
Felix Roos 5fb3aadad9 cleanup / allow ops to target node types 2024-05-17 23:11:23 +02:00
Felix Roos 3b4ae44fb3 transpiler (for future reference) 2024-05-17 23:09:43 +02:00
Felix Roos 2499c4057c allow using tidal function as template literal
+ add d1...d9
2024-05-17 21:29:39 +02:00
Felix Roos 71f8c0745c update examples after making hs2js sync 2024-05-17 20:51:43 +02:00
Felix Roos d132344d11 bump 2024-05-17 20:48:11 +02:00
Felix Roos b4f1cedc20 update readme after making it sync 2024-05-17 20:44:44 +02:00
Felix Roos 5b89500acd fix: lint + format 2024-05-17 20:38:30 +02:00
Felix Roos a35e9aef0e add tidal package to website 2024-05-17 20:37:04 +02:00
Felix Roos fd950d4476 tidy tidal package
- move tidal repl to examples
- make hs2js synchronous
2024-05-17 20:36:41 +02:00
Felix Roos d108de40d0 rename 2024-05-17 18:41:43 +02:00
Felix Roos 5f8da04cd8 remove graphviz 2024-05-17 18:41:02 +02:00
Jade (Rose) Rowland def5bc858f Merge pull request #1102 from daslyfe/upload_individual_samples
Samples tab improvements
2024-05-17 08:43:58 -04:00
Felix Roos a9fa6f14fb add npmignore 2024-05-17 10:49:37 +02:00
Felix Roos 846c12806e attribution 2024-05-17 10:47:06 +02:00
Felix Roos 70e938b767 hs2js api docs 2024-05-17 10:42:59 +02:00
Felix Roos f54f80e698 bump version 2024-05-17 10:14:26 +02:00
Felix Roos 8c480807d2 remove prepare script for now 2024-05-17 10:11:22 +02:00
Felix Roos bbeb243cea replace postinstall with prepare for dev only 2024-05-17 10:05:18 +02:00
Felix Roos 0123f4e5f3 fix lint errors + cleanup 2024-05-17 10:02:14 +02:00
Felix Roos c232fb7dd5 move hs2js package here + improve readme 2024-05-17 10:00:00 +02:00
Felix Roos ed506cabb5 hs2js test 2024-05-17 09:09:31 +02:00
Jade (Rose) Rowland d49c9dce4f working 2024-05-17 00:27:34 -04:00
Jade (Rose) Rowland 16ad27406d sort samples in tabs 2024-05-16 17:10:41 -04:00
Jade (Rose) Rowland 803b5e56fc working 2024-05-16 16:49:28 -04:00
Felix Roos 29598d1744 fix: lint 2024-05-16 14:32:45 +02:00
Felix Roos eba7dec1a5 rename haskell -> tidal 2024-05-16 14:31:00 +02:00
Felix Roos 09e455e17e move haskell-tree-sitter-playground 2024-05-16 14:30:00 +02:00
Felix Roos 2aac085bb8 lint fix 2024-05-16 14:12:30 +02:00
Felix Roos 733d6ec673 pnpm i 2024-05-16 14:12:11 +02:00
Felix Roos 179931be88 fix lockfile: reset to main one 2024-05-16 14:10:42 +02:00
Felix Roos 9d5acc0f32 Merge branch 'main' into haskell-parser 2024-05-16 14:08:51 +02:00
Felix Roos 4ade86f75c hotfix: rebuild parser to remove logs 2024-05-16 10:59:57 +02:00
Kaspars Jaudzems e268802f3c OSC/SuperDirt fixes (#1093)
* If note is a midi note, and it's sent together with octave to SuperDirt, then that will be wrong. instead we should to midinote whenever sure that it's a midi note.
* mapbank to work like tidal's drumFrom
* roomsize is size in SuperDirt
* fix tidal-sniffer
* export `registerControl` (but see #1098)
2024-05-13 09:10:34 +01:00
Felix Roos 55c7f2b575 Merge pull request #1091 from kasparsj/use-sessionStorage
Use sessionStorage for viewingPatternData and activePattern
2024-05-13 06:31:20 +02:00
Kaspars Jaudzems a69563cb9b repl: set document.title from @title (#1090)
* fixes #811 set document.title from @title
2024-05-12 17:23:08 +01:00
Kaspars 33ebf08e81 simplify more 2024-05-07 15:20:59 +02:00
Kaspars 02ea4d1116 bug fix 2024-05-07 14:52:06 +02:00
Felix Roos 27d8bae671 Merge pull request #1089 from tidalcycles/fix-worklets
fix: missing events due to premature worklet cleanup
2024-05-07 14:29:22 +02:00
Kaspars 5dc2ecd741 cleanup 2024-05-07 12:55:52 +02:00
Kaspars 6e6a2439c3 simplify implementation 2024-05-07 12:54:33 +02:00
Kaspars 6249631a72 implement sessionAtom function (same api as persistenAtom, just used sessionStorage instead of localStorage) 2024-05-07 11:40:09 +02:00
Kaspars b69b9c37cd fix events, add comment 2024-05-07 11:01:02 +02:00
Kaspars 7f22040a0c use sessionStorage instead of localStorage 2024-05-07 10:44:58 +02:00
Felix Roos 956ceb8aa4 fix: missing events due to premature worklet cleanup 2024-05-06 21:29:15 +02:00
Alex McLean e47d67a9da Benchmarks (#1079)
* tactus-oriented benchmarks
* add benchmarks for individual example tunes
2024-05-03 10:52:56 +01:00
Felix Roos 8e2d883e86 Merge pull request #1086 from tidalcycles/fix-dough-csound-timing
fix: csound + dough timing
2024-05-03 08:46:51 +02:00
Felix Roos 8db443ba26 fix: csound + dough timing 2024-05-03 00:42:13 +02:00
Alex McLean 34e9d73908 Improve performance of ! (replicate) (#1084)
* Implement ! with weighted repeatCycles
* reimplement repeatCycles without array filling
2024-05-02 23:37:32 +01:00
Felix Roos deed379dba Merge pull request #1083 from tidalcycles/fix-urls-with-extras
fix: url parsing with extra params
2024-05-02 23:40:22 +02:00
Felix Roos 5b4322c964 fix: url parsing with extra params 2024-05-02 22:39:39 +02:00
Alex McLean 91bb0a1d36 Add calculateTactus() for turning off calculation of tactus in (#1081)
core functions.
Also while I'm here stop fast/slow from manipulating tactus.
2024-04-29 11:36:10 +01:00
Jade (Rose) Rowland 47b65846e9 Merge pull request #1076 from diegodorado/patch-1
fix docs on alignment.mdx
2024-04-28 14:56:21 -04:00
Jade (Rose) Rowland 55eafaf4d8 Merge pull request #1073 from cleary/patch-5
add signals to recap in first-effects.mdx
2024-04-28 14:49:17 -04:00
Jade (Rose) Rowland 2e3869c0e0 Merge pull request #1072 from cleary/patch-4
fix translation issue in first-effects.mdx
2024-04-28 14:44:56 -04:00
Jade (Rose) Rowland 4015a11bd2 Merge pull request #1077 from daslyfe/fix_formatting_test
fix failing format test
2024-04-28 14:44:29 -04:00
Jade (Rose) Rowland 15f16006d0 ran prettier again? 2024-04-28 14:43:35 -04:00
Jade (Rose) Rowland ce79a44848 fix failing format test 2024-04-28 14:36:52 -04:00
Diego Dorado d253e35b6c fix docs on alignment.mdx 2024-04-28 10:53:53 -03:00
Jade (Rose) Rowland 19ef941c9d Merge pull request #1075 from cleary/patch-7
add nesting to `off` example variation in pattern-effects.mdx
2024-04-27 16:50:17 -04:00
Jade (Rose) Rowland ac7658d0a0 Merge pull request #1070 from cleary/patch-3
add `<...>` to first-sounds.mdx recap
2024-04-27 16:48:58 -04:00
Jade (Rose) Rowland 635a6c62b0 Merge pull request #1069 from cleary/patch-2
fix first sounds typo
2024-04-27 16:48:07 -04:00
Jade (Rose) Rowland acc100da5c Merge pull request #1068 from cleary/patch-1
fix cr typo on first-sounds.mdx
2024-04-27 16:47:44 -04:00
Bernard Gray 69f8498d4b Use "Alternate" for <...>, consistent with recap page in first-sounds.mdx 2024-04-27 14:20:26 +10:00
Bernard Gray 975eb684b9 add nesting to off example variation in pattern-effects.mdx
I was immediately curious whether `off` could be nested - it turns out it can, so I added an example of it in action.
This example should also add clarity to the flexibility of pattern naming inside `off` arguments as well
2024-04-27 11:35:08 +10:00
Bernard Gray 6b919132aa clarify off in pattern-effects.mdx
I had a bit of difficulty understanding `off` as it was presented, so I've attempted to simplify it:
1. take out the `delay` effect which is obscuring things (since we're using a delayed `off` pattern)
2. add a more complete explanation of the syntax
2024-04-27 11:18:51 +10:00
Bernard Gray 5c4ad57309 add signals to recap in first-effects.mdx 2024-04-27 10:47:30 +10:00
Bernard Gray 0be5f77247 fix translation issue in first-effects.mdx 2024-04-27 10:15:55 +10:00
Alex McLean bbf8577f85 More tactus tidying (#1071)
* preserve tactus for register (only non-formatting change is adding args to register)
* support undefined tactus values
2024-04-26 14:12:30 +01:00
Bernard Gray d8eb186ee4 add <...> to recap 2024-04-25 06:35:34 +10:00
Bernard Gray 54829bd28c fix first sounds typo 2024-04-25 06:24:56 +10:00
Bernard Gray 5c01760b28 fix cr typo on first-sounds.mdx 2024-04-25 06:01:49 +10:00
Alex McLean a189626e8b Fix stepjoin (#1067)
* sort fractions properly - fixes #1066
2024-04-23 22:37:21 +01:00
Alex McLean 6a1fa7e67f clarify license (#1064) 2024-04-23 22:32:00 +01:00
Alex McLean 267f58b7be Tactus tweaks - fixes for maintaining tactus and highlight locations (#1065)
* outer bind/join should keep outer tactus
* keep locations of outer pattern in stepJoin
2024-04-23 14:14:30 +01:00
Jade (Rose) Rowland 9348a8015a Merge pull request #1062 from daslyfe/osc_time_fix
fix OSC timing for recent scheduler updates
2024-04-21 17:57:57 -04:00
Jade (Rose) Rowland 57ad278137 fixed osc server 2024-04-21 17:52:22 -04:00
Jade (Rose) Rowland 5d8eea7299 fixed 2024-04-21 17:29:29 -04:00
Alex McLean 0a3694fb82 Stepwise functions from Tidal (#1060)
* rename new stepwise functions to match tidal, adding s_expand and s_contract
* created a `stepJoin` for stepwise patternification
2024-04-21 21:17:07 +01:00
Alexandre G.-Raymond a194180130 Fix wchooseCycles not picking the whole pattern (#1061) 2024-04-21 10:03:55 +01:00
Felix Roos 64cc8caeec Merge pull request #1038 from tidalcycles/swing
add swing + swingBy
2024-04-19 00:05:52 +02:00
Felix Roos f427b56062 Merge pull request #1059 from tidalcycles/dollar-colon
anonymous patterns + muting
2024-04-19 00:05:08 +02:00
Felix Roos 93d151b943 anonymous patterns + muting with _ 2024-04-17 12:37:19 +02:00
Felix Roos e0c4997f1e Merge pull request #1053 from tidalcycles/workshop-edits
improve tutorial + custom samples doc
2024-04-12 12:34:27 +02:00
Felix Roos 07791001dd Merge branch 'main' into workshop-edits 2024-04-12 12:32:14 +02:00
Felix Roos 936d6fdafb Merge pull request #1054 from tidalcycles/midin-fix
fix: do not reset cc input values on each eval
2024-04-12 12:31:49 +02:00
Felix Roos 81b33afcdb fix: do not reset cc input values on each eval 2024-04-12 12:30:13 +02:00
Felix Roos 0190604a7f fix: don't reset on each eval 2024-04-12 12:28:03 +02:00
Felix Roos 8494353ace bump @strudel/sampler 2024-04-12 10:57:59 +02:00
Felix Roos 971d497e81 docs: custom samples
- strudel.json
- github shortcut
- "import sounds"
- @strudel/sampler
- improve structure
- add drumset
2024-04-12 10:56:31 +02:00
Felix Roos ca36a31f79 slightly reword 2024-04-11 12:55:02 +02:00
Felix Roos 559f31792f update german tutorial 2024-04-11 11:58:38 +02:00
Felix Roos 83eedcb2f5 bring back division 2024-04-11 11:40:18 +02:00
Felix Roos a3bd04732c tutorial tweaks:
- add drumset image
- introduce angle brackets earlier
2024-04-11 11:26:46 +02:00
Felix Roos e8ed57f588 Merge pull request #1048 from tidalcycles/transpose-numbers
transpose: support all combinations of numbers and strings for notes and intervals
2024-04-08 17:25:27 +02:00
Alex McLean ebeefc0ac4 More experimental step functions: stepwax, stepwane, steptaper, steptour and taperlist (#1042)
* group tactus-oriented functions
* add stepwax, stepwane, steptaper and steptour
* taperlist
2024-04-08 09:46:18 +01:00
Felix Roos 3d06528393 transpose: support alls combinations of numbers and strings for notes and intervals 2024-04-07 01:25:47 +02:00
Jade (Rose) Rowland 558e9e36ff Merge pull request #1047 from daslyfe/mfix
Midi Time hotfix for scheduler updates
2024-04-06 17:44:57 -04:00
Jade (Rose) Rowland 0a6373f64c adjust latency 2024-04-06 17:40:08 -04:00
Jade (Rose) Rowland 0f24e97855 Merge branch 'mfix' of github.com:daslyfe/strudel into mfix 2024-04-06 17:32:07 -04:00
Jade (Rose) Rowland cbfa09a2da remove unessecary changes 2024-04-06 17:31:56 -04:00
Jade (Rose) Rowland 3805195109 Merge branch 'tidalcycles:main' into mfix 2024-04-06 17:29:37 -04:00
Jade (Rose) Rowland 183d3ea4a1 fix desktop midi 2024-04-06 17:28:30 -04:00
Jade (Rose) Rowland 8e4549d6d3 mfix 2024-04-06 17:07:54 -04:00
Felix Roos 506989b0d7 Merge pull request #1041 from tidalcycles/freq-wheel
pitchwheel visual
2024-04-05 12:48:02 +02:00
Felix Roos 8f475515e9 Merge pull request #1046 from tidalcycles/fix-cyclist
fix cyclist fizzling out
2024-04-05 12:47:19 +02:00
Felix Roos df23ea6cdf fix: cyclist desync 2024-04-05 12:23:04 +02:00
Felix Roos bea25a28cb add superdough warning when scheduling in the past 2024-04-05 12:21:45 +02:00
Felix Roos a4897adb83 fix: import 2024-04-04 00:38:08 +02:00
Felix Roos 0ba4be0571 bg 2024-04-04 00:13:43 +02:00
Felix Roos ca8707f32a cooler example 2024-04-04 00:13:37 +02:00
Felix Roos 18abcaccbb add fallback theme 2024-04-04 00:03:43 +02:00
Felix Roos 6ff7697cfd fix: use foreground color for pianoroll playhead 2024-04-04 00:03:32 +02:00
Felix Roos 915a4d2149 fix: example encoding for testing builds locally without server 2024-04-03 23:53:25 +02:00
Felix Roos 3a4ffb13ad add more info on using strings for patterns in web package 2024-04-03 23:48:08 +02:00
Felix Roos 610c848ff4 fix: reify hydra pattern 2024-04-03 23:06:02 +02:00
Felix Roos d06b70c28d fix: call setTime in initStrudel
+ return promise with scheduler
2024-04-03 23:02:42 +02:00
Felix Roos fde0da3b19 dont ignore hydra.mjs in eslint + fix lint 2024-04-03 22:43:35 +02:00
Felix Roos 97d292adbb add swing / swingBy to time modifiers page 2024-04-03 21:32:39 +02:00
Felix Roos e242e82b3f ireal voicings add aug alias for + 2024-04-01 23:21:55 +02:00
Felix Roos 6479c60387 tweaks 2024-04-01 23:21:32 +02:00
Felix Roos c7f811e14d fix: ignore haps that error at getFrequency 2024-04-01 17:04:03 +02:00
Felix Roos 539160630d more settings 2024-04-01 15:30:19 +02:00
Felix Roos 8a3e9171a4 basic pitchwheel vis 2024-04-01 15:19:50 +02:00
Felix Roos 010329dcfc fix: proper error log with call stack 2024-04-01 15:19:22 +02:00
Felix Roos 16851cf8f8 snap 2024-04-01 03:01:04 +02:00
Felix Roos 8f06bc4b57 add swing + swingBy 2024-04-01 02:59:17 +02:00
Felix Roos 7839d6b4bf Merge pull request #1030 from tidalcycles/forbidden-arithmetic
remove dangerous arithmetic feature
2024-03-30 16:05:59 +01:00
Felix Roos eb3e6d60b9 Merge pull request #1034 from tidalcycles/fix-sampler-paths
Fix sampler paths
2024-03-30 16:05:27 +01:00
Felix Roos fd08b25388 simplify readme 2024-03-30 16:03:43 +01:00
Felix Roos ff923a6b6e remove starting sampler by default 2024-03-30 16:02:02 +01:00
Felix Roos 30a15cb4b7 bump version 2024-03-30 16:00:21 +01:00
Felix Roos 7d8cb59a7a fix: char encoding 2024-03-30 16:00:10 +01:00
Felix Roos 1aca5db447 better warning 2024-03-30 15:05:52 +01:00
Felix Roos 2fbd9a3d54 Merge pull request #1033 from tidalcycles/local-sample-server
local sample server cli
2024-03-30 14:43:08 +01:00
Felix Roos 934a3cdec4 bump 2024-03-30 14:40:59 +01:00
Felix Roos f987633498 fix: lint 2024-03-30 14:40:43 +01:00
Felix Roos 1a209c8b82 doc: options 2024-03-30 14:39:26 +01:00
Felix Roos 3e66e1ee93 add LOG env option 2024-03-30 14:38:23 +01:00
Felix Roos e39cd2c9cd bump 2024-03-30 14:27:38 +01:00
Felix Roos 8a8f6a66a4 improve logging + skip folders that throw 2024-03-30 14:27:18 +01:00
Felix Roos b6171600c2 fix: lint 2024-03-30 14:10:35 +01:00
Felix Roos 3a60f63fd2 improve log 2024-03-30 05:07:43 +01:00
Felix Roos 422a9ad525 fix: lint 2024-03-30 05:00:49 +01:00
Felix Roos 29fe4fd907 make sample server available over local network 2024-03-30 04:59:43 +01:00
Felix Roos 22774596ca run sampler in parallel by default + move samples folder to root 2024-03-30 04:48:55 +01:00
Felix Roos 8f355dcf96 add sampler script + samples folder with README 2024-03-30 04:42:45 +01:00
Felix Roos abe775c3fd fix: lint 2024-03-30 03:26:36 +01:00
Felix Roos 95e0496d51 proper sample server 2024-03-30 03:25:42 +01:00
Felix Roos bd9a80827e clean lockfile 2024-03-30 01:59:30 +01:00
Felix Roos 39204ad94d add simple local sample serving 2024-03-30 01:48:07 +01:00
Felix Roos 1e8a3c2918 Merge pull request #1032 from tidalcycles/font-caching
add font file types to offline cache
2024-03-29 17:14:27 +01:00
Felix Roos 1af55c0d19 add font file types to offline cache 2024-03-29 17:13:01 +01:00
Felix Roos a1e07f3079 Merge pull request #1031 from tidalcycles/close-brackets-setting
add closeBrackets setting
2024-03-29 17:10:16 +01:00
Felix Roos 4e7f5e9cda add closeBrackets setting 2024-03-29 17:07:48 +01:00
Felix Roos 12d8cdff92 remove dangerous arithmetic feature 2024-03-29 16:58:34 +01:00
Alex McLean 9c66d9f1a7 Tactus tidy (#1027)
* preserve tactus across calls to filterValues (fixes tactus for struct)
* timeCat -> timecat (maintaining old name as an alias), beatCat -> stepcat
2024-03-29 13:55:05 +00:00
Felix Roos e2b4f6f5ac Merge pull request #1025 from tidalcycles/sync-toggle
add setting for sync flag
2024-03-28 17:06:44 +01:00
Felix Roos 50ed466895 disable sync by default? 2024-03-28 12:00:12 +01:00
Felix Roos 126556634a add setting for sync flag 2024-03-28 11:49:32 +01:00
Felix Roos aa25265cc2 Merge pull request #1024 from tidalcycles/fix-draw-bugs
better theme integration for visuals + various fixes
2024-03-28 11:37:56 +01:00
Felix Roos 73558eb49c Merge pull request #1023 from tidalcycles/more-fonts
More fonts
2024-03-28 11:33:25 +01:00
Felix Roos cc49e0f78e clean 2024-03-28 11:15:48 +01:00
Felix Roos 6c07feb312 add 3 new fonts 2024-03-28 11:15:23 +01:00
Felix Roos a8c2586da6 fix: lint 2024-03-28 10:23:46 +01:00
Felix Roos c400deac74 visuals now adhere to theme by default 2024-03-28 10:23:07 +01:00
Felix Roos 6a2486d9f2 fix: id widgets by type index to prevent erasing memory in some cases 2024-03-28 09:31:43 +01:00
Felix Roos b5720355f9 fix: allow multiple visuals for the same pattern
+ using .tag function instead of single .id
2024-03-28 09:31:12 +01:00
Felix Roos ab015ff48a remove old .draw, rename .onFrame to .draw
+ breaking: from, to are now called lookbehind, lookahead
+ migrate .pianoroll
2024-03-28 07:35:01 +01:00
Felix Roos eb8ac29136 add memory to onFrame 2024-03-28 07:26:32 +01:00
Felix Roos 14b1a1203f fix: cyclist.now() should return 0 when stopped 2024-03-28 07:25:44 +01:00
Felix Roos 060d2cfe2b another hap time introspection method 2024-03-28 06:50:13 +01:00
Felix Roos 04a0952a22 simplify draw plumbing 2024-03-28 06:25:24 +01:00
Felix Roos 1a0dff4081 add some hap methods for time introspection 2024-03-28 05:47:50 +01:00
Alex McLean 2fd2bdba60 Feature: tactus marking (#1021)
* rename `beat` option to `stackBy` to `repeat`
* fix parse error reporting
* rename `weight` to `tactus` (it might in the end be pulse, step, or tap)
* tactus marking with ^
* and add some tests
2024-03-27 12:06:05 +00:00
Jade (Rose) Rowland 2e8ae38d33 Merge pull request #1020 from daslyfe/hotfix_now_calculation
hotfix for 1017
2024-03-25 01:02:01 -04:00
Jade (Rose) Rowland bbfdd2cc32 working 2024-03-25 00:59:42 -04:00
Felix Roos 4086df95b7 Merge pull request #1015 from ilesinge/document_signals
Document signals
2024-03-24 15:06:33 +01:00
Alexandre Gravel-Raymond aba4e72d29 Fewer undocumented functions 2024-03-24 14:18:25 +01:00
Alexandre Gravel-Raymond 8b96e3a6d1 Fix snapshot undegrade function name 2024-03-24 14:17:48 +01:00
Alexandre Gravel-Raymond 731d545a3f Fix undegrade doc naming 2024-03-24 14:15:20 +01:00
Alexandre Gravel-Raymond e6eb6c19a8 Add random modifiers docs to its own page 2024-03-24 14:15:01 +01:00
Alexandre Gravel-Raymond af35a1af09 Add examples test snapshots 2024-03-24 12:40:53 +01:00
Alexandre Gravel-Raymond fc4c70c94f Fix codestyle 2024-03-24 12:25:54 +01:00
Alexandre Gravel-Raymond 064bff6e40 Document conditional modifiers 2024-03-24 12:22:46 +01:00
Alexandre Gravel-Raymond 69211b8701 Document signals 2024-03-24 12:22:26 +01:00
Felix Roos 33f4f5fccc Merge pull request #1014 from daslyfe/repl_sync_fixes
Repl sync fixes
2024-03-24 10:16:11 +01:00
Jade (Rose) Rowland af740c3abb Merge branch 'tidalcycles:main' into repl_sync_fixes 2024-03-23 16:47:22 -04:00
Jade (Rose) Rowland 8e2407cfa9 cleanup 2024-03-23 16:45:34 -04:00
Jade (Rose) Rowland 86c945114d removed unessecary variable 2024-03-23 16:35:48 -04:00
Jade (Rose) Rowland bd16b403e3 build working properly 2024-03-23 16:28:37 -04:00
Jade (Rose) Rowland f00a08ef9e working 2024-03-23 16:18:16 -04:00
Felix Roos 31067d5cb5 Merge pull request #1004 from tidalcycles/worker-repl
eliminate chromium clock jitter
2024-03-23 20:21:51 +01:00
Jade (Rose) Rowland bc917dcf8b sync false 2024-03-23 14:53:07 -04:00
Jade (Rose) Rowland 619ca5385d sync mostly working 2024-03-23 14:52:44 -04:00
Jade (Rose) Rowland aa36d25b9a still kinda working 2024-03-23 14:45:35 -04:00
Felix Roos fe1fc81f42 Merge pull request #1013 from tidalcycles/undocumented-update
update undocumented script
2024-03-23 15:51:07 +01:00
Felix Roos 34373a9d73 Merge pull request #1012 from tidalcycles/fix-flok-setcps
fix: await injectPatternMethods
2024-03-23 15:26:52 +01:00
Felix Roos 16210e121f fix: await injectPatternMethods 2024-03-23 15:24:49 +01:00
Felix Roos 53af147ede Merge pull request #1010 from tidalcycles/trigzero-restart
rename trig -> reset, trigzero -> restart
2024-03-23 15:18:12 +01:00
Felix Roos fc32981886 update undocumented script 2024-03-23 14:08:02 +01:00
Felix Roos aede6e4b26 Merge pull request #1008 from tidalcycles/inline-punchcard
Inline punchcard
2024-03-23 12:30:03 +01:00
Felix Roos ac36423f85 Merge remote-tracking branch 'origin/main' into inline-punchcard 2024-03-23 12:28:23 +01:00
Felix Roos 4071c1af18 Merge pull request #1007 from tidalcycles/color-in-hap-value
Color in hap value
2024-03-23 12:27:22 +01:00
Felix Roos 49e381f258 make spiral size param 3 in one 2024-03-23 12:26:32 +01:00
Felix Roos 21f5d639a3 fix _pianoroll 2024-03-23 12:20:01 +01:00
Felix Roos 0eccbae048 improve spiral colors 2024-03-23 12:17:25 +01:00
Felix Roos a629c5c931 fix: id at wrong place 2024-03-23 12:14:36 +01:00
Felix Roos f83a156317 Merge branch 'color-in-hap-value' into inline-punchcard 2024-03-23 12:07:11 +01:00
Felix Roos 2857e816d2 add Pattern.id + use it for _punchcard and _spiral 2024-03-23 12:06:31 +01:00
Felix Roos c2ef90d67c rename trig -> reset, trigzero -> restart 2024-03-23 11:22:30 +01:00
Felix Roos 2cc92a2b93 Merge branch 'main' into inline-punchcard 2024-03-23 00:33:33 +01:00
Felix Roos 13d56b6b28 snapshots 2024-03-23 00:28:38 +01:00
Felix Roos d036635915 move color to hap value 2024-03-23 00:20:00 +01:00
Felix Roos 5537652497 Merge pull request #1006 from tidalcycles/update-examples
using strudel in your project guide + cleanup examples
2024-03-22 23:53:51 +01:00
Felix Roos f1d491d52e Merge pull request #1000 from eefano/scale-accidentals
accidentals in scale degrees
2024-03-22 23:41:49 +01:00
Felix Roos d299e76c82 remove embed and repl deps from website 2024-03-22 16:25:10 +01:00
Felix Roos 52d3ee0f85 remove demo + scripts on all doc pages 2024-03-22 16:23:18 +01:00
Felix Roos 4f0f388ca2 a few more words 2024-03-22 16:19:45 +01:00
Felix Roos df03e9a379 finish project start page + link it in main readme 2024-03-22 16:17:09 +01:00
Felix Roos 52a311aae6 add link to StrudelMirror 2024-03-22 15:55:12 +01:00
Felix Roos 6365e1626c begin project-start doc
+ improve embed + repl package doc
2024-03-22 15:52:38 +01:00
Felix Roos 8d96a071d6 document umbrella packages 2024-03-22 14:52:28 +01:00
Felix Roos e8f6124a07 + make cyclist trigger backwards compatible for now
+ add absolute time as new 5th onTrigger param
2024-03-22 01:22:35 +01:00
Felix Roos 14a5e7dcb6 make regular cyclist work in the background
+ use worker-timers in Repl
+ repl / cyclist now accept custom interval functions
2024-03-22 01:01:17 +01:00
Felix Roos 7889017434 delete now obsolete poc 2024-03-22 00:46:08 +01:00
Felix Roos ba8f996103 fix: clock jitter in cyclist 2024-03-22 00:41:30 +01:00
Felix Roos 8d8c843ccf working poc cyclist with cps change 2024-03-22 00:29:25 +01:00
Felix Roos a46bd9b36e Merge pull request #978 from daslyfe/oscillator_enhancements
supersaw oscillator
2024-03-21 22:53:55 +01:00
Felix Roos 96a977990a add missing dep 2024-03-21 22:47:03 +01:00
Felix Roos 41a40c74ef add run instruction 2024-03-21 22:45:11 +01:00
Felix Roos 837dfcaba4 add readme 2024-03-21 22:44:50 +01:00
Felix Roos ac441b4340 add working poc for precise cyclist 2024-03-21 22:41:45 +01:00
Felix Roos 4362cb9317 export createClock from core 2024-03-21 22:39:01 +01:00
Felix Roos dc977b292d more options for zyklus api:
+ allow setting custom interval functions
+ allow disabling phase rounding
2024-03-21 22:38:20 +01:00
Felix Roos 10bd668033 allow absolute time for superdough with prefix = 2024-03-21 22:37:15 +01:00
Felix Roos 5d0abe4151 disable sync 2024-03-21 21:31:02 +01:00
Felix Roos 89ec87d32a Merge branch 'oscillator_enhancements' of https://github.com/daslyfe/strudel into oscillator_enhancements 2024-03-21 21:26:46 +01:00
Felix Roos 1d48d5d5ef halve detune range 2024-03-21 21:26:43 +01:00
Felix Roos 0565558e21 scheduling in a worker, draft 2024-03-21 13:27:53 +01:00
Felix Roos 6320d3c08c Merge pull request #1003 from tidalcycles/cleanup
remove canvas, externalize samples, delete junk
2024-03-21 13:00:04 +01:00
Felix Roos 4511732252 remove canvas dependency 2024-03-21 08:35:44 +01:00
Felix Roos f5c087fc2a outsource piano samples 2024-03-21 07:30:21 +01:00
Felix Roos d2fc784060 delete astro favicon 2024-03-21 07:30:05 +01:00
Felix Roos be2ec969c4 use script tag instead of skypack 2024-03-21 07:20:07 +01:00
Felix Roos 456250a176 delete redundant emu samples 2024-03-21 07:19:47 +01:00
Jade (Rose) Rowland cbe7e69e13 remove unecessary multiplier 2024-03-20 15:02:20 -04:00
Felix Roos d3a17891f8 move rollup plugin to dev dependencies 2024-03-20 17:11:35 +01:00
eefano e32c965a4b accidents in scale grades 2024-03-19 16:04:35 +01:00
Felix Roos 5982e3ebea fix: center supersaw if only 1 voice 2024-03-19 09:00:25 +01:00
Felix Roos ea58238453 Merge branch 'main' into oscillator_enhancements 2024-03-19 08:48:09 +01:00
Felix Roos 1e1dbd6085 add inline _punchcard 2024-03-19 08:44:43 +01:00
Felix Roos 6b98e414c6 make onPaint work with mutiple repls on screen
+ add warning if stock onPaint is not overloaded
2024-03-19 08:44:05 +01:00
Alex McLean 29cb6195b3 Fix pure mini highlight (#994)
* preserve locations for pure values via mininotation

* preserve weight
2024-03-18 10:37:55 +00:00
Felix Roos d99af7c4ad Merge pull request #989 from tidalcycles/claviature
inline viz / widgets package
2024-03-18 07:12:14 +01:00
Jade (Rose) Rowland 48e4051310 remove unessecary math 2024-03-17 13:18:52 -04:00
Felix Roos 95446a76ef breakout widgets package for now 2024-03-17 12:22:24 +01:00
Jade (Rose) Rowland 6d874af45f fixed end time 2024-03-17 01:27:11 -04:00
Jade (Rose) Rowland dad18a24fb disconnect worklet at scheduled time to destroy faster 2024-03-17 01:15:07 -04:00
Felix Roos 8b9a2e1d84 Merge remote-tracking branch 'origin/main' into claviature 2024-03-17 04:49:12 +01:00
Felix Roos dd16a1cbf4 hotfix: disable sync, it somehow breaks playback in the built version
(pnpm build && pnpm preview)
2024-03-17 04:48:25 +01:00
Felix Roos 2900401d3e Merge remote-tracking branch 'origin/main' into claviature 2024-03-17 04:38:05 +01:00
Felix Roos b5312c27dc move stuff for less changes 2024-03-17 04:36:48 +01:00
Felix Roos 4319c43ceb comment out _spiral for now 2024-03-17 04:31:30 +01:00
Felix Roos be77882d70 rename inline functions to match global ones, prefixed with _ 2024-03-17 04:15:59 +01:00
Felix Roos 78f816978c Merge pull request #991 from tidalcycles/labeled-statements
Labeled statements
2024-03-17 04:06:59 +01:00
Felix Roos 076b6f1c82 move canvas functions to codemirror package
+ fix id collisions
2024-03-17 04:00:28 +01:00
Felix Roos 0978fbb15e Merge branch 'labeled-statements' into claviature 2024-03-17 03:19:40 +01:00
Felix Roos 2de18846b0 add comment 2024-03-17 03:17:47 +01:00
Felix Roos a71a167133 fix: enable locations again 2024-03-17 03:16:17 +01:00
Felix Roos 6359bbe139 transpile label statements to p calls 2024-03-17 03:14:05 +01:00
Felix Roos d1c713fa29 make labeled statements look good on custom themes 2024-03-17 02:29:48 +01:00
Felix Roos 16506c5ae2 improve scope memory footprint 2024-03-17 01:58:30 +01:00
Alex McLean 398533877c Beat-oriented functionality (#976)
* annotate pure values with their value, allowing single mininotation values to maintain their labels as pure
* Don't use any 'patternified' arguments if they're all 'pure'
* allow pattern weights (roughly, beats-per-cycle) to be inferred where possible, including from mininotation and across many transformations (e.g. `fast` with a 'pure' factor)
* Add `beatCat`, similar to `timeCat` but funkier
* `silence` has a weight of 1, add alternative `nothing` with a weight of 0, and `gap` function with weight argument
* preserve weight across applicative operations (weight comes with the structure)
* add `stack` alternatives that take advantage of pattern weights to align patterns differently - `stackLeft`, `stackRight`, `stackCentre`, `stackExpand`, with `stackBy` with an argument for patterning the alignment.
2024-03-16 17:24:37 +00:00
Felix Roos cdc200e1da draw straight line when no analyser is defined yet
+ add todo for memory leak
2024-03-16 03:12:00 +01:00
Felix Roos dd78dc9606 fix: animation frame cleanup 2024-03-16 02:42:26 +01:00
Felix Roos ef284e9d13 fix: bugs catched by linter 2024-03-16 02:16:50 +01:00
Felix Roos f4032dad22 inline multichannel scopes 2024-03-16 02:14:24 +01:00
Jade (Rose) Rowland 5d55b9c9ef remove hyperpop test 2024-03-15 11:55:18 -04:00
Felix Roos d9d05e21c0 full size piano by default 2024-03-15 12:24:11 +01:00
Felix Roos 1043baf08a fix: catch errors in pianoroll getValue 2024-03-15 12:24:03 +01:00
Felix Roos dfd22b5e44 fix: claviature height
+ better defaults
+ break out solid web component creation
2024-03-15 11:04:35 +01:00
Felix Roos 4a95bf67da move widget registry to codemirror package
+ add transpiler as dependency to codemirror
2024-03-15 10:00:07 +01:00
Felix Roos 8742d50bad simplify widget creation + fix bugs 2024-03-15 09:50:17 +01:00
Felix Roos 6dce9d5deb support multiple animationFrames
+ break out spiral draw logic
2024-03-15 09:49:00 +01:00
Felix Roos a8712fd8ce remove this mess 2024-03-15 09:48:04 +01:00
Jade (Rose) Rowland 8f291b0f80 fix test again 2024-03-14 22:31:26 -04:00
Jade (Rose) Rowland 7f9cc7cd2d merging main 2024-03-14 22:19:45 -04:00
Jade (Rose) Rowland d102e1dbbc Merge pull request #900 from daslyfe/repl_sync
REPL sync between windows
2024-03-14 20:47:35 -04:00
Felix Roos 83ea3bb138 encapsulate canvas logic 2024-03-15 01:47:35 +01:00
Jade (Rose) Rowland 1f39e1c04d fix tests 2024-03-14 20:41:47 -04:00
Felix Roos 48e0691eec add pianoroll widget 2024-03-15 01:40:21 +01:00
Jade (Rose) Rowland fd946107df use lerp 2024-03-14 20:35:56 -04:00
Felix Roos 094b30cd26 use hap color for claviature 2024-03-15 00:39:31 +01:00
Felix Roos 708675077f rename claviature package to widgets 2024-03-15 00:34:13 +01:00
Felix Roos 82c4926f19 allow any web component to become a widget 2024-03-15 00:27:16 +01:00
Felix Roos 29dab578e7 can now load claviature as a codemirror widget 2024-03-14 23:49:38 +01:00
Felix Roos 1e75f045b7 remove other watch tasks for now 2024-03-14 15:31:50 +01:00
Felix Roos cfdd4ac36c add readme 2024-03-14 15:28:06 +01:00
Felix Roos f2e16f946c add working claviature method 2024-03-14 15:17:19 +01:00
Felix Roos 0a1da6f651 claviature lockfile 2024-03-14 14:20:56 +01:00
Felix Roos 062e45ee6a Merge remote-tracking branch 'origin/main' into claviature 2024-03-14 14:20:45 +01:00
Jade (Rose) Rowland 83145784d7 remove console statement 2024-03-14 00:18:59 -04:00
Jade (Rose) Rowland 486e6457a9 Merge pull request #984 from andresgottlieb/patch-1
Update synths.mdx
2024-03-13 19:20:07 -04:00
Felix Roos 7efea5726b Merge pull request #985 from tidalcycles/share-fix
fix: share now shares what's visible instead of active
2024-03-13 00:36:22 +01:00
Felix Roos 7eb2f7659e fix: share now shares what's visible instead of active 2024-03-12 08:38:11 +01:00
Jade (Rose) Rowland 112ca1875a stop clock if all stopped 2024-03-10 14:35:43 -04:00
Andrés Gottlieb bc641d8e4c Update synths.mdx
Typo fix
2024-03-10 12:49:12 +01:00
Felix Roos d0c68e91dc hotfix: hydra-canvas might already be removed 2024-03-10 02:09:46 +01:00
Felix Roos c373a0473b Merge pull request #967 from tidalcycles/default-dict
use ireal as default voicing dict
2024-03-10 01:18:57 +01:00
Felix Roos 708fea637c fix update migration snippet 2024-03-10 01:10:37 +01:00
Felix Roos c5a4184437 Merge branch 'main' into default-dict 2024-03-10 00:54:13 +01:00
Felix Roos 446cf8c694 Merge pull request #980 from eefano/little-bag-fix
little fix for withVal
2024-03-10 00:50:23 +01:00
Felix Roos 3df776d062 Merge pull request #974 from tidalcycles/velocity-in-value
Velocity in value
2024-03-10 00:46:51 +01:00
Felix Roos ae7807643b Merge remote-tracking branch 'origin/main' into velocity-in-value 2024-03-10 00:44:02 +01:00
Felix Roos 5ebaa3e1ce Merge pull request #983 from tidalcycles/clear-hydra
fix: clear hydra on reset
2024-03-10 00:42:50 +01:00
Felix Roos 4a25eab01a Merge remote-tracking branch 'origin/main' into clear-hydra 2024-03-10 00:40:29 +01:00
Felix Roos e885e05a7a Merge pull request #971 from tidalcycles/draw-package
move canvas related helpers from core to new draw package
2024-03-10 00:38:43 +01:00
Felix Roos 012b19c79e fix: clear hydra on reset 2024-03-10 00:29:52 +01:00
Jade (Rose) Rowland 0875e805b4 Merge pull request #982 from daslyfe/fix_distort_doc
replace shape with distort in learn doc
2024-03-07 23:38:07 -05:00
Jade (Rose) Rowland f1700d5e47 fixed 2024-03-07 23:35:04 -05:00
Jade (Rose) Rowland 30a5176090 fix android support 2024-03-07 23:04:46 -05:00
Felix Roos 43c40f7bb0 no need for publishConfig 2024-03-08 01:07:17 +01:00
Felix Roos 9b3509c062 global watch task 2024-03-08 00:55:19 +01:00
Felix Roos 85fee0cc01 add claviature package 2024-03-08 00:53:48 +01:00
Felix Roos 0f07bfc9d7 rename: canvas -> draw 2024-03-07 13:24:52 +01:00
Felix Roos 75098adbdb allow offsetting onFrame 2024-03-07 13:17:40 +01:00
Felix Roos 8dba865ca7 add note to future 2024-03-07 12:35:39 +01:00
Felix Roos 2fcbffeaf9 add sync flag for neocyclist 2024-03-07 12:14:17 +01:00
Felix Roos 619ffdd5e1 rename zyklus 2024-03-07 12:01:08 +01:00
Felix Roos 1af2da0063 remove changes to prevent conflicts with draw branch 2024-03-07 12:00:27 +01:00
Jade (Rose) Rowland 9d1231ce0a comments 2024-03-06 22:25:31 -05:00
Jade (Rose) Rowland 4d1110b3f3 faster is odd 2024-03-06 20:27:05 -05:00
Jade (Rose) Rowland 6a5bf521c2 unity gain 2024-03-06 16:05:51 -05:00
Jade (Rose) Rowland 3410c332d5 gain adjustment 2024-03-06 10:13:56 -05:00
Alex McLean 8e41332dfa alias - for ~ (#981) 2024-03-06 11:14:48 +00:00
eefano 9f56a335b7 little fix for withVal 2024-03-05 21:41:11 +01:00
Jade (Rose) Rowland 8db8307c9e fix test 2024-03-05 00:48:47 -05:00
Jade (Rose) Rowland 641d9f5ebd Merge branch 'main' into oscillator_enhancements 2024-03-05 00:45:56 -05:00
Jade (Rose) Rowland 05887967e0 added back (possibly unessecary? stop fm 2024-03-05 00:44:45 -05:00
Jade (Rose) Rowland 232b9c13d7 disable lint for global worklet variables 2024-03-05 00:34:59 -05:00
Jade (Rose) Rowland c0b00339c5 implemented modulators 2024-03-05 00:23:19 -05:00
Jade (Rose) Rowland bfcb2070d1 adding modulators 2024-03-04 23:17:58 -05:00
Jade (Rose) Rowland 1e3a23e017 100 2024-03-04 20:37:48 -05:00
Jade (Rose) Rowland 524109056f Merge pull request #963 from daslyfe/worklet_improvements
Worklet Improvents / fixes
2024-03-04 10:04:23 -05:00
Jade (Rose) Rowland c2df8aa6b9 cleaning 2024-03-04 00:43:03 -05:00
Jade (Rose) Rowland 1666b50857 params 2024-03-03 22:19:26 -05:00
Jade (Rose) Rowland e839c271cd fixed output 2024-03-03 22:05:00 -05:00
Jade (Rose) Rowland 823c208c71 cleaning 2024-03-03 21:22:17 -05:00
Jade (Rose) Rowland ee01b4a3a0 working on it 2024-03-03 18:57:30 -05:00
Felix Roos d5ed065011 fix: hydra canvas adjust to screen size
+ simplify hydra init (use getDrawContext)
+ make getDrawContext more versatile
2024-03-03 22:50:51 +01:00
Jade (Rose) Rowland 78a6ec2f98 stereo 2024-03-03 13:16:32 -05:00
Jade (Rose) Rowland 7f60a9e52b yay 2024-03-03 01:23:08 -05:00
Jade (Rose) Rowland 7383dca9ee working 2024-03-03 00:37:33 -05:00
Jade (Rose) Rowland 8a7d72bf24 working 2024-03-02 22:35:26 -05:00
Jade (Rose) Rowland 4af7262f45 fixed performance issue 2024-03-02 01:16:41 -05:00
Jade (Rose) Rowland daa62202fa default values needed in superdough for worklet input 2024-03-01 13:12:52 -05:00
Jade (Rose) Rowland f911fceb9e trying things 2024-03-01 13:04:44 -05:00
Felix Roos e9e5fac610 fix tunes + tests 2024-03-01 18:29:24 +01:00
Felix Roos 5017ac6f74 delete file from wrong branch 2024-03-01 18:01:51 +01:00
Felix Roos 1d92ee76b9 make velocity a regular value prop 2024-03-01 18:00:34 +01:00
Felix Roos 7d01764a30 Merge pull request #973 from tidalcycles/nested-controls
Nested controls
2024-03-01 17:30:19 +01:00
Felix Roos 62d8955da9 onFrame function 2024-03-01 16:46:34 +01:00
Felix Roos 4f94de86db fix: migrate more imports 2024-03-01 13:09:14 +01:00
Jade (Rose) Rowland a6f7c57d19 working 2024-02-29 22:55:56 -05:00
Jade (Rose) Rowland 1690030b1d envelope 2024-02-29 21:07:43 -05:00
Felix Roos bf343eb499 simplify _composeOp 2024-03-01 00:32:45 +01:00
Felix Roos 4f55144232 nested controls poc 2024-03-01 00:31:33 +01:00
Jade (Rose) Rowland c9b18f6fa4 fix test 2024-02-29 18:15:30 -05:00
Jade (Rose) Rowland 416a03aea1 updated params 2024-02-29 18:09:26 -05:00
Jade (Rose) Rowland f62b268f1d playing 2024-02-29 17:53:26 -05:00
Felix Roos 6ce8a3feba move draw logic to separate package 2024-02-29 22:14:53 +01:00
Felix Roos 94dd547a74 canvas package boilerplate 2024-02-29 21:56:31 +01:00
Jade (Rose) Rowland f5c7b58b3a testing 2024-02-29 14:49:08 -05:00
Felix Roos fa7c16dbe7 Merge pull request #959 from tidalcycles/euclid-inverted
feat: can now invert euclid pulses with negative numbers
2024-02-29 15:33:12 +01:00
Felix Roos 6a43eb6755 Merge pull request #950 from eefano/pickr-pickrmod
pickOut(), pickRestart(), pickReset()
2024-02-29 15:31:23 +01:00
Felix Roos c524ebc7f3 fix: add name tags 2024-02-29 15:25:31 +01:00
Felix Roos 0deb905cdf rename pick*Outer > pickOut
+ add pick*Squeeze alias to inhabit*
2024-02-29 15:21:44 +01:00
Felix Roos 4cadbcbbdf Merge pull request #965 from tidalcycles/legato-fix
remove legacy legato + duration implementations
2024-02-29 15:12:45 +01:00
Felix Roos 96eef75f74 fix: end / begin. sampler now needs clip to choose duration... 2024-02-29 10:33:27 +01:00
Felix Roos 51d9e02e07 Merge pull request #966 from eefano/transpose-fix
fix for transpose(): preserve hap value object structure
2024-02-29 08:47:53 +01:00
Felix Roos c4c21c4f0c Merge pull request #968 from tidalcycles/log-debounce
add debounce to logger
2024-02-29 08:32:00 +01:00
Jade (Rose) Rowland c00ed14bc3 playing 2024-02-29 00:54:24 -05:00
Felix Roos 8f39a5eeac snapshot 2024-02-29 05:02:22 +01:00
Felix Roos 305715277c repl reset default dict
+ add mechanism to get a module by name from dynamic imports
2024-02-29 04:54:03 +01:00
Felix Roos d4eebf1a77 set ireal as default dict
+ add global way to set and reset default dict
2024-02-29 04:53:11 +01:00
Felix Roos fcb247b62a add debounce to logger 2024-02-29 04:25:00 +01:00
Felix Roos 7556da7839 snapshot 2024-02-29 04:06:15 +01:00
Felix Roos eb93a6c149 refactor: duration is now a regular control 2024-02-29 04:05:05 +01:00
eefano b870ab204a fix for transpose(): preserve hap value object structure 2024-02-29 00:13:08 +01:00
Jade (Rose) Rowland fba2422a64 update test 2024-02-28 17:32:30 -05:00
Jade (Rose) Rowland da674d3e05 updated controls 2024-02-28 17:26:59 -05:00
Jade (Rose) Rowland 57e1c1bd89 Merge branch 'tidalcycles:main' into worklet_improvements 2024-02-28 17:10:09 -05:00
Jade (Rose) Rowland e3463b3c3d merged new controls file 2024-02-28 17:08:58 -05:00
Jade (Rose) Rowland a3898c7589 cleanup 2024-02-28 14:36:50 -05:00
Jade (Rose) Rowland 1221c6144c added distortion effect 2024-02-28 14:31:07 -05:00
Jade (Rose) Rowland c77b0098a7 adjust param range 2024-02-28 13:17:36 -05:00
Felix Roos f779e81993 remove old snapshots 2024-02-28 18:52:08 +01:00
Felix Roos 5c71bb95a0 remove legacy legato
+ make legato a synonym of clip
2024-02-28 18:51:07 +01:00
Felix Roos 1525992d73 Merge pull request #962 from tidalcycles/control-refactoring
controls refactoring: simplify exports
2024-02-28 18:43:51 +01:00
Jade (Rose) Rowland a227c69408 adjusting params 2024-02-28 10:24:26 -05:00
Jade (Rose) Rowland 972bfa3319 cleaning up 2024-02-28 00:04:12 -05:00
Jade (Rose) Rowland 360b920935 cleaning_up 2024-02-27 23:42:33 -05:00
Jade (Rose) Rowland 1416a0de53 working 2024-02-27 23:38:37 -05:00
Felix Roos abe25be05e fix examples 2024-02-28 00:36:59 +01:00
Felix Roos 664a19a5c4 refactor: remove now redundant controls imports 2024-02-28 00:36:44 +01:00
Felix Roos 8efadd2547 refactor: controls are now exported directly 2024-02-28 00:34:39 +01:00
eefano ed1f3e1e8c Merge remote-tracking branch 'upstream/main' into pickr-pickrmod 2024-02-26 01:15:27 +01:00
eefano 4aaf9eaed4 mux operator, muxers.mjs with pick variants
prettier

prettier2

clamp?

Revert "clamp?"

This reverts commit 83a3b9123aece9f0e366c83d97f99e8b8d20db54.

Revert "prettier2"

This reverts commit de13871a8be4a450b79a4d9e65b9d1982485577e.

Revert "prettier"

This reverts commit 1550bf49527f591d05fa2766ca66ccb16e142011.

Revert "mux operator, muxers.mjs with pick variants"

This reverts commit af062f93c23ded4440f968186bfc69851d8aec67.
2024-02-26 01:11:56 +01:00
Felix Roos 0416e4d212 Merge pull request #960 from tidalcycles/reset-global-fx
fix: reset global fx on pattern change
2024-02-25 19:17:00 +01:00
Felix Roos b9247db001 fix: reset global fx on pattern change 2024-02-25 15:09:15 +01:00
eefano 74ef157fa1 Merge remote-tracking branch 'upstream/main' into pickr-pickrmod 2024-02-25 14:52:12 +01:00
Felix Roos d4724b0e12 feat: can now invert euclid pulses with negative numbers 2024-02-25 14:43:02 +01:00
eefano dbfdbcf2b7 typo 2024-02-25 14:39:38 +01:00
eefano 0986b14508 added pickOuter, pickmodOuter 2024-02-25 14:31:07 +01:00
Felix Roos 325152c782 Merge pull request #936 from Enelg52/midi-issues
fix midi issue on firefox and added quote error
2024-02-25 14:15:30 +01:00
Felix Roos c136d12194 Merge pull request #956 from eefano/bracketMatching
'Enable Bracket Matching' option in Codemirror
2024-02-25 13:19:46 +01:00
Felix Roos 7557952155 format 2024-02-25 13:17:52 +01:00
Felix Roos b0b41e6718 revert unintended change 2024-02-25 13:17:29 +01:00
Felix Roos 22e3067b8c set bracket matching true by default 2024-02-25 13:16:53 +01:00
Jade (Rose) Rowland e54b2fb207 lower weighted average 2024-02-23 20:13:08 -05:00
Felix Roos e9ab01ab06 Merge pull request #957 from tidalcycles/web-iife
fix script importable packages (web + repl)
2024-02-23 14:37:45 +01:00
Felix Roos 058e554b0f update example 2024-02-23 14:33:07 +01:00
Felix Roos 6d9327aebc adapt repl package similar to web package 2024-02-23 14:30:29 +01:00
Felix Roos c23d7850c7 simplify web example 2024-02-23 14:30:07 +01:00
Felix Roos 2854f0cf34 try different main / module setting + bump 2024-02-23 14:28:43 +01:00
Felix Roos a89e87ca2e add web package example using script tag 2024-02-23 14:21:59 +01:00
Felix Roos 5419ca431b bump web package version 2024-02-23 14:17:11 +01:00
Felix Roos e8b674badd add iife build to web package 2024-02-23 14:16:54 +01:00
eefano 8af40a9c61 added <> for mininotation 2024-02-22 23:35:00 +01:00
eefano f54aa3d823 'Enable Bracket Matching' option in Codemirror 2024-02-22 22:04:57 +01:00
Jade (Rose) Rowland d544bf4665 seperated service worker zyklus and cyclist zyklus because of import constraints on service workers 2024-02-22 00:16:36 -05:00
Jade (Rose) Rowland e5570a6017 restore old cyclist 2024-02-21 23:42:19 -05:00
Jade (Rose) Rowland 5e91287e3e Merge pull request #954 from daslyfe/midi_duration_fix
account for cps in midi time duration
2024-02-21 10:17:36 -05:00
Felix Roos 4e3d4a7060 hotfix: some samples were broken 2024-02-21 15:35:59 +01:00
Felix Roos 3d80da9816 Merge pull request #955 from tidalcycles/auto-await-samples
Auto await samples
2024-02-21 10:27:12 +01:00
Felix Roos bf63b716af refactor: remove await before samples calls in docs etc..
+ remove /master from dirt-samples calls (there is now a main branch)
+ remove /main endings from some places
2024-02-21 10:17:45 +01:00
Felix Roos 38d389b838 add test 2024-02-21 09:50:21 +01:00
Felix Roos ebdca32c80 automatically add await to samples call 2024-02-21 09:48:26 +01:00
Jade (Rose) Rowland 751a825616 fix test 2024-02-19 23:54:15 -05:00
Jade (Rose) Rowland c06a3710c1 fixed 2024-02-19 23:43:35 -05:00
eefano d5e67fe13f Update signal.mjs
typo
2024-02-18 19:01:05 +01:00
eefano 00c7da45c0 Update signal.mjs
now pickRestart() and pickReset()
2024-02-18 18:55:05 +01:00
eefano 3347817350 Update signal.mjs
using trigZeroJoin in pickr() instead
2024-02-18 18:41:06 +01:00
eefano 88ece92a78 Update signal.mjs
Make pickr() handle dictionaries also
2024-02-18 16:40:27 +01:00
eefano 03a73fb95e Update signal.mjs
Fixed pickr() description
2024-02-18 16:21:50 +01:00
eefano 23c55c9636 Update signal.mjs
prettier fix
2024-02-18 16:03:01 +01:00
eefano 12c6f4e95f Update signal.mjs
added pickr() and pickrmod()
2024-02-18 15:30:24 +01:00
Felix Roos a468ddb85b Merge pull request #945 from tidalcycles/cjs-fixes
remove cjs builds
2024-02-08 13:16:15 +01:00
Felix Roos 051bdaccc7 Publish
- @strudel/codemirror@1.0.1
 - @strudel/core@1.0.1
 - @strudel/csound@1.0.1
 - @strudel/hydra@1.0.1
 - @strudel/midi@1.0.1
 - @strudel/mini@1.0.1
 - @strudel/osc@1.0.1
 - @strudel/repl@1.0.1
 - @strudel/serial@1.0.1
 - @strudel/soundfonts@1.0.1
 - superdough@1.0.1
 - @strudel/tonal@1.0.1
 - @strudel/transpiler@1.0.1
 - @strudel/web@1.0.1
 - @strudel/webaudio@1.0.1
 - @strudel/xen@1.0.1
2024-02-08 13:04:25 +01:00
Felix Roos 5dc04d9574 make sure to overwrite main field 2024-02-08 12:36:04 +01:00
Felix Roos 60129413d3 remove cjs builds
+ add type module everywhere
2024-02-08 12:19:11 +01:00
Jade (Rose) Rowland 15df20d22d cleanup 2024-02-08 00:37:16 -05:00
Jade (Rose) Rowland b37c560ea5 importScripts 2024-02-08 00:20:40 -05:00
Jade (Rose) Rowland 5a3272fe29 fix repl merge 2024-02-07 22:23:24 -05:00
Felix Roos 0baa8383f2 fix: test 2024-02-04 23:32:28 +01:00
Felix Roos d2bf9370ea Update README.md
add link to usage example for repl package
2024-02-04 23:24:43 +01:00
Felix Roos fd11aa5eec Merge pull request #941 from mhetrick/patch-1
Minor documentation error: Update first-sounds.mdx
2024-02-04 23:15:37 +01:00
Jade (Rose) Rowland cea33783a2 remove console statement 2024-02-04 17:10:43 -05:00
Jade (Rose) Rowland 0d3eaf7f9a add a broadcast channel for recieving messages from clock to ensure that they hit at the same time 2024-02-04 17:06:28 -05:00
Jade (Rose) Rowland c761cd54b4 try to fix test again: 2024-02-04 14:32:42 -05:00
Jade (Rose) Rowland a8a055d32c fixing test 2024-02-04 14:25:16 -05:00
Jade (Rose) Rowland cc323d0d61 fixed import 2024-02-04 14:09:19 -05:00
Jade (Rose) Rowland fc87a6776a cleaning up 2024-02-04 14:04:12 -05:00
Jade (Rose) Rowland f5ac57f87b cleaning up 2024-02-04 13:56:46 -05:00
Jade (Rose) Rowland 90624abf2e added worker, cleaned up, added setcps function 2024-02-04 13:54:40 -05:00
Jade (Rose) Rowland e5ec62695c Merge branch 'tidalcycles:main' into repl_sync 2024-02-04 13:29:52 -05:00
Michael Hetrick fb7c2b61d3 Update first-sounds.mdx
Update the tutorial text to reflect that the default cpm setting has changed from 60 to 30.
2024-01-26 07:13:09 -08:00
Felix Roos dd64bf0fc9 Merge pull request #938 from tidalcycles/pianoroll-sorting
fix: pianoroll sorting
2024-01-24 16:48:57 +01:00
Felix Roos 434eb3b256 fix: pianoroll sorting 2024-01-24 16:40:26 +01:00
Enelg52 6da98a5f31 fix midi issue on firefox and quote error 2024-01-23 16:47:46 +01:00
Felix Roos 8fef75385a Update README.md 2024-01-23 01:35:28 +01:00
Felix Roos c66b9219a8 Update README.md 2024-01-23 01:31:11 +01:00
Felix Roos 2a1f8823e8 Merge pull request #935 from tidalcycles/v1-release-notes
V1 release notes
2024-01-23 00:04:03 +01:00
Felix Roos 3da65f90e3 shorter titles for release notes 2024-01-22 23:55:57 +01:00
Felix Roos a36cc24087 v1 release notes 2024-01-22 23:55:49 +01:00
Felix Roos 1829d8cadf Publish
- @strudel/codemirror@1.0.0
 - @strudel/core@1.0.0
 - @strudel/csound@1.0.0
 - @strudel/embed@1.0.0
 - @strudel/hydra@1.0.0
 - @strudel/midi@1.0.0
 - @strudel/mini@1.0.0
 - @strudel/osc@1.0.0
 - @strudel/repl@1.0.0
 - @strudel/serial@1.0.0
 - @strudel/soundfonts@1.0.0
 - superdough@1.0.0
 - @strudel/tonal@1.0.0
 - @strudel/transpiler@1.0.0
 - @strudel/web@1.0.0
 - @strudel/webaudio@1.0.0
 - @strudel/xen@1.0.0
2024-01-22 22:46:35 +01:00
Felix Roos 02efc090ef hotfix: examples were still 1 cps based 2024-01-22 20:46:10 +01:00
Felix Roos b184348f55 hotfix: youtube embed styles were lost on prod 2024-01-22 20:38:34 +01:00
Felix Roos 06743cb072 Merge pull request #929 from tidalcycles/2-years
2 years blog post
2024-01-22 20:20:52 +01:00
Felix Roos 6fa66fb1d0 finish year 2 post 2024-01-22 20:19:01 +01:00
Felix Roos 23c0ab9648 offset blog post anchor links 2024-01-22 20:18:51 +01:00
Alex McLean 6422047cff make 0.5hz cps the default (#931)
* 0.5 default cps

* 1 -> 0.5 cps defaults

* start moving examples to 2Hz

* more 2Hz doc edits

* small tweaks

* format

* adapt cycles page

* adapt pitch page

* tonal page

* accumulation

* synth page

* adapt conditional-modifiers

* audio effects page

* adapt signals doc

* fix: errors for signals

* adapt signals page

* start time modifiers

* adapt time modifiers

* adapt factories

* hydra + pattern intro

* adapt mini notation page

* start recipes

* adapt recipes page

* use code_v1 table

* delete old dbdump + add new csv based tool

* fix: tests

* fix: cpm

* shuffle featured patterns

* fix: snapshot

---------

Co-authored-by: Felix Roos <flix91@gmail.com>
2024-01-22 19:02:34 +00:00
Jade (Rose) Rowland 46c2c72637 Merge pull request #934 from daslyfe/patterntab_fix
Fix pattern tab not showing patterns without created date
2024-01-21 18:52:01 -05:00
Jade (Rose) Rowland c088590244 remove console statement 2024-01-21 18:51:06 -05:00
Jade (Rose) Rowland 192ba69682 fixed if statement 2024-01-21 18:49:06 -05:00
Jade (Rose) Rowland 2cc428e968 Merge pull request #897 from daslyfe/pattern_selection
Add useful pattern selection behavior for performing.
2024-01-21 14:46:27 -05:00
Jade (Rose) Rowland 15c451a6d9 default pattern filtername 2024-01-21 14:35:17 -05:00
Jade (Rose) Rowland b46e8c07be reset/autoplay when window PARENT does not exist or does not contain oodles 2024-01-21 12:44:14 -05:00
Jade (Rose) Rowland 961e931c31 autoplay and reset cps on default 2024-01-21 12:12:33 -05:00
Jade (Rose) Rowland b1d49552ae title order 2024-01-21 11:27:48 -05:00
Jade (Rose) Rowland d31d154841 fix oven imports 2024-01-21 11:24:58 -05:00
Jade (Rose) Rowland d03892bbcf Merge branch 'tidalcycles:main' into pattern_selection 2024-01-21 11:19:06 -05:00
Felix Roos 0cf0c1c585 Merge pull request #933 from tidalcycles/refactor-cps-functions
Refactor cps functions
2024-01-21 01:30:27 +01:00
Felix Roos 0695b4bee9 clean + comment 2024-01-21 01:29:31 +01:00
Felix Roos 0cfec3a7bd show repls in community bakery 2024-01-21 01:21:21 +01:00
Felix Roos 6224cd40d4 mini repl max height option 2024-01-21 01:21:13 +01:00
Felix Roos 18ae82174d fix: "can't redefine non-configurable property" 2024-01-21 01:13:32 +01:00
Felix Roos aa1b9d11dc inject scheduler into Pattern methods right before eval 2024-01-21 00:59:31 +01:00
Felix Roos 0a82471112 implement loopAt with pat state 2024-01-21 00:45:40 +01:00
Felix Roos 8052e10f0c implement fit with pat state 2024-01-21 00:39:26 +01:00
Jade (Rose) Rowland 1146f6ba52 fix annoying div 2024-01-20 18:14:42 -05:00
Alex McLean 738cea0025 Make splice cps-aware (#932)
* make splice cps-aware

* format

* copypaste fix
2024-01-20 22:47:31 +00:00
Jade (Rose) Rowland 6c28398d20 fix scroll 2024-01-20 14:25:42 -05:00
Jade (Rose) Rowland 4fe75143d0 cleanup 2024-01-20 14:10:46 -05:00
Jade (Rose) Rowland 3cd59cdea5 cleaning up 2024-01-20 14:02:48 -05:00
Jade (Rose) Rowland 9dca7eb878 add pattern filter behaves like sounds tab 2024-01-20 13:59:32 -05:00
Jade (Rose) Rowland b45fd5ce3a fix invalid date 2024-01-20 13:00:33 -05:00
Felix Roos 229b1470f7 fix: localhost links 2024-01-20 10:59:34 +01:00
Jade (Rose) Rowland 0a349e1707 avoid json errors in firefox 2024-01-19 23:04:41 -05:00
Felix Roos d176e9ccf0 add stats 2024-01-19 21:51:52 +01:00
Felix Roos 710c3acb5d more writing 2024-01-19 21:21:33 +01:00
Felix Roos a7f5d0e0c1 Merge pull request #923 from tidalcycles/feed
community bakery
2024-01-19 18:50:57 +01:00
Felix Roos 9928526dda Merge pull request #924 from geikha/main
add pickF and pickmodF
2024-01-19 18:49:53 +01:00
Renzo Torr- 2f8111be0e update the .snap file 2024-01-19 14:47:03 -03:00
Renzo Torr- 68113937ff code format 2024-01-19 14:06:24 -03:00
Felix Roos c598fa026b write more 2024-01-19 15:49:16 +01:00
Alex McLean 491c99c348 Mini-notation additions towards tidal compatibility (#926)
- `.` feet now work, e.g. `"a b c . d e . f"` is the same as `"[a b c] [d e] [f]"`
- `_` elongation now works, e.g. `"a _ _ b"` is the same as `"a@3 b"` (`"a @ @ b"` also works, like in tidal)
- standalone `!` now works, e.g. `"a ! ! b"` is the same as `"a!3 b"`

Reworks #47 and #49
Ref #30
2024-01-19 14:10:47 +00:00
Felix Roos d49aa8242a begin 2 year post 2024-01-19 13:50:28 +01:00
Renzo Torr- 9ffbc06cf9 add pickF and pickmodF
allows to pick functions via a pattern of numbers, to apply to another pattern
2024-01-19 02:45:54 -03:00
Jade (Rose) Rowland af52ac30ba prettier 2024-01-19 00:28:55 -05:00
Jade (Rose) Rowland 7aea1b5fc8 fixed imports 2024-01-19 00:27:09 -05:00
Jade (Rose) Rowland 500ddf3946 Merge branch 'main' into pattern_selection 2024-01-19 00:23:06 -05:00
Jade (Rose) Rowland 3431eaf309 cleaning up 2024-01-19 00:20:10 -05:00
Jade (Rose) Rowland 9a13c58583 cleaning up 2024-01-19 00:14:08 -05:00
Jade (Rose) Rowland 77a48e8351 persistent pattern data 2024-01-18 22:30:53 -05:00
Jade (Rose) Rowland 6cb156d876 cleaning up 2024-01-18 20:01:54 -05:00
Felix Roos d8677c6261 hotfix: rss links 2024-01-18 23:40:22 +01:00
Felix Roos 6ef9ca18e9 Merge pull request #919 from tidalcycles/blog-improvements
Blog improvements
2024-01-18 23:34:10 +01:00
Felix Roos 6667738dc9 more rename 2024-01-18 21:34:20 +01:00
Felix Roos 4f7415b0ec rename 2024-01-18 21:31:21 +01:00
Felix Roos c6f3a8b7c7 more text 2024-01-18 18:30:57 +01:00
Felix Roos 4229d673c9 basic community oven 2024-01-18 18:21:56 +01:00
Alex McLean 98b7859605 pick, pickmod, inhabit, inhabitmod (#921)
* the args for `pick` are now reversed as standard (old behaviour still supported to avoid breaking change)
* `pick` is also now a pattern method
* `pick` now also accepts a lookup table for pick-by-name as an alternative to pick-by-index from a list
* `inhabit` added with same behaviour as `pick`, except cycles from source patterns are squeezed into events of inhabited patterns
* Also some general doc tidying, sorry for the noise..
* There is also `pickmod` and `inhabitmod`, for wrapping indexes around rather than clamping them
2024-01-18 17:08:29 +00:00
Alex McLean a8db707440 Revert "pick now accepts lookup tables, with alternate cycle squeezing behaviour as new inhabit function" (#920) 2024-01-18 17:04:26 +00:00
Alex McLean 873a412179 pick now accepts lookup tables, with alternate cycle squeezing behaviour as new inhabit function (#918)
- the args for `pick` are now reversed (breaking change!)
- `pick` is also now a method on the inhabited pattern
- `pick` now also accepts a lookup table
- `inhabit` added with same behaviour as `pick`, except cycles from source patterns are squeezed into events of inhabited patterns
- Also some general doc tidying, sorry for the noise..
- There is now also `pickmod` and `inhabitmod`, for wrapping indexes around rather than clamping them
2024-01-18 16:45:39 +00:00
Felix Roos 0778b4809e format 2024-01-18 13:47:34 +01:00
Felix Roos f85fb35c66 minirepl multi tune support + add other getting-started examples (more to come) 2024-01-18 13:46:35 +01:00
Felix Roos 5ada0daf40 add rss link to top + fix typo 2024-01-18 13:01:06 +01:00
Felix Roos 2ed5f5aa65 blog style fixes 2024-01-18 12:50:35 +01:00
Felix Roos ae5e6e3ab8 add rss.xml 2024-01-18 12:49:22 +01:00
Felix Roos 9ba7b9b4d4 Merge pull request #917 from tidalcycles/deprecate-old-package-names
rename @strudel.cycles/* packages to @strudel/*
2024-01-18 10:30:08 +01:00
Felix Roos a45f1fba45 update versions in example html files 2024-01-18 10:25:24 +01:00
Felix Roos e6b47d2cf2 fix link 2024-01-18 10:04:53 +01:00
Felix Roos b04a262433 Publish
- @strudel/codemirror@0.11.0
 - @strudel/core@0.11.0
 - @strudel/csound@0.11.0
 - @strudel/embed@0.11.0
 - @strudel/hydra@0.11.0
 - @strudel/midi@0.11.0
 - @strudel/mini@0.11.0
 - @strudel/osc@0.11.0
 - @strudel/repl@0.11.0
 - @strudel/serial@0.11.0
 - @strudel/soundfonts@0.11.0
 - @strudel/tonal@0.11.0
 - @strudel/transpiler@0.11.0
 - @strudel/web@0.11.0
 - @strudel/webaudio@0.11.0
 - @strudel/xen@0.11.0
2024-01-18 09:59:57 +01:00
Felix Roos 96bafa7f0b the big rename: @strudel.cycles/* -> @strudel/* 2024-01-18 09:54:37 +01:00
Felix Roos b10612da5c Publish
- @strudel/codemirror@0.10.0
 - @strudel.cycles/core@0.10.0
 - @strudel.cycles/csound@0.10.0
 - @strudel.cycles/embed@0.10.0
 - @strudel/hydra@0.10.0
 - @strudel.cycles/midi@0.10.0
 - @strudel.cycles/mini@0.10.0
 - @strudel.cycles/osc@0.10.0
 - @strudel/repl@0.10.0
 - @strudel.cycles/serial@0.10.0
 - @strudel.cycles/soundfonts@0.10.0
 - superdough@0.10.0
 - @strudel.cycles/tonal@0.10.0
 - @strudel.cycles/transpiler@0.10.0
 - @strudel/web@0.10.0
 - @strudel.cycles/webaudio@0.10.0
 - @strudel.cycles/xen@0.10.0
2024-01-18 09:35:20 +01:00
Felix Roos 171f4206a8 Merge branch 'main' into deprecate-old-package-names 2024-01-18 09:33:16 +01:00
Felix Roos bd83d19197 Merge pull request #913 from tidalcycles/pitch-envelopes
pitch envelope
2024-01-18 06:54:33 +01:00
Felix Roos 95cf201c6d fix: heading 2024-01-18 06:49:09 +01:00
Felix Roos 4107711dae snapshot 2024-01-18 06:48:21 +01:00
Felix Roos 67f0758cb5 document pitch envelope 2024-01-18 06:47:54 +01:00
Felix Roos 1c99944a32 pitch envelope now also works when setting one of the controls (penv not needed) 2024-01-18 06:47:44 +01:00
Felix Roos d3ea4959b4 - more flexible pitch envelopes
- fix: exponential envelopes could break due to 0 values
2024-01-18 06:47:12 +01:00
Felix Roos 70b27fbff2 Merge pull request #916 from oscarbyrne/fix-swatch-static-paths
Fix: swatch/[name].png.js static path
2024-01-18 05:09:38 +01:00
Oscar Byrne 96c2144857 Use astro Response class for swatch/[name].png.js endpoint 2024-01-17 17:09:11 -08:00
Felix Roos 0655af4d4f add pcurve 2024-01-17 17:35:35 +01:00
Jade (Rose) Rowland 7adfe6f2df fixing edge cases... 2024-01-17 09:45:00 -05:00
Felix Roos 3fc0fdbe62 remove todo 2024-01-16 17:57:52 +01:00
Felix Roos fd73571744 make sure the sustained pitch is the selected note 2024-01-16 17:56:38 +01:00
Felix Roos 80d861c12c Merge pull request #907 from fnordomat/more-vowels
Add more vowel qualities for the vowels function
2024-01-16 08:22:27 +01:00
Jade (Rose) Rowland 45df7bfea3 making things consistent 2024-01-16 00:45:00 -05:00
Jade (Rose) Rowland 106772e480 Merge branch 'main' into pattern_selection 2024-01-15 18:37:18 -05:00
Felix Roos 3506de8d1a vibrato for soundfonts
+ add getVibratoOscillator helper
2024-01-16 00:07:56 +01:00
Felix Roos 32456d6966 pitch envelope for sampler and soundfonts
+ added getPitchEnvelope helper
2024-01-15 23:55:49 +01:00
Felix Roos bdc2af9733 basic pitch envelope 2024-01-15 23:25:22 +01:00
Felix Roos a7daf41934 Merge pull request #911 from tidalcycles/blog
adds a blog
2024-01-14 23:56:36 +01:00
Felix Roos fe6ff2528a Merge pull request #894 from rjulian/feat/add-header-workshop
Remove hideHeader for better mobile UI and consistency
2024-01-14 23:52:41 +01:00
Felix Roos 7ceb8708b0 fix: do not format autogenerated file 2024-01-14 23:47:50 +01:00
Felix Roos 4d22d957a1 add more videos to showcase 2024-01-14 23:45:39 +01:00
Felix Roos 79f1a863b6 add date + author to posts + dedupe headings 2024-01-14 23:39:03 +01:00
Felix Roos 98db3d37ef add anchor links 2024-01-14 23:30:26 +01:00
Felix Roos c02976782b add video component 2024-01-14 23:22:10 +01:00
Felix Roos ceb4376eb8 format autogenerated types 2024-01-14 23:16:44 +01:00
Felix Roos 0566b7bcb3 basic blog with release notes 2024-01-14 23:16:09 +01:00
Jade (Rose) Rowland 6d0ecb9f5e Merge pull request #868 from daslyfe/envelope_improvements
Further Envelope improvements
2024-01-14 16:43:06 -05:00
Felix Roos 0901948564 fanchor default to 0.5 for now 2024-01-14 21:32:38 +01:00
Felix Roos e6d028fd6b soundfont mixing 2024-01-14 16:27:11 +01:00
Alex McLean 945e7082cd Update metadata.mdx 2024-01-14 10:15:40 +00:00
Alex McLean 0d6bdfb514 Link to creative commons chooser 2024-01-14 10:14:43 +00:00
fnordomat 70712bdab6 fix codeformat 2024-01-14 10:19:49 +01:00
Felix Roos 53771d7f95 hotfix: avoid crash for anonymous patterns 2024-01-14 01:50:48 +01:00
Felix Roos 6d32d4024d hotfix: fix built version loading at runtime.. 2024-01-14 01:08:01 +01:00
Felix Roos f387589342 hotfix: fix build error 2024-01-14 00:53:43 +01:00
Felix Roos 47a5080352 Merge pull request #910 from tidalcycles/browse
public sharing
2024-01-14 00:48:03 +01:00
Felix Roos e7e60d3ed8 Merge branch 'main' into pattern_selection 2024-01-14 00:18:57 +01:00
Felix Roos 5314c83534 delete browse page for now 2024-01-14 00:02:27 +01:00
Felix Roos 204c964050 integrate public patterns into patterns tab 2024-01-13 23:53:46 +01:00
Felix Roos 1ede99f71e ask for public sharing (very cheap) + order browse list 2024-01-13 23:12:22 +01:00
Felix Roos 3c1844046a basic browse page 2024-01-13 23:01:16 +01:00
Felix Roos eefa90a2bc fix: synth default envelope 2024-01-13 22:24:59 +01:00
fnordomat efa0737660 added some aliases for the vowels (cosmetics) 2024-01-13 15:11:30 +01:00
fnordomat 6232560273 document extra vowels in quick reference 2024-01-13 11:11:31 +01:00
fnordomat e051d383a0 add 10 new vowel qualities to formant table 2024-01-13 11:11:31 +01:00
Felix Roos 4fb5c1828d i have no clue why this works 2024-01-13 01:16:26 +01:00
Felix Roos 6f3b2fa66b negative fenv values now stay in the same range as positives 2024-01-13 00:26:16 +01:00
Jade (Rose) Rowland ccb42c831c cleaning 2024-01-12 18:21:23 -05:00
Felix Roos f17459fdc3 Merge branch 'main' into envelope_improvements 2024-01-12 19:41:05 +01:00
Felix Roos a053f40e59 Merge pull request #902 from tidalcycles/build-warnings
fix some build warnings
2024-01-12 18:31:41 +01:00
Felix Roos 9a0459fdd3 move non pages out of pages dir 2024-01-12 18:16:57 +01:00
Felix Roos 46a859736a Merge pull request #891 from daslyfe/remove_jsx_exports_for_vite
prevent vite from complaining about additional exports in jsx files
2024-01-12 18:01:55 +01:00
Felix Roos e4524a2be9 Merge pull request #892 from daslyfe/update_vite
Update Vite version so hot reload works properly with newest pnpm version
2024-01-12 17:05:46 +01:00
Felix Roos f3ee1cc947 Merge pull request #886 from tidalcycles/polymeter-slowcat
support , in < >
2024-01-12 15:09:40 +01:00
Jade (Rose) Rowland 2386782280 cleaning... 2024-01-12 00:24:23 -05:00
Jade (Rose) Rowland 4d92922263 fixed animation drops 2024-01-12 00:20:20 -05:00
Felix Roos 86bc28dfb3 add deprecation notes 2024-01-11 20:46:22 +01:00
Jade (Rose) Rowland 721f707c94 working but animation is weird 2024-01-10 23:49:02 -05:00
Jade (Rose) Rowland 0c7b731fa6 latency :( 2024-01-10 20:05:27 -05:00
Jade (Rose) Rowland f1eaa83af5 trying stuff 2024-01-10 14:43:18 -05:00
Felix Roos 1bc0934228 Merge branch 'main' into envelope_improvements 2024-01-10 15:15:22 +01:00
Felix Roos f26fd18c7a use 0.001 as linear mintime as well (to prevent cracks) 2024-01-10 15:02:10 +01:00
Felix Roos c68dba8c21 fix: use end 2024-01-10 14:28:38 +01:00
Felix Roos f768e1eece Merge pull request #898 from tidalcycles/fix-autocomplete
fix: autocomplete / tooltip code example bug
2024-01-10 13:55:39 +01:00
Jade (Rose) Rowland d329ccc4e3 converting 2024-01-10 01:40:19 -05:00
Jade (Rose) Rowland 19f2434694 workerizing 2024-01-09 22:49:39 -05:00
Jade (Rose) Rowland 0006f6483d playing with workers 2024-01-09 19:39:21 -05:00
Felix Roos f99557cc05 fix: fanchor + default fanchor to 0 (breaking change) 2024-01-10 00:08:49 +01:00
Felix Roos f785823e00 add fenv to filter cutoff controls 2024-01-10 00:08:13 +01:00
Felix Roos 62cd2263c2 add silent flag to nanFallback 2024-01-10 00:03:18 +01:00
Felix Roos 7157634db0 fix: autocomplete / tooltip code example bug 2024-01-09 22:34:16 +01:00
Jade (Rose) Rowland 6eec4277c1 playing around 2024-01-08 23:34:12 -05:00
Jade (Rose) Rowland 78f6fdf169 reset for example patterns 2024-01-08 10:49:26 -05:00
Jade (Rose) Rowland e01c6dc18d prettier 2024-01-08 00:25:58 -05:00
Jade (Rose) Rowland 51dd408733 all working 2024-01-08 00:15:07 -05:00
Jade (Rose) Rowland 1ff7548b52 refactoring 2024-01-07 22:48:07 -05:00
Jade (Rose) Rowland f7fe44063a handle deletion cases 2024-01-07 00:58:03 -05:00
Jade (Rose) Rowland 0355c358b8 fixed create and duplicate behavior 2024-01-06 23:49:37 -05:00
Jade (Rose) Rowland 4e0a60db00 example code logic 2024-01-06 22:10:14 -05:00
Jade (Rose) Rowland 3604d3940c mutate viewing pattern 2024-01-06 17:53:51 -05:00
Jade (Rose) Rowland 02b9bd82cf highlight viewing pattern 2024-01-06 17:33:24 -05:00
Jade (Rose) Rowland 86442adb20 prettier 2024-01-06 15:09:09 -05:00
Richard Julian 569137eb22 Formatting for passing CI 2024-01-06 12:05:39 -08:00
Jade (Rose) Rowland 5e3a37c9e8 init 2024-01-06 14:55:45 -05:00
Richard Julian 4651066472 Remove hideHeader for better mobile UI and consistency 2024-01-06 11:55:00 -08:00
Jade (Rose) Rowland 8d325b96ea init 2024-01-06 14:49:52 -05:00
Jade (Rose) Rowland 150a1b8eed restore buffer hold behavior 2024-01-05 17:45:09 -05:00
Jade (Rose) Rowland b2f63897f9 change default FM env to exp because it modulates frequency and sounds way better :) 2024-01-05 01:07:54 -05:00
Jade (Rose) Rowland 2ee392be9b fixed all the things 2024-01-05 01:00:22 -05:00
Felix Roos a2974099c1 add dec synonym for decay 2024-01-03 23:06:23 +01:00
Felix Roos 041a809b07 add ar function 2024-01-03 22:41:03 +01:00
Felix Roos 2ecc6b30aa add ad function 2024-01-03 22:37:35 +01:00
Felix Roos d582bbbb60 fix: format 2024-01-03 22:17:11 +01:00
Felix Roos ca3eda8fc7 support , in < > 2024-01-03 21:54:10 +01:00
Jade (Rose) Rowland 1bc86c359e state 2024-01-03 01:04:39 -05:00
Jade (Rose) Rowland 1e2c26123b working 2024-01-03 00:35:47 -05:00
Jade (Rose) Rowland 83c820a18c prettier again 2024-01-01 18:50:23 -05:00
Jade (Rose) Rowland 3eface706c Merge branch 'tidalcycles:main' into envelope_improvements 2024-01-01 18:47:28 -05:00
Jade (Rose) Rowland 9756d63cf1 prettier 2023-12-31 11:58:06 -05:00
Jade (Rose) Rowland a800b32529 Merge branch 'main' into envelope_improvements 2023-12-31 11:51:49 -05:00
Felix Roos eefd8b37b1 ignore their wasm file 2023-12-30 16:48:21 +01:00
Felix Roos 89ae241bf7 some styling 2023-12-30 16:45:03 +01:00
Felix Roos a39b4c1637 parser viz 2023-12-30 16:43:08 +01:00
Felix Roos 4d332cd544 Merge branch 'main' into haskell-parser 2023-12-30 13:06:13 +01:00
Jade (Rose) Rowland 12451ba5fc Merge branch 'tidalcycles:main' into envelope_improvements 2023-12-29 22:27:52 -05:00
Felix Roos 64dbdd97ba little update 2023-12-29 21:27:54 +01:00
Felix Roos 756f37e265 format 2023-12-21 10:54:12 +01:00
Felix Roos 9e1ba442a9 tree-sitter experiment 2023-12-21 10:52:27 +01:00
Jade (Rose) Rowland 77fee0b866 fixed popping on font envelope 2023-12-20 17:39:11 -05:00
Jade (Rose) Rowland 5671b40707 account for phase complete 2023-12-20 15:24:14 -05:00
Jade (Rose) Rowland dec039ead3 fixed filter envelope popping... 2023-12-20 13:09:04 -05:00
Jade (Rose) Rowland fb81f6f268 still working on popping issue with firefox 2023-12-20 10:50:06 -05:00
Jade (Rose) Rowland deb973afa5 fixed hold behavior 2023-12-20 10:17:12 -05:00
Jade (Rose) Rowland 2dea3911ba remove unused variable 2023-12-20 09:29:07 -05:00
Jade (Rose) Rowland e0a7fb8290 filter should decay to set frequency 2023-12-20 09:28:40 -05:00
Jade (Rose) Rowland 30e402b9f1 fixed release bug 2023-12-20 01:52:43 -05:00
Jade (Rose) Rowland f6d9ad51c6 trying to fix divergent firefox behavior 2023-12-20 01:44:34 -05:00
Jade (Rose) Rowland 34ba81a841 fixed test complaint 2023-12-20 00:25:56 -05:00
Jade (Rose) Rowland d7fae2620e create release audio param method, make volume envelopes consistant 2023-12-20 00:21:14 -05:00
Jade Rowland fe60f34015 removed accidental file commit 2023-12-17 12:13:05 -05:00
Jade Rowland b5dc65d52a format 2023-12-16 13:36:05 -05:00
Jade Rowland 93a4efcf6d updated fontloader 2023-12-16 13:21:23 -05:00
Jade Rowland 96e0cce2d2 improve response 2023-12-16 00:30:35 -05:00
Jade Rowland 88f677e910 fix envelope behavior 2023-12-15 18:54:04 -05:00
Jade (Rose) Rowland 9f50f6ef0e Merge branch 'tidalcycles:main' into envelope_improvements 2023-12-15 18:27:23 -05:00
Jade Rowland 9663c2ec85 fileter envelopes 2023-12-15 12:16:28 -07:00
Jade Rowland 7da7554493 it works 2023-12-15 11:32:23 -07:00
418 changed files with 28551 additions and 24540 deletions
+2 -1
View File
@@ -21,4 +21,5 @@ vite.config.js
/src-tauri/target/**/*
reverbGen.mjs
hydra.mjs
jsdoc-synonyms.js
jsdoc-synonyms.js
packages/hs2js/src/hs2js.mjs
+1
View File
@@ -1,5 +1,6 @@
{
"env": {
"node": true,
"browser": true,
"es2021": true
},
+3
View File
@@ -127,3 +127,6 @@ fabric.properties
.idea/caches/build_file_checksums.ser
# END JetBrains -> BEGIN JetBrains
samples/*
!samples/README.md
+1
View File
@@ -10,3 +10,4 @@ paper
pnpm-lock.yaml
pnpm-workspace.yaml
**/dev-dist
website/.astro
+1 -1
View File
@@ -114,7 +114,7 @@ You can run the same check with `pnpm check`
## Package Workflow
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel/<package-name>` to get the local version,
allowing to develop multiple packages at the same time.
## Package Publishing
+6 -15
View File
@@ -2,37 +2,28 @@
[![Strudel test status](https://github.com/tidalcycles/strudel/actions/workflows/test.yml/badge.svg)](https://github.com/tidalcycles/strudel/actions)
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.
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is a bit more stable now, but please continue to tread carefully.
- 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>
- 2 Years of Strudel Blog Post: <https://strudel.cc/blog/#year-2>
## Running Locally
After cloning the project, you can run the REPL locally:
```bash
pnpm run setup
pnpm run repl
pnpm i
pnpm dev
```
## Using Strudel In Your Project
There are multiple npm packages you can use to use strudel, or only parts of it, in your project:
This project is organized into many [packages](./packages), which are also available on [npm](https://www.npmjs.com/search?q=%40strudel).
- [`core`](./packages/core/): tidal pattern engine
- [`mini`](./packages/mini): mini notation parser + core binding
- [`transpiler`](./packages/transpiler): user code transpiler
- [`webaudio`](./packages/webaudio): webaudio output
- [`osc`](./packages/osc): bindings to communicate via OSC
- [`midi`](./packages/midi): webmidi bindings
- [`serial`](./packages/serial): webserial bindings
- [`tonal`](./packages/tonal): tonal functions
- ... [and there are more](./packages/)
Click on the package names to find out more about each one.
Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start).
## Contributing
+22
View File
@@ -0,0 +1,22 @@
import { queryCode, testCycles } from '../test/runtime.mjs';
import * as tunes from '../website/src/repl/tunes.mjs';
import { describe, bench } from 'vitest';
import { calculateTactus } from '../packages/core/index.mjs';
const tuneKeys = Object.keys(tunes);
describe('renders tunes', () => {
tuneKeys.forEach((key) => {
describe(key, () => {
calculateTactus(true);
bench(`+tactus`, async () => {
await queryCode(tunes[key], testCycles[key] || 1);
});
calculateTactus(false);
bench(`-tactus`, async () => {
await queryCode(tunes[key], testCycles[key] || 1);
});
calculateTactus(true);
});
});
});
+1 -1
View File
@@ -7,7 +7,7 @@
/>
<div id="output"></div>
<script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
const strudel = await import('https://cdn.skypack.dev/@strudel/core@0.6.8');
Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval
const input = document.getElementById('text');
const getEvents = () => {
+1 -1
View File
@@ -8,7 +8,7 @@
/>
<canvas id="canvas"></canvas>
<script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
const strudel = await import('https://cdn.skypack.dev/@strudel/core@0.6.8');
// this adds all strudel functions to the global scope, to be used by eval
Object.assign(window, strudel);
// setup elements
+29 -8
View File
@@ -1,10 +1,31 @@
<!doctype html>
<button id="play">play</button>
<button id="stop">stop</button>
<script type="module">
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
<html>
<head>
<meta charset="UTF-8" />
<!-- <script src="../../packages/web/dist/index.js"></script> -->
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
</head>
<body style="background: #222">
<button id="play">play</button>
<button id="stop">stop</button>
<script>
strudel.initStrudel();
document.getElementById('play').addEventListener('click', () =>
evaluate(`
//@title washover @by Switch Angel
//@social https://www.instagram.com/_switch_angel/
initStrudel();
document.getElementById('play').addEventListener('click', () => evaluate('note("c a f e").jux(rev)'));
document.getElementById('play').addEventListener('stop', () => hush());
</script>
n("{0 1 3 5 2 }%16")
.add(n(tri.range(0, 6).slow(3)))
.scale("C4:pentatonic")
.sound("sawtooth").att(saw.range(0, 0.05).fast(6))
.release(3).pan(rand).decay(rand.range(0.1, 0.3))
.lpf(tri.range(200, 8000).slow(5)).lpq(0)
.gain(.4).vib(1).vibmod(.1)
.scope({pos:.5})
`),
);
document.getElementById('stop').addEventListener('click', () => strudel.hush());
</script>
</body>
</html>
+23 -15
View File
@@ -1,16 +1,24 @@
<!doctype html>
<button id="a">A</button>
<button id="b">B</button>
<button id="c">C</button>
<button id="stop">stop</button>
<script type="module">
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
initStrudel({
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
});
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
click('a', () => evaluate(`s('bd,jvbass(3,8)').jux(rev)`));
click('b', () => s('bd*2,hh(3,4),jvbass(5,8,1)').jux(rev).play());
click('c', () => s('bd*2,hh(3,4),jvbass:[0 4](5,8,1)').jux(rev).stack(s('~ sd')).play());
click('stop', () => hush());
</script>
<html>
<head>
<meta charset="UTF-8" />
<!-- <script src="../../packages/web/dist/index.js"></script> -->
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
</head>
<body style="background: #222">
<button id="a">A</button>
<button id="b">B</button>
<button id="c">C</button>
<button id="stop">stop</button>
<script>
initStrudel({
prebake: () => samples('github:tidalcycles/dirt-samples'),
});
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
click('a', () => evaluate(`s('bd,jvbass(3,8)').jux(rev)`));
click('b', () => s('bd*2,hh(3,4),jvbass(5,8,1)').jux(rev).play());
click('c', () => s('bd*2,hh(3,4),jvbass:[0 4](5,8,1)').jux(rev).stack(s('~ sd')).play());
click('stop', () => hush());
</script>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
<script src="https://unpkg.com/hs2js@0.0.4"></script>
<button id="hello">hello</button>
<script>
hs2js.setBase('https://unpkg.com/hs2js@0.0.4/dist/');
hs2js.loadParser();
document.getElementById('hello').addEventListener('click', () => {
hs2js.evaluate('alert "hello from haskell!"');
});
</script>
+20 -13
View File
@@ -16,38 +16,45 @@
</div>
<div id="output"></div>
<script type="module">
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.6.8';
import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0';
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.6.0';
// TODO: refactor to use newer version without controls import
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel/core@0.11.0';
import { mini } from 'https://cdn.skypack.dev/@strudel/mini@0.11.0';
import { transpiler } from 'https://cdn.skypack.dev/@strudel/transpiler@0.11.0';
import {
getAudioContext,
webaudioOutput,
initAudioOnFirstClick,
} from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0';
registerSynthSounds,
} from 'https://cdn.skypack.dev/@strudel/webaudio@0.11.0';
initAudioOnFirstClick();
const ctx = getAudioContext();
const input = document.getElementById('text');
input.innerHTML = getTune();
evalScope(
const loadModules = evalScope(
controls,
import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'),
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0'),
import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.6.0'),
import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0'),
import('https://cdn.skypack.dev/@strudel/core@0.11.0'),
import('https://cdn.skypack.dev/@strudel/mini@0.11.0'),
import('https://cdn.skypack.dev/@strudel/tonal@0.11.0'),
import('https://cdn.skypack.dev/@strudel/webaudio@0.11.0'),
);
const initAudio = Promise.all([initAudioOnFirstClick(), registerSynthSounds()]);
const { evaluate } = repl({
defaultOutput: webaudioOutput,
getTime: () => ctx.currentTime,
transpiler,
});
document.getElementById('start').addEventListener('click', () => evaluate(input.value));
document.getElementById('start').addEventListener('click', async () => {
await loadModules;
await initAudio;
evaluate(input.value);
});
function getTune() {
return `await samples('github:tidalcycles/Dirt-Samples/master')
return `samples('github:tidalcycles/dirt-samples')
setcps(1);
stack(
// amen
n("0 1 2 3 4 5 6 7")
+98
View File
@@ -0,0 +1,98 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>tidal.html</title>
</head>
<body style="margin: 0; padding: 0">
<textarea id="code" style="width: 100%; height: 200px"></textarea>
<pre id="result"></pre>
<script src="https://unpkg.com/hs2js@0.0.8"></script>
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
<script>
const { initStrudel } = strudel;
const { run, parse, setBase, loadParser } = hs2js;
setBase('https://unpkg.com/hs2js@0.0.4/dist/');
const ready = loadParser();
initStrudel({
prebake: () => samples('github:tidalcycles/dirt-samples'),
});
const textarea = document.getElementById('code');
if (window.location.hash) {
textarea.value = atob(window.location.hash.slice(1));
} else {
textarea.value = 'd1 $ s "jvbass(3,8)"';
}
textarea.addEventListener('input', (e) => {
window.location.hash = btoa(e.target.value);
update();
});
update();
function getInfixOperators() {
let operators = {
'>': 'set',
'#': 'set',
'+': 'add',
'-': 'sub',
'*': 'mul',
'/': 'div',
};
let alignments = {
in: (s) => '|' + s,
out: (s) => s + '|',
mix: (s) => '|' + s + '|',
};
let ops = {};
Object.entries(operators).forEach(([o, name]) => {
// operator without alignment
ops[o] = (l, r) => reify(l)[name](reify(r));
Object.entries(alignments).forEach(([a, getSymbol]) => {
// get symbol with alignment
let symbol = getSymbol(o);
ops[symbol] = (l, r) => reify(l)[name][a](reify(r));
});
});
ops['~>'] = (l, r) => reify(l).late(reify(r));
ops['<~'] = (l, r) => reify(l).early(reify(r));
ops['<$>'] = (l, r) => reify(r).fmap(l).outerJoin(); // is this right?
return ops;
}
const ops = getInfixOperators();
async function update() {
let result, tree;
await ready;
try {
tree = parse(textarea.value);
} catch (err) {
console.warn('parse error');
console.error(err);
}
console.log('parsed tree');
console.log(tree.rootNode.toString());
try {
let patterns = {};
window.p = (name, pattern) => {
patterns[name] = pattern;
};
window.d1 = (pat) => window.p(1, pat);
window.d2 = (pat) => window.p(2, pat);
window.d3 = (pat) => window.p(3, pat);
window.rot = late;
result = run(tree.rootNode, window, ops);
if (Object.values(patterns).length) {
stack(...Object.values(patterns)).play();
}
} catch (err) {
console.warn('eval error');
console.error(err);
result = 'ERROR: ' + err.message;
}
document.getElementById('result').innerHTML = 'Result: ' + result;
}
</script>
</body>
</html>
+1 -1
View File
@@ -1,4 +1,4 @@
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
<script src="https://unpkg.com/@strudel/embed@0.11.0"></script>
<!-- <script src="./embed.js"></script> -->
<strudel-repl>
<!--
@@ -1,4 +1,4 @@
<script src="https://unpkg.com/@strudel/repl@0.9.4"></script>
<script src="https://unpkg.com/@strudel/repl@1.0.2"></script>
<strudel-editor>
<!--
// @date 23-08-15
+8
View File
@@ -0,0 +1,8 @@
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
<button id="play">PLAY</button>
<script>
initStrudel({
prebake: () => samples('github:tidalcycles/dirt-samples'),
});
document.getElementById('play').addEventListener('click', () => s('bd sd').play());
</script>
+11 -10
View File
@@ -1,11 +1,12 @@
import { StrudelMirror } from '@strudel/codemirror';
import { funk42 } from './tunes';
import { drawPianoroll, evalScope, controls } from '@strudel.cycles/core';
import { evalScope } from '@strudel/core';
import { drawPianoroll } from '@strudel/draw';
import './style.css';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel.cycles/webaudio';
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
import { initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel/transpiler';
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel/webaudio';
import { registerSoundfonts } from '@strudel/soundfonts';
// init canvas
const canvas = document.getElementById('roll');
@@ -25,11 +26,11 @@ const editor = new StrudelMirror({
prebake: async () => {
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
const loadModules = evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/webaudio'),
import('@strudel/core'),
import('@strudel/draw'),
import('@strudel/mini'),
import('@strudel/tonal'),
import('@strudel/webaudio'),
);
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
},
+7 -6
View File
@@ -13,11 +13,12 @@
},
"dependencies": {
"@strudel/codemirror": "workspace:*",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/draw": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*"
}
}
+6 -6
View File
@@ -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.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
samples('github:felixroos/samples')
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
@@ -33,8 +33,8 @@ await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tid
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
await samples('github:felixroos/samples/main')
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
samples('github:felixroos/samples')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
addVoicings('hip', {
m11: ['2M 3m 4P 7m'],
@@ -69,8 +69,8 @@ export const funk42 = `// froos - how to funk in 42 lines of code
// adapted from "how to funk in two minutes" by marc rebillet https://www.youtube.com/watch?v=3vBwRfQbXkg
// thanks to peach for the transcription: https://www.youtube.com/watch?v=8eiPXvIgda4
await samples('github:felixroos/samples/main')
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
samples('github:felixroos/samples')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
setcps(.5)
+1 -1
View File
@@ -15,7 +15,7 @@
<script type="module">
import { initStrudel } from '@strudel/web';
initStrudel({
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
prebake: () => samples('github:tidalcycles/dirt-samples'),
});
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
+5 -10
View File
@@ -1,20 +1,15 @@
import { controls, repl, evalScope } from '@strudel.cycles/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { repl, evalScope } from '@strudel/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick, registerSynthSounds } from '@strudel/webaudio';
import { transpiler } from '@strudel/transpiler';
import tune from './tune.mjs';
const ctx = getAudioContext();
const input = document.getElementById('text');
input.innerHTML = tune;
initAudioOnFirstClick();
registerSynthSounds();
evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/tonal'),
);
evalScope(import('@strudel/core'), import('@strudel/mini'), import('@strudel/webaudio'), import('@strudel/tonal'));
const { evaluate } = repl({
defaultOutput: webaudioOutput,
+5 -5
View File
@@ -13,10 +13,10 @@
"vite": "^5.0.10"
},
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/tonal": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*",
"@strudel/tonal": "workspace:*"
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
export default `await samples('github:tidalcycles/Dirt-Samples/master')
export default `samples('github:tidalcycles/dirt-samples')
setcps(1)
stack(
// amen
n("0 1 2 3 4 5 6 7")
+1 -1
View File
@@ -12,7 +12,7 @@
const init = Promise.all([
initAudioOnFirstClick(),
samples('github:tidalcycles/Dirt-Samples/master'),
samples('github:tidalcycles/dirt-samples'),
registerSynthSounds(),
]);
+27
View File
@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
public/tree-sitter.wasm
public/tree-sitter-haskell.wasm
+10
View File
@@ -0,0 +1,10 @@
# @strudel/tidal
This is an experiment in implementing tree-sitter for parsing haskell.
```sh
pnpm i
cd haskell
pnpm copy-wasm
pnpm dev
```
+32
View File
@@ -0,0 +1,32 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tree sitter test</title>
<style>
body {
background-color: #121213;
color: white;
}
textarea {
padding: 10px;
color: white;
background-color: transparent;
outline: none;
}
a {
color: white;
}
</style>
</head>
<body style="margin: 0; padding: 0">
<textarea id="code" style="width: 100%; height: 200px"></textarea>
<pre id="result"></pre>
<script
type="module"
src="/main.js
"
></script>
</body>
</html>
+83
View File
@@ -0,0 +1,83 @@
import { run, parse, loadParser } from 'hs2js';
import { initStrudel, reify, late, samples, stack } from '@strudel/web';
initStrudel({
prebake: () => samples('github:tidalcycles/dirt-samples'),
});
const ready = loadParser();
const textarea = document.getElementById('code');
if (window.location.hash) {
textarea.value = atob(window.location.hash.slice(1));
} else {
textarea.value = 'd1 $ s "jvbass(3,8)"';
}
textarea.addEventListener('input', (e) => {
window.location.hash = btoa(e.target.value);
update();
});
update();
function getInfixOperators() {
let operators = {
'>': 'set',
'#': 'set',
'+': 'add',
'-': 'sub',
'*': 'mul',
'/': 'div',
};
let alignments = {
in: (s) => '|' + s,
out: (s) => s + '|',
mix: (s) => '|' + s + '|',
};
let ops = {};
Object.entries(operators).forEach(([o, name]) => {
// operator without alignment
ops[o] = (l, r) => reify(l)[name](reify(r));
Object.entries(alignments).forEach(([a, getSymbol]) => {
// get symbol with alignment
let symbol = getSymbol(o);
ops[symbol] = (l, r) => reify(l)[name][a](reify(r));
});
});
ops['~>'] = (l, r) => reify(l).late(reify(r));
ops['<~'] = (l, r) => reify(l).early(reify(r));
ops['<$>'] = (l, r) => reify(r).fmap(l).outerJoin(); // is this right?
return ops;
}
const ops = getInfixOperators();
async function update() {
let result, tree;
await ready;
try {
tree = parse(textarea.value);
} catch (err) {
console.warn('parse error');
console.error(err);
}
console.log('parsed tree');
console.log(tree.rootNode.toString());
try {
let patterns = {};
window.p = (name, pattern) => {
patterns[name] = pattern;
};
window.d1 = (pat) => window.p(1, pat);
window.d2 = (pat) => window.p(2, pat);
window.d3 = (pat) => window.p(3, pat);
window.rot = late;
result = run(tree.rootNode, window, ops);
if (Object.values(patterns).length) {
stack(...Object.values(patterns)).play();
}
} catch (err) {
console.warn('eval error');
console.error(err);
result = 'ERROR: ' + err.message;
}
document.getElementById('result').innerHTML = 'Result: ' + result;
}
+36
View File
@@ -0,0 +1,36 @@
{
"name": "tidal-repl",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "pnpm copy-wasm && vite build",
"preview": "vite preview",
"copy-wasm": "cp node_modules/hs2js/dist/tree-sitter.wasm public && cp node_modules/hs2js/dist/tree-sitter-haskell.wasm public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"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/web": "workspace:*",
"hs2js": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.8"
}
}
+3
View File
@@ -0,0 +1,3 @@
# public
this file is just here to make sure the public folder exists
+7 -2
View File
@@ -1,15 +1,20 @@
// this barrel export is currently only used to find undocumented exports
export * from './packages/codemirror/index.mjs';
export * from './packages/core/index.mjs';
export * from './packages/csound/index.mjs';
export * from './packages/embed/index.mjs';
export * from './packages/desktopbridge/index.mjs';
export * from './packages/draw/index.mjs';
export * from './packages/embed/index.mjs';
export * from './packages/hydra/index.mjs';
export * from './packages/midi/index.mjs';
export * from './packages/mini/index.mjs';
export * from './packages/osc/index.mjs';
export * from './packages/react/index.mjs';
export * from './packages/repl/index.mjs';
export * from './packages/serial/index.mjs';
export * from './packages/soundfonts/index.mjs';
export * from './packages/superdough/index.mjs';
export * from './packages/tonal/index.mjs';
export * from './packages/transpiler/index.mjs';
export * from './packages/web/index.mjs';
export * from './packages/webaudio/index.mjs';
export * from './packages/xen/index.mjs';
+11 -9
View File
@@ -1,5 +1,5 @@
{
"name": "@strudel.cycles/monorepo",
"name": "@strudel/monorepo",
"version": "0.5.0",
"private": true,
"description": "Port of tidalcycles to javascript",
@@ -11,6 +11,7 @@
"test": "npm run pretest && vitest run --version",
"test-ui": "npm run pretest && vitest --ui",
"test-coverage": "npm run pretest && vitest --coverage",
"bench": "npm run pretest && vitest bench",
"snapshot": "npm run pretest && vitest run -u --silent",
"repl": "npm run prestart && cd website && npm run dev",
"start": "npm run prestart && cd website && npm run dev",
@@ -25,6 +26,7 @@
"format-check": "prettier --check .",
"report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json",
"check": "npm run format-check && npm run lint && npm run test",
"sampler": "cd samples && node ../packages/sampler/sample-server.mjs",
"iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf"
},
"repository": {
@@ -45,18 +47,18 @@
},
"homepage": "https://strudel.cc",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*",
"@strudel/xen": "workspace:*"
},
"devDependencies": {
"dependency-tree": "^10.0.9",
"@tauri-apps/cli": "^1.5.9",
"@vitest/ui": "^1.1.0",
"canvas": "^2.11.2",
"acorn": "^8.11.3",
"dependency-tree": "^10.0.9",
"eslint": "^8.56.0",
"eslint-plugin-import": "^2.29.1",
"events": "^3.3.0",
+1 -1
View File
@@ -1,5 +1,5 @@
# Packages
Each folder represents one of the @strudel.cycles/* packages [published to npm](https://www.npmjs.com/org/strudel.cycles).
Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel).
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
+7 -1
View File
@@ -3,6 +3,12 @@ import jsdoc from '../../doc.json';
import { autocompletion } from '@codemirror/autocomplete';
import { h } from './html';
function plaintext(str) {
const div = document.createElement('div');
div.innerText = str;
return div.innerHTML;
}
const getDocLabel = (doc) => doc.name || doc.longname;
const getInnerText = (html) => {
var div = document.createElement('div');
@@ -21,7 +27,7 @@ ${doc.description}
)}
</ul>
<div>
${doc.examples?.map((example) => `<div><pre>${example}</pre></div>`)}
${doc.examples?.map((example) => `<div><pre>${plaintext(example)}</pre></div>`)}
</div>
</div>`[0];
/*
-38
View File
@@ -1,38 +0,0 @@
// import { SelectionRange } from '@codemirror/state';
import { RectangleMarker } from '@codemirror/view';
import { layer } from '@codemirror/view';
// currently stuck with: https://discuss.codemirror.net/t/line-background-layer/7666
export const backlayer = layer({
update: (update) => {
return update.docChanged;
},
markers: (view) => {
const offsetTop = 14; // how to know this number ? .view.documentTop is scroll relative..
const offsetLeft = 4; // how to know this number ?
return view.viewportLineBlocks.map((block) => {
const { left } = view.coordsAtPos(block.to);
return new RectangleMarker('cm-backlayer', offsetLeft, block.top + offsetTop, left, block.height);
});
},
});
/* const len = view.state.doc.length;
const markers = RectangleMarker.forRange(
view,
'cm-backlayer',
SelectionRange.fromJSON({
from: 0,
to: len,
anchor: 0,
head: len,
empty: true,
assoc: -1,
bidiLevel: null,
}),
);
console.log('markser', markers);
return markers;
*/
+48 -19
View File
@@ -2,7 +2,7 @@ import { closeBrackets } from '@codemirror/autocomplete';
// import { search, highlightSelectionMatches } from '@codemirror/search';
import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { defaultHighlightStyle, syntaxHighlighting, bracketMatching } from '@codemirror/language';
import { Compartment, EditorState, Prec } from '@codemirror/state';
import {
EditorView,
@@ -12,19 +12,22 @@ import {
lineNumbers,
drawSelection,
} from '@codemirror/view';
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
import { Pattern, repl } from '@strudel/core';
import { Drawer, cleanupDraw } from '@strudel/draw';
import { isAutoCompletionEnabled } from './autocomplete.mjs';
import { isTooltipEnabled } from './tooltip.mjs';
import { flash, isFlashEnabled } from './flash.mjs';
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
import { keybindings } from './keybindings.mjs';
import { initTheme, activateTheme, theme } from './themes.mjs';
import { updateWidgets, sliderPlugin } from './slider.mjs';
import { sliderPlugin, updateSliderWidgets } from './slider.mjs';
import { widgetPlugin, updateWidgets } from './widget.mjs';
import { persistentAtom } from '@nanostores/persistent';
import { backlayer } from './backlayer.mjs';
const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
isBracketMatchingEnabled: (on) => (on ? bracketMatching({ brackets: '()[]{}<>' }) : []),
isBracketClosingEnabled: (on) => (on ? closeBrackets() : []),
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
theme,
isAutoCompletionEnabled,
@@ -38,6 +41,8 @@ const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [ke
export const defaultSettings = {
keybindings: 'codemirror',
isBracketMatchingEnabled: false,
isBracketClosingEnabled: true,
isLineNumbersDisplayed: true,
isActiveLineHighlighted: false,
isAutoCompletionEnabled: false,
@@ -70,14 +75,13 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
...initialSettings,
javascript(),
sliderPlugin,
widgetPlugin,
// indentOnInput(), // works without. already brought with javascript extension?
// bracketMatching(), // does not do anything
closeBrackets(),
syntaxHighlighting(defaultHighlightStyle),
history(),
EditorView.updateListener.of((v) => onChange(v)),
drawSelection({ cursorBlinkRate: 0 }),
backlayer,
Prec.highest(
keymap.of([
{
@@ -125,6 +129,7 @@ export class StrudelMirror {
id,
initialCode = '',
onDraw,
drawContext,
drawTime = [0, 0],
autodraw,
prebake,
@@ -137,23 +142,16 @@ export class StrudelMirror {
this.widgets = [];
this.painters = [];
this.drawTime = drawTime;
this.onDraw = onDraw;
const self = this;
this.drawContext = drawContext;
this.onDraw = onDraw || this.draw;
this.id = id || s4();
this.drawer = new Drawer((haps, time) => {
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
const currentFrame = haps.filter((hap) => hap.isActive(time));
this.highlight(currentFrame, time);
this.onDraw?.(haps, time, currentFrame, this.painters);
this.onDraw(haps, time, this.painters);
}, drawTime);
// this approach does not work with multiple repls on screen
// TODO: refactor onPaint usages + find fix, maybe remove painters here?
Pattern.prototype.onPaint = function (onPaint) {
self.painters.push(onPaint);
return this;
};
this.prebaked = prebake();
autodraw && this.drawFirstFrame();
@@ -179,6 +177,14 @@ export class StrudelMirror {
beforeEval: async () => {
cleanupDraw();
this.painters = [];
const self = this;
// this is similar to repl.mjs > injectPatternMethods
// maybe there is a solution without prototype hacking, but hey, it works
// we need to do this befor every eval to make sure it works with multiple StrudelMirror's side by side
Pattern.prototype.onPaint = function (onPaint) {
self.painters.push(onPaint);
return this;
};
await this.prebaked;
await replOptions?.beforeEval?.();
},
@@ -186,7 +192,10 @@ export class StrudelMirror {
// remember for when highlighting is toggled on
this.miniLocations = options.meta?.miniLocations;
this.widgets = options.meta?.widgets;
updateWidgets(this.editor, this.widgets);
const sliders = this.widgets.filter((w) => w.type === 'slider');
updateSliderWidgets(this.editor, sliders);
const widgets = this.widgets.filter((w) => w.type !== 'slider');
updateWidgets(this.editor, widgets);
updateMiniLocations(this.editor, this.miniLocations);
replOptions?.afterEval?.(options);
this.adjustDrawTime();
@@ -230,6 +239,9 @@ export class StrudelMirror {
// when no painters are set, [0,0] is enough (just highlighting)
this.drawer.setDrawTime(this.painters.length ? this.drawTime : [0, 0]);
}
draw(haps, time) {
this.painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
}
async drawFirstFrame() {
if (!this.onDraw) {
return;
@@ -240,7 +252,7 @@ export class StrudelMirror {
await this.repl.evaluate(this.code, false);
this.drawer.invalidate(this.repl.scheduler, -0.001);
// draw at -0.001 to avoid haps at 0 to be visualized as active
this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters);
this.onDraw?.(this.drawer.visibleHaps, -0.001, this.painters);
} catch (err) {
console.warn('first frame could not be painted');
}
@@ -292,9 +304,15 @@ export class StrudelMirror {
setLineWrappingEnabled(enabled) {
this.reconfigureExtension('isLineWrappingEnabled', enabled);
}
setBracketMatchingEnabled(enabled) {
this.reconfigureExtension('isBracketMatchingEnabled', enabled);
}
setLineNumbersDisplayed(enabled) {
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
}
setBracketClosingEnabled(enabled) {
this.reconfigureExtension('isBracketClosingEnabled', enabled);
}
setTheme(theme) {
this.reconfigureExtension('theme', theme);
}
@@ -327,6 +345,17 @@ export class StrudelMirror {
clear() {
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
}
getCursorLocation() {
return this.editor.state.selection.main.head;
}
setCursorLocation(col) {
return this.editor.dispatch({ selection: { anchor: col } });
}
appendCode(code) {
const cursor = this.getCursorLocation();
this.setCode(this.code + code);
this.setCursorLocation(cursor);
}
}
function parseBooleans(value) {
+4 -2
View File
@@ -57,6 +57,9 @@ const visibleMiniLocations = StateField.define({
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range
const haps = new Map();
for (let hap of e.value.haps) {
if (!hap.context?.locations || !hap.whole) {
continue;
}
for (let { start, end } of hap.context.locations) {
let id = `${start}:${end}`;
if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) {
@@ -64,7 +67,6 @@ const visibleMiniLocations = StateField.define({
}
}
}
visible = { atTime: e.value.atTime, haps };
}
}
@@ -90,7 +92,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
if (haps.has(id)) {
const hap = haps.get(id);
const color = hap.context.color ?? 'var(--foreground)';
const color = hap.value?.color ?? 'var(--foreground)';
// Get explicit channels for color values
/*
const swatch = document.createElement('div');
+1 -1
View File
@@ -3,4 +3,4 @@ export * from './highlight.mjs';
export * from './flash.mjs';
export * from './slider.mjs';
export * from './themes.mjs';
export * from './backlayer.mjs';
export * from './widget.mjs';
+7 -6
View File
@@ -1,11 +1,10 @@
{
"name": "@strudel/codemirror",
"version": "0.9.0",
"version": "1.1.0",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
@@ -41,14 +40,16 @@
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.23.0",
"@lezer/highlight": "^1.2.0",
"@nanostores/persistent": "^0.9.1",
"@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.1.0",
"@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel.cycles/core": "workspace:*",
"@strudel/core": "workspace:*",
"@strudel/draw": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@uiw/codemirror-themes": "^4.21.21",
"@uiw/codemirror-themes-all": "^4.21.21",
"nanostores": "^0.9.5",
"@nanostores/persistent": "^0.9.1"
"nanostores": "^0.9.5"
},
"devDependencies": {
"vite": "^5.0.10"
+16 -14
View File
@@ -1,6 +1,6 @@
import { ref, pure } from '@strudel.cycles/core';
import { ref, pure } from '@strudel/core';
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
import { StateEffect, StateField } from '@codemirror/state';
import { StateEffect } from '@codemirror/state';
export let sliderValues = {};
const getSliderID = (from) => `slider_${from}`;
@@ -60,19 +60,21 @@ export class SliderWidget extends WidgetType {
}
}
export const setWidgets = StateEffect.define();
export const setSliderWidgets = StateEffect.define();
export const updateWidgets = (view, widgets) => {
view.dispatch({ effects: setWidgets.of(widgets) });
export const updateSliderWidgets = (view, widgets) => {
view.dispatch({ effects: setSliderWidgets.of(widgets) });
};
function getWidgets(widgetConfigs, view) {
return widgetConfigs.map(({ from, to, value, min, max, step }) => {
return Decoration.widget({
widget: new SliderWidget(value, min, max, from, to, step, view),
side: 0,
}).range(from /* , to */);
});
function getSliders(widgetConfigs, view) {
return widgetConfigs
.filter((w) => w.type === 'slider')
.map(({ from, to, value, min, max, step }) => {
return Decoration.widget({
widget: new SliderWidget(value, min, max, from, to, step, view),
side: 0,
}).range(from /* , to */);
});
}
export const sliderPlugin = ViewPlugin.fromClass(
@@ -99,8 +101,8 @@ export const sliderPlugin = ViewPlugin.fromClass(
}
}
for (let e of tr.effects) {
if (e.is(setWidgets)) {
this.decorations = Decoration.set(getWidgets(e.value, update.view));
if (e.is(setSliderWidgets)) {
this.decorations = Decoration.set(getSliders(e.value, update.view));
}
}
});
+2
View File
@@ -37,6 +37,7 @@ import whitescreen, { settings as whitescreenSettings } from './themes/whitescre
import teletext, { settings as teletextSettings } from './themes/teletext';
import algoboy, { settings as algoboySettings } from './themes/algoboy';
import terminal, { settings as terminalSettings } from './themes/terminal';
import { setTheme } from '@strudel/draw';
export const themes = {
strudelTheme,
@@ -513,6 +514,7 @@ export function activateTheme(name) {
.map(([key, value]) => `--${key}: ${value} !important;`)
.join('\n')}
}`;
setTheme(themeSettings);
// tailwind dark mode
if (themeSettings.light) {
document.documentElement.classList.remove('dark');
+1
View File
@@ -18,6 +18,7 @@ export default createTheme({
theme: 'light',
settings,
styles: [
{ tag: t.labelName, color: '#0f380f' },
{ tag: t.keyword, color: '#0f380f' },
{ tag: t.operator, color: '#0f380f' },
{ tag: t.special(t.variableName), color: '#0f380f' },
+1
View File
@@ -15,6 +15,7 @@ export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.labelName, color: 'white' },
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
+1
View File
@@ -18,6 +18,7 @@ export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.labelName, color: 'white' },
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
+1
View File
@@ -15,6 +15,7 @@ export default createTheme({
gutterForeground: '#8a919966',
},
styles: [
{ tag: t.labelName, color: '#89ddff' },
{ tag: t.keyword, color: '#c792ea' },
{ tag: t.operator, color: '#89ddff' },
{ tag: t.special(t.variableName), color: '#eeffff' },
+1
View File
@@ -27,6 +27,7 @@ export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.labelName, color: colorB },
{ tag: t.keyword, color: colorA },
{ tag: t.operator, color: mini },
{ tag: t.special(t.variableName), color: colorA },
+1
View File
@@ -14,6 +14,7 @@ export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.labelName, color: '#41FF00' },
{ tag: t.keyword, color: '#41FF00' },
{ tag: t.operator, color: '#41FF00' },
{ tag: t.special(t.variableName), color: '#41FF00' },
+1
View File
@@ -16,6 +16,7 @@ export default createTheme({
theme: 'light',
settings,
styles: [
{ tag: t.labelName, color: 'black' },
{ tag: t.keyword, color: 'black' },
{ tag: t.operator, color: 'black' },
{ tag: t.special(t.variableName), color: 'black' },
+2 -2
View File
@@ -8,8 +8,8 @@ export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
formats: ['es'],
fileName: (ext) => ({ es: 'index.mjs' })[ext],
},
rollupOptions: {
external: [...Object.keys(dependencies)],
+135
View File
@@ -0,0 +1,135 @@
import { StateEffect, StateField } from '@codemirror/state';
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
import { getWidgetID, registerWidgetType } from '@strudel/transpiler';
import { Pattern } from '@strudel/core';
export const addWidget = StateEffect.define({
map: ({ from, to }, change) => {
return { from: change.mapPos(from), to: change.mapPos(to) };
},
});
export const updateWidgets = (view, widgets) => {
view.dispatch({ effects: addWidget.of(widgets) });
};
function getWidgets(widgetConfigs) {
return (
widgetConfigs
// codemirror throws an error if we don't sort
.sort((a, b) => a.to - b.to)
.map((widgetConfig) => {
return Decoration.widget({
widget: new BlockWidget(widgetConfig),
side: 0,
block: true,
}).range(widgetConfig.to);
})
);
}
const widgetField = StateField.define(
/* <DecorationSet> */ {
create() {
return Decoration.none;
},
update(widgets, tr) {
widgets = widgets.map(tr.changes);
for (let e of tr.effects) {
if (e.is(addWidget)) {
try {
widgets = widgets.update({
filter: () => false,
add: getWidgets(e.value),
});
} catch (error) {
console.log('err', error);
}
}
}
return widgets;
},
provide: (f) => EditorView.decorations.from(f),
},
);
const widgetElements = {};
export function setWidget(id, el) {
widgetElements[id] = el;
el.id = id;
}
export class BlockWidget extends WidgetType {
constructor(widgetConfig) {
super();
this.widgetConfig = widgetConfig;
}
eq() {
return true;
}
toDOM() {
const id = getWidgetID(this.widgetConfig);
const el = widgetElements[id];
return el;
}
ignoreEvent(e) {
return true;
}
}
export const widgetPlugin = [widgetField];
// widget implementer API to create a new widget type
export function registerWidget(type, fn) {
registerWidgetType(type);
if (fn) {
Pattern.prototype[type] = function (id, options = { fold: 1 }) {
// fn is expected to create a dom element and call setWidget(id, el);
// fn should also return the pattern
return fn(id, options, this);
};
}
}
// wire up @strudel/draw functions
function getCanvasWidget(id, options = {}) {
const { width = 500, height = 60, pixelRatio = window.devicePixelRatio } = options;
let canvas = document.getElementById(id) || document.createElement('canvas');
canvas.width = width * pixelRatio;
canvas.height = height * pixelRatio;
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
setWidget(id, canvas);
return canvas;
}
registerWidget('_pianoroll', (id, options = {}, pat) => {
const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.tag(id).pianoroll({ fold: 1, ...options, ctx, id });
});
registerWidget('_punchcard', (id, options = {}, pat) => {
const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.tag(id).punchcard({ fold: 1, ...options, ctx, id });
});
registerWidget('_spiral', (id, options = {}, pat) => {
let _size = options.size || 275;
options = { width: _size, height: _size, ...options, size: _size / 5 };
const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.tag(id).spiral({ ...options, ctx, id });
});
registerWidget('_scope', (id, options = {}, pat) => {
options = { width: 500, height: 60, pos: 0.5, scale: 1, ...options };
const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.tag(id).scope({ ...options, ctx, id });
});
registerWidget('_pitchwheel', (id, options = {}, pat) => {
let _size = options.size || 200;
options = { width: _size, height: _size, ...options, size: _size / 5 };
const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.pitchwheel({ ...options, ctx, id });
});
+4 -4
View File
@@ -1,17 +1,17 @@
# @strudel.cycles/core
# @strudel/core
This package contains the bare essence of strudel.
## Install
```sh
npm i @strudel.cycles/core --save
npm i @strudel/core --save
```
## Example
```js
import { sequence } from '@strudel.cycles/core';
import { sequence } from '@strudel/core';
const pattern = sequence('a', ['b', 'c']);
@@ -33,7 +33,7 @@ b: 3/2 - 7/4
c: 7/4 - 2
```
- [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js).
- [play with @strudel/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js).
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html)
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html)
- [open minimal vite example](./examples/vite-vanilla-repl/)
+46
View File
@@ -0,0 +1,46 @@
import { describe, bench } from 'vitest';
import { calculateTactus, sequence, stack } from '../index.mjs';
const pat64 = sequence(...Array(64).keys());
describe('tactus', () => {
calculateTactus(true);
bench(
'+tactus',
() => {
pat64.iter(64).fast(64).firstCycle();
},
{ time: 1000 },
);
calculateTactus(false);
bench(
'-tactus',
() => {
pat64.iter(64).fast(64).firstCycle();
},
{ time: 1000 },
);
});
describe('stack', () => {
calculateTactus(true);
bench(
'+tactus',
() => {
stack(pat64, pat64, pat64, pat64, pat64, pat64, pat64, pat64).fast(64).firstCycle();
},
{ time: 1000 },
);
calculateTactus(false);
bench(
'-tactus',
() => {
stack(pat64, pat64, pat64, pat64, pat64, pat64, pat64, pat64).fast(64).firstCycle();
},
{ time: 1000 },
);
});
calculateTactus(true);
+168
View File
@@ -0,0 +1,168 @@
// eslint-disable-next-line no-undef
// TODO: swap below line with above one when firefox supports esm imports in service workers
// see https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker?retiredLocale=de#browser_compatibility
// import createClock from './zyklus.mjs';
function getTime() {
const precision = 10 ** 4;
const seconds = performance.now() / 1000;
return Math.round(seconds * precision) / precision;
}
let num_cycles_at_cps_change = 0;
let num_ticks_since_cps_change = 0;
let num_seconds_at_cps_change = 0;
let cps = 0.5;
// {id: {started: boolean}}
const clients = new Map();
const duration = 0.1;
const channel = new BroadcastChannel('strudeltick');
const sendMessage = (type, payload) => {
channel.postMessage({ type, payload });
};
const sendTick = (phase, duration, tick, time) => {
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration;
const tickdeadline = phase - time;
const lastTick = time + tickdeadline;
const num_cycles_since_cps_change = num_seconds_since_cps_change * cps;
const begin = num_cycles_at_cps_change + num_cycles_since_cps_change;
const secondsSinceLastTick = time - lastTick - duration;
const eventLength = duration * cps;
const end = begin + eventLength;
const cycle = begin + secondsSinceLastTick * cps;
sendMessage('tick', {
begin,
end,
cps,
tickdeadline,
num_cycles_at_cps_change,
num_seconds_at_cps_change,
num_seconds_since_cps_change,
cycle,
});
num_ticks_since_cps_change++;
};
//create clock method from zyklus
const clock = createClock(getTime, sendTick, duration);
let started = false;
const startClock = (id) => {
clients.set(id, { started: true });
if (started) {
return;
}
clock.start();
started = true;
};
const stopClock = async (id) => {
clients.set(id, { started: false });
const otherClientStarted = Array.from(clients.values()).some((c) => c.started);
//dont stop the clock if other instances are running...
if (!started || otherClientStarted) {
return;
}
clock.stop();
setCycle(0);
started = false;
};
const setCycle = (cycle) => {
num_ticks_since_cps_change = 0;
num_cycles_at_cps_change = cycle;
};
const processMessage = (message) => {
const { type, payload } = message;
switch (type) {
case 'cpschange': {
if (payload.cps !== cps) {
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration;
num_cycles_at_cps_change = num_cycles_at_cps_change + num_seconds_since_cps_change * cps;
num_seconds_at_cps_change = num_seconds_at_cps_change + num_seconds_since_cps_change;
cps = payload.cps;
num_ticks_since_cps_change = 0;
}
break;
}
case 'setcycle': {
setCycle(payload.cycle);
break;
}
case 'toggle': {
if (payload.started) {
startClock(message.id);
} else {
stopClock(message.id);
}
break;
}
}
};
self.onconnect = function (e) {
// the incoming port
const port = e.ports[0];
port.addEventListener('message', function (e) {
processMessage(e.data);
});
port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
};
// used to consistently schedule events, for use in a service worker - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/clockworker.mjs>
function createClock(
getTime,
callback, // called slightly before each cycle
duration = 0.05, // duration of each cycle
interval = 0.1, // interval between callbacks
overlap = 0.1, // overlap between callbacks
) {
let tick = 0; // counts callbacks
let phase = 0; // next callback time
let precision = 10 ** 4; // used to round phase
let minLatency = 0.01;
const setDuration = (setter) => (duration = setter(duration));
overlap = overlap || interval / 2;
const onTick = () => {
const t = getTime();
const lookahead = t + interval + overlap; // the time window for this tick
if (phase === 0) {
phase = t + minLatency;
}
// callback as long as we're inside the lookahead
while (phase < lookahead) {
phase = Math.round(phase * precision) / precision;
phase >= t && callback(phase, duration, tick, t);
phase < t && console.log('TOO LATE', phase); // what if latency is added from outside?
phase += duration; // increment phase by duration
tick++;
}
};
let intervalID;
const start = () => {
clear(); // just in case start was called more than once
onTick();
intervalID = setInterval(onTick, interval * 1000);
};
const clear = () => intervalID !== undefined && clearInterval(intervalID);
const pause = () => clear();
const stop = () => {
tick = 0;
phase = 0;
clear();
};
const getPhase = () => phase;
// setCallback
return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency };
}
+1556 -1360
View File
File diff suppressed because it is too large Load Diff
+36 -23
View File
@@ -1,5 +1,5 @@
/*
cyclist.mjs - <short description TODO>
cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/neocyclist.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/cyclist.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -8,49 +8,55 @@ import createClock from './zyklus.mjs';
import { logger } from './logger.mjs';
export class Cyclist {
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1, setInterval, clearInterval }) {
this.started = false;
this.cps = 1;
this.cps = 0.5;
this.num_ticks_since_cps_change = 0;
this.lastTick = 0; // absolute time when last tick (clock callback) happened
this.lastBegin = 0; // query begin of last tick
this.lastEnd = 0; // query end of last tick
this.getTime = getTime; // get absolute time
this.num_cycles_since_last_cps_change = 0;
this.num_cycles_at_cps_change = 0;
this.seconds_at_cps_change; // clock phase when cps was changed
this.onToggle = onToggle;
this.latency = latency; // fixed trigger time offset
this.clock = createClock(
getTime,
// called slightly before each cycle
(phase, duration, tick) => {
if (tick === 0) {
this.origin = phase;
}
(phase, duration, _, t) => {
if (this.num_ticks_since_cps_change === 0) {
this.num_cycles_since_last_cps_change = this.lastEnd;
this.num_cycles_at_cps_change = this.lastEnd;
this.seconds_at_cps_change = phase;
}
this.num_ticks_since_cps_change++;
const seconds_since_cps_change = this.num_ticks_since_cps_change * duration;
const num_cycles_since_cps_change = seconds_since_cps_change * this.cps;
try {
const time = getTime();
const begin = this.lastEnd;
this.lastBegin = begin;
//convert ticks to cycles, so you can query the pattern for events
const eventLength = duration * this.cps;
const end = this.num_cycles_since_last_cps_change + this.num_ticks_since_cps_change * eventLength;
const end = this.num_cycles_at_cps_change + num_cycles_since_cps_change;
this.lastEnd = end;
this.lastTick = phase;
if (phase < t) {
// avoid querying haps that are in the past anyway
console.log(`skip query: too late`);
return;
}
// query the pattern for events
const haps = this.pattern.queryArc(begin, end);
const tickdeadline = phase - time; // time left until the phase is a whole number
this.lastTick = time + tickdeadline;
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
haps.forEach((hap) => {
if (hap.part.begin.equals(hap.whole.begin)) {
const deadline = (hap.whole.begin - begin) / this.cps + tickdeadline + latency;
if (hap.hasOnset()) {
const targetTime =
(hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency;
const duration = hap.duration / this.cps;
onTrigger?.(hap, deadline, duration, this.cps);
// the following line is dumb and only here for backwards compatibility
// see https://github.com/tidalcycles/strudel/pull/1004
const deadline = targetTime - phase;
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
}
});
} catch (e) {
@@ -59,9 +65,16 @@ export class Cyclist {
}
},
interval, // duration of each cycle
0.1,
0.1,
setInterval,
clearInterval,
);
}
now() {
if (!this.started) {
return 0;
}
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
}
@@ -71,7 +84,7 @@ export class Cyclist {
}
start() {
this.num_ticks_since_cps_change = 0;
this.num_cycles_since_last_cps_change = 0;
this.num_cycles_at_cps_change = 0;
if (!this.pattern) {
throw new Error('Scheduler: no pattern set! call .setPattern first.');
}
@@ -96,7 +109,7 @@ export class Cyclist {
this.start();
}
}
setCps(cps = 1) {
setCps(cps = 0.5) {
if (this.cps === cps) {
return;
}
+8 -2
View File
@@ -41,11 +41,17 @@ const _bjork = function (n, x) {
};
export const bjork = function (ons, steps) {
const inverted = ons < 0;
ons = Math.abs(ons);
const offs = steps - ons;
const x = Array(ons).fill([1]);
const y = Array(offs).fill([0]);
const result = _bjork([ons, offs], [x, y]);
return flatten(result[1][0]).concat(flatten(result[1][1]));
const p = flatten(result[1][0]).concat(flatten(result[1][1]));
if (inverted) {
return p.map((x) => (x === 0 ? 1 : 0));
}
return p;
};
/**
@@ -148,7 +154,7 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun
* @param {number} pulses the number of onsets / beats
* @param {number} steps the number of steps to fill
* @example
* n("g2").decay(.1).sustain(.3).euclidLegato(3,8)
* note("c3").euclidLegato(3,8)
*/
const _euclidLegato = function (pulses, steps, rotation, pat) {
+1
View File
@@ -22,6 +22,7 @@ export const evalScope = async (...args) => {
globalThis[name] = value;
});
});
return modules;
};
function safeEval(str, options = {}) {
+43
View File
@@ -6,6 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import Fraction from 'fraction.js';
import { TimeSpan } from './timespan.mjs';
import { removeUndefineds } from './util.mjs';
// Returns the start of the cycle.
Fraction.prototype.sam = function () {
@@ -47,14 +48,39 @@ Fraction.prototype.eq = function (other) {
return this.compare(other) == 0;
};
Fraction.prototype.ne = function (other) {
return this.compare(other) != 0;
};
Fraction.prototype.max = function (other) {
return this.gt(other) ? this : other;
};
Fraction.prototype.maximum = function (...others) {
others = others.map((x) => new Fraction(x));
return others.reduce((max, other) => other.max(max), this);
};
Fraction.prototype.min = function (other) {
return this.lt(other) ? this : other;
};
Fraction.prototype.mulmaybe = function (other) {
return other !== undefined ? this.mul(other) : undefined;
};
Fraction.prototype.divmaybe = function (other) {
return other !== undefined ? this.div(other) : undefined;
};
Fraction.prototype.addmaybe = function (other) {
return other !== undefined ? this.add(other) : undefined;
};
Fraction.prototype.submaybe = function (other) {
return other !== undefined ? this.sub(other) : undefined;
};
Fraction.prototype.show = function (/* excludeWhole = false */) {
// return this.toFraction(excludeWhole);
return this.s * this.n + '/' + this.d;
@@ -80,9 +106,26 @@ const fraction = (n) => {
};
export const gcd = (...fractions) => {
fractions = removeUndefineds(fractions);
if (fractions.length === 0) {
return undefined;
}
return fractions.reduce((gcd, fraction) => gcd.gcd(fraction), fraction(1));
};
export const lcm = (...fractions) => {
fractions = removeUndefineds(fractions);
if (fractions.length === 0) {
return undefined;
}
return fractions.reduce(
(lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)),
fraction(1),
);
};
fraction._original = Fraction;
export default fraction;
+36 -1
View File
@@ -3,6 +3,7 @@ hap.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/hap.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Fraction from './fraction.mjs';
export class Hap {
/*
@@ -32,13 +33,43 @@ export class Hap {
}
get duration() {
return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1);
let duration;
if (typeof this.value?.duration === 'number') {
duration = Fraction(this.value.duration);
} else {
duration = this.whole.end.sub(this.whole.begin);
}
if (typeof this.value?.clip === 'number') {
return duration.mul(this.value.clip);
}
return duration;
}
get endClipped() {
return this.whole.begin.add(this.duration);
}
isActive(currentTime) {
return this.whole.begin <= currentTime && this.endClipped >= currentTime;
}
isInPast(currentTime) {
return currentTime > this.endClipped;
}
isInNearPast(margin, currentTime) {
return currentTime - margin <= this.endClipped;
}
isInFuture(currentTime) {
return currentTime < this.whole.begin;
}
isInNearFuture(margin, currentTime) {
return currentTime < this.whole.begin && currentTime > this.whole.begin - margin;
}
isWithinTime(min, max) {
return this.whole.begin <= max && this.endClipped >= min;
}
wholeOrPart() {
return this.whole ? this.whole : this.part;
}
@@ -60,6 +91,10 @@ export class Hap {
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
}
hasTag(tag) {
return this.context.tags?.includes(tag);
}
resolveState(state) {
if (this.stateful && this.hasOnset()) {
console.log('stateful');
+7 -9
View File
@@ -4,11 +4,13 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import controls from './controls.mjs';
import * as controls from './controls.mjs'; // legacy
export * from './euclid.mjs';
import Fraction from './fraction.mjs';
import createClock from './zyklus.mjs';
import { logger } from './logger.mjs';
export { Fraction, controls };
export { Fraction, controls, createClock };
export * from './controls.mjs';
export * from './hap.mjs';
export * from './pattern.mjs';
export * from './signal.mjs';
@@ -21,21 +23,17 @@ export * from './repl.mjs';
export * from './cyclist.mjs';
export * from './logger.mjs';
export * from './time.mjs';
export * from './draw.mjs';
export * from './animate.mjs';
export * from './pianoroll.mjs';
export * from './spiral.mjs';
export * from './ui.mjs';
export { default as drawLine } from './drawLine.mjs';
// below won't work with runtime.mjs (json import fails)
/* import * as p from './package.json';
export const version = p.version; */
logger('🌀 @strudel.cycles/core loaded 🌀');
logger('🌀 @strudel/core loaded 🌀');
if (globalThis._strudelLoaded) {
console.warn(
`@strudel.cycles/core was loaded more than once...
`@strudel/core was loaded more than once...
This might happen when you have multiple versions of strudel installed.
Please check with "npm ls @strudel.cycles/core".`,
Please check with "npm ls @strudel/core".`,
);
}
globalThis._strudelLoaded = true;
+10
View File
@@ -1,6 +1,16 @@
export const logKey = 'strudel.log';
let debounce = 1000,
lastMessage,
lastTime;
export function logger(message, type, data = {}) {
let t = performance.now();
if (lastMessage === message && t - lastTime < debounce) {
return;
}
lastMessage = message;
lastTime = t;
console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px');
if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') {
document.dispatchEvent(
+147
View File
@@ -0,0 +1,147 @@
/*
neocyclist.mjs - event scheduler like cyclist, except recieves clock pulses from clockworker in order to sync across multiple instances.
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/neocyclist.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { logger } from './logger.mjs';
export class NeoCyclist {
constructor({ onTrigger, onToggle, getTime }) {
this.started = false;
this.cps = 0.5;
this.lastTick = 0; // absolute time when last tick (clock callback) happened
this.getTime = getTime; // get absolute time
this.time_at_last_tick_message = 0;
this.num_cycles_at_cps_change = 0;
this.onToggle = onToggle;
this.latency = 0.1; // fixed trigger time offset
this.cycle = 0;
this.id = Math.round(Date.now() * Math.random());
this.worker_time_dif;
this.worker = new SharedWorker(new URL('./clockworker.js', import.meta.url));
this.worker.port.start();
this.channel = new BroadcastChannel('strudeltick');
let weight = 0; // the amount of weight that is applied to the current average when averaging a new time dif
const maxWeight = 20;
const precision = 10 ** 3; //round off time diff to prevent accumulating outliers
// the clock of the worker and the audio context clock can drift apart over time
// aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
// we need to keep a rolling weighted average of the time difference between the worker clock and audio context clock
// in order to schedule events consistently.
const setTimeReference = (num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline) => {
const time_dif = getTime() - (num_seconds_at_cps_change + num_seconds_since_cps_change) + tickdeadline;
if (this.worker_time_dif == null) {
this.worker_time_dif = time_dif;
} else {
const w = 1; //weight of new time diff;
const new_dif =
Math.round(((this.worker_time_dif * weight + time_dif * w) / (weight + w)) * precision) / precision;
if (new_dif != this.worker_time_dif) {
// reset the weight so the clock recovers faster from an audio context freeze/dropout if it happens
weight = 4;
}
this.worker_time_dif = new_dif;
}
weight = Math.min(weight + 1, maxWeight);
};
const tickCallback = (payload) => {
const {
num_cycles_at_cps_change,
cps,
num_seconds_at_cps_change,
num_seconds_since_cps_change,
begin,
end,
tickdeadline,
cycle,
} = payload;
this.cps = cps;
this.cycle = cycle;
setTimeReference(num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline);
processHaps(begin, end, num_cycles_at_cps_change, num_seconds_at_cps_change);
this.time_at_last_tick_message = this.getTime();
};
const processHaps = (begin, end, num_cycles_at_cps_change, seconds_at_cps_change) => {
if (this.started === false) {
return;
}
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
haps.forEach((hap) => {
if (hap.hasOnset()) {
const targetTime =
(hap.whole.begin - num_cycles_at_cps_change) / this.cps +
seconds_at_cps_change +
this.latency +
this.worker_time_dif;
const duration = hap.duration / this.cps;
onTrigger?.(hap, 0, duration, this.cps, targetTime);
}
});
};
// receive messages from worker clock and process them
this.channel.onmessage = (message) => {
if (!this.started) {
return;
}
const { payload, type } = message.data;
switch (type) {
case 'tick': {
tickCallback(payload);
}
}
};
}
sendMessage(type, payload) {
this.worker.port.postMessage({ type, payload, id: this.id });
}
now() {
const gap = (this.getTime() - this.time_at_last_tick_message) * this.cps;
return this.cycle + gap;
}
setCps(cps = 1) {
this.sendMessage('cpschange', { cps });
}
setCycle(cycle) {
this.sendMessage('setcycle', { cycle });
}
setStarted(started) {
this.sendMessage('toggle', { started });
this.started = started;
this.onToggle?.(started);
}
start() {
logger('[cyclist] start');
this.setStarted(true);
}
stop() {
this.worker_time_dif = null;
logger('[cyclist] stop');
this.setStarted(false);
}
setPattern(pat, autostart = false) {
this.pattern = pat;
if (autostart && !this.started) {
this.start();
}
}
log(begin, end, haps) {
const onsets = haps.filter((h) => h.hasOnset());
console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`);
}
}
+4 -4
View File
@@ -1,15 +1,15 @@
{
"name": "@strudel.cycles/core",
"version": "0.9.0",
"name": "@strudel/core",
"version": "1.1.0",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
"main": "dist/index.mjs"
},
"scripts": {
"test": "vitest run",
"bench": "vitest bench",
"build": "vite build",
"prepublishOnly": "pnpm build"
},
+894 -351
View File
File diff suppressed because it is too large Load Diff
+83 -72
View File
@@ -1,3 +1,4 @@
import { NeoCyclist } from './neocyclist.mjs';
import { Cyclist } from './cyclist.mjs';
import { evaluate as _evaluate } from './evaluate.mjs';
import { logger } from './logger.mjs';
@@ -6,9 +7,7 @@ import { evalScope } from './evaluate.mjs';
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
export function repl({
interval,
defaultOutput,
onSchedulerError,
onEvalError,
beforeEval,
afterEval,
@@ -17,6 +16,9 @@ export function repl({
onToggle,
editPattern,
onUpdateState,
sync = false,
setInterval,
clearInterval,
}) {
const state = {
schedulerError: undefined,
@@ -37,21 +39,27 @@ export function repl({
onUpdateState?.(state);
};
const scheduler = new Cyclist({
interval,
const schedulerOptions = {
onTrigger: getTrigger({ defaultOutput, getTime }),
onError: onSchedulerError,
getTime,
onToggle: (started) => {
updateState({ started });
onToggle?.(started);
},
});
setInterval,
clearInterval,
};
// NeoCyclist uses a shared worker to communicate between instances, which is not supported on mobile chrome
const scheduler =
sync && typeof SharedWorker != 'undefined' ? new NeoCyclist(schedulerOptions) : new Cyclist(schedulerOptions);
let pPatterns = {};
let anonymousIndex = 0;
let allTransform;
const hush = function () {
pPatterns = {};
anonymousIndex = 0;
allTransform = undefined;
return silence;
};
@@ -61,12 +69,76 @@ export function repl({
scheduler.setPattern(pattern, autostart);
};
setTime(() => scheduler.now()); // TODO: refactor?
const stop = () => scheduler.stop();
const start = () => scheduler.start();
const pause = () => scheduler.pause();
const toggle = () => scheduler.toggle();
const setCps = (cps) => scheduler.setCps(cps);
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
const all = function (transform) {
allTransform = transform;
return silence;
};
// set pattern methods that use this repl via closure
const injectPatternMethods = () => {
Pattern.prototype.p = function (id) {
if (id.startsWith('_') || id.endsWith('_')) {
// allows muting a pattern x with x_ or _x
return silence;
}
if (id === '$') {
// allows adding anonymous patterns with $:
id = `$${anonymousIndex}`;
anonymousIndex++;
}
pPatterns[id] = this;
return this;
};
Pattern.prototype.q = function (id) {
return silence;
};
try {
for (let i = 1; i < 10; ++i) {
Object.defineProperty(Pattern.prototype, `d${i}`, {
get() {
return this.p(i);
},
configurable: true,
});
Object.defineProperty(Pattern.prototype, `p${i}`, {
get() {
return this.p(i);
},
configurable: true,
});
Pattern.prototype[`q${i}`] = silence;
}
} catch (err) {
console.warn('injectPatternMethods: error:', err);
}
const cpm = register('cpm', function (cpm, pat) {
return pat._fast(cpm / 60 / scheduler.cps);
});
return evalScope({
all,
hush,
cpm,
setCps,
setcps: setCps,
setCpm,
setcpm: setCpm,
});
};
const evaluate = async (code, autostart = true, shouldHush = true) => {
if (!code) {
throw new Error('no code to evaluate');
}
try {
updateState({ code, pending: true });
await injectPatternMethods();
await beforeEval?.({ code });
shouldHush && hush();
let { pattern, meta } = await _evaluate(code, transpiler);
@@ -94,88 +166,27 @@ export function repl({
afterEval?.({ code, pattern, meta });
return pattern;
} catch (err) {
// console.warn(`[repl] eval error: ${err.message}`);
logger(`[eval] error: ${err.message}`, 'error');
console.error(err);
updateState({ evalError: err, pending: false });
onEvalError?.(err);
}
};
const stop = () => scheduler.stop();
const start = () => scheduler.start();
const pause = () => scheduler.pause();
const toggle = () => scheduler.toggle();
const setCps = (cps) => scheduler.setCps(cps);
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
// the following functions use the cps value, which is why they are defined here..
const loopAt = register('loopAt', (cycles, pat) => {
return pat.loopAtCps(cycles, scheduler.cps);
});
Pattern.prototype.p = function (id) {
pPatterns[id] = this;
return this;
};
Pattern.prototype.q = function (id) {
return silence;
};
const all = function (transform) {
allTransform = transform;
return silence;
};
try {
for (let i = 1; i < 10; ++i) {
Object.defineProperty(Pattern.prototype, `d${i}`, {
get() {
return this.p(i);
},
});
Object.defineProperty(Pattern.prototype, `p${i}`, {
get() {
return this.p(i);
},
});
Pattern.prototype[`q${i}`] = silence;
}
} catch (err) {
// already defined..
}
const fit = register('fit', (pat) =>
pat.withHap((hap) =>
hap.withValue((v) => ({
...v,
speed: scheduler.cps / hap.whole.duration, // overwrite speed completely?
unit: 'c',
})),
),
);
evalScope({
loopAt,
fit,
all,
hush,
setCps,
setcps: setCps,
setCpm,
setcpm: setCpm,
});
const setCode = (code) => updateState({ code });
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
}
export const getTrigger =
({ getTime, defaultOutput }) =>
async (hap, deadline, duration, cps) => {
async (hap, deadline, duration, cps, t) => {
// TODO: get rid of deadline after https://github.com/tidalcycles/strudel/pull/1004
try {
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
await defaultOutput(hap, deadline, duration, cps);
await defaultOutput(hap, deadline, duration, cps, t);
}
if (hap.context.onTrigger) {
// call signature of output / onTrigger is different...
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps, t);
}
} catch (err) {
logger(`[cyclist] error: ${err.message}`, 'error');
+283 -37
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, _mod, clamp } from './util.mjs';
import { id, _mod, clamp, objectMap } from './util.mjs';
export function steady(value) {
// A continuous value
@@ -27,9 +27,11 @@ export const isaw2 = isaw.toBipolar();
*
* @return {Pattern}
* @example
* "c3 [eb3,g3] g2 [g3,bb3]".note().clip(saw.slow(4))
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
* .clip(saw.slow(2))
* @example
* saw.range(0,8).segment(8).scale('C major').slow(4).note()
* n(saw.range(0,8).segment(8))
* .scale('C major')
*
*/
export const saw = signal((t) => t % 1);
@@ -42,7 +44,8 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
*
* @return {Pattern}
* @example
* sine.segment(16).range(0,15).slow(2).scale('C minor').note()
* n(sine.segment(16).range(0,15))
* .scale("C:minor")
*
*/
export const sine = sine2.fromBipolar();
@@ -52,7 +55,8 @@ export const sine = sine2.fromBipolar();
*
* @return {Pattern}
* @example
* stack(sine,cosine).segment(16).range(0,15).slow(2).scale('C minor').note()
* n(stack(sine,cosine).segment(16).range(0,15))
* .scale("C:minor")
*
*/
export const cosine = sine._early(Fraction(1).div(4));
@@ -63,7 +67,7 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
*
* @return {Pattern}
* @example
* square.segment(2).range(0,7).scale('C minor').note()
* n(square.segment(4).range(0,7)).scale("C:minor")
*
*/
export const square = signal((t) => Math.floor((t * 2) % 2));
@@ -74,7 +78,7 @@ export const square2 = square.toBipolar();
*
* @return {Pattern}
* @example
* tri.segment(8).range(0,7).scale('C minor').note()
* n(tri.segment(8).range(0,7)).scale("C:minor")
*
*/
export const tri = fastcat(isaw, saw);
@@ -82,6 +86,38 @@ export const tri2 = fastcat(isaw2, saw2);
export const time = signal(id);
/**
* The mouse's x position value ranges from 0 to 1.
* @name mousex
* @return {Pattern}
* @example
* n(mousex.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The mouse's y position value ranges from 0 to 1.
* @name mousey
* @return {Pattern}
* @example
* n(mousey.segment(4).range(0,7)).scale("C:minor")
*
*/
let _mouseY = 0,
_mouseX = 0;
if (typeof window !== 'undefined') {
//document.onmousemove = (e) => {
document.addEventListener('mousemove', (e) => {
_mouseY = e.clientY / document.body.clientHeight;
_mouseX = e.clientX / document.body.clientWidth;
});
}
export const mousey = signal(() => _mouseY);
export const mouseY = signal(() => _mouseY);
export const mousex = signal(() => _mouseX);
export const mouseX = signal(() => _mouseX);
// random signals
const xorwise = (x) => {
@@ -118,8 +154,8 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
/**
* A discrete pattern of numbers from 0 to n-1
* @example
* run(4).scale('C4 major').note()
* // "0 1 2 3".scale('C4 major').note()
* n(run(4)).scale("C4:pentatonic")
* // n("0 1 2 3").scale("C4:pentatonic")
*/
export const run = (n) => saw.range(0, n).floor().segment(n);
@@ -129,7 +165,7 @@ export const run = (n) => saw.range(0, n).floor().segment(n);
* @name rand
* @example
* // randomly change the cutoff
* s("bd sd,hh*4").cutoff(rand.range(500,2000))
* s("bd*4,hh*8").cutoff(rand.range(500,8000))
*
*/
export const rand = signal(timeToRand);
@@ -139,7 +175,24 @@ export const rand = signal(timeToRand);
export const rand2 = rand.toBipolar();
export const _brandBy = (p) => rand.fmap((x) => x < p);
/**
* A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1
*
* @name brandBy
* @param {number} probability - a number between 0 and 1
* @example
* s("hh*10").pan(brandBy(0.2))
*/
export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
/**
* A continuous pattern of 0 or 1 (binary random)
*
* @name brand
* @example
* s("hh*10").pan(brand)
*/
export const brand = _brandBy(0.5);
export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
@@ -151,36 +204,188 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
* @param {number} n max value (exclusive)
* @example
* // randomly select scale notes from 0 - 7 (= C to C)
* irand(8).struct("x(3,8)").scale('C minor').note()
* n(irand(8)).struct("x x*2 x x*3").scale("C:minor")
*
*/
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
const _pick = function (lookup, pat, modulo = true) {
const array = Array.isArray(lookup);
const len = Object.keys(lookup).length;
lookup = objectMap(lookup, reify);
if (len === 0) {
return silence;
}
return pat.fmap((i) => {
let key = i;
if (array) {
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
}
return lookup[key];
});
};
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `inhabit`, but maintains the structure of the original patterns.
* @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"]))
* note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g" , "g c d"]))
* @example
* sound("<0 1 [2,0]>".pick(["bd sd", "cp cp", "hh hh"]))
* @example
* sound("<0!2 [0,1] 1>".pick(["bd(3,8)", "sd sd"]))
* @example
* s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
*/
export const pick = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
export const pick = function (lookup, pat) {
// backward compatibility - the args used to be flipped
if (Array.isArray(pat)) {
[pat, lookup] = [lookup, pat];
}
return pat
.fmap((i) => {
const key = clamp(Math.round(i), 0, xs.length - 1);
return xs[key];
})
.innerJoin();
return __pick(lookup, pat);
};
const __pick = register('pick', function (lookup, pat) {
return _pick(lookup, pat, false).innerJoin();
});
/** * The same as `pick`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmod = register('pickmod', function (lookup, pat) {
return _pick(lookup, pat, true).innerJoin();
});
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
* @example
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
* @example
* note("<c2 d2>(3,8)").s("square")
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
*/
export const pickF = register('pickF', function (lookup, funcs, pat) {
return pat.apply(pick(lookup, funcs));
});
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
*/
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
return pat.apply(pickmod(lookup, funcs));
});
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickOut = register('pickOut', function (lookup, pat) {
return _pick(lookup, pat, false).outerJoin();
});
/** * The same as `pickOut`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodOut = register('pickmodOut', function (lookup, pat) {
return _pick(lookup, pat, true).outerJoin();
});
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickRestart = register('pickRestart', function (lookup, pat) {
return _pick(lookup, pat, false).restartJoin();
});
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
return _pick(lookup, pat, true).restartJoin();
});
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickReset = register('pickReset', function (lookup, pat) {
return _pick(lookup, pat, false).resetJoin();
});
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
return _pick(lookup, pat, true).resetJoin();
});
/**
* pick from the list of values (or patterns of values) via the index using the given
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
* @name inhabit
* @synonyms pickSqueeze
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
b: s("cp sd")
})
* @example
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
*/
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, false).squeezeJoin();
});
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @name inhabitmod
* @synonyms pickmodSqueeze
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, true).squeezeJoin();
});
/**
* 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
@@ -241,6 +446,8 @@ export const chooseInWith = (pat, xs) => {
* Chooses randomly from the given list of elements.
* @param {...any} xs values / patterns to choose from.
* @returns {Pattern} - a continuous pattern.
* @example
* note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6"))
*/
export const choose = (...xs) => chooseWith(rand, xs);
@@ -267,11 +474,12 @@ Pattern.prototype.choose2 = function (...xs) {
/**
* Picks one of the elements at random each cycle.
* @synonyms randcat
* @returns {Pattern}
* @example
* chooseCycles("bd", "hh", "sd").s().fast(4)
* chooseCycles("bd", "hh", "sd").s().fast(8)
* @example
* "bd | hh | sd".s().fast(4)
* s("bd | hh | sd").fast(8)
*/
export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
@@ -295,9 +503,27 @@ const _wchooseWith = function (pat, ...pairs) {
const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
/**
* Chooses randomly from the given list of elements by giving a probability to each element
* @param {...any} pairs arrays of value and weight
* @returns {Pattern} - a continuous pattern.
* @example
* note("c2 g2!2 d2 f1").s(wchoose(["sine",10], ["triangle",1], ["bd:6",1]))
*/
export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
export const wchooseCycles = (...pairs) => _wchooseWith(rand, ...pairs).innerJoin();
/**
* Picks one of the elements at random each cycle by giving a probability to each element
* @synonyms wrandcat
* @returns {Pattern}
* @example
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
*/
export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin();
export const wrandcat = wchooseCycles;
// this function expects pat to be a pattern of floats...
export const perlinWith = (pat) => {
@@ -314,7 +540,7 @@ export const perlinWith = (pat) => {
* @name perlin
* @example
* // randomly change the cutoff
* s("bd sd,hh*4").cutoff(perlin.range(500,2000))
* s("bd*4,hh*8").cutoff(perlin.range(500,8000))
*
*/
export const perlin = perlinWith(time.fmap((v) => Number(v)));
@@ -356,7 +582,7 @@ export const degradeBy = register('degradeBy', function (x, pat) {
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
/**
* Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
* Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount.
* 0 = 100% chance of removal
* 1 = 0% chance of removal
* Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example).
@@ -367,6 +593,11 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
* @returns Pattern
* @example
* s("hh*8").undegradeBy(0.2)
* @example
* s("hh*10").layer(
* x => x.degradeBy(0.2).pan(0),
* x => x.undegradeBy(0.8).pan(1)
* )
*/
export const undegradeBy = register('undegradeBy', function (x, pat) {
return pat._degradeByWith(
@@ -375,12 +606,27 @@ export const undegradeBy = register('undegradeBy', function (x, pat) {
);
});
/**
* Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)`
* Events that would be removed by degrade are let through by undegrade and vice versa (see second example).
*
* @name undegrade
* @memberof Pattern
* @returns Pattern
* @example
* s("hh*8").undegrade()
* @example
* s("hh*10").layer(
* x => x.degrade().pan(0),
* x => x.undegrade().pan(1)
* )
*/
export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
/**
*
* Randomly applies the given function by the given probability.
* Similar to {@link Pattern#someCyclesBy}
* Similar to `someCyclesBy`
*
* @name sometimesBy
* @memberof Pattern
@@ -388,7 +634,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
* @param {function} function - the transformation to apply
* @returns Pattern
* @example
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
* s("hh*8").sometimesBy(.4, x=>x.speed("0.5"))
*/
export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
@@ -406,7 +652,7 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
* @param {function} function - the transformation to apply
* @returns Pattern
* @example
* s("hh*4").sometimes(x=>x.speed("0.5"))
* s("hh*8").sometimes(x=>x.speed("0.5"))
*/
export const sometimes = register('sometimes', function (func, pat) {
return pat._sometimesBy(0.5, func);
@@ -415,7 +661,7 @@ export const sometimes = register('sometimes', function (func, pat) {
/**
*
* Randomly applies the given function by the given probability on a cycle by cycle basis.
* Similar to {@link Pattern#sometimesBy}
* Similar to `sometimesBy`
*
* @name someCyclesBy
* @memberof Pattern
@@ -423,7 +669,7 @@ export const sometimes = register('sometimes', function (func, pat) {
* @param {function} function - the transformation to apply
* @returns Pattern
* @example
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
* s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5"))
*/
export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) {
@@ -445,7 +691,7 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat)
* @memberof Pattern
* @returns Pattern
* @example
* s("hh(3,8)").someCycles(x=>x.speed("0.5"))
* s("bd,hh*8").someCycles(x=>x.speed("0.5"))
*/
export const someCycles = register('someCycles', function (func, pat) {
return pat._someCyclesBy(0.5, func);
+19 -5
View File
@@ -4,25 +4,39 @@ Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import controls from '../controls.mjs';
import { s, pan } from '../controls.mjs';
import { mini } from '../../mini/mini.mjs';
import { describe, it, expect } from 'vitest';
import Fraction from '../fraction.mjs';
describe('controls', () => {
it('should support controls', () => {
expect(controls.s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
expect(s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
});
it('should support compound controls', () => {
expect(controls.s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
expect(controls.s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
expect(s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
expect(s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
{ s: 'bd', n: 3 },
{ s: 'sd', n: 4, gain: 1.4 },
]);
});
it('should support ignore extra elements in compound controls', () => {
expect(controls.s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
expect(s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
{ s: 'bd', n: 3, gain: 0.4 },
{ s: 'sd', n: 4, gain: 0.5 },
]);
});
it('should support nested controls', () => {
expect(s(mini('bd').pan(1)).firstCycleValues).toEqual([{ s: 'bd', pan: 1 }]);
expect(s(mini('bd:1').pan(1)).firstCycleValues).toEqual([{ s: 'bd', n: 1, pan: 1 }]);
});
it('preserves tactus of the left pattern', () => {
expect(s(mini('bd cp mt').pan(mini('1 2 3 4'))).tactus).toEqual(Fraction(3));
});
it('preserves tactus of the right pattern for .out', () => {
expect(s(mini('bd cp mt').set.out(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(4));
});
it('combines tactus of the pattern for .mix as lcm', () => {
expect(s(mini('bd cp mt').set.mix(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(12));
});
});
+172 -20
View File
@@ -21,8 +21,8 @@ import {
cat,
sequence,
palindrome,
polymeter,
polymeterSteps,
s_polymeter,
s_polymeterSteps,
polyrhythm,
silence,
fast,
@@ -46,13 +46,18 @@ import {
rev,
time,
run,
pick,
stackLeft,
stackRight,
stackCentre,
s_cat,
calculateTactus,
} from '../index.mjs';
import { steady } from '../signal.mjs';
import controls from '../controls.mjs';
import { n, s } from '../controls.mjs';
const { n, s } = controls;
const st = (begin, end) => new State(ts(begin, end));
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context);
@@ -181,18 +186,18 @@ describe('Pattern', () => {
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 7),
]);
});
it('can Trig() structure', () => {
it('can Reset() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
.add.trig(20, 30)
.add.reset(20, 30)
.early(2),
sequence(26, 27, 36, 37),
);
});
it('can Trigzero() structure', () => {
it('can Restart() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
.add.trigzero(20, 30)
.add.restart(20, 30)
.early(2),
sequence(21, 22, 31, 32),
);
@@ -233,18 +238,18 @@ describe('Pattern', () => {
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
]);
});
it('can Trig() structure', () => {
it('can Reset() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
.keep.trig(20, 30)
.keep.reset(20, 30)
.early(2),
sequence(6, 7, 6, 7),
);
});
it('can Trigzero() structure', () => {
it('can Restart() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
.keep.trigzero(20, 30)
.keep.restart(20, 30)
.early(2),
sequence(1, 2, 1, 2),
);
@@ -279,18 +284,18 @@ describe('Pattern', () => {
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
]);
});
it('can Trig() structure', () => {
it('can Reset() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
.keepif.trig(false, true)
.keepif.reset(false, true)
.early(2),
sequence(silence, silence, 6, 7),
);
});
it('can Trigzero() structure', () => {
it('can Restart() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
.keepif.trigzero(false, true)
.keepif.restart(false, true)
.early(2),
sequence(silence, silence, 1, 2),
);
@@ -603,16 +608,19 @@ describe('Pattern', () => {
);
});
});
describe('polymeter()', () => {
it('Can layer up cycles, stepwise', () => {
expect(polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual(
describe('s_polymeter()', () => {
it('Can layer up cycles, stepwise, with lists', () => {
expect(s_polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual(
fastcat(pure('d'), pure('e'), pure('d')).firstCycle(),
);
expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
expect(s_polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
stack(sequence('a', 'b', 'c', 'a', 'b', 'c'), sequence('d', 'e', 'd', 'e', 'd', 'e')).firstCycle(),
);
});
it('Can layer up cycles, stepwise, with weighted patterns', () => {
sameFirst(s_polymeterSteps(3, sequence('a', 'b')).fast(2), sequence('a', 'b', 'a', 'b', 'a', 'b'));
});
});
describe('firstOf()', () => {
@@ -1057,4 +1065,148 @@ describe('Pattern', () => {
expect(slowcat(0, 1).repeatCycles(2).fast(6).firstCycleValues).toStrictEqual([0, 0, 1, 1, 0, 0]);
});
});
describe('inhabit', () => {
it('Can pattern named patterns', () => {
expect(
sameFirst(
sequence('a', 'b', stack('a', 'b')).inhabit({ a: sequence(1, 2), b: sequence(10, 20, 30) }),
sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])),
),
);
});
it('Can pattern indexed patterns', () => {
expect(
sameFirst(
sequence('0', '1', stack('0', '1')).inhabit([sequence(1, 2), sequence(10, 20, 30)]),
sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])),
),
);
});
});
describe('pick', () => {
it('Can pattern named patterns', () => {
expect(
sameFirst(
sequence('a', 'b', 'a', stack('a', 'b')).pick({ a: sequence(1, 2, 3, 4), b: sequence(10, 20, 30, 40) }),
sequence(1, 20, 3, stack(4, 40)),
),
);
});
it('Can pattern indexed patterns', () => {
expect(
sameFirst(
sequence(0, 1, 0, stack(0, 1)).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]),
sequence(1, 20, 3, stack(4, 40)),
),
);
});
it('Clamps indexes', () => {
expect(
sameFirst(sequence(0, 1, 2, 3).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), sequence(1, 20, 30, 40)),
);
});
it('Is backwards compatible', () => {
expect(
sameFirst(
pick([sequence('a', 'b'), sequence('c', 'd')], sequence(0, 1)),
pick(sequence(0, 1), [sequence('a', 'b'), sequence('c', 'd')]),
),
);
});
});
describe('pickmod', () => {
it('Wraps indexes', () => {
expect(
sameFirst(
sequence(0, 1, 2, 3).pickmod([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]),
sequence(1, 20, 3, 40),
),
);
});
});
describe('tactus', () => {
it('Is correctly preserved/calculated through transformations', () => {
expect(sequence(0, 1, 2, 3).linger(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).iter(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).fast(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).hurry(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).rev().tactus).toStrictEqual(Fraction(4));
expect(sequence(1).segment(10).tactus).toStrictEqual(Fraction(10));
expect(sequence(1, 0, 1).invert().tactus).toStrictEqual(Fraction(3));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4).tactus).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4).tactus).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual(
Fraction(4),
);
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).splice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual(
Fraction(4),
);
expect(sequence({ n: 0 }, { n: 1 }, { n: 2 }).chop(4).tactus).toStrictEqual(Fraction(12));
expect(
pure((x) => x + 1)
.setTactus(3)
.appBoth(pure(1).setTactus(2)).tactus,
).toStrictEqual(Fraction(6));
expect(
pure((x) => x + 1)
.setTactus(undefined)
.appBoth(pure(1).setTactus(2)).tactus,
).toStrictEqual(Fraction(2));
expect(
pure((x) => x + 1)
.setTactus(3)
.appBoth(pure(1).setTactus(undefined)).tactus,
).toStrictEqual(Fraction(3));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(6));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setTactus(undefined)).tactus).toStrictEqual(Fraction(3));
expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4)).tactus).toStrictEqual(Fraction(4));
expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
// maybe this should double when they are either all even or all odd
expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
expect(fastcat(0, 1).ply(3).tactus).toStrictEqual(Fraction(6));
expect(fastcat(0, 1).setTactus(undefined).ply(3).tactus).toStrictEqual(undefined);
expect(fastcat(0, 1).fast(3).tactus).toStrictEqual(Fraction(2));
expect(fastcat(0, 1).setTactus(undefined).fast(3).tactus).toStrictEqual(undefined);
});
});
describe('s_cat', () => {
it('can cat', () => {
expect(sameFirst(s_cat(fastcat(0, 1, 2, 3), fastcat(4, 5)), fastcat(0, 1, 2, 3, 4, 5)));
expect(sameFirst(s_cat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3)));
});
it('calculates undefined tactuses as the average', () => {
expect(sameFirst(s_cat(pure(1), pure(2), pure(3).setTactus(undefined)), fastcat(1, 2, 3)));
});
});
describe('s_taper', () => {
it('can taper', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(1, 5), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0)));
});
it('can taper backwards', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(-1, 5), sequence(0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4)));
});
});
describe('s_add and s_sub', () => {
it('can add from the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(2), sequence(0, 1)));
});
it('can sub to the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(2), sequence(0, 1, 2)));
});
it('can add from the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(-2), sequence(3, 4)));
});
it('can sub to the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(-2), sequence(2, 3, 4)));
});
it('can subtract nothing', () => {
expect(sameFirst(pure('a').s_sub(0), pure('a')));
});
it('can subtract nothing, repeatedly', () => {
expect(sameFirst(pure('a').s_sub(0, 0), fastcat('a', 'a')));
for (var i = 0; i < 100; ++i) {
expect(sameFirst(pure('a').s_sub(...Array(i).fill(0)), fastcat(...Array(i).fill('a'))));
}
});
});
});
+1 -6
View File
@@ -6,8 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import { describe, it, expect } from 'vitest';
import { map, valued, mul } from '../value.mjs';
import controls from '../controls.mjs';
const { n } = controls;
import { n } from '../controls.mjs';
describe('Value', () => {
it('unionWith', () => {
@@ -23,8 +22,4 @@ describe('Value', () => {
expect(valued(mul).ap(3).ap(3).value).toEqual(9);
expect(valued(3).mul(3).value).toEqual(9);
});
it('union bare numbers for numeral props', () => {
expect(n(3).cutoff(500).add(10).firstCycleValues).toEqual([{ n: 13, cutoff: 510 }]);
expect(n(3).cutoff(500).mul(2).firstCycleValues).toEqual([{ n: 6, cutoff: 1000 }]);
});
});
-18
View File
@@ -4,19 +4,6 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getTime } from './time.mjs';
function frame(callback) {
if (window.strudelAnimation) {
cancelAnimationFrame(window.strudelAnimation);
}
const animate = (animationTime) => {
callback(animationTime, getTime());
window.strudelAnimation = requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
}
export const backgroundImage = function (src, animateOptions = {}) {
const container = document.getElementById('code');
const bg = 'background-image:url(' + src + ');background-size:contain;';
@@ -35,11 +22,6 @@ export const backgroundImage = function (src, animateOptions = {}) {
if (funcOptions.length === 0) {
return;
}
frame((_, t) =>
funcOptions.forEach(([option, value]) => {
handleOption(option, value(t));
}),
);
};
export const cleanupUi = () => {
+65 -1
View File
@@ -61,6 +61,11 @@ export const valueToMidi = (value, fallbackValue) => {
return fallbackValue;
};
// used to schedule external event like midi and osc out
export const getEventOffsetMs = (targetTimeSeconds, currentTimeSeconds) => {
return (targetTimeSeconds - currentTimeSeconds) * 1000;
};
/**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
* @noAutocomplete
@@ -86,7 +91,7 @@ export const midi2note = (n) => {
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
export const _mod = (n, m) => ((n % m) + m) % m;
export function nanFallback(value, fallback) {
export function nanFallback(value, fallback = 0) {
if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
return fallback;
@@ -231,6 +236,14 @@ export const splitAt = function (index, value) {
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
export const pairs = function (xs) {
const result = [];
for (let i = 0; i < xs.length - 1; ++i) {
result.push([xs[i], xs[i + 1]]);
}
return result;
};
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
/* solmization, not used yet */
@@ -289,6 +302,30 @@ export const sol2note = (n, notation = 'letters') => {
return note + oct;
};
// Remove duplicates from list
export function uniq(a) {
var seen = {};
return a.filter(function (item) {
return seen.hasOwn(item) ? false : (seen[item] = true);
});
}
// Remove duplicates from list, sorting in the process. Mutates argument!
export function uniqsort(a) {
return a.sort().filter(function (item, pos, ary) {
return !pos || item != ary[pos - 1];
});
}
// rational version
export function uniqsortr(a) {
return a
.sort((x, y) => x.compare(y))
.filter(function (item, pos, ary) {
return !pos || item.ne(ary[pos - 1]);
});
}
// code hashing helpers
export function unicodeToBase64(text) {
@@ -316,3 +353,30 @@ export function hash2code(hash) {
return base64ToUnicode(decodeURIComponent(hash));
//return atob(decodeURIComponent(codeParam || ''));
}
export function objectMap(obj, fn) {
if (Array.isArray(obj)) {
return obj.map(fn);
}
return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]));
}
// Floating point versions, see Fraction for rational versions
// // greatest common divisor
// export const gcd = function (x, y, ...z) {
// if (!y && z.length > 0) {
// return gcd(x, ...z);
// }
// if (!y) {
// return x;
// }
// return gcd(y, x % y, ...z);
// };
// // lowest common multiple
// export const lcm = function (x, y, ...z) {
// if (z.length == 0) {
// return (x * y) / gcd(x, y);
// }
// return lcm((x * y) / gcd(x, y), ...z);
// };
+5 -6
View File
@@ -5,14 +5,13 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { curry } from './util.mjs';
import { logger } from './logger.mjs';
export function unionWithObj(a, b, func) {
if (typeof b?.value === 'number') {
// https://github.com/tidalcycles/strudel/issues/262
const numKeys = Object.keys(a).filter((k) => typeof a[k] === 'number');
const numerals = Object.fromEntries(numKeys.map((k) => [k, b.value]));
b = Object.assign(b, numerals);
delete b.value;
if (b?.value !== undefined && Object.keys(b).length === 1) {
// https://github.com/tidalcycles/strudel/issues/1026
logger(`[warn]: Can't do arithmetic on control pattern.`);
return a;
}
const common = Object.keys(a).filter((k) => Object.keys(b).includes(k));
return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])])));
+2 -2
View File
@@ -8,8 +8,8 @@ export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
formats: ['es'],
fileName: (ext) => ({ es: 'index.mjs' })[ext],
},
rollupOptions: {
external: [...Object.keys(dependencies)],
+9 -4
View File
@@ -7,6 +7,9 @@ function createClock(
duration = 0.05, // duration of each cycle
interval = 0.1, // interval between callbacks
overlap = 0.1, // overlap between callbacks
setInterval = globalThis.setInterval,
clearInterval = globalThis.clearInterval,
round = true,
) {
let tick = 0; // counts callbacks
let phase = 0; // next callback time
@@ -22,9 +25,8 @@ function createClock(
}
// callback as long as we're inside the lookahead
while (phase < lookahead) {
phase = Math.round(phase * precision) / precision;
phase >= t && callback(phase, duration, tick);
phase < t && console.log('TOO LATE', phase); // what if latency is added from outside?
phase = round ? Math.round(phase * precision) / precision : phase;
callback(phase, duration, tick, t); // callback has to skip / handle phase < t!
phase += duration; // increment phase by duration
tick++;
}
@@ -35,7 +37,10 @@ function createClock(
onTick();
intervalID = setInterval(onTick, interval * 1000);
};
const clear = () => intervalID !== undefined && clearInterval(intervalID);
const clear = () => {
intervalID !== undefined && clearInterval(intervalID);
intervalID = undefined;
};
const pause = () => clear();
const stop = () => {
tick = 0;
+1
View File
@@ -0,0 +1 @@
# @strudel/csound
+9 -5
View File
@@ -1,5 +1,5 @@
import { getFrequency, logger, register } from '@strudel.cycles/core';
import { getAudioContext } from '@strudel.cycles/webaudio';
import { getFrequency, logger, register } from '@strudel/core';
import { getAudioContext } from '@strudel/webaudio';
import csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw';
import presetsOrc from './presets.orc?raw';
@@ -23,7 +23,7 @@ export const csound = register('csound', (instrument, pat) => {
instrument = instrument || 'triangle';
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
// TODO: find a alternative way to wait for csound to load (to wait with first time playback)
return pat.onTrigger((time, hap) => {
return pat.onTrigger((time_deprecate, hap, currentTime, _cps, targetTime) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
@@ -38,9 +38,11 @@ export const csound = register('csound', (instrument, pat) => {
.join('/');
// TODO: find out how to send a precise ctx based time
// http://www.csounds.com/manual/html/i.html
const timeOffset = targetTime - currentTime; // latency ?
//const timeOffset = time_deprecate - getAudioContext().currentTime
const params = [
`"${instrument}"`, // p1: instrument name
time - getAudioContext().currentTime, //.toFixed(precision), // p2: starting time in arbitrary unit called beats
timeOffset, // p2: starting time in arbitrary unit called beats
hap.duration + 0, // p3: duration in beats
// instrument specific params:
freq, //.toFixed(precision), // p4: frequency
@@ -152,12 +154,14 @@ export const csoundm = register('csoundm', (instrument, pat) => {
const p2 = tidal_time - getAudioContext().currentTime;
const p3 = hap.duration.valueOf() + 0;
const frequency = getFrequency(hap);
let { gain = 1, velocity = 0.9 } = hap.value;
velocity = gain * velocity;
// Translate frequency to MIDI key number _without_ rounding.
const C4 = 261.62558;
let octave = Math.log(frequency / C4) / Math.log(2.0) + 8.0;
const p4 = octave * 12.0 - 36.0;
// We prefer floating point precision, but over the MIDI range [0, 127].
const p5 = 127 * (hap.context?.velocity ?? 0.9);
const p5 = 127 * velocity;
// The Strudel controls as a string.
const p6 = Object.entries({ ...hap.value, frequency })
.flat()
+6 -6
View File
@@ -1,11 +1,11 @@
{
"name": "@strudel.cycles/csound",
"version": "0.9.0",
"name": "@strudel/csound",
"version": "1.1.0",
"description": "csound bindings for strudel",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
@@ -33,8 +33,8 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@csound/browser": "6.18.7",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.10"
+2 -2
View File
@@ -8,8 +8,8 @@ export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
formats: ['es'],
fileName: (ext) => ({ es: 'index.mjs' })[ext],
},
rollupOptions: {
external: [...Object.keys(dependencies)],
+8 -6
View File
@@ -1,16 +1,18 @@
import { Invoke } from './utils.mjs';
import { Pattern, noteToMidi } from '@strudel.cycles/core';
import { Pattern, getEventOffsetMs, noteToMidi } from '@strudel/core';
const ON_MESSAGE = 0x90;
const OFF_MESSAGE = 0x80;
const CC_MESSAGE = 0xb0;
Pattern.prototype.midi = function (output) {
return this.onTrigger((time, hap, currentTime) => {
const { note, nrpnn, nrpv, ccn, ccv } = hap.value;
const offset = (time - currentTime) * 1000;
const velocity = Math.floor((hap.context?.velocity ?? 0.9) * 100); // TODO: refactor velocity
const duration = Math.floor(hap.duration.valueOf() * 1000 - 10);
return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
let { note, nrpnn, nrpv, ccn, ccv, velocity = 0.9, gain = 1 } = hap.value;
//magic number to get audio engine to line up, can probably be calculated somehow
const latencyMs = 34;
const offset = getEventOffsetMs(targetTime, currentTime) + latencyMs;
velocity = Math.floor(gain * velocity * 100);
const duration = Math.floor((hap.duration.valueOf() / cps) * 1000 - 10);
const roundedOffset = Math.round(offset);
const midichan = (hap.value.midichan ?? 1) - 1;
const requestedport = output ?? 'IAC';
+3 -3
View File
@@ -1,8 +1,8 @@
import { parseNumeral, Pattern } from '@strudel.cycles/core';
import { parseNumeral, Pattern, getEventOffsetMs } from '@strudel/core';
import { Invoke } from './utils.mjs';
Pattern.prototype.osc = function () {
return this.onTrigger(async (time, hap, currentTime, cps = 1) => {
return this.onTrigger(async (time, hap, currentTime, cps = 1, targetTime) => {
hap.ensureObjectValue();
const cycle = hap.wholeOrPart().begin.valueOf();
const delta = hap.duration.valueOf();
@@ -13,7 +13,7 @@ Pattern.prototype.osc = function () {
const params = [];
const timestamp = Math.round(Date.now() + (time - currentTime) * 1000);
const timestamp = Math.round(Date.now() + getEventOffsetMs(targetTime, currentTime));
Object.keys(controls).forEach((key) => {
const val = controls[key];
+1 -1
View File
@@ -22,7 +22,7 @@
"url": "https://github.com/tidalcycles/strudel/issues"
},
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel/core": "workspace:*",
"@tauri-apps/api": "^1.5.3"
},
"homepage": "https://github.com/tidalcycles/strudel#readme"
+9
View File
@@ -0,0 +1,9 @@
# @strudel/canvas
Helpers for drawing with the Canvas API and Strudel
## Install
```sh
npm i @strudel/canvas --save
```
@@ -1,13 +1,14 @@
import { Pattern, getDrawContext, silence, register, pure } from './index.mjs';
import controls from './controls.mjs'; // do not import from index.mjs as it breaks for some reason..
const { createParams } = controls;
import { Pattern, silence, register, pure, createParams } from '@strudel/core';
import { getDrawContext } from './draw.mjs';
let clearColor = '#22222210';
Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } = {}) {
window.frame && cancelAnimationFrame(window.frame);
const ctx = getDrawContext();
const { clientWidth: ww, clientHeight: wh } = ctx.canvas;
let { clientWidth: ww, clientHeight: wh } = ctx.canvas;
ww *= window.devicePixelRatio;
wh *= window.devicePixelRatio;
let smearPart = smear === 0 ? '99' : Number((1 - smear) * 100).toFixed(0);
smearPart = smearPart.length === 1 ? `0${smearPart}` : smearPart;
clearColor = `#200010${smearPart}`;
@@ -1,80 +1,88 @@
/*
draw.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/draw.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/canvas/draw.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, getTime, State, TimeSpan } from './index.mjs';
import { Pattern, getTime, State, TimeSpan } from '@strudel/core';
export const getDrawContext = (id = 'test-canvas') => {
export const getDrawContext = (id = 'test-canvas', options) => {
let { contextType = '2d', pixelated = false, pixelRatio = window.devicePixelRatio } = options || {};
let canvas = document.querySelector('#' + id);
if (!canvas) {
const scale = 2; // 2 = crisp on retina screens
canvas = document.createElement('canvas');
canvas.id = id;
canvas.width = window.innerWidth * scale;
canvas.height = window.innerHeight * scale;
canvas.width = window.innerWidth * pixelRatio;
canvas.height = window.innerHeight * pixelRatio;
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0';
pixelated && (canvas.style.imageRendering = 'pixelated');
document.body.prepend(canvas);
let timeout;
window.addEventListener('resize', () => {
timeout && clearTimeout(timeout);
timeout = setTimeout(() => {
canvas.width = window.innerWidth * scale;
canvas.height = window.innerHeight * scale;
canvas.width = window.innerWidth * pixelRatio;
canvas.height = window.innerHeight * pixelRatio;
}, 200);
});
}
return canvas.getContext('2d');
return canvas.getContext(contextType);
};
Pattern.prototype.draw = function (callback, { from, to, onQuery } = {}) {
let animationFrames = {};
function stopAnimationFrame(id) {
if (animationFrames[id] !== undefined) {
cancelAnimationFrame(animationFrames[id]);
delete animationFrames[id];
}
}
function stopAllAnimations() {
Object.keys(animationFrames).forEach((id) => stopAnimationFrame(id));
}
let memory = {};
Pattern.prototype.draw = function (fn, options) {
if (typeof window === 'undefined') {
return this;
}
if (window.strudelAnimation) {
cancelAnimationFrame(window.strudelAnimation);
}
const ctx = getDrawContext();
let cycle,
events = [];
const animate = (time) => {
const t = getTime();
if (from !== undefined && to !== undefined) {
const currentCycle = Math.floor(t);
if (cycle !== currentCycle) {
cycle = currentCycle;
const begin = currentCycle + from;
const end = currentCycle + to;
setTimeout(() => {
events = this.query(new State(new TimeSpan(begin, end)))
.filter(Boolean)
.filter((event) => event.part.begin.equals(event.whole.begin));
onQuery?.(events);
}, 0);
}
}
callback(ctx, events, t, time);
window.strudelAnimation = requestAnimationFrame(animate);
let { id = 1, lookbehind = 0, lookahead = 0 } = options;
let __t = Math.max(getTime(), 0);
stopAnimationFrame(id);
lookbehind = Math.abs(lookbehind);
// init memory, clear future haps of old pattern
memory[id] = (memory[id] || []).filter((h) => !h.isInFuture(__t));
let newFuture = this.queryArc(__t, __t + lookahead).filter((h) => h.hasOnset());
memory[id] = memory[id].concat(newFuture);
let last;
const animate = () => {
const _t = getTime();
const t = _t + lookahead;
// filter out haps that are too far in the past
memory[id] = memory[id].filter((h) => h.isInNearPast(lookbehind, _t));
// begin where we left off in last frame, but max -0.1s (inactive tab throttles to 1fps)
let begin = Math.max(last || t, t - 1 / 10);
const haps = this.queryArc(begin, t).filter((h) => h.hasOnset());
memory[id] = memory[id].concat(haps);
last = t; // makes sure no haps are missed
fn(memory[id], _t, t, this);
animationFrames[id] = requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
animationFrames[id] = requestAnimationFrame(animate);
return this;
};
export const cleanupDraw = (clearScreen = true) => {
const ctx = getDrawContext();
clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.width);
if (window.strudelAnimation) {
cancelAnimationFrame(window.strudelAnimation);
}
stopAllAnimations();
if (window.strudelScheduler) {
clearInterval(window.strudelScheduler);
}
};
Pattern.prototype.onPaint = function (onPaint) {
// this is evil! TODO: add pattern.context
this.context = { onPaint };
Pattern.prototype.onPaint = function () {
console.warn('[draw] onPaint was not overloaded. Some drawings might not work');
return this;
};
@@ -134,7 +142,7 @@ export class Drawer {
this.lastFrame = phase;
this.visibleHaps = (this.visibleHaps || [])
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
.filter((h) => h.whole.end >= phase - lookbehind - lookahead)
.filter((h) => h.endClipped >= phase - lookbehind - lookahead)
// add new haps with onset (think right edge bars scrolling in)
.concat(haps.filter((h) => h.hasOnset()));
const time = phase - lookahead;
@@ -175,3 +183,27 @@ export class Drawer {
}
}
}
export function getComputedPropertyValue(name) {
if (typeof window === 'undefined') {
return '#fff';
}
return getComputedStyle(document.documentElement).getPropertyValue(name);
}
let theme = {
background: '#222',
foreground: '#75baff',
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
lineHighlight: '#00000050',
gutterBackground: 'transparent',
gutterForeground: '#8a919966',
};
export function getTheme() {
return theme;
}
export function setTheme(_theme) {
theme = _theme;
}
+6
View File
@@ -0,0 +1,6 @@
export * from './animate.mjs';
export * from './color.mjs';
export * from './draw.mjs';
export * from './pianoroll.mjs';
export * from './spiral.mjs';
export * from './pitchwheel.mjs';
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@strudel/draw",
"version": "1.1.0",
"description": "Helpers for drawing with Strudel",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"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/core": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.10"
}
}
@@ -1,10 +1,11 @@
/*
pianoroll.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/pianoroll.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/canvas/pianoroll.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, noteToMidi, getDrawContext, freqToMidi, isNote } from './index.mjs';
import { Pattern, noteToMidi, freqToMidi } from '@strudel/core';
import { getTheme, getDrawContext } from './draw.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min;
const getValue = (e) => {
@@ -18,7 +19,13 @@ const getValue = (e) => {
}
note = note ?? n;
if (typeof note === 'string') {
return noteToMidi(note);
try {
// TODO: n(run(32)).scale("D:minor") fails when trying to query negative time..
return noteToMidi(note);
} catch (err) {
// console.warn(`error converting note to midi: ${err}`); // this spams to crazy
return 0;
}
}
if (typeof note === 'number') {
return note;
@@ -30,25 +37,24 @@ const getValue = (e) => {
};
Pattern.prototype.pianoroll = function (options = {}) {
let { cycles = 4, playhead = 0.5, overscan = 1, hideNegative = false } = options;
let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = options;
let from = -cycles * playhead;
let to = cycles * (1 - playhead);
const inFrame = (hap, t) => (!hideNegative || hap.whole.begin >= 0) && hap.isWithinTime(t + from, t + to);
this.draw(
(ctx, haps, t) => {
const inFrame = (event) =>
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.endClipped >= t + from;
(haps, time) => {
pianoroll({
...options,
time: t,
time,
ctx,
haps: haps.filter(inFrame),
haps: haps.filter((hap) => inFrame(hap, time)),
});
},
{
from: from - overscan,
to: to + overscan,
lookbehind: from - overscan,
lookahead: to + overscan,
id,
},
);
return this;
@@ -98,14 +104,11 @@ export function pianoroll({
flipTime = 0,
flipValues = 0,
hideNegative = false,
// inactive = '#C9E597',
// inactive = '#FFCA28',
inactive = '#7491D2',
active = '#FFCA28',
// background = '#2A3236',
inactive = getTheme().foreground,
active = getTheme().foreground,
background = 'transparent',
smear = 0,
playheadColor = 'white',
playheadColor = getTheme().foreground,
minMidi = 10,
maxMidi = 90,
autorange = 0,
@@ -121,12 +124,17 @@ export function pianoroll({
colorizeInactive = 1,
fontFamily,
ctx,
id,
} = {}) {
const w = ctx.canvas.width;
const h = ctx.canvas.height;
let from = -cycles * playhead;
let to = cycles * (1 - playhead);
if (id) {
haps = haps.filter((hap) => hap.hasTag(id));
}
if (timeframeProp) {
console.warn('timeframe is deprecated! use from/to instead');
from = 0;
@@ -160,8 +168,13 @@ export function pianoroll({
maxMidi = max;
valueExtent = maxMidi - minMidi + 1;
}
// foldValues = values.sort((a, b) => a - b);
foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
foldValues = values.sort((a, b) =>
typeof a === 'number' && typeof b === 'number'
? a - b
: typeof a === 'number'
? 1
: String(a).localeCompare(String(b)),
);
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
ctx.fillStyle = background;
ctx.globalAlpha = 1; // reset!
@@ -176,13 +189,14 @@ export function pianoroll({
if (hideInactive && !isActive) {
return;
}
let color = event.value?.color || event.context?.color;
let color = event.value?.color;
active = color || active;
inactive = colorizeInactive ? color || inactive : inactive;
color = isActive ? active : inactive;
ctx.fillStyle = fillCurrent ? color : 'transparent';
ctx.strokeStyle = color;
ctx.globalAlpha = event.context.velocity ?? event.value?.gain ?? 1;
const { velocity = 1, gain = 1 } = event.value || {};
ctx.globalAlpha = velocity * gain;
const timeProgress = (event.whole.begin - (flipTime ? to : from)) / timeExtent;
const timePx = scale(timeProgress, ...timeRange);
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
@@ -258,8 +272,8 @@ export function getDrawOptions(drawTime, options = {}) {
export const getPunchcardPainter =
(options = {}) =>
(ctx, time, haps, drawTime, paintOptions = {}) =>
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) });
(ctx, time, haps, drawTime) =>
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
Pattern.prototype.punchcard = function (options) {
return this.onPaint(getPunchcardPainter(options));
+127
View File
@@ -0,0 +1,127 @@
import { Pattern, midiToFreq, getFrequency } from '@strudel/core';
import { getTheme, getDrawContext } from './draw.mjs';
const c = midiToFreq(36);
const circlePos = (cx, cy, radius, angle) => {
angle = angle * Math.PI * 2;
const x = Math.sin(angle) * radius + cx;
const y = Math.cos(angle) * radius + cy;
return [x, y];
};
const freq2angle = (freq, root) => {
return 0.5 - (Math.log2(freq / root) % 1);
};
export function pitchwheel({
haps,
ctx,
id,
hapcircles = 1,
circle = 0,
edo = 12,
root = c,
thickness = 3,
hapRadius = 6,
mode = 'flake',
margin = 10,
} = {}) {
const connectdots = mode === 'polygon';
const centerlines = mode === 'flake';
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.clearRect(0, 0, w, h);
const color = getTheme().foreground;
const size = Math.min(w, h);
const radius = size / 2 - thickness / 2 - hapRadius - margin;
const centerX = w / 2;
const centerY = h / 2;
if (id) {
haps = haps.filter((hap) => hap.hasTag(id));
}
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.globalAlpha = 1;
ctx.lineWidth = thickness;
if (circle) {
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.stroke();
}
if (edo) {
Array.from({ length: edo }, (_, i) => {
const angle = freq2angle(root * Math.pow(2, i / edo), root);
const [x, y] = circlePos(centerX, centerY, radius, angle);
ctx.beginPath();
ctx.arc(x, y, hapRadius, 0, 2 * Math.PI);
ctx.fill();
});
ctx.stroke();
}
let shape = [];
ctx.lineWidth = hapRadius;
haps.forEach((hap) => {
let freq;
try {
freq = getFrequency(hap);
} catch (err) {
return;
}
const angle = freq2angle(freq, root);
const [x, y] = circlePos(centerX, centerY, radius, angle);
const hapColor = hap.value.color || color;
ctx.strokeStyle = hapColor;
ctx.fillStyle = hapColor;
const { velocity = 1, gain = 1 } = hap.value || {};
const alpha = velocity * gain;
ctx.globalAlpha = alpha;
shape.push([x, y, angle, hapColor, alpha]);
ctx.beginPath();
if (hapcircles) {
ctx.moveTo(x + hapRadius, y);
ctx.arc(x, y, hapRadius, 0, 2 * Math.PI);
ctx.fill();
}
if (centerlines) {
ctx.moveTo(centerX, centerY);
ctx.lineTo(x, y);
}
ctx.stroke();
});
ctx.strokeStyle = color;
ctx.globalAlpha = 1;
if (connectdots && shape.length) {
shape = shape.sort((a, b) => a[2] - b[2]);
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);
shape.forEach(([x, y, _, color, alpha]) => {
ctx.strokeStyle = color;
ctx.globalAlpha = alpha;
ctx.lineTo(x, y);
});
ctx.lineTo(shape[0][0], shape[0][1]);
ctx.stroke();
}
return;
}
Pattern.prototype.pitchwheel = function (options = {}) {
let { ctx = getDrawContext(), id = 1 } = options;
return this.tag(id).onPaint((_, time, haps) =>
pitchwheel({
...options,
time,
ctx,
haps: haps.filter((hap) => hap.isActive(time)),
id,
}),
);
};
@@ -1,4 +1,5 @@
import { Pattern } from './index.mjs';
import { Pattern } from '@strudel/core';
import { getTheme } from './draw.mjs';
// polar coords -> xy
function fromPolar(angle, radius, cx, cy) {
@@ -19,7 +20,7 @@ function spiralSegment(options) {
cy = 100,
rotate = 0,
thickness = margin / 2,
color = '#0000ff30',
color = getTheme().foreground,
cap = 'round',
stretch = 1,
fromOpacity = 1,
@@ -49,70 +50,81 @@ function spiralSegment(options) {
ctx.stroke();
}
Pattern.prototype.spiral = function (options = {}) {
const {
function drawSpiral(options) {
let {
stretch = 1,
size = 80,
thickness = size / 2,
cap = 'butt', // round butt squar,
inset = 3, // start angl,
playheadColor = '#ffffff90',
playheadColor = '#ffffff',
playheadLength = 0.02,
playheadThickness = thickness,
padding = 0,
steady = 1,
inactiveColor = '#ffffff20',
activeColor = getTheme().foreground,
inactiveColor = getTheme().gutterForeground,
colorizeInactive = 0,
fade = true,
// logSpiral = true,
ctx,
time,
haps,
drawTime,
id,
} = options;
function spiral({ ctx, time, haps, drawTime }) {
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
ctx.clearRect(0, 0, w * 2, h * 2);
const [cx, cy] = [w / 2, h / 2];
const settings = {
margin: size / stretch,
cx,
cy,
stretch,
cap,
thickness,
};
const playhead = {
...settings,
thickness: playheadThickness,
from: inset - playheadLength,
to: inset,
color: playheadColor,
};
const [min] = drawTime;
const rotate = steady * time;
haps.forEach((hap) => {
const isActive = hap.whole.begin <= time && hap.endClipped > time;
const from = hap.whole.begin - time + inset;
const to = hap.endClipped - time + inset - padding;
const { color } = hap.context;
const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
spiralSegment({
ctx,
...settings,
from,
to,
rotate,
color: colorizeInactive || isActive ? color : inactiveColor,
fromOpacity: opacity,
toOpacity: opacity,
});
});
spiralSegment({
ctx,
...playhead,
rotate,
});
if (id) {
haps = haps.filter((hap) => hap.hasTag(id));
}
return this.onPaint((ctx, time, haps, drawTime) => spiral({ ctx, time, haps, drawTime }));
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
ctx.clearRect(0, 0, w * 2, h * 2);
const [cx, cy] = [w / 2, h / 2];
const settings = {
margin: size / stretch,
cx,
cy,
stretch,
cap,
thickness,
};
const playhead = {
...settings,
thickness: playheadThickness,
from: inset - playheadLength,
to: inset,
color: playheadColor,
};
const [min] = drawTime;
const rotate = steady * time;
haps.forEach((hap) => {
const isActive = hap.whole.begin <= time && hap.endClipped > time;
const from = hap.whole.begin - time + inset;
const to = hap.endClipped - time + inset - padding;
const hapColor = hap.value?.color || activeColor;
const color = colorizeInactive || isActive ? hapColor : inactiveColor;
const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
spiralSegment({
ctx,
...settings,
from,
to,
rotate,
color,
fromOpacity: opacity,
toOpacity: opacity,
});
});
spiralSegment({
ctx,
...playhead,
rotate,
});
}
Pattern.prototype.spiral = function (options = {}) {
return this.onPaint((ctx, time, haps, drawTime) => drawSpiral({ ctx, time, haps, drawTime, ...options }));
};
+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, 'index.mjs'),
formats: ['es'],
fileName: (ext) => ({ es: 'index.mjs' })[ext],
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
+53 -22
View File
@@ -1,33 +1,64 @@
# @strudel.cycles/embed
# @strudel/embed
This package contains a embeddable web component for the Strudel REPL.
## Usage
## Usage via Script Tag
Either install with `npm i @strudel.cycles/embed` or just use a cdn to import the script:
Use this code in any HTML file:
```html
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
<script src="https://unpkg.com/@strudel/embed@latest"></script>
<strudel-repl>
<!--
note(`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
[[~ d5] [~ f5] a5 [g5 f5]]
[e5 [~ c5] e5 [d5 c5]]
[b4 [b4 c5] d5 e5]
[c5 a4 a4 ~]],
[[e2 e3]*4]
[[a2 a3]*4]
[[g#2 g#3]*2 [e2 e3]*2]
[a2 a3 a2 a3 a2 a3 b1 c2]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]`).slow(16)
-->
setcps(1)
n("<0 1 2 3 4>*8").scale('G4 minor')
.s("gm_lead_6_voice")
.clip(sine.range(.2,.8).slow(8))
.jux(rev)
.room(2)
.sometimes(add(note("12")))
.lpf(perlin.range(200,20000).slow(4))
-->
</strudel-repl>
```
Note that the Code is placed inside HTML comments to prevent the browser from treating it as HTML.
This will load the strudel website in an iframe, using the code provided within the HTML comments `<!-- -->`.
The HTML comments are needed to make sure the browser won't interpret it as HTML.
Alternatively you can create a REPL from JavaScript like this:
```html
<script src="https://unpkg.com/@strudel/embed@1.0.2"></script>
<div id="strudel"></div>
<script>
let editor = document.createElement('strudel-repl');
editor.setAttribute(
'code',
`setcps(1)
n("<0 1 2 3 4>*8").scale('G4 minor')
.s("gm_lead_6_voice")
.clip(sine.range(.2,.8).slow(8))
.jux(rev)
.room(2)
.sometimes(add(note("12")))
.lpf(perlin.range(200,20000).slow(4))`,
);
document.getElementById('strudel').append(editor);
</script>
```
When you're using JSX, you could also use the `code` attribute in your markup:
```html
<script src="https://unpkg.com/@strudel/embed@1.0.2"></script>
<strudel-repl code={`
setcps(1)
n("<0 1 2 3 4>*8").scale('G4 minor')
.s("gm_lead_6_voice")
.clip(sine.range(.2,.8).slow(8))
.jux(rev)
.room(2)
.sometimes(add(note("12")))
.lpf(perlin.range(200,20000).slow(4))
`}></strudel-repl>
```
+1 -1
View File
@@ -4,7 +4,7 @@ class Strudel extends HTMLElement {
}
connectedCallback() {
setTimeout(() => {
const code = (this.innerHTML + '').replace('<!--', '').replace('-->', '').trim();
const code = this.getAttribute('code') || (this.innerHTML + '').replace('<!--', '').replace('-->', '').trim();
const iframe = document.createElement('iframe');
const src = `https://strudel.cc/#${encodeURIComponent(btoa(code))}`;
// const src = `http://localhost:3000/#${encodeURIComponent(btoa(code))}`;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/embed",
"version": "0.2.0",
"name": "@strudel/embed",
"version": "1.1.0",
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
"main": "embed.js",
"type": "module",
+2
View File
@@ -0,0 +1,2 @@
pnpm-lock.yaml
vite.config.js
+661
View File
@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
+127
View File
@@ -0,0 +1,127 @@
# hs2js
Experimental haskell in javascript interpreter. Many haskell features are not implemented.
This projects mainly exists to be able to write and interpret [Tidal Cycles](https://tidalcycles.org/) code in the browser,
as part of [Strudel](https://github.com/tidalcycles/strudel). This project could only exist thanks to [tree-sitter-haskell](https://github.com/tree-sitter/tree-sitter-haskell).
## Installation
### Via Script Tag
You can load the library directly from a script tag via unpkg:
```html
<script src="https://unpkg.com/hs2js@0.0.3"></script>
<button id="hello">hello</button>
<script>
hs2js.setBase('https://unpkg.com/hs2js@0.0.3/dist/');
hs2js.loadParser().then(()=>{
document.getElementById('hello').addEventListener('click', () => {
hs2js.evaluate('alert "hello from haskell!"');
});
})
</script>
```
### Via npm
You need to add `postinstall` to your `package.json` script to copy the parser to your `public` folder:
```json
{
"scripts": {
"postinstall": "cp node_modules/hs2js/dist/tree-sitter.wasm public && cp node_modules/hs2js/dist/tree-sitter-haskell.wasm public"
}
}
```
Depending on your setup, replace `public` with the folder that will serve your assets to `/`. Then install the package:
```sh
npm i hs2js
```
and use it:
```js
import * as hs2js from 'hs2js';
hs2js.loadParser();
document.getElementById('hello').addEventListener('click', () => {
hs2js.evaluate('alert "hello from haskell!"');
});
```
## API
These are all functions exported by the package:
### evaluate
Evaluates a piece of haskell code
- `code`: [valid](https://github.com/tree-sitter/tree-sitter-haskell?tab=readme-ov-file#supported-language-extensions) haskell code
- `scope`: global scope, defaults to globalThis. Allows you to pass an object of your own functions / variables from JS to Haskell.
- `ops`: mapping for custom infix operator
Example:
```js
// simple
hs2js.evaluate(`2 + 2`) // = 4
// passing variables via scope:
hs2js.evaluate(`a + b`, { a: 1, b: 2 }) // = 3
// custom operator
hs2js.evaluate(`2 |* 3`, {}, { '|*': (l, r) => l * r }) // = 6
```
### parse
[Parses](https://github.com/tree-sitter/tree-sitter-haskell) a piece of haskell code, returning its AST representation.
Example:
```js
const ast = hs2js.parse(`2 + 2`)
console.log(ast.toString())
// (haskell declarations: (declarations (top_splice (apply function: (variable) argument: (literal (integer))))))
```
### run
Evaluates `rootNode` of haskell AST (used by evaluate internally).
- `rootNode`: haskell AST root node, as returned by `parse`
- `scope`: see evaluate
- `ops`: see evaluate
Example:
```js
const ast = hs2js.parse(`2 + 3`);
const res = hs2js.run(ast.rootNode);
console.log(res); // = 5
```
### loadParser
Loads and caches the parser by fetching `tree-sitter.wasm` and `tree-sitter-haskell.wasm`.
Make sure to call and await this function before calling `parse` or `evaluate`.
```js
hs2js.loadParser().then(() => hs2js.evaluate('alert "ready"'))
```
### setBase
Sets the base path where the WASM files are expected by `loadParser`. Defaults to `/`.
Expects `tree-sitter.wasm` and `tree-sitter-haskell.wasm` to be present.
Can either be a relative path or a URL.
```js
hs2js.setBase('https://unpkg.com/hs2js@0.0.4/dist/');
hs2js.loadParser();
/* loads
- https://unpkg.com/hs2js@0.0.4/dist/tree-sitter.wasm
- https://unpkg.com/hs2js@0.0.4/dist/tree-sitter-haskell.wasm
*/
```
+37
View File
@@ -0,0 +1,37 @@
{
"name": "hs2js",
"version": "0.1.0",
"description": "Experimental Haskell in JavaScript interpreter",
"main": "src/index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": {
"build-wasm": "tree-sitter build-wasm node_modules/tree-sitter-haskell && mv tree-sitter-haskell.wasm ./dist/ && cp node_modules/web-tree-sitter/tree-sitter.wasm ./dist/",
"build": "vite build && npm run build-wasm",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"haskell",
"javascript"
],
"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",
"dependencies": {
"web-tree-sitter": "^0.20.8"
},
"devDependencies": {
"tree-sitter-haskell": "^0.21.0",
"vite": "^5.0.10"
}
}
+520
View File
@@ -0,0 +1,520 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
web-tree-sitter:
specifier: ^0.20.8
version: 0.20.8
devDependencies:
tree-sitter-haskell:
specifier: ^0.21.0
version: 0.21.0(tree-sitter@0.21.1)
vite:
specifier: ^5.0.10
version: 5.2.11
packages:
/@esbuild/aix-ppc64@0.20.2:
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm64@0.20.2:
resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm@0.20.2:
resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-x64@0.20.2:
resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-arm64@0.20.2:
resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-x64@0.20.2:
resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-arm64@0.20.2:
resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-x64@0.20.2:
resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm64@0.20.2:
resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm@0.20.2:
resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ia32@0.20.2:
resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64@0.20.2:
resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-mips64el@0.20.2:
resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ppc64@0.20.2:
resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-riscv64@0.20.2:
resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-s390x@0.20.2:
resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-x64@0.20.2:
resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/netbsd-x64@0.20.2:
resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/openbsd-x64@0.20.2:
resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/sunos-x64@0.20.2:
resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-arm64@0.20.2:
resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-ia32@0.20.2:
resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-x64@0.20.2:
resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-android-arm-eabi@4.17.2:
resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-android-arm64@4.17.2:
resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-darwin-arm64@4.17.2:
resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-darwin-x64@4.17.2:
resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm-gnueabihf@4.17.2:
resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm-musleabihf@4.17.2:
resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm64-gnu@4.17.2:
resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm64-musl@4.17.2:
resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-powerpc64le-gnu@4.17.2:
resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-riscv64-gnu@4.17.2:
resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-s390x-gnu@4.17.2:
resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-x64-gnu@4.17.2:
resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-x64-musl@4.17.2:
resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-arm64-msvc@4.17.2:
resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-ia32-msvc@4.17.2:
resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-x64-msvc@4.17.2:
resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true
/esbuild@0.20.2:
resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/aix-ppc64': 0.20.2
'@esbuild/android-arm': 0.20.2
'@esbuild/android-arm64': 0.20.2
'@esbuild/android-x64': 0.20.2
'@esbuild/darwin-arm64': 0.20.2
'@esbuild/darwin-x64': 0.20.2
'@esbuild/freebsd-arm64': 0.20.2
'@esbuild/freebsd-x64': 0.20.2
'@esbuild/linux-arm': 0.20.2
'@esbuild/linux-arm64': 0.20.2
'@esbuild/linux-ia32': 0.20.2
'@esbuild/linux-loong64': 0.20.2
'@esbuild/linux-mips64el': 0.20.2
'@esbuild/linux-ppc64': 0.20.2
'@esbuild/linux-riscv64': 0.20.2
'@esbuild/linux-s390x': 0.20.2
'@esbuild/linux-x64': 0.20.2
'@esbuild/netbsd-x64': 0.20.2
'@esbuild/openbsd-x64': 0.20.2
'@esbuild/sunos-x64': 0.20.2
'@esbuild/win32-arm64': 0.20.2
'@esbuild/win32-ia32': 0.20.2
'@esbuild/win32-x64': 0.20.2
dev: true
/fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/node-addon-api@8.0.0:
resolution: {integrity: sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==}
engines: {node: ^18 || ^20 || >= 21}
dev: true
/node-gyp-build@4.8.1:
resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
hasBin: true
dev: true
/picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
dev: true
/postcss@8.4.38:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.7
picocolors: 1.0.1
source-map-js: 1.2.0
dev: true
/rollup@4.17.2:
resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.17.2
'@rollup/rollup-android-arm64': 4.17.2
'@rollup/rollup-darwin-arm64': 4.17.2
'@rollup/rollup-darwin-x64': 4.17.2
'@rollup/rollup-linux-arm-gnueabihf': 4.17.2
'@rollup/rollup-linux-arm-musleabihf': 4.17.2
'@rollup/rollup-linux-arm64-gnu': 4.17.2
'@rollup/rollup-linux-arm64-musl': 4.17.2
'@rollup/rollup-linux-powerpc64le-gnu': 4.17.2
'@rollup/rollup-linux-riscv64-gnu': 4.17.2
'@rollup/rollup-linux-s390x-gnu': 4.17.2
'@rollup/rollup-linux-x64-gnu': 4.17.2
'@rollup/rollup-linux-x64-musl': 4.17.2
'@rollup/rollup-win32-arm64-msvc': 4.17.2
'@rollup/rollup-win32-ia32-msvc': 4.17.2
'@rollup/rollup-win32-x64-msvc': 4.17.2
fsevents: 2.3.3
dev: true
/source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
dev: true
/tree-sitter-haskell@0.21.0(tree-sitter@0.21.1):
resolution: {integrity: sha512-b2RLegPHuYPh7nJ3YKWgkWnFYxmYlQDE8TDJuNH+iuNuBcCMYyaA9JlJlMHfCvf7DmJNPtqqbO9Kh9NXEmbatQ==}
requiresBuild: true
peerDependencies:
tree-sitter: ^0.21.0
tree_sitter: '*'
peerDependenciesMeta:
tree_sitter:
optional: true
dependencies:
node-addon-api: 8.0.0
node-gyp-build: 4.8.1
tree-sitter: 0.21.1
dev: true
/tree-sitter@0.21.1:
resolution: {integrity: sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==}
requiresBuild: true
dependencies:
node-addon-api: 8.0.0
node-gyp-build: 4.8.1
dev: true
/vite@5.2.11:
resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
esbuild: 0.20.2
postcss: 8.4.38
rollup: 4.17.2
optionalDependencies:
fsevents: 2.3.3
dev: true
/web-tree-sitter@0.20.8:
resolution: {integrity: sha512-weOVgZ3aAARgdnb220GqYuh7+rZU0Ka9k9yfKtGAzEYMa6GgiCzW9JjQRJyCJakvibQW+dfjJdihjInKuuCAUQ==}
dev: false

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