Compare commits

...

420 Commits

Author SHA1 Message Date
Mehmet Ali Akmanalp bb347badbd Format fix 2025-03-06 13:07:53 +03:00
Mehmet Ali "Mali" Akmanalp b135d3dc63 Add elongation _ 2025-03-06 03:02:43 +03:00
Mehmet Ali "Mali" Akmanalp 297712e7ef Add degradation to mini notation page 2025-03-06 02:51:08 +03:00
Alex McLean b15843f3a7 change behaviour of polymeter, and remove polymeterSteps (#1302)
* change behaviour of polymeter, and remove polymeterSteps

* format
2025-03-04 18:17:37 +00:00
Jade (Rose) Rowland 86333ab6f6 Merge pull request #1304 from daslyfe/jade/pwm
feat: Create Pulse Oscillator  with variable PWM
2025-03-03 21:36:57 -05:00
Jade (Rose) Rowland 003e3b790d bounds 2025-03-03 21:29:30 -05:00
Jade (Rose) Rowland 4048ced7e6 format 2025-03-03 21:27:05 -05:00
Jade (Rose) Rowland 64b99f5a8c gain adj 2025-03-03 21:26:42 -05:00
Jade (Rose) Rowland b4af840240 docs 2025-03-03 21:05:23 -05:00
Jade (Rose) Rowland 8a502e886e vibrato working 2025-03-03 20:36:31 -05:00
Jade (Rose) Rowland c7ee90922f pwm 2025-03-03 01:32:04 -05:00
Jade (Rose) Rowland 530339ed8f Merge pull request #1295 from daslyfe/jade/uwu2000
feat: Theme improvements
2025-03-02 11:08:31 -05:00
Alex McLean fece4e8b57 bugfix zoom stepcount (#1301)
* bugfix zoom stepcount
2025-03-02 10:58:46 +00:00
Jade (Rose) Rowland afdcd42039 atomify log 2025-02-28 19:48:58 -05:00
Jade (Rose) Rowland 690b8648a8 Merge pull request #1298 from nkymut/fixtesterror
Fix test error #1297
2025-02-28 10:01:20 -05:00
Yuta Nakayama 7c1e968d41 Fix test error #1297 2025-02-28 19:13:46 +08:00
Jade (Rose) Rowland c0b3187029 Merge pull request #1297 from daslyfe/jade/fix/importerror
Hotfix: prevent undefined pattern code from crashing strudel on load
2025-02-27 20:53:41 -05:00
Jade (Rose) Rowland af1f7658c7 Merge branch 'main' into jade/fix/importerror 2025-02-27 20:47:24 -05:00
Jade (Rose) Rowland b531962b1f fixed 2025-02-27 20:42:40 -05:00
Jade (Rose) Rowland 8867a843ac fallback 2025-02-27 20:17:06 -05:00
Mehmet Ali "Mali" Akmanalp 6e972f4380 Fix misplaced ending sentence (#1296) 2025-02-27 07:24:58 +00:00
Jade (Rose) Rowland 65282bf5ea lint 2025-02-24 22:26:22 -05:00
Jade (Rose) Rowland 3df9744d79 theme attr 2025-02-24 22:24:29 -05:00
Jade (Rose) Rowland 5a5bbfc8da lint 2025-02-24 22:22:03 -05:00
Jade (Rose) Rowland f66478b9da button hover color 2025-02-24 22:15:38 -05:00
Jade (Rose) Rowland 904c213d03 foreground text color 2025-02-24 22:03:50 -05:00
Jade (Rose) Rowland d9c3f051b9 Merge branch 'main' into jade/uwu2000 2025-02-24 20:12:12 -05:00
Alex McLean 470a345296 Signpost licenses for source code and samples a bit more, ref #1277 (#1289)
* Signpost licenses for source code and samples a bit more, ref #1277

* format
2025-02-23 09:54:13 +00:00
Alex McLean 0925ea56dd Allow wchooseCycles probabilities to be patterned (#1292)
* allow wchooseCycles probabilities to be patterned
2025-02-23 09:52:27 +00:00
Jade (Rose) Rowland 1f233b9e7d Merge pull request #1287 from daslyfe/jade/supabasequery
Create Pattern Page Pagination
2025-02-21 20:18:57 -05:00
Jade (Rose) Rowland 5c5d411588 lint 2025-02-21 20:11:39 -05:00
Jade (Rose) Rowland bbb88a2302 accessibility 2025-02-21 20:03:17 -05:00
Alex McLean 64c94b3623 showcase tweaks (#1291) 2025-02-21 21:38:10 +00:00
Jade (Rose) Rowland 42c2262576 working 2025-02-21 14:41:01 -05:00
Jade (Rose) Rowland 4959bff060 font settings 2025-02-21 11:06:21 -05:00
Jade (Rose) Rowland 1ded398468 fixed style 2025-02-21 01:31:06 -05:00
Jade (Rose) Rowland b664bd3d1f girl 2025-02-20 00:54:33 -05:00
Jade (Rose) Rowland dfb7d7bb7f prettier 2025-02-18 11:13:33 -05:00
Jade (Rose) Rowland 3e6a45cf8b fix offset 2025-02-18 11:10:15 -05:00
Jade (Rose) Rowland f47f5c1344 prettier 2025-02-18 10:52:48 -05:00
Jade (Rose) Rowland 47f65da621 remove unesseccary code 2025-02-18 10:47:48 -05:00
Jade (Rose) Rowland c210ea3f89 fix paginator 2025-02-18 10:41:16 -05:00
Jade (Rose) Rowland 50f076084f working 2025-02-18 01:48:17 -05:00
Jade (Rose) Rowland 5cd98880ca reorganize panels 2025-02-17 12:22:45 -05:00
Alex McLean aabc82bedd Fixes inverted triangle wave by renaming it to itri, making non-inverted tri (#1283)
* add itri / itri2 .. but should tri and itri be swapped??

* swap tri/tri2 with itri/itri2. Add some basic docs for bipolar signals
2025-02-11 15:34:23 +00:00
Alex McLean 84581b22aa Fix for squeezejoin and functions using it, including bite (#1286) 2025-02-11 09:02:34 +00:00
Alex McLean 3575fc3cc8 rename repeat back to extend (#1285) 2025-02-09 15:18:28 +00:00
Alex McLean cf8203a4db mqtt bugfix - connection check (#1282) 2025-02-07 17:11:52 +00:00
Alex McLean cda79f3e5c Bugfix: update mqtt connections dictionary (#1281) 2025-02-07 16:38:28 +00:00
Alex McLean 07b0a2e0f0 Make mqtt topic patternable (#1280) 2025-02-07 10:39:53 +00:00
Felix Roos 5e81b542dc Merge pull request #1274 from tidalcycles/midimaps
midimaps
2025-02-07 11:07:32 +01:00
Alex McLean 8bde61fa27 allow duration and cps metadata to be added to mqtt messages (#1279) 2025-02-06 14:59:03 +00:00
Felix Roos efd4071637 remove midisounds for now 2025-02-06 13:51:53 +01:00
Alex McLean 981ad1e242 Add DOI 2025-02-06 09:06:24 +00:00
Alex McLean 49e9b84962 [breaking change] Sample signals from query onset, rather than midpoint (#1278)
This changes almost everything involving signals, including all use of random numbers.
2025-02-05 15:10:53 +00:00
Felix Roos 848edb1f69 doc: midisounds 2025-02-03 22:56:51 +01:00
Felix Roos 88fb0b3b75 fix: pnpm check 2025-02-03 22:45:19 +01:00
Felix Roos dc8becc610 chore: delete old files 2025-02-03 22:44:56 +01:00
Felix Roos e99f229b57 doc: defaultmidimap + midimaps 2025-02-03 22:37:59 +01:00
Felix Roos c810a02a79 - add defaultmidimap function
- add midisounds function
- add midiport control
2025-02-03 21:55:57 +01:00
Alex McLean 018d1ccd31 Stepwise documentation tweaks, with mridangam samples (#1275)
* add mridangam sample bank
* stepwise documentation tweaks
2025-02-02 23:55:36 +00:00
Felix Roos d86df33b8c allow loading midimap from url 2025-02-02 23:49:17 +01:00
Felix Roos b74d5becd5 allow passing ccn numbers directly to midimapped control name 2025-02-02 23:37:20 +01:00
Felix Roos b87b2aff9a simplify more 2025-02-02 22:18:56 +01:00
Felix Roos 95b1548e5f simplify 2025-02-02 22:16:11 +01:00
Felix Roos 0b1bc74c3b feat: basic midimap handling 2025-02-02 22:01:53 +01:00
Alex McLean ce9d23049a Polish, rename, and document stepwise functions (#1262)
* polish, rename, and document stepwise functions: `pace`, `take`, `drop`, `expand`, `contract`, `repeat`, `zip`, `grow`, `shrink`, and `tour`
2025-02-02 20:26:44 +00:00
Felix Roos 0338c5b222 Merge pull request #1272 from tidalcycles/fix-sf2-timing
Fix sf2 timing
2025-02-01 22:57:00 +01:00
Felix Roos 1bf74024d4 fix: sf2 soundfont timing 2025-02-01 22:54:20 +01:00
Felix Roos 63ee218d53 fix: make sure ac is defined 2025-02-01 22:54:06 +01:00
Felix Roos 3d37478577 Merge pull request #1245 from TodePond/lu/add-drum-suffixes
Add bank aliasing and case insensitivity
2025-02-01 22:32:59 +01:00
Felix Roos f558b4b8f6 hotfix: update action versions 2025-01-31 09:45:36 +01:00
Felix Roos f26a2af38e Merge pull request #1217 from nkymut/devicemotion
Add Device Motion module
2025-01-31 09:39:54 +01:00
Felix Roos 75f5f45652 fix: bring back examples test
+ ignore device motion examples
2025-01-31 08:40:16 +01:00
Felix Roos 5cef1a8cf9 refactor: simplify test runtime 2025-01-31 08:40:00 +01:00
Felix Roos bcb1da826e fix: doc example 2025-01-31 08:26:09 +01:00
Felix Roos 1f5f1a63a6 docs: cpm -> setcpm 2025-01-31 08:23:52 +01:00
Felix Roos 44828e2047 docs: move dev info down 2025-01-31 08:22:04 +01:00
Felix Roos bcef67e5a1 chore: bump vite + update lockfile 2025-01-31 08:19:26 +01:00
Felix Roos b4ef786108 Merge branch 'main' into devicemotion 2025-01-31 08:16:31 +01:00
Felix Roos d03fc43f03 docs: add info on how to test repl package 2025-01-30 21:03:05 +01:00
Felix Roos ca00f7c5d0 Merge branch 'main' into lu/add-drum-suffixes 2025-01-29 15:46:21 +01:00
Felix Roos dbbe2d7c98 Merge pull request #1247 from tidalcycles/get-control
add "as" function + getControlName
2025-01-29 15:40:00 +01:00
Felix Roos 1058c272a4 doc: add as to controls page 2025-01-29 15:34:14 +01:00
Felix Roos 81e97a5643 Merge branch 'main' into get-control 2025-01-29 15:31:33 +01:00
Felix Roos f105ea6677 Merge pull request #1268 from tidalcycles/theme-glowup
Theme glowup
2025-01-29 15:30:04 +01:00
Felix Roos 6307f755f7 Merge branch 'main' into theme-glowup 2025-01-29 15:24:45 +01:00
Felix Roos fac698e39c Merge pull request #1264 from tidalcycles/housekeeping
patchday
2025-01-29 15:22:18 +01:00
Felix Roos d54022a65a fix: build error -> workbox max file size limit 2025-01-29 15:19:49 +01:00
Felix Roos 892a686462 fix: react 19 error 2025-01-29 14:52:44 +01:00
Felix Roos fbf9d06d84 Merge remote-tracking branch 'origin/main' into housekeeping 2025-01-29 14:50:49 +01:00
Felix Roos 06415d7f14 fix: error 2025-01-29 00:05:29 +01:00
Felix Roos d353eff1f6 comment out xcode theme 2025-01-29 00:03:39 +01:00
Felix Roos ce5add4a56 improve vscodeDark 2025-01-29 00:03:16 +01:00
Felix Roos 212a478161 improve sublime theme 2025-01-28 23:30:48 +01:00
Felix Roos 33b31fb434 rename okaidia to monokai + improve it 2025-01-28 23:30:33 +01:00
Felix Roos b51dacb11c improve material dark theme 2025-01-28 23:19:37 +01:00
Felix Roos 1e59b4efa4 improve gruvbox dark 2025-01-28 23:06:24 +01:00
Felix Roos e31a9965d5 improve github dark theme 2025-01-28 22:52:48 +01:00
Felix Roos 6c537cb134 improve duotoneDark 2025-01-28 22:47:59 +01:00
Felix Roos 6062a2e757 glow up android studio theme 2025-01-28 22:47:50 +01:00
Felix Roos ac83ff8817 theme glowup 2025-01-28 00:34:25 +01:00
Felix Roos b55661b21e migrate remaining themes 2025-01-28 00:33:44 +01:00
Alex McLean 638e247a5f Revert "Fix sometimes (#1243)" (#1267)
This reverts commit 00b0341839.
2025-01-28 00:00:03 +01:00
Felix Roos ff4d4357b2 format 2025-01-27 09:10:13 +01:00
Felix Roos 1460defa42 5 more themes 2025-01-27 09:08:30 +01:00
Felix Roos 1053c7eb38 4 more themes 2025-01-27 08:55:39 +01:00
Felix Roos c09b590cf6 4 more themes 2025-01-27 08:45:56 +01:00
Felix Roos 63f9f6cba0 5 more themes 2025-01-27 08:34:54 +01:00
Felix Roos 9112a0c4f5 migrate 5 more themes 2025-01-27 08:21:56 +01:00
Felix Roos e754070a59 abcdef + androidstudio 2025-01-27 00:03:37 +01:00
Felix Roos afa202e2a8 migrate custom themes awa from @uiw/codemirror-themes 2025-01-26 23:48:27 +01:00
Felix Roos fc3d7ade11 remove uiw codemirror themes to fix site
+ todo: refactor themes to not use uiw package
2025-01-26 23:05:17 +01:00
Lu Wilson d192201e07 Update website/public/tidal-drum-machines-alias.json 2025-01-26 17:01:47 +00:00
Lu[ke] Wilson c9bdfe57dd add more aliases 2025-01-26 16:51:11 +00:00
Lu[ke] Wilson 7e1c6c1c22 setup default aliasing 2025-01-26 16:43:04 +00:00
Lu[ke] Wilson 5d3778fb27 allow aliasing via a path 2025-01-26 16:37:51 +00:00
Lu[ke] Wilson f362f1c9f6 add comments and case insensitivity 2025-01-26 16:28:33 +00:00
Lu[ke] Wilson fd68ad9bde add docs and allow arrays of aliases 2025-01-26 16:08:54 +00:00
Lu[ke] Wilson 7222c29c55 fix and overload 2025-01-26 15:54:25 +00:00
Lu[ke] Wilson 234beff00e revert manual bank aliases 2025-01-26 15:34:12 +00:00
Lu[ke] Wilson 90fe4d3816 set by value so it actually updates 2025-01-26 14:58:58 +00:00
Felix Roos 5a188f7461 cleanup 2025-01-26 15:50:44 +01:00
Felix Roos 5d98fd2ab3 remove unused check task 2025-01-26 15:46:13 +01:00
Felix Roos 46400effb7 fix: lint error 2025-01-26 15:45:54 +01:00
Lu[ke] Wilson 710da1d28d improve function 2025-01-26 14:30:47 +00:00
Felix Roos f4b8753171 fix: tailwind setup for real now 2025-01-26 14:58:23 +01:00
Felix Roos aa1efcc7ea ignore ts error 2025-01-26 14:58:03 +01:00
Felix Roos 3e721d91a5 update all the things again 2025-01-26 14:51:08 +01:00
Felix Roos badbcdcf25 update codemirror package 2025-01-26 14:42:23 +01:00
Felix Roos 4f35027d9f update pnpm version in workflows 2025-01-26 14:38:48 +01:00
Felix Roos de7c652706 chore: update test action versions 2025-01-26 14:35:55 +01:00
Felix Roos 0e59a233ed chore: remove vite-plugin-pwa dependency
- its only used for typings
- it creates problems with peerDependencies
- just let it die
2025-01-26 14:32:28 +01:00
Felix Roos b3e0f180b3 update pnpm action-setup 2025-01-26 13:30:56 +01:00
Lu[ke] Wilson a2c896f765 fix repeated calls 2025-01-26 12:30:37 +00:00
Felix Roos c7cd03a2aa migrate eslint 2025-01-26 13:24:51 +01:00
Lu[ke] Wilson e6604491a9 add aliasbank function 2025-01-26 12:21:26 +00:00
Felix Roos 1d7f755b89 chore: update website packages 2025-01-26 13:06:48 +01:00
Lu[ke] Wilson 4f6e38f714 Merge remote-tracking branch 'origin/main' into lu/add-drum-suffixes 2025-01-26 11:28:10 +00:00
Felix Roos 35911f3082 update tailwind 2025-01-24 15:55:42 +01:00
Felix Roos 024f424bf5 update mini package 2025-01-24 15:50:26 +01:00
Felix Roos 1b8ccc05e1 update main package + core package + vite / vitest 2025-01-24 15:45:49 +01:00
Felix Roos 77267a80e3 Merge pull request #1252 from tidalcycles/reference-package
add reference package
2025-01-24 15:32:48 +01:00
Felix Roos 0ca540b671 add reference readme 2025-01-24 15:22:23 +01:00
Alex McLean 5731ae0cdc support all(pianoroll) and all(pianoroll({labels: true})) (#1234) 2025-01-24 14:16:55 +00:00
Felix Roos 5d562e8cd6 add reference package 2025-01-24 14:31:29 +01:00
Alex McLean 9432f780f2 MQTT - if password isn't provided, prompt for one
Helpful for cases where you don't want to risk putting a password in the code.
2025-01-24 11:13:49 +00:00
Jade (Rose) Rowland 52ebfa1d09 Merge pull request #1248 from daslyfe/fixbeatdoc
fix docs for beat function
2025-01-21 00:24:03 -05:00
Jade (Rose) Rowland 6cde970e0d snapshot 2025-01-21 00:22:31 -05:00
Jade (Rose) Rowland 188d318125 fixed 2025-01-21 00:20:55 -05:00
Felix Roos 8d28317d0e Merge pull request #1230 from tidalcycles/voicings-doc
understand voicings page
2025-01-18 23:27:51 +01:00
Felix Roos fc41bd1a70 snapshot 2025-01-18 22:28:51 +01:00
Felix Roos 773420fd65 add "as" function + getControlName 2025-01-18 22:27:29 +01:00
Lu[ke] Wilson 45fffa514a add shortened bank names 2025-01-18 11:09:54 +00:00
Yuta Nakayama cdf50cbaab Update README.md
minor edit on SSL installation procedure
2025-01-18 07:09:43 +08:00
Felix Roos f61916fffa Merge pull request #1226 from heerman/binary
Add binary and binaryN
2025-01-17 19:27:00 +01:00
Alex McLean 00b0341839 Fix sometimes (#1243)
* test and fix for sometimes bug
2025-01-16 11:18:33 +00:00
Jade (Rose) Rowland d5ab391a98 Merge pull request #1237 from daslyfe/origin/daslyfe/subcycle
onSubCycle function for "step sequencer" style rhythm notation
2025-01-15 23:55:46 -05:00
Jade (Rose) Rowland d2a9b9b750 format 2025-01-15 23:51:57 -05:00
Jade (Rose) Rowland 2348023b22 merge main 2025-01-15 23:35:36 -05:00
Jade (Rose) Rowland 6e5d8eed19 Merge branch 'main' into origin/daslyfe/subcycle 2025-01-15 23:35:06 -05:00
Jade (Rose) Rowland c33c8b5469 remove possibly not needed alt 2025-01-15 23:30:56 -05:00
Jade (Rose) Rowland 7b6074ccef update function 2025-01-15 23:30:19 -05:00
Alex McLean 55c226cda6 Add stepBind, and some toplevel aliases for binds and withValue (#1241)
* Add toplevel withValue function
* add stepBind, and add toplevel aliases for this and other binds
2025-01-14 13:39:14 +00:00
Jade (Rose) Rowland c5de21ef61 Merge pull request #1235 from daslyfe/orgin/daslyfe/keydown
Add onKey function for custom key commands for patterns
2025-01-14 00:11:10 -05:00
Jade (Rose) Rowland 36374fb3fb format 2025-01-14 00:06:57 -05:00
Jade (Rose) Rowland 570c7a0d6b _keyDown 2025-01-14 00:03:44 -05:00
Sarah Gillespie c3be21e6e5 Update value-modifiers.mdx (#1238)
Revises outdated documentation in light of PR #1030
2025-01-12 10:32:24 +00:00
Jade (Rose) Rowland 7410ac5cec snapshot 2025-01-08 00:44:40 -05:00
Jade (Rose) Rowland f5127147de channge function name 2025-01-08 00:43:02 -05:00
Jade (Rose) Rowland 1d144faed7 update snapshot 2025-01-02 22:39:56 -05:00
Jade (Rose) Rowland 7b914e7f22 working 2025-01-02 22:33:04 -05:00
Jade (Rose) Rowland 052addd67e add doc link 2025-01-02 22:16:46 -05:00
Jade (Rose) Rowland 3d162536aa add keyDown 2025-01-02 22:12:48 -05:00
Jade (Rose) Rowland 2443aad01b key alias and breakout 2024-12-31 11:58:32 -05:00
Jade (Rose) Rowland 08f6c1ad5e fix test 2024-12-30 23:19:37 -05:00
Jade (Rose) Rowland 974376d404 fix conflict 2024-12-30 22:03:51 -05:00
Jade (Rose) Rowland 1da80db048 merge main 2024-12-30 21:59:40 -05:00
Jade (Rose) Rowland 42519581c1 add func 2024-12-30 21:49:59 -05:00
Alex McLean 1e3bbc57cc Documentation for all/each, and bugfix for each (#1233)
* bugfix for each(), and documentation for each() and all()
* had to hide each/all examples from tests
2024-12-29 12:16:08 +00:00
Alex McLean 9bf0f624f5 Make all() post-stack again, and add each() for pre-stack (#1229)
* Rename `all` to `each`, re-instating old `all`
2024-12-29 10:29:52 +00:00
Luke Heerman c6418789c4 Fix lint 2024-12-27 21:49:56 -05:00
Felix Roos 62d6ef2287 Merge pull request #1231 from bwagner/pr-1230
suggested changes to voicings.mdx
2024-12-27 22:16:54 +01:00
Luke Heerman 8b909c54db Merge branch 'main' into binary 2024-12-27 15:15:54 -05:00
Luke Heerman 9d029ac914 Add binary and binaryN to learn pages 2024-12-27 15:14:58 -05:00
Luke Heerman 8644b3d163 Fix accidental multi-line jsdoc name 2024-12-27 15:14:31 -05:00
Luke Heerman 426c5edf25 Update tests snapshot 2024-12-27 07:43:41 -05:00
Bernhard Wagner d30623dad4 minor amendments 2024-12-26 15:11:48 +01:00
Felix Roos 49d4fb499f some voicing doc edits 2024-12-26 01:29:48 +01:00
Felix Roos e4f89f072e delete timbre draft 2024-12-24 01:05:01 +01:00
Felix Roos e12dadb33f write more 2024-12-24 00:59:03 +01:00
Felix Roos 991d8e4717 Merge branch 'main' into voicings-doc 2024-12-23 23:07:46 +01:00
Felix Roos 26cc7e2920 Merge pull request #1213 from tidalcycles/spectrum
add basic spectrum function
2024-12-23 11:53:11 +01:00
Alex McLean 04eac3e7c5 Fix regression for d1, p1, p(n) (#1227)
* fix for `d1` and `.p(1)`
2024-12-22 20:04:45 +00:00
Luke Heerman 7cf7575e6d Make the docs friendlier 2024-12-17 08:49:49 -05:00
Yuta Nakayama b7d8593a4d Merge branch 'main' into devicemotion 2024-12-17 17:34:31 +08:00
nkymut a5b05d211e Add DOC for devicemotion 2024-12-17 17:28:53 +08:00
Luke Heerman 111cea2381 Fix lint 2024-12-16 21:31:21 -05:00
Felix Roos 896a3828f6 fix: spectrum example test 2024-12-16 23:19:57 +01:00
Felix Roos eb02997836 spectrum doc 2024-12-16 23:18:20 +01:00
Felix Roos 8a33c171f5 spectrum widget 2024-12-16 23:11:01 +01:00
Luke Heerman f87f279198 Support those who prefer no left padding 2024-12-10 23:51:32 -05:00
Luke Heerman 4cc019f038 Rename variable 2024-12-10 23:51:32 -05:00
Luke Heerman 34f4afad01 Rework binary to binaryN 2024-12-10 23:51:32 -05:00
Luke Heerman 947b263b9c Add tests 2024-12-10 23:51:32 -05:00
Luke Heerman 0a408e464a Make example more concise 2024-12-10 23:51:32 -05:00
Luke Heerman 0d660277f6 Put msb on the right 2024-12-10 23:51:32 -05:00
Luke Heerman 33fef379ad Get a basic version of binary working 2024-12-10 23:51:32 -05:00
eefano 0531369613 Make cps patternable (#1001)
Supports e.g. `note("c d e f g a").cps("<1 1.2 1.3 1.4 1.5 1.6>")`
2024-11-30 09:09:36 +00:00
Alex McLean c66f19108c MQTT support (#1224)
* Add mqtt() method and documentation
* Documentation clarifications for MIDI and OSC
2024-11-30 09:07:55 +00:00
Alex McLean ef53367ba9 Apply all function to individual patterns rather than final stack (#1209) 2024-11-30 08:46:13 +00:00
nkymut f33ec0e2af Add Device Motion package
Introduces smart-phone device's acceleration, gravity, rotation, and orientation signals for dynamic pattern creation in Strudel.
2024-11-21 12:01:17 +08:00
Felix Roos 726abf76ce Merge pull request #1214 from bthj/main
REPL: solo and sync configuration
2024-11-20 16:32:51 +01:00
Björn Þór Jónsson a7d74ed6d6 REPL: solo and sync configuration; codeformat 2024-11-20 16:28:07 +01:00
Felix Roos 648fbf99fa add basic spectrum function 2024-11-09 20:57:09 +01:00
Björn Þór Jónsson b913e1c50d REPL: solo and sync configuration 2024-11-09 15:13:24 +01:00
Alex McLean ae335ac40c Add s_zip for 'cat'-ing patterns together step-by-step (#1208)
Fix tactus returned by 'step'
Fix name of toplevel s_extend (was 's_cycles')
2024-10-30 21:29:43 +01:00
Alex McLean 9ecad810b1 Preserve tactus for 'degrade' and friends, and tidy up 'pick' and friends (#1205)
* separate pick methods into their own module
* preserve tactus in degrade and friends
2024-10-30 21:28:31 +01:00
Felix Roos f18cf76dde Merge pull request #1206 from ChinoUkaegbu/chore/edit-setup-instructions
chore: Edit run locally instructions in README.md
2024-10-30 17:11:52 +01:00
ChinoUkaegbu 08c918f13e edit run locally instructions in README.md 2024-10-29 11:20:05 +01:00
Felix Roos 2e72c7f4ed Merge pull request #1203 from tidalcycles/console-colors
colorize console + tweak header
2024-10-23 23:08:14 +02:00
Felix Roos 614f393a5d more cleanup 2024-10-23 23:05:17 +02:00
Felix Roos 75f7f4deac cleanup 2024-10-23 23:03:48 +02:00
Felix Roos 2a67698616 - remove header icons (except play/stop button)
- use unicode spiral instead of emoji spiral
- fix header height on mobile
2024-10-23 02:29:23 +02:00
Felix Roos 801879bddf use fontFamily + fontSize from settings in console 2024-10-23 01:17:18 +02:00
Felix Roos 7d8984b706 fix: lint 2024-10-23 01:11:03 +02:00
Felix Roos 1a2847d953 - move console logic to console tab
- colorize logs when color is set
2024-10-23 01:08:32 +02:00
Felix Roos e71db24073 Merge pull request #1202 from tidalcycles/markcss
markcss
2024-10-22 22:54:59 +02:00
Felix Roos f61de0c47c use color inherit in monochrome themes
(to be able to set color in markcss)
2024-10-22 22:48:36 +02:00
Felix Roos 53b8bbb732 snapshot + add markcss in test runtime 2024-10-22 22:27:57 +02:00
Felix Roos aeaf447142 add doc 2024-10-22 22:27:14 +02:00
Felix Roos 1e6e0a61b3 feat: markcss 2024-10-22 22:23:48 +02:00
Felix Roos dd696ba663 Merge pull request #1200 from tidalcycles/more-settings
add 2 new ui settings
2024-10-21 22:56:37 +02:00
Felix Roos be4c8cbdce hide docs link when embedded 2024-10-21 22:47:05 +02:00
Felix Roos b6b54a8553 add 2 new settings
- disable css animations
- hide buttons
- add docs link to left (to use when buttons are hidden)
2024-10-21 21:50:40 +02:00
Jade (Rose) Rowland 1d05051fd6 Merge pull request #1199 from tidalcycles/origin/daslyfe/panel_fixes
Make panel hover behavior optional
2024-10-21 11:22:52 -07:00
Jade (Rose) Rowland 6bca8e2a2f Merge branch 'origin/daslyfe/panel_fixes' of github.com:tidalcycles/strudel into origin/daslyfe/panel_fixes 2024-10-21 11:18:35 -07:00
Jade (Rose) Rowland 630ba62cc3 prettier 2024-10-21 11:18:08 -07:00
Jade (Rose) Rowland 8ac98262bc Delete website/.astro/types.d.ts 2024-10-21 11:17:00 -07:00
Jade (Rose) Rowland 6057e9ada1 Delete website/.astro/astro/content.d.ts 2024-10-21 11:16:40 -07:00
Jade (Rose) Rowland 94e8b18ffa remove astro 2024-10-21 11:15:37 -07:00
Jade (Rose) Rowland 2ec20c4ed4 gitignore 2024-10-21 08:39:11 -07:00
Jade (Rose) Rowland c4fe2c7497 fix horizontal button 2024-10-20 23:33:02 -07:00
Jade (Rose) Rowland df2d28f0e5 panel options 2024-10-20 21:54:33 -07:00
Felix Roos d9bd6f9e7d hotfix: alt+dot breakage 2024-10-20 21:04:18 +02:00
Felix Roos 1e0cc2ab17 hotfix: ignore .astro folder + delete files from git 2024-10-20 20:45:49 +02:00
Jade (Rose) Rowland 835c7b6879 Merge pull request #1193 from daslyfe/orgin/daslyfe/feat/pinmenu
Menu Panel Improvements!
2024-10-18 22:28:35 -04:00
Felix Roos 721e7f06b8 Merge pull request #1198 from tidalcycles/patchday2
update lockfile + minor versions
2024-10-19 01:10:06 +02:00
Felix Roos a46bbeaa10 fix: format + ignore samples 2024-10-19 01:05:23 +02:00
Felix Roos e3a3c0c5f9 Revert "failing to upgrade eslint"
This reverts commit 67964c7e23.
2024-10-19 00:56:55 +02:00
Felix Roos 67964c7e23 failing to upgrade eslint 2024-10-19 00:31:52 +02:00
Felix Roos 9e5f9d37ef update vitest from 1 to 2 2024-10-18 23:45:54 +02:00
Felix Roos 0c1234a35f update minor versions 2024-10-18 23:38:35 +02:00
Felix Roos c6cc487fc6 update lockfile version from 6 to 9 2024-10-18 23:35:19 +02:00
Jade (Rose) Rowland dd171f8320 panel position height dependent 2024-10-15 13:33:37 -04:00
Jade (Rose) Rowland c22c786cf2 welcome width 2024-10-14 15:19:16 -04:00
Jade (Rose) Rowland c191471e1e better width calc 2024-10-14 15:12:32 -04:00
Jade (Rose) Rowland 0d0cb3fd93 fix: tiny vertical menu 2024-10-14 15:05:10 -04:00
Jade (Rose) Rowland bb4b7dc533 fix: vertical breakpoints on mobile 2024-10-14 15:01:05 -04:00
Jade (Rose) Rowland 8440fa42ea remove unessecary hover logic 2024-10-14 13:51:14 -04:00
Jade (Rose) Rowland 61f070744a fix: lint 2024-10-14 13:09:24 -04:00
Jade (Rose) Rowland 2e6a561184 remove spillover 2024-10-14 13:08:39 -04:00
Jade (Rose) Rowland 8703d684c4 fix: lint 2024-10-14 13:01:02 -04:00
Jade (Rose) Rowland b0d2115f43 feat: mobile responsiveness 2024-10-14 12:45:55 -04:00
Jade (Rose) Rowland 96802a271c feat: menu pinning 2024-10-13 23:24:54 -04:00
Jade (Rose) Rowland a91f4576f3 Merge branch 'main' of github.com:daslyfe/strudel 2024-10-13 00:01:12 -04:00
Felix Roos ad080d02b3 Merge pull request #1189 from kdiab/main
remove redundant example for cat, update snapshot
2024-09-27 00:17:00 +02:00
Khalid e9e7e02547 Fixed access to merged description in factories.mdx 2024-09-26 17:58:59 -04:00
Felix Roos 2ffc6c9601 Merge pull request #1185 from Bubobubobubobubo/search-bar
Adding search bar (soundtab.jsx)
2024-09-25 13:18:19 +02:00
Raphaël Forment 3a9349d9f5 format code for tests 2024-09-25 12:59:18 +02:00
Raphaël Forment bd3f2845f8 Reverting code 2024-09-25 12:57:20 +02:00
Alex McLean 2d320c459b Screenreader improvements (#1158)
* hide ascii art for screen readers
* aria-labels for minirepl buttons
2024-09-23 16:18:34 +01:00
Raphaël Forment ff9f4a8a0d Simplify search bar code 2024-09-22 23:40:27 +02:00
Raphaël Forment d7b94807cb Merge branch 'tidalcycles:main' into search-bar 2024-09-22 23:30:11 +02:00
kdiab 2c16a37a68 Combine examples for stack, match wording for cat, stack and seq 2024-09-22 10:42:46 -04:00
kdiab 717e4a2c1d Combine examples for seq and remove extra entry 2024-09-22 10:31:50 -04:00
kdiab c225b4719f Combine examples for cat 2024-09-21 11:01:00 -04:00
kdiab 25f762c393 remove redundant example for cat, update snapshot 2024-09-20 23:38:21 -04:00
Alex McLean b75561e3c7 Fix serial timing (#1188)
* fix serial timing
* format
2024-09-20 21:26:41 +01:00
Alex McLean abaab89061 Add bite function (#1187)
For patterning slices of patterns. ref #31.
2024-09-19 23:26:30 +01:00
Felix Roos e409086b34 Merge pull request #1147 from tidalcycles/bettern-zen-spacing
better spacing in zen mode
2024-09-14 13:30:53 +02:00
Felix Roos 532f1435d9 revert lockfile 2024-09-14 13:15:44 +02:00
Raphaël Forment 6cb61ade03 Adding search bar in soundtab.jsx 2024-09-14 11:39:59 +02:00
Felix Roos a40219ba06 Merge pull request #1039 from tidalcycles/filter
add filter + filterWhen + within
2024-09-14 10:50:23 +02:00
Felix Roos 9c99f5ada2 Merge pull request #1101 from tidalcycles/sampler-refactoring
refactor sampler
2024-09-14 10:49:21 +02:00
Felix Roos 2d72c8faa0 Merge pull request #1146 from tidalcycles/midi-error-msg
handle midin device not found error
2024-09-14 10:43:17 +02:00
Felix Roos b188fc6b6d Merge branch 'main' into sampler-refactoring 2024-09-14 10:38:27 +02:00
Martín Rodríguez bfb22228cc Add a search bar to the REPL Reference tab (#1165)
* Add a search bar to the REPL Reference tab
Simple text input that sticks to the top.
Filters by just checking if the search term is in the entry name or synonyms, if available.
2024-09-13 20:59:23 +01:00
Edward Betts 9714d88d61 Correct spelling mistakes (#1183) 2024-09-13 20:58:47 +01:00
Alex McLean bdddb91510 add seqPLoop, ref #31 (#1182) 2024-09-07 22:41:29 +01:00
Jade (Rose) Rowland 07bcaa65a9 create meta params 2024-09-06 00:16:30 -04:00
Jade (Rose) Rowland 5bf1805534 Merge pull request #1180 from daslyfe/phaser_controls
make phaser control match superdirt
2024-09-04 23:52:50 -04:00
Jade (Rose) Rowland 202685e8c4 update test snapshot 2024-09-04 23:50:48 -04:00
Jade (Rose) Rowland aba633e16e fix 2024-09-04 23:42:38 -04:00
Jade (Rose) Rowland da45ce689c fixed formatting 2024-09-04 23:36:20 -04:00
Jade (Rose) Rowland acea7e435e Merge pull request #1179 from tidalcycles/revert-1178-consistent_phaser_controls
Revert "Make phaser control consistent with superdirt"
2024-09-04 23:33:41 -04:00
Jade (Rose) Rowland cb4810cbbc Revert "Make phaser control consistent with superdirt" 2024-09-04 23:32:35 -04:00
Jade (Rose) Rowland 5ec205b383 Merge pull request #1178 from daslyfe/consistent_phaser_controls
Make phaser control consistent with superdirt
2024-09-04 23:29:11 -04:00
Jade (Rose) Rowland ebc0b293a9 updated 2024-09-04 23:20:42 -04:00
Jade (Rose) Rowland 4608c321bd Merge pull request #1172 from daslyfe/speed-cps-fix
fix sample speed when using splice and fit with superdirt
2024-09-02 22:37:15 -04:00
Jade (Rose) Rowland c4243f41c6 spelling 2024-09-01 16:15:55 -04:00
Jade (Rose) Rowland d95b8bc667 Merge pull request #1160 from daslyfe/audio_target_selector
Create audio target selector for OSC/Superdirt
2024-09-01 10:02:24 -04:00
Alex McLean 263c13c36e Fixes fit so it works after a chop or slice (#1171)
* fix fit so it works after a chop or slice

* format
2024-09-01 14:03:47 +01:00
Jade (Rose) Rowland ddceb36613 prettier 2024-08-31 23:49:56 -04:00
Jade (Rose) Rowland ff8ec628be spelling 2024-08-31 23:43:07 -04:00
Jade (Rose) Rowland 11a07df120 add docs for OSC target 2024-08-31 23:38:23 -04:00
Jade (Rose) Rowland 8befa4382b Merge branch 'main' into audio_target_selector 2024-08-31 22:20:53 -04:00
Alex McLean 39006e4d71 polyJoin (#1168)
* create polymetric join - `polyJoin`
* add `poly` as an op alignment to support e.g. `"c e f [g a]".add.poly("0 12 7")`
2024-08-30 13:24:08 +01:00
Alex McLean 812ed6b60d snapshot 2024-08-23 16:12:28 +01:00
Alex McLean 8b6c8eb35d Add scramble and shuffle (#1167)
* add scramble and shuffle functions from Tidal
2024-08-23 16:05:10 +01:00
Jade (Rose) Rowland dcda9a5753 cleanup 2024-08-18 13:21:37 -04:00
Jade (Rose) Rowland d3733d4209 prettier 2024-08-18 13:07:41 -04:00
Jade (Rose) Rowland ef6af96947 Merge branch 'tidalcycles:main' into audio_target_selector 2024-08-18 12:39:28 -04:00
Jade (Rose) Rowland d86ff0a536 Merge pull request #1164 from daslyfe/improve_neocyclist_timing
Improve + simplify neocyclist timing
2024-08-18 12:22:20 -04:00
Jade (Rose) Rowland 2c9ea03fde prettier 2024-08-18 12:08:40 -04:00
Jade (Rose) Rowland 4eae366bee cleaning up 2024-08-18 12:05:03 -04:00
Jade (Rose) Rowland 117f7fa4b5 cleaning up 2024-08-17 17:26:46 -04:00
Jade (Rose) Rowland 73ee84a1fb working 2024-08-17 17:11:51 -04:00
Jade (Rose) Rowland 9c899d5308 testing 2024-08-17 15:31:21 -04:00
Jade (Rose) Rowland b60beaf508 prettier 2024-08-16 10:56:09 -04:00
Jade (Rose) Rowland 796f741a92 use performance clock for osc mode 2024-08-16 10:46:18 -04:00
Jade (Rose) Rowland 8da1d9fbf3 reset collator after n seconds 2024-08-16 00:44:18 -04:00
Jade (Rose) Rowland 5345986b5b Merge branch 'main' into audio_target_selector 2024-08-15 16:28:19 -04:00
Jade (Rose) Rowland 3945abd7e9 Merge pull request #1163 from daslyfe/cross_orgin_check
containerize/seperate out boolean checks for repl types/Repl logic into bespoke components.
2024-08-14 23:18:33 -04:00
Jade (Rose) Rowland 053e4ee54b Merge branch 'tidalcycles:main' into cross_orgin_check 2024-08-12 20:50:15 -04:00
Jade (Rose) Rowland 25d61a2e31 prettier 2024-08-12 20:38:29 -04:00
Jade (Rose) Rowland 5e6131af24 reorganized repl 2024-08-12 20:32:58 -04:00
Jade (Rose) Rowland 2899ee5aef testing: 2024-08-12 18:11:52 -04:00
Jade (Rose) Rowland 9d1674b13c Merge pull request #1162 from daslyfe/cors_pathname_issue
[CORS HOTFIX]
2024-08-12 12:57:21 -04:00
Jade (Rose) Rowland bfead58212 prettier 2024-08-12 12:56:04 -04:00
Jade (Rose) Rowland 926c893c4f check iframe 2024-08-12 12:54:05 -04:00
Jade (Rose) Rowland 3cd31e4841 fixed prettier 2024-08-11 12:50:14 -04:00
Jade (Rose) Rowland c2be2ed76a fixed osc clock 2024-08-11 12:40:38 -04:00
Jade (Rose) Rowland ac0dc7a8de remove desktop restriction 2024-08-11 11:04:20 -04:00
Jade (Rose) Rowland cba9169b2f update select dialog 2024-08-11 11:01:17 -04:00
Jade (Rose) Rowland e9a0a06b77 fixed dialog 2024-08-11 00:57:49 -04:00
Jade (Rose) Rowland d80c06cd55 fix confirm dialog 2024-08-11 00:35:20 -04:00
Jade (Rose) Rowland d850726910 Merge pull request #1157 from daslyfe/osc_clock_jitter
Fix OSC clock jitter
2024-08-08 23:11:28 -04:00
Jade (Rose) Rowland 1b01f41ffd fix test 2024-08-08 23:10:39 -04:00
Jade (Rose) Rowland ad00fa65f8 Merge branch 'tidalcycles:main' into osc_clock_jitter 2024-08-08 22:53:07 -04:00
Jade (Rose) Rowland 814babbf7a cleaning up 2024-08-08 22:52:06 -04:00
Jade (Rose) Rowland 28334ec94d rolling clock average 2024-08-08 20:55:49 -04:00
Jade (Rose) Rowland 7d2110d75f clock fail strategy 2024-08-08 20:35:12 -04:00
Jade (Rose) Rowland 94ccfa631e offset calc 2024-08-08 17:25:45 -04:00
Jack Armitage c7c56a1357 fix 2024-08-06 12:25:52 +00:00
Jack Armitage d40e4dac2f pickRestart example 2024-08-03 18:48:30 +00:00
Jade (Rose) Rowland 686247f0cb Merge branch 'main' of github.com:daslyfe/strudel 2024-07-28 13:33:57 -04:00
Felix Roos dffeac41af Merge remote-tracking branch 'origin/main' into bettern-zen-spacing 2024-07-27 15:16:14 +02:00
Alex McLean 6e496b7a4a Fix loop at tactus (#1145)
* fix loopAt tactus
2024-07-27 11:02:38 +02:00
Jade (Rose) Rowland 71e843e1a3 Merge pull request #1130 from daslyfe/phase_vocode
Phase vocode
2024-07-25 22:37:34 -04:00
Jade (Rose) Rowland 61ba33043e Merge pull request #1132 from daslyfe/udels
Udels (MultiFrame Strudel) Revisited
2024-07-25 22:37:17 -04:00
Felix Roos 943d78f268 better spacing in zen mode 2024-07-25 16:44:02 +02:00
Felix Roos 5ac59e95dd handle midin device not found error 2024-07-25 16:07:58 +02:00
Felix Roos 03bbc0bc27 hotfix: remove log 2024-07-25 09:19:41 +02:00
Alex McLean 4c3457438d Fix tactus marking in mininotation (#1144)
* more tactus tweaks
* format
2024-07-24 10:40:28 +01:00
Jade (Rose) Rowland 85bccdcc19 Merge branch 'main' of github.com:daslyfe/strudel 2024-07-13 13:17:28 -04:00
Jade (Rose) Rowland 94c07b6b7a prettier 2024-07-10 19:11:06 -04:00
Jade (Rose) Rowland 42000a7628 push code to window on aftereval 2024-07-09 22:48:14 -04:00
Felix Roos f514cd85b1 Merge pull request #1136 from tidalcycles/cm-commands
export comment commands
2024-06-25 18:13:27 +02:00
Felix Roos d53929cf68 export comment commands 2024-06-25 10:00:26 +02:00
Alex McLean a4bd0ae100 Chop chop (#1078)
* failing test

* support chopping chops
2024-06-24 17:19:22 +01:00
Alex McLean 283a071c86 Fix bug in Fraction.lcm (#1133)
* bugfix lcm
2024-06-18 22:58:08 +01:00
Jade (Rose) Rowland b5fbcd108f cleanup 2024-06-17 23:41:34 -04:00
Jade (Rose) Rowland 6350cfdb9c Merge pull request #1129 from mindofmatthew/worker-path-in-modules
Fix clock worker dependency path in module builds
2024-06-17 23:32:12 -04:00
Jade (Rose) Rowland 5e9da78e64 fixed window: 2024-06-14 20:42:39 -04:00
Jade (Rose) Rowland f3b83b41f5 prettier 2024-06-14 15:40:37 -04:00
Jade (Rose) Rowland 1d95da7a3a fixed build window issue: 2024-06-14 15:28:24 -04:00
Jade (Rose) Rowland 2a2ddf205a need to fix window message 2024-06-14 14:26:23 -04:00
Jade (Rose) Rowland 6c9e0aaa1a added udels editor and header 2024-06-14 01:24:08 -04:00
Jade (Rose) Rowland d23038d386 Merge branch 'tidalcycles:main' into udels 2024-06-13 22:52:06 -04:00
Jade (Rose) Rowland 248f67d1ab prettier 2024-06-12 10:28:12 -04:00
Jade (Rose) Rowland f3fa1483d7 fix pnpm lock 2024-06-12 01:14:59 -04:00
Jade (Rose) Rowland 11102adb75 unessecary import 2024-06-12 01:14:20 -04:00
Jade (Rose) Rowland 9f958abb08 fixed offset time 2024-06-12 01:11:58 -04:00
Matthew Kaney b72e71b0bd Update base path build setting for core and repl packages 2024-06-11 10:02:57 -04:00
Jade (Rose) Rowland 065b24a2ee working 2024-06-10 23:51:38 -04:00
Matthew Kaney 758b94776d Configure base url for @strudel/web package 2024-06-10 19:10:17 -04:00
Jade (Rose) Rowland e6f3b1f1ef copied files 2024-06-10 00:06:35 -04:00
Jade (Rose) Rowland e62d5759e7 revert 2024-06-10 00:00:43 -04:00
Felix Roos c403a92542 Merge pull request #1126 from tidalcycles/labeled-statements-doc
Labeled statements doc
2024-06-04 00:26:48 +02:00
Felix Roos 269d6dde31 remove stack from workshop 2024-06-04 00:22:49 +02:00
Felix Roos 666c695c63 don't use labeled statements in example for now 2024-06-04 00:10:46 +02:00
Felix Roos 771eb31d3f use labels instead of stacks 2024-06-04 00:05:51 +02:00
Felix Roos c05bbf3422 don't clear hydra in minirepl for now 2024-06-03 23:53:08 +02:00
Felix Roos 364f511a7c Merge pull request #1125 from tidalcycles/viz-doc
doc: visual functions + refactor onPaint
2024-06-03 22:40:21 +02:00
Felix Roos 300022d8ac no custom line height 2024-06-03 20:34:51 +02:00
Felix Roos 357fbf8c82 snapshot 2024-06-02 23:14:32 +02:00
Felix Roos 1acb675f5a doc: visual functions 2024-06-02 23:12:31 +02:00
Jade (Rose) Rowland ab1086318c Merge pull request #1124 from daslyfe/bug_sampledb
Fix indexDB failing with large amount of files
2024-06-02 12:36:29 -04:00
Jade (Rose) Rowland 8c73ad088a support aac 2024-06-02 12:32:54 -04:00
Felix Roos 25981092b1 snapshot 2024-06-02 14:11:52 +02:00
Felix Roos e6b55baed0 fix: step based naming 2024-06-02 14:08:56 +02:00
Felix Roos 6adf1ff977 snapshot 2024-06-02 14:03:30 +02:00
Felix Roos b6e277f1ea use inline scope in examples 2024-06-02 14:03:05 +02:00
Felix Roos d0510f862a clear hydra in mini repl 2024-06-02 14:02:39 +02:00
Felix Roos 4a87df6426 small example tweaks 2024-06-02 14:00:47 +02:00
Felix Roos bf90867685 fix: mini repl punchcard + claviature flags 2024-06-02 10:49:07 +02:00
Felix Roos 3dc5fd0e7e fix: editPattern bug 2024-06-02 10:48:20 +02:00
Felix Roos a7650f1af3 add autodraw flag + scope 2024-06-02 03:08:16 +02:00
Felix Roos aadb2fee84 support onload inline viz with autodraw 2024-06-02 03:05:26 +02:00
Felix Roos bf391ad6ce simplify: painters are now handled by Drawer 2024-06-02 03:03:36 +02:00
Felix Roos faba4a4e4e implement onPaint with pattern state 2024-06-02 03:00:57 +02:00
Felix Roos 94e411aa7a add beforeStart callback 2024-06-02 02:34:33 +02:00
Jade (Rose) Rowland ffa21ddf0d remove console statement 2024-06-01 19:19:47 -04:00
Jade (Rose) Rowland 6b2d080afc remove unessecary change 2024-06-01 19:17:00 -04:00
Jade (Rose) Rowland 321a888921 add catch 2024-06-01 12:42:07 -04:00
Jade (Rose) Rowland bf7fe2bf75 fixed indexdb 2024-06-01 12:39:45 -04:00
Jade (Rose) Rowland 1fd9ba5dbf convert to blob 2024-06-01 12:11:28 -04:00
Jade (Rose) Rowland 1fca6f25b8 faster upload 2024-06-01 11:35:24 -04:00
Felix Roos 0e6a17fc77 start visual feedback article 2024-06-01 15:42:09 +02:00
Felix Roos 375c68775c make sure draw logic works with multiple repls 2024-06-01 15:41:55 +02:00
Felix Roos 827993a8c9 chore: simplify publish docs 2024-05-31 23:29:38 +02:00
Jade (Rose) Rowland e29876e7f6 debugging 2024-05-31 13:57:02 -04:00
Felix Roos 69a5d62027 rename "samples" to "bank" 2024-05-17 09:42:03 +02:00
Felix Roos 226cf356d6 fix: lint 2024-05-16 11:05:38 +02:00
Felix Roos 73d49f152a refactor sampler:
- delegate getSampleBufferSource logic to getSampleInfo + getSampleBuffer
- move buffer logic up from onTriggerSample to those functions
2024-05-16 10:52:54 +02:00
Felix Roos 83e46037d2 typo 2024-05-16 08:59:07 +02:00
Felix Roos 95ae5191ce begin "understanding voicings" 2024-05-16 03:36:35 +02:00
Jade (Rose) Rowland 5ca9afa9e8 restored 2024-04-15 19:53:12 -04:00
Felix Roos cdba9efb59 add filter + filterWhen + within 2024-04-01 03:17:15 +02:00
238 changed files with 30012 additions and 21045 deletions
-25
View File
@@ -1,25 +0,0 @@
krill-parser.js
krill.pegjs
.eslintrc.json
server.js
tidal-sniffer.js
*.jsx
tunejs.js
out/**
postcss.config.js
postcss.config.cjs
tailwind.config.js
tailwind.config.cjs
vite.config.js
/**/dist/**/*
!**/*.mjs
**/*.tsx
**/*.ts
**/*.json
**/dev-dist
**/dist
/src-tauri/target/**/*
reverbGen.mjs
hydra.mjs
jsdoc-synonyms.js
packages/hs2js/src/hs2js.mjs
+2 -1
View File
@@ -14,5 +14,6 @@
"rules": { "rules": {
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }], "no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }],
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}] "import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
} },
"files": ["**/*.mjs", "**/*.js"]
} }
+5 -5
View File
@@ -22,10 +22,10 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v4
with: with:
version: 8.11.0 version: 9.12.2
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: "pnpm" cache: "pnpm"
@@ -39,11 +39,11 @@ jobs:
uses: actions/configure-pages@v2 uses: actions/configure-pages@v2
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v1 uses: actions/upload-pages-artifact@v3
with: with:
# Upload entire repository # Upload entire repository
path: "./website/dist" path: "./website/dist"
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v1 uses: actions/deploy-pages@v4
+3 -3
View File
@@ -11,10 +11,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v4
with: with:
version: 8.11.0 version: 9.12.2
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'
+2
View File
@@ -11,3 +11,5 @@ pnpm-lock.yaml
pnpm-workspace.yaml pnpm-workspace.yaml
**/dev-dist **/dev-dist
website/.astro website/.astro
!tidal-drum-machines.json
!tidal-drum-machines-alias.json
+12 -6
View File
@@ -130,20 +130,26 @@ npx lerna version --no-private
# publish all packages inside /packages using pnpm! don't use lerna to publish!! # publish all packages inside /packages using pnpm! don't use lerna to publish!!
pnpm --filter "./packages/**" publish --dry-run pnpm --filter "./packages/**" publish --dry-run
# the last command was only a dry-run, make sure everything looks ok, if yes, run the same command without flag # the last command was only a dry-run. if everything looks ok, run this:
pnpm --filter "./packages/**" publish --access public
``` ```
To manually publish a single package, increase the version in the `package.json`, then run `pnpm publish`. To manually publish a single package, increase the version in the `package.json`, then run `pnpm publish`.
Important: Always publish with `pnpm`, as `npm` does not support overriding main files in `publishConfig`, which is done in all the packages. Important: Always publish with `pnpm`, as `npm` does not support overriding main files in `publishConfig`, which is done in all the packages.
### New Packages
To add a new package, you have to publish it manually the first time, using:
## useful commands
```sh ```sh
cd packages/<package-name> && pnpm publish --access public #regenerate the test snapshots (ex: when updating or creating new pattern functions)
``` pnpm snapshot
#start the OSC server
pnpm run osc
#build the standalone version
pnpm tauri build
```
## Have Fun ## Have Fun
Remember to have fun, and that this project is driven by the passion of volunteers! Remember to have fun, and that this project is driven by the passion of volunteers!
+11 -1
View File
@@ -1,6 +1,6 @@
# strudel # strudel
[![Strudel test status](https://github.com/tidalcycles/strudel/actions/workflows/test.yml/badge.svg)](https://github.com/tidalcycles/strudel/actions) [![Strudel test status](https://github.com/tidalcycles/strudel/actions/workflows/test.yml/badge.svg)](https://github.com/tidalcycles/strudel/actions) [![DOI](https://zenodo.org/badge/450927247.svg)](https://doi.org/10.5281/zenodo.6659278)
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. 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.
@@ -14,8 +14,14 @@ An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using w
After cloning the project, you can run the REPL locally: After cloning the project, you can run the REPL locally:
1. Install [Node.js](https://nodejs.org/)
2. Install [pnpm](https://pnpm.io/installation)
3. Install dependencies by running the following command:
```bash ```bash
pnpm i pnpm i
```
4. Run the development server:
```bash
pnpm dev pnpm dev
``` ```
@@ -25,6 +31,10 @@ This project is organized into many [packages](./packages), which are also avail
Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start). Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start).
You will need to abide by the terms of the [GNU Affero Public Licence v3](LICENSE.md). As such, Strudel code can only be shared within free/open source projects under the same license -- see the license for details.
Licensing info for the default sound banks can be found over on the [dough-samples](https://github.com/felixroos/dough-samples/blob/main/README.md) repository.
## Contributing ## Contributing
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md). There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
+6 -6
View File
@@ -1,22 +1,22 @@
import { queryCode, testCycles } from '../test/runtime.mjs'; import { queryCode, testCycles } from '../test/runtime.mjs';
import * as tunes from '../website/src/repl/tunes.mjs'; import * as tunes from '../website/src/repl/tunes.mjs';
import { describe, bench } from 'vitest'; import { describe, bench } from 'vitest';
import { calculateTactus } from '../packages/core/index.mjs'; import { calculateSteps } from '../packages/core/index.mjs';
const tuneKeys = Object.keys(tunes); const tuneKeys = Object.keys(tunes);
describe('renders tunes', () => { describe('renders tunes', () => {
tuneKeys.forEach((key) => { tuneKeys.forEach((key) => {
describe(key, () => { describe(key, () => {
calculateTactus(true); calculateSteps(true);
bench(`+tactus`, async () => { bench(`+steps`, async () => {
await queryCode(tunes[key], testCycles[key] || 1); await queryCode(tunes[key], testCycles[key] || 1);
}); });
calculateTactus(false); calculateSteps(false);
bench(`-tactus`, async () => { bench(`-steps`, async () => {
await queryCode(tunes[key], testCycles[key] || 1); await queryCode(tunes[key], testCycles[key] || 1);
}); });
calculateTactus(true); calculateSteps(true);
}); });
}); });
}); });
+86
View File
@@ -0,0 +1,86 @@
import _import from 'eslint-plugin-import';
import { fixupPluginRules } from '@eslint/compat';
import globals from 'globals';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default [
{
ignores: [
'**/krill-parser.js',
'**/krill.pegjs',
'**/.eslintrc.json',
'**/server.js',
'**/tidal-sniffer.js',
'**/*.jsx',
'**/tunejs.js',
'out/**/*',
'**/postcss.config.js',
'**/postcss.config.cjs',
'**/tailwind.config.js',
'**/tailwind.config.cjs',
'**/vite.config.js',
'**/dist/**/*',
'!**/*.mjs',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/dev-dist',
'**/dist',
'src-tauri/target/**/*',
'**/reverbGen.mjs',
'**/hydra.mjs',
'**/jsdoc-synonyms.js',
'packages/hs2js/src/hs2js.mjs',
'**/samples',
],
},
...compat.extends('eslint:recommended').map((config) => ({
...config,
files: ['**/*.mjs', '**/*.js'],
})),
{
files: ['**/*.mjs', '**/*.js'],
plugins: {
import: fixupPluginRules(_import),
},
languageOptions: {
globals: {
...globals.node,
...globals.browser,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
'no-unused-vars': [
'warn',
{
destructuredArrayIgnorePattern: '.',
ignoreRestSiblings: false,
},
],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
},
},
];
+1 -1
View File
@@ -9,7 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^6.0.11"
}, },
"dependencies": { "dependencies": {
"@strudel/codemirror": "workspace:*", "@strudel/codemirror": "workspace:*",
+1 -1
View File
@@ -10,7 +10,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^6.0.11"
}, },
"dependencies": { "dependencies": {
"@strudel/web": "workspace:*" "@strudel/web": "workspace:*"
+1 -1
View File
@@ -10,7 +10,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^6.0.11"
}, },
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel/core": "workspace:*",
+1 -1
View File
@@ -12,6 +12,6 @@
"superdough": "workspace:*" "superdough": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^6.0.11"
} }
} }
+1 -1
View File
@@ -31,6 +31,6 @@
"hs2js": "workspace:*" "hs2js": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.8" "vite": "^6.0.11"
} }
} }
+16 -13
View File
@@ -21,7 +21,7 @@
"osc": "cd packages/osc && npm run server", "osc": "cd packages/osc && npm run server",
"jsdoc": "jsdoc packages/ -c jsdoc/jsdoc.config.json", "jsdoc": "jsdoc packages/ -c jsdoc/jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc/jsdoc.config.json", "jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc/jsdoc.config.json",
"lint": "eslint . --ext mjs,js --quiet", "lint": "eslint . --quiet",
"codeformat": "prettier --write .", "codeformat": "prettier --write .",
"format-check": "prettier --check .", "format-check": "prettier --check .",
"report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json", "report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json",
@@ -55,19 +55,22 @@
"@strudel/xen": "workspace:*" "@strudel/xen": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.5.9", "@eslint/compat": "^1.2.5",
"@vitest/ui": "^1.1.0", "@eslint/eslintrc": "^3.2.0",
"acorn": "^8.11.3", "@eslint/js": "^9.19.0",
"dependency-tree": "^10.0.9", "@tauri-apps/cli": "^2.2.7",
"eslint": "^8.56.0", "@vitest/coverage-v8": "3.0.4",
"eslint-plugin-import": "^2.29.1", "@vitest/ui": "^3.0.4",
"acorn": "^8.14.0",
"dependency-tree": "^11.0.1",
"eslint": "^9.19.0",
"eslint-plugin-import": "^2.31.0",
"events": "^3.3.0", "events": "^3.3.0",
"jsdoc": "^4.0.2", "globals": "^15.14.0",
"jsdoc": "^4.0.4",
"jsdoc-json": "^2.0.2", "jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^8.0.0", "lerna": "^8.1.9",
"lerna": "^8.0.1", "prettier": "^3.4.2",
"prettier": "^3.1.1", "vitest": "^3.0.4"
"rollup-plugin-visualizer": "^5.12.0",
"vitest": "^1.1.0"
} }
} }
+32 -32
View File
@@ -1,4 +1,5 @@
import { closeBrackets } from '@codemirror/autocomplete'; import { closeBrackets } from '@codemirror/autocomplete';
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
// import { search, highlightSelectionMatches } from '@codemirror/search'; // import { search, highlightSelectionMatches } from '@codemirror/search';
import { history } from '@codemirror/commands'; import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
@@ -12,7 +13,7 @@ import {
lineNumbers, lineNumbers,
drawSelection, drawSelection,
} from '@codemirror/view'; } from '@codemirror/view';
import { Pattern, repl } from '@strudel/core'; import { repl, registerControl } from '@strudel/core';
import { Drawer, cleanupDraw } from '@strudel/draw'; import { Drawer, cleanupDraw } from '@strudel/draw';
import { isAutoCompletionEnabled } from './autocomplete.mjs'; import { isAutoCompletionEnabled } from './autocomplete.mjs';
import { isTooltipEnabled } from './tooltip.mjs'; import { isTooltipEnabled } from './tooltip.mjs';
@@ -66,6 +67,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
const initialSettings = Object.keys(compartments).map((key) => const initialSettings = Object.keys(compartments).map((key) =>
compartments[key].of(extensions[key](parseBooleans(settings[key]))), compartments[key].of(extensions[key](parseBooleans(settings[key]))),
); );
initTheme(settings.theme); initTheme(settings.theme);
let state = EditorState.create({ let state = EditorState.create({
doc: initialCode, doc: initialCode,
@@ -98,10 +100,8 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
}, },
{ {
key: 'Alt-.', key: 'Alt-.',
run: (_, e) => { preventDefault: true,
e.preventDefault(); run: () => onStop?.(),
onStop?.();
},
}, },
/* { /* {
key: 'Ctrl-Shift-.', key: 'Ctrl-Shift-.',
@@ -134,57 +134,50 @@ export class StrudelMirror {
autodraw, autodraw,
prebake, prebake,
bgFill = true, bgFill = true,
solo = true,
...replOptions ...replOptions
} = options; } = options;
this.code = initialCode; this.code = initialCode;
this.root = root; this.root = root;
this.miniLocations = []; this.miniLocations = [];
this.widgets = []; this.widgets = [];
this.painters = [];
this.drawTime = drawTime; this.drawTime = drawTime;
this.drawContext = drawContext; this.drawContext = drawContext;
this.onDraw = onDraw || this.draw; this.onDraw = onDraw || this.draw;
this.id = id || s4(); this.id = id || s4();
this.solo = solo;
this.drawer = new Drawer((haps, time) => { this.drawer = new Drawer((haps, time, _, painters) => {
const currentFrame = haps.filter((hap) => hap.isActive(time)); const currentFrame = haps.filter((hap) => hap.isActive(time));
this.highlight(currentFrame, time); this.highlight(currentFrame, time);
this.onDraw(haps, time, this.painters); this.onDraw(haps, time, painters);
}, drawTime); }, drawTime);
this.prebaked = prebake(); this.prebaked = prebake();
autodraw && this.drawFirstFrame(); autodraw && this.drawFirstFrame();
this.repl = repl({ this.repl = repl({
...replOptions, ...replOptions,
id,
onToggle: (started) => { onToggle: (started) => {
replOptions?.onToggle?.(started); replOptions?.onToggle?.(started);
if (started) { if (started) {
this.adjustDrawTime();
this.drawer.start(this.repl.scheduler); this.drawer.start(this.repl.scheduler);
if (this.solo) {
// stop other repls when this one is started // stop other repls when this one is started
document.dispatchEvent( document.dispatchEvent(
new CustomEvent('start-repl', { new CustomEvent('start-repl', {
detail: this.id, detail: this.id,
}), }),
); );
}
} else { } else {
this.drawer.stop(); this.drawer.stop();
updateMiniLocations(this.editor, []); updateMiniLocations(this.editor, []);
cleanupDraw(false); cleanupDraw(true, id);
} }
}, },
beforeEval: async () => { beforeEval: async () => {
cleanupDraw(); cleanupDraw(true, id);
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 this.prebaked;
await replOptions?.beforeEval?.(); await replOptions?.beforeEval?.();
}, },
@@ -198,8 +191,11 @@ export class StrudelMirror {
updateWidgets(this.editor, widgets); updateWidgets(this.editor, widgets);
updateMiniLocations(this.editor, this.miniLocations); updateMiniLocations(this.editor, this.miniLocations);
replOptions?.afterEval?.(options); replOptions?.afterEval?.(options);
this.adjustDrawTime(); // if no painters are set (.onPaint was not called), then we only need the present moment (for highlighting)
this.drawer.invalidate(); const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
this.drawer.setDrawTime(drawTime);
// invalidate drawer after we've set the appropriate drawTime
this.drawer.invalidate(this.repl.scheduler);
}, },
}); });
this.editor = initEditor({ this.editor = initEditor({
@@ -228,19 +224,14 @@ export class StrudelMirror {
// stop this repl when another repl is started // stop this repl when another repl is started
this.onStartRepl = (e) => { this.onStartRepl = (e) => {
if (e.detail !== this.id) { if (this.solo && e.detail !== this.id) {
this.stop(); this.stop();
} }
}; };
document.addEventListener('start-repl', this.onStartRepl); document.addEventListener('start-repl', this.onStartRepl);
} }
// adjusts draw time depending on if there are painters draw(haps, time, painters) {
adjustDrawTime() { painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
// 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() { async drawFirstFrame() {
if (!this.onDraw) { if (!this.onDraw) {
@@ -252,7 +243,7 @@ export class StrudelMirror {
await this.repl.evaluate(this.code, false); await this.repl.evaluate(this.code, false);
this.drawer.invalidate(this.repl.scheduler, -0.001); this.drawer.invalidate(this.repl.scheduler, -0.001);
// draw at -0.001 to avoid haps at 0 to be visualized as active // 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.drawer.painters);
} catch (err) { } catch (err) {
console.warn('first frame could not be painted'); console.warn('first frame could not be painted');
} }
@@ -368,3 +359,12 @@ function s4() {
.toString(16) .toString(16)
.substring(1); .substring(1);
} }
/**
* Overrides the css of highlighted events. Make sure to use single quotes!
* @name markcss
* @example
* note("c a f e")
* .markcss('text-decoration:underline')
*/
export const markcss = registerControl('markcss');
+2 -1
View File
@@ -93,6 +93,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
if (haps.has(id)) { if (haps.has(id)) {
const hap = haps.get(id); const hap = haps.get(id);
const color = hap.value?.color ?? 'var(--foreground)'; const color = hap.value?.color ?? 'var(--foreground)';
const style = hap.value?.markcss || `outline: solid 2px ${color}`;
// Get explicit channels for color values // Get explicit channels for color values
/* /*
const swatch = document.createElement('div'); const swatch = document.createElement('div');
@@ -114,7 +115,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
to, to,
Decoration.mark({ Decoration.mark({
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` }, // attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
attributes: { style: `outline: solid 2px ${color}` }, attributes: { style },
}), }),
); );
} }
+13 -15
View File
@@ -32,26 +32,24 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.11.1", "@codemirror/autocomplete": "^6.18.4",
"@codemirror/commands": "^6.3.3", "@codemirror/commands": "^6.8.0",
"@codemirror/lang-javascript": "^6.2.1", "@codemirror/lang-javascript": "^6.2.2",
"@codemirror/language": "^6.10.0", "@codemirror/language": "^6.10.8",
"@codemirror/search": "^6.5.5", "@codemirror/search": "^6.5.8",
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.5.1",
"@codemirror/view": "^6.23.0", "@codemirror/view": "^6.36.2",
"@lezer/highlight": "^1.2.0", "@lezer/highlight": "^1.2.1",
"@nanostores/persistent": "^0.9.1", "@nanostores/persistent": "^0.10.2",
"@replit/codemirror-emacs": "^6.0.1", "@replit/codemirror-emacs": "^6.1.0",
"@replit/codemirror-vim": "^6.1.0", "@replit/codemirror-vim": "^6.2.1",
"@replit/codemirror-vscode-keymap": "^6.0.2", "@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel/draw": "workspace:*", "@strudel/draw": "workspace:*",
"@strudel/transpiler": "workspace:*", "@strudel/transpiler": "workspace:*",
"@uiw/codemirror-themes": "^4.21.21", "nanostores": "^0.11.3"
"@uiw/codemirror-themes-all": "^4.21.21",
"nanostores": "^0.9.5"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^6.0.11"
} }
} }
+81 -396
View File
@@ -1,42 +1,42 @@
import { import strudelTheme, { settings as strudelThemeSettings } from './themes/strudel-theme.mjs';
abcdef, import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen.mjs';
androidstudio, import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen.mjs';
atomone, import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen.mjs';
aura, import teletext, { settings as teletextSettings } from './themes/teletext.mjs';
bespin, import algoboy, { settings as algoboySettings } from './themes/algoboy.mjs';
darcula, import CutiePi, { settings as CutiePiSettings } from './themes/CutiePi.mjs';
dracula, import terminal, { settings as terminalSettings } from './themes/terminal.mjs';
duotoneDark, import abcdef, { settings as abcdefSettings } from './themes/abcdef.mjs';
eclipse, import androidstudio, { settings as androidstudioSettings } from './themes/androidstudio.mjs';
githubDark, import atomone, { settings as atomOneSettings } from './themes/atomone.mjs';
gruvboxDark, import aura, { settings as auraSettings } from './themes/aura.mjs';
materialDark, import bespin, { settings as bespinSettings } from './themes/bespin.mjs';
nord, import darcula, { settings as darculaSettings } from './themes/darcula.mjs';
okaidia, import dracula, { settings as draculaSettings } from './themes/dracula.mjs';
solarizedDark, import duotoneDark, { settings as duotoneDarkSettings } from './themes/duotoneDark.mjs';
sublime, import duotoneLight, { settings as duotoneLightSettings } from './themes/duotoneLight.mjs';
tokyoNight, import eclipse, { settings as eclipseSettings } from './themes/eclipse.mjs';
tokyoNightStorm, import githubDark, { settings as githubDarkSettings } from './themes/githubDark.mjs';
vscodeDark, import githubLight, { settings as githubLightSettings } from './themes/githubLight.mjs';
xcodeDark, import gruvboxDark, { settings as gruvboxDarkSettings } from './themes/gruvboxDark.mjs';
bbedit, import gruvboxLight, { settings as gruvboxLightSettings } from './themes/gruvboxLight.mjs';
duotoneLight, import materialDark, { settings as materialDarkSettings } from './themes/materialDark.mjs';
githubLight, import materialLight, { settings as materialLightSettings } from './themes/materialLight.mjs';
gruvboxLight, import nord, { settings as nordSettings } from './themes/nord.mjs';
materialLight, import monokai, { settings as monokaiSettings } from './themes/monokai.mjs';
noctisLilac, import solarizedDark, { settings as solarizedDarkSettings } from './themes/solarizedDark.mjs';
solarizedLight, import solarizedLight, { settings as solarizedLightSettings } from './themes/solarizedLight.mjs';
tokyoNightDay, import sublime, { settings as sublimeSettings } from './themes/sublime.mjs';
xcodeLight, import tokyoNight, { settings as tokyoNightSettings } from './themes/tokyoNight.mjs';
} from '@uiw/codemirror-themes-all'; import tokyoNightStorm, { settings as tokyoNightStormSettings } from './themes/tokioNightStorm.mjs';
import tokyoNightDay, { settings as tokyoNightDaySettings } from './themes/tokyoNightDay.mjs';
import vscodeDark, { settings as vscodeDarkSettings } from './themes/vscodeDark.mjs';
import vscodeLight, { settings as vscodeLightSettings } from './themes/vscodeLight.mjs';
// import xcodeDark, { settings as xcodeDarkSettings } from './themes/xcodeDark.mjs';
import xcodeLight, { settings as xcodeLightSettings } from './themes/xcodeLight.mjs';
import bbedit, { settings as bbeditSettings } from './themes/bbedit.mjs';
import noctisLilac, { settings as noctisLilacSettings } from './themes/noctisLilac.mjs';
import strudelTheme from './themes/strudel-theme';
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen';
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen';
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen';
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'; import { setTheme } from '@strudel/draw';
export const themes = { export const themes = {
@@ -46,395 +46,80 @@ export const themes = {
whitescreen, whitescreen,
teletext, teletext,
algoboy, algoboy,
terminal,
abcdef,
androidstudio,
atomone, atomone,
aura, aura,
bespin,
darcula, darcula,
dracula, dracula,
// todo: optimize
// bespin,
//abcdef,
androidstudio,
duotoneDark, duotoneDark,
eclipse,
githubDark, githubDark,
CutiePi,
gruvboxDark, gruvboxDark,
materialDark, materialDark,
nord, nord,
okaidia, monokai,
solarizedDark, solarizedDark,
sublime, sublime,
tokyoNight, tokyoNight,
tokyoNightStorm, tokyoNightStorm,
vscodeDark, vscodeDark,
xcodeDark, //xcodeDark,
// LIGHT
bbedit, bbedit,
duotoneLight, //duotoneLight,
eclipse,
githubLight, githubLight,
gruvboxLight, gruvboxLight,
materialLight, materialLight,
vscodeLight,
noctisLilac, noctisLilac,
solarizedLight, solarizedLight,
tokyoNightDay, tokyoNightDay,
xcodeLight, xcodeLight,
}; };
// lineBackground is background with 50% opacity, to make sure the selection below is visible
export const settings = { export const settings = {
strudelTheme: { strudelTheme: strudelThemeSettings,
background: '#222',
lineBackground: '#22222299',
foreground: '#fff',
// foreground: '#75baff',
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
},
bluescreen: bluescreenSettings, bluescreen: bluescreenSettings,
blackscreen: blackscreenSettings, blackscreen: blackscreenSettings,
whitescreen: whitescreenSettings, whitescreen: whitescreenSettings,
teletext: teletextSettings, teletext: teletextSettings,
algoboy: algoboySettings, algoboy: algoboySettings,
terminal: terminalSettings, terminal: terminalSettings,
abcdef: { abcdef: abcdefSettings,
background: '#0f0f0f', androidstudio: androidstudioSettings,
lineBackground: '#0f0f0f99', atomone: atomOneSettings,
foreground: '#defdef', aura: auraSettings,
caret: '#00FF00', bbedit: bbeditSettings,
selection: '#515151', bespin: bespinSettings,
selectionMatch: '#515151', darcula: darculaSettings,
gutterBackground: '#555', dracula: draculaSettings,
gutterForeground: '#FFFFFF', duotoneLight: duotoneLightSettings,
lineHighlight: '#314151', duotoneDark: duotoneDarkSettings,
}, eclipse: eclipseSettings,
androidstudio: { CutiePi: CutiePiSettings,
background: '#282b2e', githubLight: githubLightSettings,
lineBackground: '#282b2e99', githubDark: githubDarkSettings,
foreground: '#a9b7c6', gruvboxDark: gruvboxDarkSettings,
caret: '#00FF00', gruvboxLight: gruvboxLightSettings,
selection: '#343739', materialDark: materialDarkSettings,
selectionMatch: '#343739', materialLight: materialLightSettings,
lineHighlight: '#343739', noctisLilac: noctisLilacSettings,
}, nord: nordSettings,
atomone: { monokai: monokaiSettings,
background: '#272C35', solarizedLight: solarizedLightSettings,
lineBackground: '#272C3599', solarizedDark: solarizedDarkSettings,
foreground: '#9d9b97', sublime: sublimeSettings,
caret: '#797977', tokyoNight: tokyoNightSettings,
selection: '#ffffff30', tokyoNightStorm: tokyoNightStormSettings,
selectionMatch: '#2B323D', vscodeDark: vscodeDarkSettings,
gutterBackground: '#272C35', vscodeLight: vscodeLightSettings,
gutterForeground: '#465063', xcodeLight: xcodeLightSettings,
gutterBorder: 'transparent', //xcodeDark: xcodeDarkSettings,
lineHighlight: '#2B323D', tokyoNightDay: tokyoNightDaySettings,
},
aura: {
background: '#21202e',
lineBackground: '#21202e99',
foreground: '#edecee',
caret: '#a277ff',
selection: '#3d375e7f',
selectionMatch: '#3d375e7f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
},
bbedit: {
light: true,
background: '#FFFFFF',
lineBackground: '#FFFFFF99',
foreground: '#000000',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
gutterBackground: '#f5f5f5',
gutterForeground: '#4D4D4C',
gutterBorder: 'transparent',
lineHighlight: '#00000012',
},
bespin: {
background: '#28211c',
lineBackground: '#28211c99',
foreground: '#9d9b97',
caret: '#797977',
selection: '#36312e',
selectionMatch: '#4f382b',
gutterBackground: '#28211c',
gutterForeground: '#666666',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
darcula: {
background: '#2B2B2B',
lineBackground: '#2B2B2B99',
foreground: '#f8f8f2',
caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: 'rgba(255, 255, 255, 0.1)',
gutterForeground: '#999',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
dracula: {
background: '#282a36',
lineBackground: '#282a3699',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6D8A88',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
duotoneLight: {
light: true,
background: '#faf8f5',
lineBackground: '#faf8f599',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#EFEFEF',
},
duotoneDark: {
background: '#2a2734',
lineBackground: '#2a273499',
foreground: '#6c6783',
caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
},
eclipse: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#000',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#f7f7f7',
gutterForeground: '#999',
lineHighlight: '#e8f2ff',
gutterBorder: 'transparent',
},
githubLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
},
githubDark: {
background: '#0d1117',
lineBackground: '#0d111799',
foreground: '#c9d1d9',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
lineHighlight: '#36334280',
},
gruvboxDark: {
background: '#282828',
lineBackground: '#28282899',
foreground: '#ebdbb2',
caret: '#ebdbb2',
selection: '#bdae93',
selectionMatch: '#bdae93',
lineHighlight: '#3c3836',
gutterBackground: '#282828',
gutterForeground: '#7c6f64',
},
gruvboxLight: {
light: true,
background: '#fbf1c7',
lineBackground: '#fbf1c799',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#ebdbb2',
selectionMatch: '#bdae93',
lineHighlight: '#ebdbb2',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
},
materialDark: {
background: '#2e3235',
lineBackground: '#2e323599',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#2e3235',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#545b61',
},
materialLight: {
light: true,
background: '#FAFAFA',
lineBackground: '#FAFAFA99',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#FAFAFA',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
},
noctisLilac: {
light: true,
background: '#f2f1f8',
lineBackground: '#f2f1f899',
foreground: '#0c006b',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
gutterBackground: '#f2f1f8',
gutterForeground: '#0c006b70',
lineHighlight: '#e1def3',
},
nord: {
background: '#2e3440',
lineBackground: '#2e344099',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#3b4252',
selectionMatch: '#e5e9f0',
gutterBackground: '#2e3440',
gutterForeground: '#4c566a',
gutterActiveForeground: '#d8dee9',
lineHighlight: '#4c566a',
},
okaidia: {
background: '#272822',
lineBackground: '#27282299',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
solarizedLight: {
light: true,
background: '#fdf6e3',
lineBackground: '#fdf6e399',
foreground: '#657b83',
caret: '#586e75',
selection: '#dfd9c8',
selectionMatch: '#dfd9c8',
gutterBackground: '#00000010',
gutterForeground: '#657b83',
lineHighlight: '#dfd9c8',
},
solarizedDark: {
background: '#002b36',
lineBackground: '#002b3699',
foreground: '#93a1a1',
caret: '#839496',
selection: '#173541',
selectionMatch: '#aafe661a',
gutterBackground: '#00252f',
gutterForeground: '#839496',
lineHighlight: '#173541',
},
sublime: {
background: '#303841',
lineBackground: '#30384199',
foreground: '#FFFFFF',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
gutterBackground: '#303841',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
tokyoNightDay: {
light: true,
background: '#e1e2e7',
lineBackground: '#e1e2e799',
foreground: '#3760bf',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
gutterBackground: '#e1e2e7',
gutterForeground: '#3760bf',
gutterBorder: 'transparent',
lineHighlight: '#5f5faf11',
},
tokyoNightStorm: {
background: '#24283b',
lineBackground: '#24283b99',
foreground: '#7982a9',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#1f2335',
gutterBackground: '#24283b',
gutterForeground: '#7982a9',
gutterBorder: 'transparent',
lineHighlight: '#292e42',
},
tokyoNight: {
background: '#1a1b26',
lineBackground: '#1a1b2699',
foreground: '#787c99',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
gutterBackground: '#1a1b26',
gutterForeground: '#787c99',
gutterBorder: 'transparent',
lineHighlight: '#1e202e',
},
vscodeDark: {
background: '#1e1e1e',
lineBackground: '#1e1e1e99',
foreground: '#9cdcfe',
caret: '#c6c6c6',
selection: '#6199ff2f',
selectionMatch: '#72a1ff59',
lineHighlight: '#ffffff0f',
gutterBackground: '#1e1e1e',
gutterForeground: '#838383',
gutterActiveForeground: '#fff',
},
xcodeLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#3D3D3D',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#AFAFAF',
lineHighlight: '#EDF4FF',
},
xcodeDark: {
background: '#292A30',
lineBackground: '#292A3099',
foreground: '#CECFD0',
caret: '#fff',
selection: '#727377',
selectionMatch: '#727377',
lineHighlight: '#2F3239',
},
}; };
function getColors(str) { function getColors(str) {
+45
View File
@@ -0,0 +1,45 @@
/**
* @name Cutie Pi
* by Switch Angel
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
const deepPurple = '#5c019a';
const yellowPink = '#fbeffc';
const grey = '#272C35';
const pinkAccent = '#fee1ff';
const lightGrey = '#465063';
const bratGreen = '#9acd3f';
const lighterGrey = '#97a1b7';
const pink = '#f6a6fd';
export const settings = {
background: 'white',
lineBackground: 'transparent',
foreground: deepPurple,
caret: '#797977',
selection: yellowPink,
selectionMatch: '#2B323D',
gutterBackground: grey,
gutterForeground: lightGrey,
gutterBorder: 'transparent',
lineHighlight: pinkAccent,
};
export default createTheme({
theme: 'light',
settings,
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: deepPurple,
},
{ tag: [t.tagName, t.heading], color: settings.foreground },
{ tag: t.comment, color: lighterGrey },
{ tag: [t.variableName, t.propertyName, t.labelName], color: pink },
{ tag: [t.attributeName, t.number], color: '#d19a66' },
{ tag: t.className, color: grey },
{ tag: t.keyword, color: deepPurple },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: bratGreen },
],
});
+55
View File
@@ -0,0 +1,55 @@
/**
* @name abcdef
* @author codemirror.net
* https://codemirror.net/5/theme/abcdef.css
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#0f0f0f',
lineBackground: '#0f0f0f99',
foreground: '#defdef',
caret: '#00FF00',
selection: '#515151',
selectionMatch: '#515151',
gutterBackground: '#555',
gutterForeground: '#FFFFFF',
lineHighlight: '#314151',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#0f0f0f',
foreground: '#defdef',
caret: '#00FF00',
selection: '#515151',
selectionMatch: '#515151',
// gutterBackground: '#555',
gutterBackground: 'transparent',
/* gutterForeground: '#FFFFFF', */
gutterForeground: '#7a7b7c',
lineHighlight: '#0a6bcb3d',
},
styles: [
{ tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: 'darkgoldenrod', fontWeight: 'bold' },
{ tag: t.atom, color: '#77F' },
{ tag: t.comment, color: '#7a7b7c', fontStyle: 'italic' },
{ tag: t.number, color: 'violet' },
{ tag: t.definition(t.variableName), color: '#fffabc' },
{ tag: t.variableName, color: '#abcdef' },
{ tag: t.function(t.variableName), color: '#fffabc' },
{ tag: t.typeName, color: '#FFDD44' },
{ tag: t.tagName, color: '#def' },
{ tag: t.string, color: '#2b4' },
{ tag: t.meta, color: '#C9F' },
// { tag: t.qualifier, color: '#FFF700' },
// { tag: t.builtin, color: '#30aabc' },
{ tag: t.bracket, color: '#8a8a8a' },
{ tag: t.attributeName, color: '#DDFF00' },
{ tag: t.heading, color: 'aquamarine', fontWeight: 'bold' },
{ tag: t.link, color: 'blueviolet', fontWeight: 'bold' },
],
});
+50 -32
View File
@@ -1,42 +1,60 @@
import { tags as t } from '@lezer/highlight'; import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from './theme-helper.mjs';
const palettes = {
// https://www.deviantart.com/advancedfan2020/art/Game-Boy-Palette-Set-Color-HEX-Part-09-920495662
'Central Florida A': ['#FFF630', '#B3AC22', '#666213', '#191905'],
'Central Florida B': ['#38CEBA', '#279082', '#16524A', '#061513'],
'Central Florida C': ['#FF8836', '#B35F26', '#663616', '#190E05'],
'Central Florida D': ['#E07070', '#9D4E4E', '#5A2D2D', '#160B0B'],
'Central Florida E': ['#7AA4CB', '#55738E', '#314251', '#0C1014'],
'Feminine Energy A': ['#DC5686', '#9A415E', '#582536', '#16090D'],
'Feminine Energy B': ['#D0463C', '#92312A', '#531c18', '#150706'],
'Feminine Energy C': ['#D86918', '#974A11', '#562A0A', '#160A02'],
'Feminine Energy D': ['#EFC54F', '#A78A36', '#604F20', '#181408'],
'Feminine Energy E': ['#866399', '#5e456b', '#36283d', '#0d0a0f'],
'Sour Watermelon A': ['#993366', '#6B2447', '#3D1429', '#0F050A'],
'Sour Watermelon B': ['#996666', '#6B4747', '#3D2929', '#0F0A0A'],
'Sour Watermelon C': ['#999966', '#686B47', '#3d3d29', '#0f0f0A'],
'Sour Watermelon D': ['#99cc66', '#6b8f47', '#3d5229', '#0f140a'],
'Sour Watermelon E': ['#99ff66', '#6bb347', '#3d6629', '#0f190a'],
//https://www.deviantart.com/advancedfan2020/art/Game-Boy-Palette-Set-Color-HEX-Part-02-920073260
'Peri Peaceful A': ['#909BE9', '#656DA3', '#3A3E5D', '#0e0f17'],
'Peri Peaceful B': ['#68628d', '#494563', '#2a2738', '#0a0a0e'], // pretty dim
'Peri Peaceful E': ['#b5a0a9', '#7f7076', '#484044', '#121011'],
'Hichem Palette B': ['#4fa3a5', '#377273', '#204142', '#081010'],
'Hichem Palette C': ['#Fe6f9b', '#b24e6d', '#662c3e', '#190b0f'],
'Hichem Palette D': ['#ffbb5a', '#b3833f', '#664b24', '#191309'],
'JSR2 A': ['#E0EFC0', '#9da786', '#5a604d', '#161813'],
};
const palette = palettes['Sour Watermelon B'];
export const settings = { export const settings = {
background: '#9bbc0f', background: palette[3],
foreground: '#0f380f', // whats that? foreground: palette[1],
caret: '#0f380f', caret: palette[0],
selection: '#306230', selection: palette[0],
selectionMatch: '#ffffff26', selectionMatch: palette[1],
lineHighlight: '#8bac0f', lineHighlight: palette[3],
lineBackground: '#9bbc0f50', lineBackground: palette[3] + '90',
//lineBackground: 'transparent', //lineBackground: 'transparent',
gutterBackground: 'transparent', gutterBackground: 'transparent',
gutterForeground: '#0f380f', gutterForeground: palette[0],
light: true, light: false,
customStyle: '.cm-line { line-height: 1 }', // customStyle: '.cm-line { line-height: 1 }',
}; };
export default createTheme({ export default createTheme({
theme: 'light', theme: 'dark',
settings, settings,
styles: [ styles: [
{ tag: t.labelName, color: '#0f380f' }, { tag: t.comment, color: palette[2] },
{ tag: t.keyword, color: '#0f380f' }, { tag: t.string, color: palette[1] },
{ tag: t.operator, color: '#0f380f' }, { tag: [t.atom, t.number], color: palette[1] },
{ tag: t.special(t.variableName), color: '#0f380f' }, { tag: [t.meta, t.labelName, t.variableName], color: palette[0] },
{ tag: t.typeName, color: '#0f380f' }, {
{ tag: t.atom, color: '#0f380f' }, tag: [t.keyword, t.tagName, t.arithmeticOperator],
{ tag: t.number, color: '#0f380f' }, color: palette[1],
{ tag: t.definition(t.variableName), color: '#0f380f' }, },
{ tag: t.string, color: '#0f380f' }, { tag: [t.function(t.variableName), t.propertyName], color: palette[0] },
{ tag: t.special(t.string), color: '#0f380f' }, { tag: t.atom, color: palette[1] },
{ tag: t.comment, color: '#0f380f' },
{ tag: t.variableName, color: '#0f380f' },
{ tag: t.tagName, color: '#0f380f' },
{ tag: t.bracket, color: '#0f380f' },
{ tag: t.meta, color: '#0f380f' },
{ tag: t.attributeName, color: '#0f380f' },
{ tag: t.propertyName, color: '#0f380f' },
{ tag: t.className, color: '#0f380f' },
{ tag: t.invalid, color: '#0f380f' },
{ tag: [t.unit, t.punctuation], color: '#0f380f' },
], ],
}); });
+43
View File
@@ -0,0 +1,43 @@
/**
* @name androidstudio
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#282b2e',
lineBackground: '#282b2e99',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#343739',
selectionMatch: '#343739',
lineHighlight: '#343739',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#282b2e',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#4e5254',
selectionMatch: '#4e5254',
gutterForeground: '#cccccc50',
lineHighlight: '#7f85891f',
},
styles: [
{ tag: t.labelName, color: 'inherit' },
{ tag: [t.keyword, t.deleted, t.className], color: '#a9b7c6' },
{ tag: [t.number, t.literal], color: '#6897bb' },
//{ tag: [t.link, t.variableName], color: '#629755' },
{ tag: [t.link, t.variableName], color: '#a9b7c6' },
{ tag: [t.comment, t.quote], color: 'grey' },
{ tag: [t.meta, t.documentMeta], color: '#bbb529' },
//{ tag: [t.string, t.propertyName, t.attributeValue], color: '#6a8759' },
{ tag: [t.propertyName, t.attributeValue], color: '#a9b7c6' },
{ tag: [t.string], color: '#6a8759' },
{ tag: [t.heading, t.typeName], color: '#ffc66d' },
{ tag: [t.attributeName], color: '#a9b7c6' },
{ tag: [t.emphasis], fontStyle: 'italic' },
],
});
+49
View File
@@ -0,0 +1,49 @@
/**
* @name Atom One
* Atom One dark syntax theme
*
* https://github.com/atom/one-dark-syntax
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#272C35',
lineBackground: '#272C3599',
foreground: 'hsl(220, 14%, 71%)',
caret: '#797977',
selection: '#ffffff30',
selectionMatch: '#2B323D',
gutterBackground: '#272C35',
gutterForeground: '#465063',
gutterBorder: 'transparent',
lineHighlight: '#2B323D',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#272C35',
foreground: '#9d9b97',
caret: '#797977',
selection: '#3d4c64',
selectionMatch: '#3d4c64',
gutterBackground: '#272C35',
gutterForeground: '#465063',
gutterBorder: 'transparent',
lineHighlight: '#2e3f5940',
},
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: 'hsl(207, 82%, 66%)',
},
{ tag: [t.tagName, t.heading], color: '#e06c75' },
{ tag: t.comment, color: '#54636D' },
{ tag: [t.variableName, t.propertyName, t.labelName], color: 'hsl(220, 14%, 71%)' },
{ tag: [t.attributeName, t.number], color: 'hsl( 29, 54%, 61%)' },
{ tag: t.className, color: 'hsl( 39, 67%, 69%)' },
{ tag: t.keyword, color: 'hsl(286, 60%, 67%)' },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: '#98c379' },
],
});
+51
View File
@@ -0,0 +1,51 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#21202e',
lineBackground: '#21202e99',
foreground: '#edecee',
caret: '#a277ff',
selection: '#3d375e7f',
selectionMatch: '#3d375e7f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#21202e',
foreground: '#edecee',
caret: '#a277ff',
selection: '#5a51898f',
selectionMatch: '#5a51898f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
},
styles: [
{ tag: t.keyword, color: '#a277ff' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#edecee' },
{ tag: [t.propertyName], color: '#ffca85' },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#61ffca' },
{ tag: [t.function(t.variableName), t.labelName], color: '#ffca85' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#61ffca' },
{ tag: [t.definition(t.name), t.separator], color: '#edecee' },
{ tag: [t.className], color: '#82e2ff' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#61ffca' },
{ tag: [t.typeName], color: '#82e2ff' },
{ tag: [t.operator, t.operatorKeyword], color: '#a277ff' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#61ffca' },
{ tag: [t.meta, t.comment], color: '#6d6d6d' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: '#a277ff' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#edecee' },
{ tag: t.invalid, color: '#ff6767' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
+46
View File
@@ -0,0 +1,46 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#FFFFFF',
lineBackground: '#FFFFFF99',
foreground: '#000000',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
gutterBackground: '#f5f5f5',
gutterForeground: '#4D4D4C',
gutterBorder: 'transparent',
lineHighlight: '#00000012',
};
export default createTheme({
theme: 'light',
settings: {
background: '#FFFFFF',
foreground: '#000000',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
gutterBackground: '#f5f5f5',
gutterForeground: '#4D4D4C',
gutterBorder: 'transparent',
lineHighlight: '#00000012',
},
styles: [
{ tag: [t.meta, t.comment], color: '#804000' },
{ tag: [t.keyword, t.strong], color: '#0000FF' },
{ tag: [t.number], color: '#FF0080' },
{ tag: [t.string], color: '#FF0080' },
{ tag: [t.variableName], color: '#006600' },
{ tag: [t.escape], color: '#33CC33' },
{ tag: [t.tagName], color: '#1C02FF' },
{ tag: [t.heading], color: '#0C07FF' },
{ tag: [t.quote], color: '#000000' },
{ tag: [t.list], color: '#B90690' },
{ tag: [t.documentMeta], color: '#888888' },
{ tag: [t.function(t.variableName)], color: '#0000A2' },
{ tag: [t.definition(t.typeName), t.typeName], color: '#6D79DE' },
],
});
+39
View File
@@ -0,0 +1,39 @@
// this is different from https://thememirror.net/bespin
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#28211c',
lineBackground: '#28211c99',
foreground: '#9d9b97',
caret: '#797977',
selection: '#36312e',
selectionMatch: '#4f382b',
gutterBackground: '#28211c',
gutterForeground: '#666666',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#28211c',
foreground: '#9d9b97',
caret: '#797977',
selection: '#4f382b',
selectionMatch: '#4f382b',
gutterBackground: '#28211c',
gutterForeground: '#666666',
lineHighlight: '#ffffff1a',
},
styles: [
{ tag: [t.atom, t.number, t.link, t.bool], color: '#9b859d' },
{ tag: t.comment, color: '#937121' },
{ tag: [t.keyword, t.tagName], color: '#cf6a4c' },
{ tag: t.string, color: '#f9ee98' },
{ tag: t.bracket, color: '#9d9b97' },
{ tag: [t.variableName], color: '#5ea6ea' },
{ tag: t.definition(t.variableName), color: '#cf7d34' },
{ tag: [t.function(t.variableName), t.className], color: '#cf7d34' },
{ tag: [t.propertyName, t.attributeName], color: '#54be0d' },
],
});
+21 -21
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight'; import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: 'black', background: 'black',
foreground: 'white', // whats that? foreground: 'white', // whats that?
@@ -15,25 +15,25 @@ export default createTheme({
theme: 'dark', theme: 'dark',
settings, settings,
styles: [ styles: [
{ tag: t.labelName, color: 'white' }, { tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: 'white' }, { tag: t.keyword, color: 'inherit' },
{ tag: t.operator, color: 'white' }, { tag: t.operator, color: 'inherit' },
{ tag: t.special(t.variableName), color: 'white' }, { tag: t.special(t.variableName), color: 'inherit' },
{ tag: t.typeName, color: 'white' }, { tag: t.typeName, color: 'inherit' },
{ tag: t.atom, color: 'white' }, { tag: t.atom, color: 'inherit' },
{ tag: t.number, color: 'white' }, { tag: t.number, color: 'inherit' },
{ tag: t.definition(t.variableName), color: 'white' }, { tag: t.definition(t.variableName), color: 'inherit' },
{ tag: t.string, color: 'white' }, { tag: t.string, color: 'inherit' },
{ tag: t.special(t.string), color: 'white' }, { tag: t.special(t.string), color: 'inherit' },
{ tag: t.comment, color: 'white' }, { tag: t.comment, color: 'inherit' },
{ tag: t.variableName, color: 'white' }, { tag: t.variableName, color: 'inherit' },
{ tag: t.tagName, color: 'white' }, { tag: t.tagName, color: 'inherit' },
{ tag: t.bracket, color: 'white' }, { tag: t.bracket, color: 'inherit' },
{ tag: t.meta, color: 'white' }, { tag: t.meta, color: 'inherit' },
{ tag: t.attributeName, color: 'white' }, { tag: t.attributeName, color: 'inherit' },
{ tag: t.propertyName, color: 'white' }, { tag: t.propertyName, color: 'inherit' },
{ tag: t.className, color: 'white' }, { tag: t.className, color: 'inherit' },
{ tag: t.invalid, color: 'white' }, { tag: t.invalid, color: 'inherit' },
{ tag: [t.unit, t.punctuation], color: 'white' }, { tag: [t.unit, t.punctuation], color: 'inherit' },
], ],
}); });
+21 -21
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight'; import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: '#051DB5', background: '#051DB5',
lineBackground: '#051DB550', lineBackground: '#051DB550',
@@ -18,25 +18,25 @@ export default createTheme({
theme: 'dark', theme: 'dark',
settings, settings,
styles: [ styles: [
{ tag: t.labelName, color: 'white' }, { tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: 'white' }, { tag: t.keyword, color: 'inherit' },
{ tag: t.operator, color: 'white' }, { tag: t.operator, color: 'inherit' },
{ tag: t.special(t.variableName), color: 'white' }, { tag: t.special(t.variableName), color: 'inherit' },
{ tag: t.typeName, color: 'white' }, { tag: t.typeName, color: 'inherit' },
{ tag: t.atom, color: 'white' }, { tag: t.atom, color: 'inherit' },
{ tag: t.number, color: 'white' }, { tag: t.number, color: 'inherit' },
{ tag: t.definition(t.variableName), color: 'white' }, { tag: t.definition(t.variableName), color: 'inherit' },
{ tag: t.string, color: 'white' }, { tag: t.string, color: 'inherit' },
{ tag: t.special(t.string), color: 'white' }, { tag: t.special(t.string), color: 'inherit' },
{ tag: t.comment, color: 'white' }, { tag: t.comment, color: 'inherit' },
{ tag: t.variableName, color: 'white' }, { tag: t.variableName, color: 'inherit' },
{ tag: t.tagName, color: 'white' }, { tag: t.tagName, color: 'inherit' },
{ tag: t.bracket, color: 'white' }, { tag: t.bracket, color: 'inherit' },
{ tag: t.meta, color: 'white' }, { tag: t.meta, color: 'inherit' },
{ tag: t.attributeName, color: 'white' }, { tag: t.attributeName, color: 'inherit' },
{ tag: t.propertyName, color: 'white' }, { tag: t.propertyName, color: 'inherit' },
{ tag: t.className, color: 'white' }, { tag: t.className, color: 'inherit' },
{ tag: t.invalid, color: 'white' }, { tag: t.invalid, color: 'inherit' },
{ tag: [t.unit, t.punctuation], color: 'white' }, { tag: [t.unit, t.punctuation], color: 'inherit' },
], ],
}); });
+48
View File
@@ -0,0 +1,48 @@
/**
* @name darcula
* @author darcula
* Name: IntelliJ IDEA darcula theme
* From IntelliJ IDEA by JetBrains
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#242424',
lineBackground: '#24242499',
foreground: '#f8f8f2',
caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: 'rgba(255, 255, 255, 0.1)',
gutterForeground: '#999',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#242424',
foreground: '#f8f8f2',
caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: 'transparent',
gutterForeground: '#999',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
styles: [
{ tag: t.labelName, color: '#CCCCCC' },
{ tag: [t.atom, t.number], color: '#7A9EC2' },
{ tag: [t.comment], color: '#707070' },
{ tag: [t.string], color: '#6A8759' },
{ tag: [t.variableName, t.operator], color: '#CCCCCC' },
{ tag: [t.function(t.variableName), t.propertyName], color: '#FFC66D' },
{ tag: [t.meta, t.className], color: '#FFC66D' },
{ tag: [t.propertyName], color: '#FFC66D' },
{ tag: [t.keyword], color: '#CC7832' },
{ tag: [t.tagName], color: '#ff79c6' },
{ tag: [t.typeName], color: '#ffb86c' },
],
});
+51
View File
@@ -0,0 +1,51 @@
/**
* @name dracula
* @author dracula
* Michael Kaminsky (http://github.com/mkaminsky11)
* Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
*/
// this is different from https://thememirror.net/dracula
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#282a36',
lineBackground: '#282a3699',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6272a4',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
};
const purple = '#BD93F9';
export default createTheme({
theme: 'dark',
settings: {
background: '#282a36',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6272a4',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
styles: [
{ tag: t.comment, color: '#6272a4' },
{ tag: t.string, color: '#f1fa8c' },
{ tag: [t.atom, t.number], color: purple },
{ tag: [t.meta, t.labelName, t.variableName], color: '#f8f8f2' },
{
tag: [t.keyword, t.tagName, t.arithmeticOperator],
color: '#ff79c6',
},
{ tag: [t.function(t.variableName), t.propertyName], color: '#50fa7b' },
{ tag: t.atom, color: '#bd93f9' },
],
});
+42
View File
@@ -0,0 +1,42 @@
/**
* @name duotone
* @author Bram de Haan
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#2a2734',
lineBackground: '#2a273499',
foreground: '#eeebff',
caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#2a2734',
foreground: '#6c6783',
caret: '#ffad5c',
selection: '#9a86fd',
selectionMatch: '#9a86fd',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
},
styles: [
{ tag: [t.comment, t.bracket, t.operator], color: '#6c6783' },
{ tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#ffcc99' },
{ tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.className, t.variableName], color: '#eeebff' },
{ tag: [t.typeName, t.url], color: '#eeebff' },
{ tag: t.string, color: '#ffb870' },
/* { tag: [t.propertyName], color: '#9a86fd' }, */
{ tag: [t.propertyName], color: '#eeebff' },
{ tag: t.labelName, color: '#eeebff' },
],
});
+45
View File
@@ -0,0 +1,45 @@
/**
* @name duotone
* @author Bram de Haan
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#faf8f5',
lineBackground: '#faf8f599',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#EFEFEF',
};
export default createTheme({
theme: 'light',
settings: {
background: '#faf8f5',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#ddceb154',
},
styles: [
{ tag: [t.comment, t.bracket], color: '#b6ad9a' },
{ tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#063289' },
{ tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.variableName], color: '#2d2006' },
{ tag: [t.typeName, t.url, t.string], color: '#896724' },
{ tag: [t.operator, t.string], color: '#1659df' },
{ tag: [t.propertyName], color: '#b29762' },
{ tag: [t.unit, t.punctuation], color: '#063289' },
],
});
+46
View File
@@ -0,0 +1,46 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#000',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#f7f7f7',
gutterForeground: '#999',
lineHighlight: '#e8f2ff',
gutterBorder: 'transparent',
};
export default createTheme({
theme: 'light',
settings: {
background: '#fff',
foreground: '#000',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#f7f7f7',
gutterForeground: '#999',
lineHighlight: '#006fff1c',
gutterBorder: 'transparent',
},
styles: [
{ tag: [t.comment], color: '#3F7F5F' },
{ tag: [t.documentMeta], color: '#FF1717' },
{ tag: t.keyword, color: '#7F0055', fontWeight: 'bold' },
{ tag: t.atom, color: '#00f' },
{ tag: t.number, color: '#164' },
{ tag: t.propertyName, color: '#164' },
{ tag: [t.variableName, t.definition(t.variableName)], color: '#0000C0' },
{ tag: t.function(t.variableName), color: '#0000C0' },
{ tag: t.string, color: '#2A00FF' },
{ tag: t.operator, color: 'black' },
{ tag: t.tagName, color: '#170' },
{ tag: t.attributeName, color: '#00c' },
{ tag: t.link, color: '#219' },
],
});
+45
View File
@@ -0,0 +1,45 @@
/**
* @name github
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#0d1117',
lineBackground: '#0d111799',
foreground: '#c9d1d9',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
lineHighlight: '#36334280',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#0d1117',
foreground: '#c9d1d9',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
lineHighlight: '#36334280',
},
styles: [
{ tag: t.labelName, color: '#d2a8ff' },
{ tag: [t.standard(t.tagName), t.tagName], color: '#7ee787' },
{ tag: [t.comment, t.bracket], color: '#8b949e' },
{ tag: [t.className, t.propertyName], color: '#d2a8ff' },
{ tag: [t.variableName, t.attributeName], color: '#d2a8ff' },
{ tag: [t.number, t.operator], color: '#79c0ff' },
{ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#ff7b72' },
{ tag: [t.string, t.meta, t.regexp], color: '#a5d6ff' },
{ tag: [t.name, t.quote], color: '#7ee787' },
{ tag: [t.heading, t.strong], color: '#d2a8ff', fontWeight: 'bold' },
{ tag: [t.emphasis], color: '#d2a8ff', fontStyle: 'italic' },
{ tag: [t.deleted], color: '#ffdcd7', backgroundColor: 'ffeef0' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#ffab70' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.invalid, color: '#f97583' },
],
});
+45
View File
@@ -0,0 +1,45 @@
/**
* @name github
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
};
export default createTheme({
theme: 'light',
settings: {
background: '#fff',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
},
styles: [
{ tag: [t.standard(t.tagName), t.tagName], color: '#116329' },
{ tag: [t.comment, t.bracket], color: '#6a737d' },
{ tag: [t.className, t.propertyName], color: '#6f42c1' },
{ tag: [t.variableName, t.attributeName, t.number, t.operator], color: '#005cc5' },
{ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#d73a49' },
{ tag: [t.string, t.meta, t.regexp], color: '#032f62' },
{ tag: [t.name, t.quote], color: '#22863a' },
{ tag: [t.heading, t.strong], color: '#24292e', fontWeight: 'bold' },
{ tag: [t.emphasis], color: '#24292e', fontStyle: 'italic' },
{ tag: [t.deleted], color: '#b31d28', backgroundColor: 'ffeef0' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#e36209' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#032f62' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.invalid, color: '#cb2431' },
],
});
+83
View File
@@ -0,0 +1,83 @@
/**
* @name gruvbox-dark
* @author morhetz
* Name: Gruvbox
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-dark.css
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#282828',
lineBackground: '#28282899',
foreground: '#ebdbb2',
caret: '#ebdbb2',
selection: '#bdae93',
selectionMatch: '#bdae93',
lineHighlight: '#3c3836',
gutterBackground: '#282828',
gutterForeground: '#7c6f64',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#282828',
foreground: '#ebdbb2',
caret: '#ebdbb2',
selection: '#b99d555c',
selectionMatch: '#b99d555c',
lineHighlight: '#baa1602b',
gutterBackground: '#282828',
gutterForeground: '#7c6f64',
},
styles: [
{ tag: t.keyword, color: '#fb4934' },
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: '#8ec07c' },
{ tag: [t.variableName], color: '#83a598' },
{ tag: [t.function(t.variableName)], color: '#8ec07c', fontStyle: 'bold' },
{ tag: [t.labelName], color: '#ebdbb2' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#d3869b' },
{ tag: [t.definition(t.name), t.separator], color: '#ebdbb2' },
{ tag: [t.brace], color: '#ebdbb2' },
{ tag: [t.annotation], color: '#fb4934d' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#d3869b' },
{ tag: [t.typeName, t.className], color: '#fabd2f' },
{ tag: [t.operatorKeyword], color: '#fb4934' },
{
tag: [t.tagName],
color: '#8ec07c',
fontStyle: 'bold',
},
{ tag: [t.squareBracket], color: '#fe8019' },
{ tag: [t.angleBracket], color: '#83a598' },
{ tag: [t.attributeName], color: '#8ec07c' },
{ tag: [t.regexp], color: '#8ec07c' },
{ tag: [t.quote], color: '#928374' },
{ tag: [t.string], color: '#ebdbb2' },
{
tag: t.link,
color: '#a89984',
textDecoration: 'underline',
textUnderlinePosition: 'under',
},
{ tag: [t.url, t.escape, t.special(t.string)], color: '#d3869b' },
{ tag: [t.meta], color: '#fabd2f' },
{ tag: [t.comment], color: '#928374', fontStyle: 'italic' },
{ tag: t.strong, fontWeight: 'bold', color: '#fe8019' },
{ tag: t.emphasis, fontStyle: 'italic', color: '#b8bb26' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: '#b8bb26' },
{ tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#b8bb26' },
{
tag: [t.heading3, t.heading4],
fontWeight: 'bold',
color: '#fabd2f',
},
{ tag: [t.heading5, t.heading6], color: '#fabd2f' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d3869b' },
{ tag: [t.processingInstruction, t.inserted], color: '#83a598' },
{ tag: [t.contentSeparator], color: '#fb4934' },
{ tag: t.invalid, color: '#fe8019', borderBottom: `1px dotted #fb4934d` },
],
});
+131
View File
@@ -0,0 +1,131 @@
/**
* @name gruvbox-light
* @author morhetz
* Name: Gruvbox
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-light.css
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#fbf1c7',
lineBackground: '#fbf1c799',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#ebdbb2',
selectionMatch: '#bdae93',
lineHighlight: '#ebdbb2',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
};
export default createTheme({
theme: 'light',
settings: {
background: '#fbf1c7',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#bdae9391',
selectionMatch: '#bdae9391',
lineHighlight: '#a37f2238',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
},
styles: [
{ tag: t.keyword, color: '#9d0006' },
{
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: '#427b58',
},
{ tag: [t.variableName], color: '#076678' },
{ tag: [t.function(t.variableName)], color: '#79740e', fontStyle: 'bold' },
{ tag: [t.labelName], color: '#3c3836' },
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: '#8f3f71',
},
{ tag: [t.definition(t.name), t.separator], color: '#3c3836' },
{ tag: [t.brace], color: '#3c3836' },
{
tag: [t.annotation],
color: '#9d0006',
},
{
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: '#8f3f71',
},
{
tag: [t.typeName, t.className],
color: '#b57614',
},
{
tag: [t.operator, t.operatorKeyword],
color: '#9d0006',
},
{
tag: [t.tagName],
color: '#427b58',
fontStyle: 'bold',
},
{
tag: [t.squareBracket],
color: '#af3a03',
},
{
tag: [t.angleBracket],
color: '#076678',
},
{
tag: [t.attributeName],
color: '#427b58',
},
{
tag: [t.regexp],
color: '#427b58',
},
{
tag: [t.quote],
color: '#928374',
},
{ tag: [t.string], color: '#3c3836' },
{
tag: t.link,
color: '#7c6f64',
textDecoration: 'underline',
textUnderlinePosition: 'under',
},
{
tag: [t.url, t.escape, t.special(t.string)],
color: '#8f3f71',
},
{ tag: [t.meta], color: '#b57614' },
{ tag: [t.comment], color: '#928374', fontStyle: 'italic' },
{ tag: t.strong, fontWeight: 'bold', color: '#af3a03' },
{ tag: t.emphasis, fontStyle: 'italic', color: '#79740e' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: '#79740e' },
{ tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#79740e' },
{
tag: [t.heading3, t.heading4],
fontWeight: 'bold',
color: '#b57614',
},
{
tag: [t.heading5, t.heading6],
color: '#b57614',
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#8f3f71' },
{
tag: [t.processingInstruction, t.inserted],
color: '#076678',
},
{
tag: [t.contentSeparator],
color: '#9d0006',
},
{ tag: t.invalid, color: '#af3a03', borderBottom: `1px dotted #9d0006` },
],
});
+77
View File
@@ -0,0 +1,77 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#212121',
lineBackground: '#21212199',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#212121',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#111111',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#212121',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f063',
selectionMatch: '#d7d4f063',
gutterBackground: '#212121',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#333333',
},
styles: [
{ tag: t.keyword, color: '#cf6edf' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#56c8d8' },
{ tag: [t.propertyName], color: '#82AAFF' },
{ tag: [t.variableName], color: '#bdbdbd' },
{ tag: [t.function(t.variableName)], color: '#82AAFF' },
{ tag: [t.labelName], color: '#cf6edf' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#facf4e' },
{ tag: [t.definition(t.name), t.separator], color: '#56c8d8' },
{ tag: [t.brace], color: '#cf6edf' },
{ tag: [t.annotation], color: '#f07178' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#f07178' },
{ tag: [t.typeName, t.className], color: '#f07178' },
{ tag: [t.operator, t.operatorKeyword], color: '#82AAFF' },
{ tag: [t.tagName], color: '#99d066' },
{ tag: [t.squareBracket], color: '#f07178' },
{ tag: [t.angleBracket], color: '#606f7a' },
{ tag: [t.attributeName], color: '#bdbdbd' },
{ tag: [t.regexp], color: '#f07178' },
{ tag: [t.quote], color: '#6abf69' },
{ tag: [t.string], color: '#99d066' },
{
tag: t.link,
color: '#56c8d8',
textDecoration: 'underline',
textUnderlinePosition: 'under',
},
{ tag: [t.url, t.escape, t.special(t.string)], color: '#facf4e' },
{ tag: [t.meta], color: '#707d8b' },
{ tag: [t.comment], color: '#707d8b', fontStyle: 'italic' },
{ tag: t.monospace, color: '#bdbdbd' },
{ tag: t.strong, fontWeight: 'bold', color: '#f07178' },
{ tag: t.emphasis, fontStyle: 'italic', color: '#99d066' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: '#facf4e' },
{ tag: t.heading1, fontWeight: 'bold', color: '#facf4e' },
{
tag: [t.heading2, t.heading3, t.heading4],
fontWeight: 'bold',
color: '#facf4e',
},
{ tag: [t.heading5, t.heading6], color: '#facf4e' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#56c8d8' },
{ tag: [t.processingInstruction, t.inserted], color: '#f07178' },
{ tag: [t.contentSeparator], color: '#56c8d8' },
{ tag: t.invalid, color: '#606f7a', borderBottom: `1px dotted #f07178` },
],
});
+52
View File
@@ -0,0 +1,52 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#FAFAFA',
lineBackground: '#FAFAFA99',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#FAFAFA',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
};
export default createTheme({
theme: 'light',
settings: {
background: '#FAFAFA',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#80CBC440',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
},
styles: [
{ tag: t.keyword, color: '#39ADB5' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#90A4AE' },
{ tag: [t.propertyName], color: '#6182B8' },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#91B859' },
{ tag: [t.function(t.variableName), t.labelName], color: '#6182B8' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#39ADB5' },
{ tag: [t.definition(t.name), t.separator], color: '#90A4AE' },
{ tag: [t.className], color: '#E2931D' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#F76D47' },
{ tag: [t.typeName], color: '#E2931D', fontStyle: '#E2931D' },
{ tag: [t.operator, t.operatorKeyword], color: '#39ADB5' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#91B859' },
{ tag: [t.meta, t.comment], color: '#90A4AE' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: '#39ADB5' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#90A4AE' },
{ tag: t.invalid, color: '#E5393570' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
+45
View File
@@ -0,0 +1,45 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#272822',
lineBackground: '#27282299',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#272822',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#0000003b',
},
styles: [
{ tag: t.labelName, color: '#bababa' },
{ tag: [t.comment, t.documentMeta], color: '#8292a2' },
{ tag: [t.number, t.bool, t.null, t.atom], color: '#ae81ff' },
{ tag: [t.attributeValue, t.className, t.name], color: '#e6db74' },
{ tag: [t.propertyName, t.attributeName], color: '#a6e22e' },
{ tag: [t.variableName], color: '#9effff' },
{ tag: [t.squareBracket], color: '#bababa' },
{ tag: [t.string, t.special(t.brace)], color: '#e6db74' },
{ tag: [t.regexp, t.className, t.typeName, t.definition(t.typeName)], color: '#66d9ef' },
{
tag: [t.definition(t.variableName), t.definition(t.propertyName), t.function(t.variableName)],
color: '#a6e22e',
},
// { tag: t.keyword, color: '#f92672' },
{ tag: [t.keyword, t.definitionKeyword, t.modifier, t.tagName, t.angleBracket], color: '#f92672' },
],
});
+50
View File
@@ -0,0 +1,50 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#f2f1f8',
lineBackground: '#f2f1f899',
foreground: '#0c006b',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
gutterBackground: '#f2f1f8',
gutterForeground: '#0c006b70',
lineHighlight: '#e1def3',
};
export default createTheme({
theme: 'light',
settings: {
background: '#f2f1f8',
foreground: '#0c006b',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
gutterBackground: '#f2f1f8',
gutterForeground: '#0c006b70',
lineHighlight: '#16067911',
},
styles: [
{ tag: t.comment, color: '#9995b7' },
{
tag: t.keyword,
color: '#ff5792',
fontWeight: 'bold',
},
{ tag: [t.definitionKeyword, t.modifier], color: '#ff5792' },
{ tag: [t.className, t.tagName, t.definition(t.typeName)], color: '#0094f0' },
{ tag: [t.number, t.bool, t.null, t.special(t.brace)], color: '#5842ff' },
{ tag: [t.definition(t.propertyName), t.function(t.variableName)], color: '#0095a8' },
{ tag: t.typeName, color: '#b3694d' },
{ tag: [t.propertyName, t.variableName], color: '#fa8900' },
{ tag: t.operator, color: '#ff5792' },
{ tag: t.self, color: '#e64100' },
{ tag: [t.string, t.regexp], color: '#00b368' },
{ tag: [t.paren, t.bracket], color: '#0431fa' },
{ tag: t.labelName, color: '#00bdd6' },
{ tag: t.attributeName, color: '#e64100' },
{ tag: t.angleBracket, color: '#9995b7' },
],
});
+78
View File
@@ -0,0 +1,78 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#2e3440',
lineBackground: '#2e344099',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#3b4252',
selectionMatch: '#e5e9f0',
gutterBackground: '#2e3440',
gutterForeground: '#4c566a',
gutterActiveForeground: '#d8dee9',
lineHighlight: '#4c566a',
};
// Colors from https://www.nordtheme.com/docs/colors-and-palettes
export default createTheme({
theme: 'dark',
settings: {
background: '#2e3440',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#00000073',
selectionMatch: '#00000073',
gutterBackground: '#2e3440',
gutterForeground: '#4c566a',
gutterActiveForeground: '#d8dee9',
lineHighlight: '#4c566a29',
},
styles: [
{ tag: t.keyword, color: '#5e81ac' },
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: '#88c0d0' },
{ tag: [t.variableName], color: '#8fbcbb' },
{ tag: [t.function(t.variableName)], color: '#8fbcbb' },
{ tag: [t.labelName], color: '#81a1c1' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#5e81ac' },
{ tag: [t.definition(t.name), t.separator], color: '#a3be8c' },
{ tag: [t.brace], color: '#8fbcbb' },
{ tag: [t.annotation], color: '#d30102' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#b48ead' },
{ tag: [t.typeName, t.className], color: '#ebcb8b' },
{ tag: [t.operator, t.operatorKeyword], color: '#a3be8c' },
{ tag: [t.tagName], color: '#b48ead' },
{ tag: [t.squareBracket], color: '#bf616a' },
{ tag: [t.angleBracket], color: '#d08770' },
{ tag: [t.attributeName], color: '#ebcb8b' },
{ tag: [t.regexp], color: '#5e81ac' },
{ tag: [t.quote], color: '#b48ead' },
{ tag: [t.string], color: '#a3be8c' },
{
tag: t.link,
color: '#a3be8c',
textDecoration: 'underline',
textUnderlinePosition: 'under',
},
{ tag: [t.url, t.escape, t.special(t.string)], color: '#8fbcbb' },
{ tag: [t.meta], color: '#88c0d0' },
{ tag: [t.monospace], color: '#d8dee9', fontStyle: 'italic' },
{ tag: [t.comment], color: '#4c566a', fontStyle: 'italic' },
{ tag: t.strong, fontWeight: 'bold', color: '#5e81ac' },
{ tag: t.emphasis, fontStyle: 'italic', color: '#5e81ac' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: '#5e81ac' },
{ tag: t.special(t.heading1), fontWeight: 'bold', color: '#5e81ac' },
{ tag: t.heading1, fontWeight: 'bold', color: '#5e81ac' },
{
tag: [t.heading2, t.heading3, t.heading4],
fontWeight: 'bold',
color: '#5e81ac',
},
{ tag: [t.heading5, t.heading6], color: '#5e81ac' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d08770' },
{ tag: [t.processingInstruction, t.inserted], color: '#8fbcbb' },
{ tag: [t.contentSeparator], color: '#ebcb8b' },
{ tag: t.invalid, color: '#434c5e', borderBottom: `1px dotted #d30102` },
],
});
+79
View File
@@ -0,0 +1,79 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#002b36',
lineBackground: '#002b3699',
foreground: '#93a1a1',
caret: '#839496',
selection: '#173541',
selectionMatch: '#aafe661a',
gutterBackground: '#00252f',
gutterForeground: '#839496',
lineHighlight: '#173541',
};
const c = {
background: '#002B36',
foreground: '#839496',
selection: '#004454AA',
selectionMatch: '#005A6FAA',
cursor: '#D30102',
dropdownBackground: '#00212B',
dropdownBorder: '#2AA19899',
activeLine: '#00cafe11',
matchingBracket: '#073642',
keyword: '#859900',
storage: '#93A1A1',
variable: '#268BD2',
parameter: '#268BD2',
function: '#268BD2',
string: '#2AA198',
constant: '#CB4B16',
type: '#859900',
class: '#268BD2',
number: '#D33682',
comment: '#586E75',
heading: '#268BD2',
invalid: '#DC322F',
regexp: '#DC322F',
tag: '#268BD2',
};
export default createTheme({
theme: 'dark',
settings: {
background: c.background,
foreground: c.foreground,
caret: c.cursor,
selection: c.selection,
selectionMatch: c.selection,
gutterBackground: c.background,
gutterForeground: c.foreground,
gutterBorder: 'transparent',
lineHighlight: c.activeLine,
},
styles: [
{ tag: t.keyword, color: c.keyword },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable },
{ tag: [t.propertyName], color: c.function },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string },
{ tag: [t.function(t.variableName), t.labelName], color: c.function },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant },
{ tag: [t.definition(t.name), t.separator], color: c.variable },
{ tag: [t.className], color: c.class },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number },
{ tag: [t.typeName], color: c.type, fontStyle: c.type },
{ tag: [t.operator, t.operatorKeyword], color: c.keyword },
{ tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp },
{ tag: [t.meta, t.comment], color: c.comment },
{ tag: t.tagName, color: c.tag },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: c.heading },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable },
{ tag: t.invalid, color: c.invalid },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
+82
View File
@@ -0,0 +1,82 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
// this is slightly different from https://thememirror.net/solarized-light
export const settings = {
light: true,
background: '#fdf6e3',
lineBackground: '#fdf6e399',
foreground: '#657b83',
caret: '#586e75',
selection: '#dfd9c8',
selectionMatch: '#dfd9c8',
gutterBackground: '#00000010',
gutterForeground: '#657b83',
lineHighlight: '#dfd9c8',
};
const c = {
background: '#FDF6E3',
foreground: '#657B83',
selection: '#EEE8D5',
selectionMatch: '#EEE8D5',
cursor: '#657B83',
dropdownBackground: '#EEE8D5',
dropdownBorder: '#D3AF86',
activeLine: '#3d392d11',
matchingBracket: '#EEE8D5',
keyword: '#859900',
storage: '#586E75',
variable: '#268BD2',
parameter: '#268BD2',
function: '#268BD2',
string: '#2AA198',
constant: '#CB4B16',
type: '#859900',
class: '#268BD2',
number: '#D33682',
comment: '#93A1A1',
heading: '#268BD2',
invalid: '#DC322F',
regexp: '#DC322F',
tag: '#268BD2',
};
export default createTheme({
theme: 'light',
settings: {
background: c.background,
foreground: c.foreground,
caret: c.cursor,
selection: c.selection,
selectionMatch: c.selectionMatch,
gutterBackground: c.background,
gutterForeground: c.foreground,
gutterBorder: 'transparent',
lineHighlight: c.activeLine,
},
styles: [
{ tag: t.keyword, color: c.keyword },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable },
{ tag: [t.propertyName], color: c.function },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string },
{ tag: [t.function(t.variableName), t.labelName], color: c.function },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant },
{ tag: [t.definition(t.name), t.separator], color: c.variable },
{ tag: [t.className], color: c.class },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number },
{ tag: [t.typeName], color: c.type, fontStyle: c.type },
{ tag: [t.operator, t.operatorKeyword], color: c.keyword },
{ tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp },
{ tag: [t.meta, t.comment], color: c.comment },
{ tag: t.tagName, color: c.tag },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: c.heading },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable },
{ tag: t.invalid, color: c.invalid },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
+11 -6
View File
@@ -1,10 +1,11 @@
import { tags as t } from '@lezer/highlight'; import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from './theme-helper.mjs';
export default createTheme({
theme: 'dark', export const settings = {
settings: {
background: '#222', background: '#222',
foreground: '#75baff', // whats that? lineBackground: '#22222299',
foreground: '#fff',
// foreground: '#75baff',
caret: '#ffcc00', caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)', selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626', selectionMatch: '#036dd626',
@@ -13,7 +14,11 @@ export default createTheme({
gutterBackground: 'transparent', gutterBackground: 'transparent',
// gutterForeground: '#8a919966', // gutterForeground: '#8a919966',
gutterForeground: '#8a919966', gutterForeground: '#8a919966',
}, };
export default createTheme({
theme: 'dark',
settings,
styles: [ styles: [
{ tag: t.labelName, color: '#89ddff' }, { tag: t.labelName, color: '#89ddff' },
{ tag: t.keyword, color: '#c792ea' }, { tag: t.keyword, color: '#c792ea' },
+43
View File
@@ -0,0 +1,43 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#303841',
lineBackground: '#30384199',
foreground: '#FFFFFF',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
gutterBackground: '#303841',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#303841',
foreground: '#FFFFFF',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
gutterBackground: '#303841',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
styles: [
{ tag: t.labelName, color: '#A2A9B5' },
{ tag: [t.meta, t.comment], color: '#A2A9B5' },
{ tag: [t.attributeName, t.keyword], color: '#B78FBA' },
{ tag: t.function(t.variableName), color: '#5AB0B0' },
{ tag: [t.string, t.regexp, t.attributeValue], color: '#99C592' },
{ tag: t.operator, color: '#f47954' },
// { tag: t.moduleKeyword, color: 'red' },
{ tag: [t.tagName, t.modifier], color: '#E35F63' },
{ tag: [t.number, t.definition(t.tagName), t.className, t.definition(t.variableName)], color: '#fbac52' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#E35F63' },
{ tag: t.variableName, color: '#539ac4' },
{ tag: [t.propertyName, t.typeName], color: '#629ccd' },
{ tag: t.propertyName, color: '#36b7b5' },
],
});
+2 -2
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight'; import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from './theme-helper.mjs';
let colorA = '#6edee4'; let colorA = '#6edee4';
//let colorB = 'magenta'; //let colorB = 'magenta';
@@ -17,7 +17,7 @@ export const settings = {
lineBackground: '#00000040', lineBackground: '#00000040',
gutterBackground: 'transparent', gutterBackground: 'transparent',
gutterForeground: '#8a919966', gutterForeground: '#8a919966',
customStyle: '.cm-line { line-height: 1 }', // customStyle: '.cm-line { line-height: 1 }',
}; };
let punctuation = colorD; let punctuation = colorD;
+20 -20
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight'; import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: 'black', background: 'black',
foreground: '#41FF00', // whats that? foreground: '#41FF00', // whats that?
@@ -14,24 +14,24 @@ export default createTheme({
theme: 'dark', theme: 'dark',
settings, settings,
styles: [ styles: [
{ tag: t.labelName, color: '#41FF00' }, { tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: '#41FF00' }, { tag: t.keyword, color: 'inherit' },
{ tag: t.operator, color: '#41FF00' }, { tag: t.operator, color: 'inherit' },
{ tag: t.special(t.variableName), color: '#41FF00' }, { tag: t.special(t.variableName), color: 'inherit' },
{ tag: t.typeName, color: '#41FF00' }, { tag: t.typeName, color: 'inherit' },
{ tag: t.atom, color: '#41FF00' }, { tag: t.atom, color: 'inherit' },
{ tag: t.number, color: '#41FF00' }, { tag: t.number, color: 'inherit' },
{ tag: t.definition(t.variableName), color: '#41FF00' }, { tag: t.definition(t.variableName), color: 'inherit' },
{ tag: t.string, color: '#41FF00' }, { tag: t.string, color: 'inherit' },
{ tag: t.special(t.string), color: '#41FF00' }, { tag: t.special(t.string), color: 'inherit' },
{ tag: t.comment, color: '#41FF00' }, { tag: t.comment, color: 'inherit' },
{ tag: t.variableName, color: '#41FF00' }, { tag: t.variableName, color: 'inherit' },
{ tag: t.tagName, color: '#41FF00' }, { tag: t.tagName, color: 'inherit' },
{ tag: t.bracket, color: '#41FF00' }, { tag: t.bracket, color: 'inherit' },
{ tag: t.meta, color: '#41FF00' }, { tag: t.meta, color: 'inherit' },
{ tag: t.attributeName, color: '#41FF00' }, { tag: t.attributeName, color: 'inherit' },
{ tag: t.propertyName, color: '#41FF00' }, { tag: t.propertyName, color: 'inherit' },
{ tag: t.className, color: '#41FF00' }, { tag: t.className, color: 'inherit' },
{ tag: t.invalid, color: '#41FF00' }, { tag: t.invalid, color: 'inherit' },
], ],
}); });
+42
View File
@@ -0,0 +1,42 @@
import { EditorView } from '@codemirror/view';
import { syntaxHighlighting } from '@codemirror/language';
import { HighlightStyle } from '@codemirror/language';
export const createTheme = ({ theme, settings, styles }) => {
const _theme = EditorView.theme(
{
'&': {
color: settings.foreground,
backgroundColor: settings.background,
},
'.cm-gutters': {
backgroundColor: settings.gutterBackground,
color: settings.gutterForeground,
//borderRightColor: settings.gutterBorder
},
'.cm-content': {
caretColor: settings.caret,
},
'.cm-cursor, .cm-dropCursor': {
borderLeftColor: settings.caret,
},
'.cm-activeLineGutter': {
// color: settings.gutterActiveForeground
backgroundColor: settings.lineHighlight,
},
'.cm-activeLine': {
backgroundColor: settings.lineHighlight,
},
'&.cm-focused .cm-selectionBackground, & .cm-line::selection, & .cm-selectionLayer .cm-selectionBackground, .cm-content ::selection':
{
background: settings.selection + ' !important',
},
'& .cm-selectionMatch': {
backgroundColor: settings.selectionMatch,
},
},
{ dark: theme === 'dark' },
);
const highlightStyle = HighlightStyle.define(styles);
return [_theme, syntaxHighlighting(highlightStyle)];
};
+52
View File
@@ -0,0 +1,52 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#24283b',
lineBackground: '#24283b99',
foreground: '#7982a9',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#1f2335',
gutterBackground: '#24283b',
gutterForeground: '#7982a9',
gutterBorder: 'transparent',
lineHighlight: '#292e42',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#24283b',
foreground: '#7982a9',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#343b5f',
gutterBackground: '#24283b',
gutterForeground: '#7982a9',
gutterBorder: 'transparent',
lineHighlight: '#292e427a',
},
styles: [
{ tag: t.keyword, color: '#bb9af7' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#c0caf5' },
{ tag: [t.propertyName], color: '#7aa2f7' },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#9ece6a' },
{ tag: [t.function(t.variableName), t.labelName], color: '#7aa2f7' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#bb9af7' },
{ tag: [t.definition(t.name), t.separator], color: '#c0caf5' },
{ tag: [t.className], color: '#c0caf5' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#ff9e64' },
{ tag: [t.typeName], color: '#2ac3de', fontStyle: '#2ac3de' },
{ tag: [t.operator, t.operatorKeyword], color: '#bb9af7' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#b4f9f8' },
{ tag: [t.meta, t.comment], color: '#565f89' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: '#89ddff' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#c0caf5' },
{ tag: t.invalid, color: '#ff5370' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
+52
View File
@@ -0,0 +1,52 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#1a1b26',
lineBackground: '#1a1b2699',
foreground: '#787c99',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
gutterBackground: '#1a1b26',
gutterForeground: '#787c99',
gutterBorder: 'transparent',
lineHighlight: '#1e202e',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#1a1b26',
foreground: '#787c99',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
gutterBackground: '#1a1b26',
gutterForeground: '#787c99',
gutterBorder: 'transparent',
lineHighlight: '#474b6611',
},
styles: [
{ tag: t.keyword, color: '#bb9af7' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#c0caf5' },
{ tag: [t.propertyName], color: '#7aa2f7' },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#9ece6a' },
{ tag: [t.function(t.variableName), t.labelName], color: '#7aa2f7' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#bb9af7' },
{ tag: [t.definition(t.name), t.separator], color: '#c0caf5' },
{ tag: [t.className], color: '#c0caf5' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#ff9e64' },
{ tag: [t.typeName], color: '#0db9d7' },
{ tag: [t.operator, t.operatorKeyword], color: '#bb9af7' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#b4f9f8' },
{ tag: [t.meta, t.comment], color: '#444b6a' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: '#89ddff' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#c0caf5' },
{ tag: t.invalid, color: '#ff5370' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
+53
View File
@@ -0,0 +1,53 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#e1e2e7',
lineBackground: '#e1e2e799',
foreground: '#3760bf',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
gutterBackground: '#e1e2e7',
gutterForeground: '#3760bf',
gutterBorder: 'transparent',
lineHighlight: '#5f5faf11',
};
export default createTheme({
theme: 'light',
settings: {
background: '#e1e2e7',
foreground: '#3760bf',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
gutterBackground: '#e1e2e7',
gutterForeground: '#3760bf',
gutterBorder: 'transparent',
lineHighlight: '#5f5faf11',
},
styles: [
{ tag: t.keyword, color: '#007197' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#3760bf' },
{ tag: [t.propertyName], color: '#3760bf' },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#587539' },
{ tag: [t.function(t.variableName), t.labelName], color: '#3760bf' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#3760bf' },
{ tag: [t.definition(t.name), t.separator], color: '#3760bf' },
{ tag: [t.className], color: '#3760bf' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#b15c00' },
{ tag: [t.typeName], color: '#007197', fontStyle: '#007197' },
{ tag: [t.operator, t.operatorKeyword], color: '#007197' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#587539' },
{ tag: [t.meta, t.comment], color: '#848cb5' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: '#b15c00' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#3760bf' },
{ tag: t.invalid, color: '#f52a65' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
+80
View File
@@ -0,0 +1,80 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#1e1e1e',
lineBackground: '#1e1e1e99',
foreground: '#fff',
caret: '#c6c6c6',
selection: '#6199ff2f',
selectionMatch: '#72a1ff59',
lineHighlight: '#ffffff0f',
gutterBackground: '#1e1e1e',
gutterForeground: '#838383',
gutterActiveForeground: '#fff',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#1e1e1e',
foreground: '#fff',
caret: '#c6c6c6',
selection: '#6199ff2f',
selectionMatch: '#72a1ff59',
lineHighlight: '#ffffff0f',
gutterBackground: '#1e1e1e',
gutterForeground: '#838383',
gutterActiveForeground: '#fff',
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
},
styles: [
{
tag: [
t.keyword,
t.operatorKeyword,
t.modifier,
t.color,
t.constant(t.name),
t.standard(t.name),
t.standard(t.tagName),
t.special(t.brace),
t.atom,
t.bool,
t.special(t.variableName),
],
color: '#569cd6',
},
{ tag: [t.controlKeyword, t.moduleKeyword], color: '#c586c0' },
{
tag: [
t.name,
t.deleted,
t.character,
t.macroName,
t.propertyName,
t.variableName,
t.labelName,
t.definition(t.name),
],
color: '#9cdcfe',
},
{ tag: t.heading, fontWeight: 'bold', color: '#9cdcfe' },
{
tag: [t.typeName, t.className, t.tagName, t.number, t.changed, t.annotation, t.self, t.namespace],
color: '#4ec9b0',
},
{ tag: [t.function(t.variableName), t.function(t.propertyName)], color: '#dcdcaa' },
{ tag: [t.number], color: '#b5cea8' },
{ tag: [t.operator, t.punctuation, t.separator, t.url, t.escape, t.regexp], color: '#d4d4d4' },
{ tag: [t.regexp], color: '#d16969' },
{ tag: [t.special(t.string), t.processingInstruction, t.string, t.inserted], color: '#ce9178' },
{ tag: [t.angleBracket], color: '#808080' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: [t.meta, t.comment], color: '#6a9955' },
{ tag: t.link, color: '#6a9955', textDecoration: 'underline' },
{ tag: t.invalid, color: '#ff0000' },
],
});
+81
View File
@@ -0,0 +1,81 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#ffffff',
lineBackground: '#ffffff50',
foreground: '#383a42',
caret: '#000',
selection: '#add6ff',
selectionMatch: '#a8ac94',
lineHighlight: '#99999926',
gutterBackground: '#fff',
gutterForeground: '#237893',
gutterActiveForeground: '#0b216f',
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
};
export default createTheme({
theme: 'light',
settings: {
background: '#ffffff',
foreground: '#383a42',
caret: '#000',
selection: '#add6ff',
selectionMatch: '#a8ac94',
lineHighlight: '#99999926',
gutterBackground: '#fff',
gutterForeground: '#237893',
gutterActiveForeground: '#0b216f',
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
},
styles: [
{
tag: [
t.keyword,
t.operatorKeyword,
t.modifier,
t.color,
t.constant(t.name),
t.standard(t.name),
t.standard(t.tagName),
t.special(t.brace),
t.atom,
t.bool,
t.special(t.variableName),
],
color: '#0000ff',
},
{ tag: [t.moduleKeyword, t.controlKeyword], color: '#af00db' },
{
tag: [
t.name,
t.deleted,
t.character,
t.macroName,
t.propertyName,
t.variableName,
t.labelName,
t.definition(t.name),
],
color: '#0070c1',
},
{ tag: t.heading, fontWeight: 'bold', color: '#0070c1' },
{
tag: [t.typeName, t.className, t.tagName, t.number, t.changed, t.annotation, t.self, t.namespace],
color: '#267f99',
},
{ tag: [t.function(t.variableName), t.function(t.propertyName)], color: '#795e26' },
{ tag: [t.number], color: '#098658' },
{ tag: [t.operator, t.punctuation, t.separator, t.url, t.escape, t.regexp], color: '#383a42' },
{ tag: [t.regexp], color: '#af00db' },
{ tag: [t.special(t.string), t.processingInstruction, t.string, t.inserted], color: '#a31515' },
{ tag: [t.angleBracket], color: '#383a42' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: [t.meta, t.comment], color: '#008000' },
{ tag: t.link, color: '#4078f2', textDecoration: 'underline' },
{ tag: t.invalid, color: '#e45649' },
],
});
+20 -20
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight'; import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: 'white', background: 'white',
foreground: 'black', // whats that? foreground: 'black', // whats that?
@@ -16,24 +16,24 @@ export default createTheme({
theme: 'light', theme: 'light',
settings, settings,
styles: [ styles: [
{ tag: t.labelName, color: 'black' }, { tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: 'black' }, { tag: t.keyword, color: 'inherit' },
{ tag: t.operator, color: 'black' }, { tag: t.operator, color: 'inherit' },
{ tag: t.special(t.variableName), color: 'black' }, { tag: t.special(t.variableName), color: 'inherit' },
{ tag: t.typeName, color: 'black' }, { tag: t.typeName, color: 'inherit' },
{ tag: t.atom, color: 'black' }, { tag: t.atom, color: 'inherit' },
{ tag: t.number, color: 'black' }, { tag: t.number, color: 'inherit' },
{ tag: t.definition(t.variableName), color: 'black' }, { tag: t.definition(t.variableName), color: 'inherit' },
{ tag: t.string, color: 'black' }, { tag: t.string, color: 'inherit' },
{ tag: t.special(t.string), color: 'black' }, { tag: t.special(t.string), color: 'inherit' },
{ tag: t.comment, color: 'black' }, { tag: t.comment, color: 'inherit' },
{ tag: t.variableName, color: 'black' }, { tag: t.variableName, color: 'inherit' },
{ tag: t.tagName, color: 'black' }, { tag: t.tagName, color: 'inherit' },
{ tag: t.bracket, color: 'black' }, { tag: t.bracket, color: 'inherit' },
{ tag: t.meta, color: 'black' }, { tag: t.meta, color: 'inherit' },
{ tag: t.attributeName, color: 'black' }, { tag: t.attributeName, color: 'inherit' },
{ tag: t.propertyName, color: 'black' }, { tag: t.propertyName, color: 'inherit' },
{ tag: t.className, color: 'black' }, { tag: t.className, color: 'inherit' },
{ tag: t.invalid, color: 'black' }, { tag: t.invalid, color: 'inherit' },
], ],
}); });
+34
View File
@@ -0,0 +1,34 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#292A30',
lineBackground: '#292A3099',
foreground: '#CECFD0',
caret: '#fff',
selection: '#727377',
selectionMatch: '#727377',
lineHighlight: '#2F3239',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#292A30',
foreground: '#CECFD0',
caret: '#fff',
selection: '#727377',
selectionMatch: '#727377',
lineHighlight: '#ffffff0f',
},
styles: [
{ tag: [t.comment, t.quote], color: '#7F8C98' },
{ tag: [t.keyword], color: '#FF7AB2', fontWeight: 'bold' },
{ tag: [t.string, t.meta], color: '#FF8170' },
{ tag: [t.typeName], color: '#DABAFF' },
{ tag: [t.definition(t.variableName)], color: '#6BDFFF' },
{ tag: [t.name], color: '#6BAA9F' },
{ tag: [t.variableName], color: '#ACF2E4' },
{ tag: [t.regexp, t.link], color: '#FF8170' },
],
});
+38
View File
@@ -0,0 +1,38 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#3D3D3D',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#AFAFAF',
lineHighlight: '#EDF4FF',
};
export default createTheme({
theme: 'light',
settings: {
background: '#fff',
foreground: '#3D3D3D',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#AFAFAF',
lineHighlight: '#d5e6ff69',
},
styles: [
{ tag: [t.comment, t.quote], color: '#707F8D' },
{ tag: [t.typeName, t.typeOperator], color: '#aa0d91' },
{ tag: [t.keyword], color: '#aa0d91', fontWeight: 'bold' },
{ tag: [t.string, t.meta], color: '#D23423' },
{ tag: [t.name], color: '#032f62' },
{ tag: [t.typeName], color: '#522BB2' },
{ tag: [t.variableName], color: '#23575C' },
{ tag: [t.definition(t.variableName)], color: '#327A9E' },
{ tag: [t.regexp, t.link], color: '#0e0eff' },
],
});
+7
View File
@@ -133,3 +133,10 @@ registerWidget('_pitchwheel', (id, options = {}, pat) => {
const ctx = getCanvasWidget(id, options).getContext('2d'); const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.pitchwheel({ ...options, ctx, id }); return pat.pitchwheel({ ...options, ctx, id });
}); });
registerWidget('_spectrum', (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.spectrum({ ...options, ctx, id });
});
+1 -1
View File
@@ -4,7 +4,7 @@ import { calculateTactus, sequence, stack } from '../index.mjs';
const pat64 = sequence(...Array(64).keys()); const pat64 = sequence(...Array(64).keys());
describe('tactus', () => { describe('steps', () => {
calculateTactus(true); calculateTactus(true);
bench( bench(
'+tactus', '+tactus',
+4 -11
View File
@@ -4,9 +4,9 @@
// import createClock from './zyklus.mjs'; // import createClock from './zyklus.mjs';
function getTime() { function getTime() {
const precision = 10 ** 4; const seconds = performance.now() * 0.001;
const seconds = performance.now() / 1000; return seconds;
return Math.round(seconds * precision) / precision; // return Math.round(seconds * precision) / precision;
} }
let num_cycles_at_cps_change = 0; let num_cycles_at_cps_change = 0;
@@ -24,27 +24,20 @@ const sendMessage = (type, payload) => {
const sendTick = (phase, duration, tick, time) => { const sendTick = (phase, duration, tick, time) => {
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration; const num_seconds_since_cps_change = num_ticks_since_cps_change * duration;
const tickdeadline = phase - time; const tickdeadline = phase - time;
const lastTick = time + tickdeadline; const lastTick = time + tickdeadline;
const num_cycles_since_cps_change = num_seconds_since_cps_change * cps; 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 begin = num_cycles_at_cps_change + num_cycles_since_cps_change;
const secondsSinceLastTick = time - lastTick - duration; const secondsSinceLastTick = time - lastTick - duration;
const eventLength = duration * cps; const eventLength = duration * cps;
const end = begin + eventLength; const end = begin + eventLength;
const cycle = begin + secondsSinceLastTick * cps; const cycle = begin + secondsSinceLastTick * cps;
sendMessage('tick', { sendMessage('tick', {
begin, begin,
end, end,
cps, cps,
tickdeadline, time,
num_cycles_at_cps_change,
num_seconds_at_cps_change,
num_seconds_since_cps_change,
cycle, cycle,
}); });
num_ticks_since_cps_change++; num_ticks_since_cps_change++;
+58 -12
View File
@@ -47,12 +47,16 @@ export function createParam(names) {
return func; return func;
} }
// maps control alias names to the "main" control name
const controlAlias = new Map();
export function registerControl(names, ...aliases) { export function registerControl(names, ...aliases) {
const name = Array.isArray(names) ? names[0] : names; const name = Array.isArray(names) ? names[0] : names;
let bag = {}; let bag = {};
bag[name] = createParam(names); bag[name] = createParam(names);
aliases.forEach((alias) => { aliases.forEach((alias) => {
bag[alias] = bag[name]; bag[alias] = bag[name];
controlAlias.set(alias, name);
Pattern.prototype[alias] = Pattern.prototype[name]; Pattern.prototype[alias] = Pattern.prototype[name];
}); });
return bag; return bag;
@@ -191,7 +195,7 @@ export const { attack, att } = registerControl('attack', 'att');
* note("c e g b g e") * note("c e g b g e")
* .fm(4) * .fm(4)
* .fmh("<1 2 1.5 1.61>") * .fmh("<1 2 1.5 1.61>")
* .scope() * ._scope()
* *
*/ */
export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh'); export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh');
@@ -205,7 +209,7 @@ export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh');
* @example * @example
* note("c e g b g e") * note("c e g b g e")
* .fm("<0 1 2 8 32>") * .fm("<0 1 2 8 32>")
* .scope() * ._scope()
* *
*/ */
export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm');
@@ -221,7 +225,7 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm');
* .fmdecay(.2) * .fmdecay(.2)
* .fmsustain(0) * .fmsustain(0)
* .fmenv("<exp lin>") * .fmenv("<exp lin>")
* .scope() * ._scope()
* *
*/ */
export const { fmenv } = registerControl('fmenv'); export const { fmenv } = registerControl('fmenv');
@@ -234,7 +238,7 @@ export const { fmenv } = registerControl('fmenv');
* note("c e g b g e") * note("c e g b g e")
* .fm(4) * .fm(4)
* .fmattack("<0 .05 .1 .2>") * .fmattack("<0 .05 .1 .2>")
* .scope() * ._scope()
* *
*/ */
export const { fmattack } = registerControl('fmattack'); export const { fmattack } = registerControl('fmattack');
@@ -248,7 +252,7 @@ export const { fmattack } = registerControl('fmattack');
* .fm(4) * .fm(4)
* .fmdecay("<.01 .05 .1 .2>") * .fmdecay("<.01 .05 .1 .2>")
* .fmsustain(.4) * .fmsustain(.4)
* .scope() * ._scope()
* *
*/ */
export const { fmdecay } = registerControl('fmdecay'); export const { fmdecay } = registerControl('fmdecay');
@@ -262,7 +266,7 @@ export const { fmdecay } = registerControl('fmdecay');
* .fm(4) * .fm(4)
* .fmdecay(.1) * .fmdecay(.1)
* .fmsustain("<1 .75 .5 0>") * .fmsustain("<1 .75 .5 0>")
* .scope() * ._scope()
* *
*/ */
export const { fmsustain } = registerControl('fmsustain'); export const { fmsustain } = registerControl('fmsustain');
@@ -392,7 +396,7 @@ export const { loop } = registerControl('loop');
* @synonyms loopb * @synonyms loopb
* @example * @example
* s("space").loop(1) * s("space").loop(1)
* .loopBegin("<0 .125 .25>").scope() * .loopBegin("<0 .125 .25>")._scope()
*/ */
export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb'); export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb');
/** /**
@@ -405,7 +409,7 @@ export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb');
* @synonyms loope * @synonyms loope
* @example * @example
* s("space").loop(1) * s("space").loop(1)
* .loopEnd("<1 .75 .5 .25>").scope() * .loopEnd("<1 .75 .5 .25>")._scope()
*/ */
export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); export const { loopEnd, loope } = registerControl('loopEnd', 'loope');
/** /**
@@ -454,9 +458,6 @@ export const { drive } = registerControl('drive');
*/ */
export const { channels, ch } = registerControl('channels', 'ch'); export const { channels, ch } = registerControl('channels', 'ch');
// superdirt only
export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
/** /**
* Phaser audio effect that approximates popular guitar pedals. * Phaser audio effect that approximates popular guitar pedals.
* *
@@ -468,7 +469,11 @@ export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
* .phaser("<1 2 4 8>") * .phaser("<1 2 4 8>")
* *
*/ */
export const { phaser, ph } = registerControl(['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph'); export const { phaserrate, ph, phaser } = registerControl(
['phaserrate', 'phaserdepth', 'phasercenter', 'phasersweep'],
'ph',
'phaser',
);
/** /**
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
@@ -800,10 +805,12 @@ export const { fanchor } = registerControl('fanchor');
* @example * @example
* note("a e") * note("a e")
* .vib("<.5 1 2 4 8 16>") * .vib("<.5 1 2 4 8 16>")
* ._scope()
* @example * @example
* // change the modulation depth with ":" * // change the modulation depth with ":"
* note("a e") * note("a e")
* .vib("<.5 1 2 4 8 16>:12") * .vib("<.5 1 2 4 8 16>:12")
* ._scope()
*/ */
export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', 'v'); export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', 'v');
/** /**
@@ -824,10 +831,12 @@ export const { noise } = registerControl('noise');
* @example * @example
* note("a e").vib(4) * note("a e").vib(4)
* .vibmod("<.25 .5 1 2 12>") * .vibmod("<.25 .5 1 2 12>")
* ._scope()
* @example * @example
* // change the vibrato frequency with ":" * // change the vibrato frequency with ":"
* note("a e") * note("a e")
* .vibmod("<.25 .5 1 2 12>:8") * .vibmod("<.25 .5 1 2 12>:8")
* ._scope()
*/ */
export const { vibmod, vmod } = registerControl(['vibmod', 'vib'], 'vmod'); export const { vibmod, vmod } = registerControl(['vibmod', 'vib'], 'vmod');
export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp'); export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp');
@@ -1379,6 +1388,17 @@ export const { compressorRelease } = registerControl('compressorRelease');
* *
*/ */
export const { speed } = registerControl('speed'); export const { speed } = registerControl('speed');
/**
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
*
* @name stretch
* @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards.
* @example
* s("gm_flute").stretch("1 2 .5")
*
*/
export const { stretch } = registerControl('stretch');
/** /**
* Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. * Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
* *
@@ -1389,6 +1409,7 @@ export const { speed } = registerControl('speed');
* @superdirtOnly * @superdirtOnly
* *
*/ */
export const { unit } = registerControl('unit'); export const { unit } = registerControl('unit');
/** /**
* Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as: * Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as:
@@ -1493,6 +1514,8 @@ export const { binshift } = registerControl('binshift');
export const { hbrick } = registerControl('hbrick'); export const { hbrick } = registerControl('hbrick');
export const { lbrick } = registerControl('lbrick'); export const { lbrick } = registerControl('lbrick');
export const { midichan } = registerControl('midichan'); export const { midichan } = registerControl('midichan');
export const { midimap } = registerControl('midimap');
export const { midiport } = registerControl('midiport');
export const { control } = registerControl('control'); export const { control } = registerControl('control');
export const { ccn } = registerControl('ccn'); export const { ccn } = registerControl('ccn');
export const { ccv } = registerControl('ccv'); export const { ccv } = registerControl('ccv');
@@ -1597,3 +1620,26 @@ export const ar = register('ar', (t, pat) => {
const [attack, release = attack] = t; const [attack, release = attack] = t;
return pat.set({ attack, release }); return pat.set({ attack, release });
}); });
export const getControlName = (alias) => {
if (controlAlias.has(alias)) {
return controlAlias.get(alias);
}
return alias;
};
/**
* Sets properties in a batch.
*
* @name as
* @param {Array} mapping the control names that are set
* @example
* "c:.5 a:1 f:.25 e:.8".as("note:clip")
*/
export const as = register('as', (mapping, pat) => {
return pat.fmap((v) => {
v = Array.isArray(v) ? v : [v];
v = Object.fromEntries(mapping.map((prop, i) => [getControlName(prop), v[i]]));
return v;
});
});
+20 -4
View File
@@ -8,8 +8,19 @@ import createClock from './zyklus.mjs';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
export class Cyclist { export class Cyclist {
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1, setInterval, clearInterval }) { constructor({
interval,
onTrigger,
onToggle,
onError,
getTime,
latency = 0.1,
setInterval,
clearInterval,
beforeStart,
}) {
this.started = false; this.started = false;
this.beforeStart = beforeStart;
this.cps = 0.5; this.cps = 0.5;
this.num_ticks_since_cps_change = 0; this.num_ticks_since_cps_change = 0;
this.lastTick = 0; // absolute time when last tick (clock callback) happened this.lastTick = 0; // absolute time when last tick (clock callback) happened
@@ -57,6 +68,10 @@ export class Cyclist {
// see https://github.com/tidalcycles/strudel/pull/1004 // see https://github.com/tidalcycles/strudel/pull/1004
const deadline = targetTime - phase; const deadline = targetTime - phase;
onTrigger?.(hap, deadline, duration, this.cps, targetTime); onTrigger?.(hap, deadline, duration, this.cps, targetTime);
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
this.cps = hap.value.cps;
this.num_ticks_since_cps_change = 0;
}
} }
}); });
} catch (e) { } catch (e) {
@@ -82,7 +97,8 @@ export class Cyclist {
this.started = v; this.started = v;
this.onToggle?.(v); this.onToggle?.(v);
} }
start() { async start() {
await this.beforeStart?.();
this.num_ticks_since_cps_change = 0; this.num_ticks_since_cps_change = 0;
this.num_cycles_at_cps_change = 0; this.num_cycles_at_cps_change = 0;
if (!this.pattern) { if (!this.pattern) {
@@ -103,10 +119,10 @@ export class Cyclist {
this.lastEnd = 0; this.lastEnd = 0;
this.setStarted(false); this.setStarted(false);
} }
setPattern(pat, autostart = false) { async setPattern(pat, autostart = false) {
this.pattern = pat; this.pattern = pat;
if (autostart && !this.started) { if (autostart && !this.started) {
this.start(); await this.start();
} }
} }
setCps(cps = 0.5) { setCps(cps = 0.5) {
+3 -4
View File
@@ -4,8 +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/>. 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 { isPattern } from './index.mjs';
export const evalScope = async (...args) => { export const evalScope = async (...args) => {
const results = await Promise.allSettled(args); const results = await Promise.allSettled(args);
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value); const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
@@ -37,11 +35,12 @@ function safeEval(str, options = {}) {
return Function(body)(); return Function(body)();
} }
export const evaluate = async (code, transpiler) => { export const evaluate = async (code, transpiler, transpilerOptions) => {
let meta = {}; let meta = {};
if (transpiler) { if (transpiler) {
// transform syntactically correct js code to semantically usable code // transform syntactically correct js code to semantically usable code
const transpiled = transpiler(code); const transpiled = transpiler(code, transpilerOptions);
code = transpiled.output; code = transpiled.output;
meta = transpiled; meta = transpiled;
} }
+2 -2
View File
@@ -119,10 +119,10 @@ export const lcm = (...fractions) => {
if (fractions.length === 0) { if (fractions.length === 0) {
return undefined; return undefined;
} }
const x = fractions.pop();
return fractions.reduce( return fractions.reduce(
(lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)), (lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)),
fraction(1), x,
); );
}; };
+1
View File
@@ -14,6 +14,7 @@ export * from './controls.mjs';
export * from './hap.mjs'; export * from './hap.mjs';
export * from './pattern.mjs'; export * from './pattern.mjs';
export * from './signal.mjs'; export * from './signal.mjs';
export * from './pick.mjs';
export * from './state.mjs'; export * from './state.mjs';
export * from './timespan.mjs'; export * from './timespan.mjs';
export * from './util.mjs'; export * from './util.mjs';
+15 -55
View File
@@ -5,6 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
import { ClockCollator, cycleToSeconds } from './util.mjs';
export class NeoCyclist { export class NeoCyclist {
constructor({ onTrigger, onToggle, getTime }) { constructor({ onTrigger, onToggle, getTime }) {
@@ -13,79 +14,38 @@ export class NeoCyclist {
this.lastTick = 0; // absolute time when last tick (clock callback) happened this.lastTick = 0; // absolute time when last tick (clock callback) happened
this.getTime = getTime; // get absolute time this.getTime = getTime; // get absolute time
this.time_at_last_tick_message = 0; this.time_at_last_tick_message = 0;
// the clock of the worker and the audio context clock can drift apart over time
this.num_cycles_at_cps_change = 0; // aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
// we need to keep a rolling average of the time difference between the worker clock and audio context clock
// in order to schedule events consistently.
this.collator = new ClockCollator({ getTargetClockTime: getTime });
this.onToggle = onToggle; this.onToggle = onToggle;
this.latency = 0.1; // fixed trigger time offset this.latency = 0.1; // fixed trigger time offset
this.cycle = 0; this.cycle = 0;
this.id = Math.round(Date.now() * Math.random()); 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 = new SharedWorker(new URL('./clockworker.js', import.meta.url));
this.worker.port.start(); this.worker.port.start();
this.channel = new BroadcastChannel('strudeltick'); 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 tickCallback = (payload) => {
const { const { cps, begin, end, cycle, time } = payload;
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.cps = cps;
this.cycle = cycle; this.cycle = cycle;
const currentTime = this.collator.calculateOffset(time) + time;
setTimeReference(num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline); processHaps(begin, end, currentTime);
this.time_at_last_tick_message = currentTime;
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) => { const processHaps = (begin, end, currentTime) => {
if (this.started === false) { if (this.started === false) {
return; return;
} }
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps }); const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
haps.forEach((hap) => { haps.forEach((hap) => {
if (hap.hasOnset()) { if (hap.hasOnset()) {
const targetTime = const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps);
(hap.whole.begin - num_cycles_at_cps_change) / this.cps + const targetTime = timeUntilTrigger + currentTime + this.latency;
seconds_at_cps_change + const duration = cycleToSeconds(hap.duration, this.cps);
this.latency +
this.worker_time_dif;
const duration = hap.duration / this.cps;
onTrigger?.(hap, 0, duration, this.cps, targetTime); onTrigger?.(hap, 0, duration, this.cps, targetTime);
} }
}); });
@@ -129,8 +89,8 @@ export class NeoCyclist {
this.setStarted(true); this.setStarted(true);
} }
stop() { stop() {
this.worker_time_dif = null;
logger('[cyclist] stop'); logger('[cyclist] stop');
this.collator.reset();
this.setStarted(false); this.setStarted(false);
} }
setPattern(pat, autostart = false) { setPattern(pat, autostart = false) {
+3 -3
View File
@@ -31,11 +31,11 @@
}, },
"homepage": "https://strudel.cc", "homepage": "https://strudel.cc",
"dependencies": { "dependencies": {
"fraction.js": "^4.3.7" "fraction.js": "^5.2.1"
}, },
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2", "gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
"devDependencies": { "devDependencies": {
"vite": "^5.0.10", "vite": "^6.0.11",
"vitest": "^1.1.0" "vitest": "^3.0.4"
} }
} }
+552 -228
View File
File diff suppressed because it is too large Load Diff
+214
View File
@@ -0,0 +1,214 @@
/*
pick.mjs - methods that use one pattern to pick events from other patterns.
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.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, reify, silence, register } from './pattern.mjs';
import { _mod, clamp, objectMap } from './util.mjs';
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("<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 = function (lookup, pat) {
// backward compatibility - the args used to be flipped
if (Array.isArray(pat)) {
[pat, lookup] = [lookup, pat];
}
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}
* @example
* "<a@2 b@2 c@2 d@2>".pickRestart({
a: n("0 1 2 0"),
b: n("2 3 4 ~"),
c: n("[4 5] [4 3] 2 0"),
d: n("0 -3 0 ~")
}).scale("C:major").s("piano")
*/
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();
});
/**
/** * 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
* @returns {Pattern}
* @example
* note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g" , "g a c d"]))
*/
export const squeeze = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
}
return pat
.fmap((i) => {
const key = _mod(Math.round(i), xs.length);
return xs[key];
})
.squeezeJoin();
};
+52 -6
View File
@@ -10,6 +10,7 @@ export function repl({
defaultOutput, defaultOutput,
onEvalError, onEvalError,
beforeEval, beforeEval,
beforeStart,
afterEval, afterEval,
getTime, getTime,
transpiler, transpiler,
@@ -19,6 +20,7 @@ export function repl({
sync = false, sync = false,
setInterval, setInterval,
clearInterval, clearInterval,
id,
}) { }) {
const state = { const state = {
schedulerError: undefined, schedulerError: undefined,
@@ -32,6 +34,10 @@ export function repl({
started: false, started: false,
}; };
const transpilerOptions = {
id,
};
const updateState = (update) => { const updateState = (update) => {
Object.assign(state, update); Object.assign(state, update);
state.isDirty = state.code !== state.activeCode; state.isDirty = state.code !== state.activeCode;
@@ -48,6 +54,7 @@ export function repl({
}, },
setInterval, setInterval,
clearInterval, clearInterval,
beforeStart,
}; };
// NeoCyclist uses a shared worker to communicate between instances, which is not supported on mobile chrome // NeoCyclist uses a shared worker to communicate between instances, which is not supported on mobile chrome
@@ -56,17 +63,20 @@ export function repl({
let pPatterns = {}; let pPatterns = {};
let anonymousIndex = 0; let anonymousIndex = 0;
let allTransform; let allTransform;
let eachTransform;
const hush = function () { const hush = function () {
pPatterns = {}; pPatterns = {};
anonymousIndex = 0; anonymousIndex = 0;
allTransform = undefined; allTransform = undefined;
eachTransform = undefined;
return silence; return silence;
}; };
const setPattern = (pattern, autostart = true) => { const setPattern = async (pattern, autostart = true) => {
pattern = editPattern?.(pattern) || pattern; pattern = editPattern?.(pattern) || pattern;
scheduler.setPattern(pattern, autostart); await scheduler.setPattern(pattern, autostart);
return pattern;
}; };
setTime(() => scheduler.now()); // TODO: refactor? setTime(() => scheduler.now()); // TODO: refactor?
@@ -76,15 +86,42 @@ export function repl({
const toggle = () => scheduler.toggle(); const toggle = () => scheduler.toggle();
const setCps = (cps) => scheduler.setCps(cps); const setCps = (cps) => scheduler.setCps(cps);
const setCpm = (cpm) => scheduler.setCps(cpm / 60); const setCpm = (cpm) => scheduler.setCps(cpm / 60);
// TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`..
/** Applies a function to all the running patterns. Note that the patterns are groups together into a single `stack` before the function is applied. This is probably what you want, but see `each` for
* a version that applies the function to each pattern separately.
* ```
* $: sound("bd - cp sd")
* $: sound("hh*8")
* all(fast("<2 3>"))
* ```
* ```
* $: sound("bd - cp sd")
* $: sound("hh*8")
* all(x => x.pianoroll())
* ```
*/
const all = function (transform) { const all = function (transform) {
allTransform = transform; allTransform = transform;
return silence; return silence;
}; };
/** Applies a function to each of the running patterns separately. This is intended for future use with upcoming 'stepwise' features. See `all` for a version that applies the function to all the patterns stacked together into a single pattern.
* ```
* $: sound("bd - cp sd")
* $: sound("hh*8")
* each(fast("<2 3>"))
* ```
*/
const each = function (transform) {
eachTransform = transform;
return silence;
};
// set pattern methods that use this repl via closure // set pattern methods that use this repl via closure
const injectPatternMethods = () => { const injectPatternMethods = () => {
Pattern.prototype.p = function (id) { Pattern.prototype.p = function (id) {
if (id.startsWith('_') || id.endsWith('_')) { if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) {
// allows muting a pattern x with x_ or _x // allows muting a pattern x with x_ or _x
return silence; return silence;
} }
@@ -123,6 +160,7 @@ export function repl({
}); });
return evalScope({ return evalScope({
all, all,
each,
hush, hush,
cpm, cpm,
setCps, setCps,
@@ -139,11 +177,19 @@ export function repl({
try { try {
updateState({ code, pending: true }); updateState({ code, pending: true });
await injectPatternMethods(); await injectPatternMethods();
setTime(() => scheduler.now()); // TODO: refactor?
await beforeEval?.({ code }); await beforeEval?.({ code });
shouldHush && hush(); shouldHush && hush();
let { pattern, meta } = await _evaluate(code, transpiler); let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
if (Object.keys(pPatterns).length) { if (Object.keys(pPatterns).length) {
pattern = stack(...Object.values(pPatterns)); let patterns = Object.values(pPatterns);
if (eachTransform) {
// Explicit lambda so only element (not index and array) are passed
patterns = patterns.map((x) => eachTransform(x));
}
pattern = stack(...patterns);
} else if (eachTransform) {
pattern = eachTransform(pattern);
} }
if (allTransform) { if (allTransform) {
pattern = allTransform(pattern); pattern = allTransform(pattern);
@@ -153,7 +199,7 @@ export function repl({
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.')); throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
} }
logger(`[eval] code updated`); logger(`[eval] code updated`);
setPattern(pattern, autostart); pattern = await setPattern(pattern, autostart);
updateState({ updateState({
miniLocations: meta?.miniLocations || [], miniLocations: meta?.miniLocations || [],
widgets: meta?.widgets || [], widgets: meta?.widgets || [],
+244 -225
View File
@@ -1,13 +1,14 @@
/* /*
signal.mjs - <short description TODO> signal.mjs - continuous patterns
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs> Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.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/>. 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 { Hap } from './hap.mjs'; import { Hap } from './hap.mjs';
import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs'; import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs';
import Fraction from './fraction.mjs'; import Fraction from './fraction.mjs';
import { id, _mod, clamp, objectMap } from './util.mjs';
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
export function steady(value) { export function steady(value) {
// A continuous value // A continuous value
@@ -15,13 +16,10 @@ export function steady(value) {
} }
export const signal = (func) => { export const signal = (func) => {
const query = (state) => [new Hap(undefined, state.span, func(state.span.midpoint()))]; const query = (state) => [new Hap(undefined, state.span, func(state.span.begin))];
return new Pattern(query); return new Pattern(query);
}; };
export const isaw = signal((t) => 1 - (t % 1));
export const isaw2 = isaw.toBipolar();
/** /**
* A sawtooth signal between 0 and 1. * A sawtooth signal between 0 and 1.
* *
@@ -35,8 +33,40 @@ export const isaw2 = isaw.toBipolar();
* *
*/ */
export const saw = signal((t) => t % 1); export const saw = signal((t) => t % 1);
/**
* A sawtooth signal between -1 and 1 (like `saw`, but bipolar).
*
* @return {Pattern}
*/
export const saw2 = saw.toBipolar(); export const saw2 = saw.toBipolar();
/**
* A sawtooth signal between 1 and 0 (like `saw`, but flipped).
*
* @return {Pattern}
* @example
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
* .clip(isaw.slow(2))
* @example
* n(isaw.range(0,8).segment(8))
* .scale('C major')
*
*/
export const isaw = signal((t) => 1 - (t % 1));
/**
* A sawtooth signal between 1 and -1 (like `saw2`, but flipped).
*
* @return {Pattern}
*/
export const isaw2 = isaw.toBipolar();
/**
* A sine signal between -1 and 1 (like `sine`, but bipolar).
*
* @return {Pattern}
*/
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t)); export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
/** /**
@@ -60,6 +90,12 @@ export const sine = sine2.fromBipolar();
* *
*/ */
export const cosine = sine._early(Fraction(1).div(4)); export const cosine = sine._early(Fraction(1).div(4));
/**
* A cosine signal between -1 and 1 (like `cosine`, but bipolar).
*
* @return {Pattern}
*/
export const cosine2 = sine2._early(Fraction(1).div(4)); export const cosine2 = sine2._early(Fraction(1).div(4));
/** /**
@@ -71,6 +107,12 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
* *
*/ */
export const square = signal((t) => Math.floor((t * 2) % 2)); export const square = signal((t) => Math.floor((t * 2) % 2));
/**
* A square signal between -1 and 1 (like `square`, but bipolar).
*
* @return {Pattern}
*/
export const square2 = square.toBipolar(); export const square2 = square.toBipolar();
/** /**
@@ -81,9 +123,37 @@ export const square2 = square.toBipolar();
* n(tri.segment(8).range(0,7)).scale("C:minor") * n(tri.segment(8).range(0,7)).scale("C:minor")
* *
*/ */
export const tri = fastcat(isaw, saw); export const tri = fastcat(saw, isaw);
export const tri2 = fastcat(isaw2, saw2);
/**
* A triangle signal between -1 and 1 (like `tri`, but bipolar).
*
* @return {Pattern}
*/
export const tri2 = fastcat(saw2, isaw2);
/**
* An inverted triangle signal between 1 and 0 (like `tri`, but flipped).
*
* @return {Pattern}
* @example
* n(itri.segment(8).range(0,7)).scale("C:minor")
*
*/
export const itri = fastcat(isaw, saw);
/**
* An inverted triangle signal between -1 and 1 (like `itri`, but bipolar).
*
* @return {Pattern}
*/
export const itri2 = fastcat(isaw2, saw2);
/**
* A signal representing the cycle time.
*
* @return {Pattern}
*/
export const time = signal(id); export const time = signal(id);
/** /**
@@ -138,7 +208,7 @@ const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x)));
const timeToRandsPrime = (seed, n) => { const timeToRandsPrime = (seed, n) => {
const result = []; const result = [];
// eslint-disable-next-line // eslint-disable-next-line
for (let i = 0; i < n; ++n) { for (let i = 0; i < n; ++i) {
result.push(intSeedToRand(seed)); result.push(intSeedToRand(seed));
seed = xorwise(seed); seed = xorwise(seed);
} }
@@ -157,7 +227,82 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
* n(run(4)).scale("C4:pentatonic") * n(run(4)).scale("C4:pentatonic")
* // n("0 1 2 3").scale("C4:pentatonic") * // n("0 1 2 3").scale("C4:pentatonic")
*/ */
export const run = (n) => saw.range(0, n).floor().segment(n); export const run = (n) => saw.range(0, n).round().segment(n);
/**
* Creates a pattern from a binary number.
*
* @name binary
* @param {number} n - input number to convert to binary
* @example
* "hh".s().struct(binary(5))
* // "hh".s().struct("1 0 1")
*/
export const binary = (n) => {
const nBits = reify(n).log2(0).floor().add(1);
return binaryN(n, nBits);
};
/**
* Creates a pattern from a binary number, padded to n bits long.
*
* @name binaryN
* @param {number} n - input number to convert to binary
* @param {number} nBits - pattern length, defaults to 16
* @example
* "hh".s().struct(binaryN(55532, 16))
* // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0")
*/
export const binaryN = (n, nBits = 16) => {
nBits = reify(nBits);
// Shift and mask, putting msb on the right-side
const bitPos = run(nBits).mul(-1).add(nBits.sub(1));
return reify(n).segment(nBits).brshift(bitPos).band(pure(1));
};
export const randrun = (n) => {
return signal((t) => {
// Without adding 0.5, the first cycle is always 0,1,2,3,...
const rands = timeToRands(t.floor().add(0.5), n);
const nums = rands
.map((n, i) => [n, i])
.sort((a, b) => a[0] > b[0] - a[0] < b[0])
.map((x) => x[1]);
const i = t.cyclePos().mul(n).floor() % n;
return nums[i];
})._segment(n);
};
const _rearrangeWith = (ipat, n, pat) => {
const pats = [...Array(n).keys()].map((i) => pat.zoom(Fraction(i).div(n), Fraction(i + 1).div(n)));
return ipat.fmap((i) => pats[i].repeatCycles(n)._fast(n)).innerJoin();
};
/**
* @name shuffle
* Slices a pattern into the given number of parts, then plays those parts in random order.
* Each part will be played exactly once per cycle.
* @example
* note("c d e f").sound("piano").shuffle(4)
* @example
* note("c d e f".shuffle(4), "g").sound("piano")
*/
export const shuffle = register('shuffle', (n, pat) => {
return _rearrangeWith(randrun(n), n, pat);
});
/**
* @name scramble
* Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`,
* but parts might be played more than once, or not at all, per cycle.
* @example
* note("c d e f").sound("piano").scramble(4)
* @example
* note("c d e f".scramble(4), "g").sound("piano")
*/
export const scramble = register('scramble', (n, pat) => {
return _rearrangeWith(_irand(n)._segment(n), n, pat);
});
/** /**
* A continuous pattern of random numbers, between 0 and 1. * A continuous pattern of random numbers, between 0 and 1.
@@ -209,204 +354,6 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
*/ */
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
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("<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 = function (lookup, pat) {
// backward compatibility - the args used to be flipped
if (Array.isArray(pat)) {
[pat, lookup] = [lookup, pat];
}
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();
});
/**
/** * 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
* @returns {Pattern}
* @example
* note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g" , "g a c d"]))
*/
export const squeeze = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
}
return pat
.fmap((i) => {
const key = _mod(Math.round(i), xs.length);
return xs[key];
})
.squeezeJoin();
};
export const __chooseWith = (pat, xs) => { export const __chooseWith = (pat, xs) => {
xs = xs.map(reify); xs = xs.map(reify);
if (xs.length == 0) { if (xs.length == 0) {
@@ -486,19 +433,30 @@ export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
export const randcat = chooseCycles; export const randcat = chooseCycles;
const _wchooseWith = function (pat, ...pairs) { const _wchooseWith = function (pat, ...pairs) {
// A list of patterns of values
const values = pairs.map((pair) => reify(pair[0])); const values = pairs.map((pair) => reify(pair[0]));
// A list of weight patterns
const weights = []; const weights = [];
let accum = 0;
let total = pure(0);
for (const pair of pairs) { for (const pair of pairs) {
accum += pair[1]; // 'add' accepts either values or patterns of values here, so no need
weights.push(accum); // to explicitly reify
total = total.add(pair[1]);
// accumulate our list of weight patterns
weights.push(total);
} }
const total = accum; // a pattern of lists of weights
const weightspat = sequenceP(weights);
// Takes a number from 0-1, returns a pattern of patterns of values
const match = function (r) { const match = function (r) {
const find = r * total; const findpat = total.mul(r);
return values[weights.findIndex((x) => x > find, weights)]; return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat);
}; };
return pat.fmap(match); // This returns a pattern of patterns.. The innerJoin is in wchooseCycles
return pat.bind(match);
}; };
const wchooseWith = (...args) => _wchooseWith(...args).outerJoin(); const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
@@ -520,6 +478,9 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8) * wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example * @example
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s() * wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
* @example
* // The probability can itself be a pattern
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
*/ */
export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin(); export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin();
@@ -545,8 +506,11 @@ export const perlinWith = (pat) => {
*/ */
export const perlin = perlinWith(time.fmap((v) => Number(v))); export const perlin = perlinWith(time.fmap((v) => Number(v)));
export const degradeByWith = register('degradeByWith', (withPat, x, pat) => export const degradeByWith = register(
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)), 'degradeByWith',
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
true,
true,
); );
/** /**
@@ -563,9 +527,14 @@ export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
* @example * @example
* s("[hh?0.2]*8") * s("[hh?0.2]*8")
*/ */
export const degradeBy = register('degradeBy', function (x, pat) { export const degradeBy = register(
'degradeBy',
function (x, pat) {
return pat._degradeByWith(rand, x); return pat._degradeByWith(rand, x);
}); },
true,
true,
);
/** /**
* *
@@ -579,7 +548,7 @@ export const degradeBy = register('degradeBy', function (x, pat) {
* @example * @example
* s("[hh?]*8") * s("[hh?]*8")
*/ */
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5)); export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, true);
/** /**
* Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount. * Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount.
@@ -599,12 +568,17 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
* x => x.undegradeBy(0.8).pan(1) * x => x.undegradeBy(0.8).pan(1)
* ) * )
*/ */
export const undegradeBy = register('undegradeBy', function (x, pat) { export const undegradeBy = register(
'undegradeBy',
function (x, pat) {
return pat._degradeByWith( return pat._degradeByWith(
rand.fmap((r) => 1 - r), rand.fmap((r) => 1 - r),
x, x,
); );
}); },
true,
true,
);
/** /**
* Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)` * Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)`
@@ -621,7 +595,7 @@ export const undegradeBy = register('undegradeBy', function (x, pat) {
* x => x.undegrade().pan(1) * x => x.undegrade().pan(1)
* ) * )
*/ */
export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5)); export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), true, true);
/** /**
* *
@@ -780,3 +754,48 @@ export const never = register('never', function (_, pat) {
export const always = register('always', function (func, pat) { export const always = register('always', function (func, pat) {
return func(pat); return func(pat);
}); });
//keyname: string | Array<string>
//keyname reference: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
export function _keyDown(keyname) {
if (Array.isArray(keyname) === false) {
keyname = [keyname];
}
const keyState = getCurrentKeyboardState();
return keyname.every((x) => {
const keyName = keyAlias.get(x) ?? x;
return keyState[keyName];
});
}
/**
*
* Do something on a keypress, or array of keypresses
* [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
*
* @name whenKey
* @memberof Pattern
* @returns Pattern
* @example
* s("bd(5,8)").whenKey("Control:j", x => x.segment(16).color("red")).whenKey("Control:i", x => x.fast(2).color("blue"))
*/
export const whenKey = register('whenKey', function (input, func, pat) {
return pat.when(_keyDown(input), func);
});
/**
*
* returns true when a key or array of keys is held
* [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
*
* @name keyDown
* @memberof Pattern
* @returns Pattern
* @example
* keyDown("Control:j").pick([s("bd(5,8)"), s("cp(3,8)")])
*/
export const keyDown = register('keyDown', function (pat) {
return pat.fmap(_keyDown);
});
+11 -7
View File
@@ -4,7 +4,7 @@ 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/>. 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 { s, pan } from '../controls.mjs'; import { s, pan, getControlName } from '../controls.mjs';
import { mini } from '../../mini/mini.mjs'; import { mini } from '../../mini/mini.mjs';
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import Fraction from '../fraction.mjs'; import Fraction from '../fraction.mjs';
@@ -30,13 +30,17 @@ describe('controls', () => {
expect(s(mini('bd').pan(1)).firstCycleValues).toEqual([{ s: 'bd', pan: 1 }]); 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 }]); expect(s(mini('bd:1').pan(1)).firstCycleValues).toEqual([{ s: 'bd', n: 1, pan: 1 }]);
}); });
it('preserves tactus of the left pattern', () => { it('preserves step count of the left pattern', () => {
expect(s(mini('bd cp mt').pan(mini('1 2 3 4'))).tactus).toEqual(Fraction(3)); expect(s(mini('bd cp mt').pan(mini('1 2 3 4')))._steps).toEqual(Fraction(3));
}); });
it('preserves tactus of the right pattern for .out', () => { it('preserves step count of the right pattern for .out', () => {
expect(s(mini('bd cp mt').set.out(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(4)); expect(s(mini('bd cp mt').set.out(pan(mini('1 2 3 4'))))._steps).toEqual(Fraction(4));
}); });
it('combines tactus of the pattern for .mix as lcm', () => { it('combines step count 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)); expect(s(mini('bd cp mt').set.mix(pan(mini('1 2 3 4'))))._steps).toEqual(Fraction(12));
});
it('finds control name by alias', () => {
expect(getControlName('lpf')).toEqual('cutoff');
expect(getControlName('cutoff')).toEqual('cutoff');
}); });
}); });
+134 -76
View File
@@ -21,8 +21,7 @@ import {
cat, cat,
sequence, sequence,
palindrome, palindrome,
s_polymeter, polymeter,
s_polymeterSteps,
polyrhythm, polyrhythm,
silence, silence,
fast, fast,
@@ -46,12 +45,13 @@ import {
rev, rev,
time, time,
run, run,
binaryN,
pick, pick,
stackLeft, stackLeft,
stackRight, stackRight,
stackCentre, stackCentre,
s_cat, stepcat,
calculateTactus, sometimes,
} from '../index.mjs'; } from '../index.mjs';
import { steady } from '../signal.mjs'; import { steady } from '../signal.mjs';
@@ -608,19 +608,12 @@ describe('Pattern', () => {
); );
}); });
}); });
describe('s_polymeter()', () => { describe('polymeter()', () => {
it('Can layer up cycles, stepwise, with lists', () => { it('Can layer up cycles, stepwise, with lists', () => {
expect(s_polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual( expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
fastcat(pure('d'), pure('e'), pure('d')).firstCycle(),
);
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(), 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()', () => { describe('firstOf()', () => {
@@ -735,21 +728,15 @@ describe('Pattern', () => {
describe('signal()', () => { describe('signal()', () => {
it('Can make saw/saw2', () => { it('Can make saw/saw2', () => {
expect(saw.struct(true, true, true, true).firstCycle()).toStrictEqual( expect(saw.struct(true, true, true, true).firstCycle()).toStrictEqual(
sequence(1 / 8, 3 / 8, 5 / 8, 7 / 8).firstCycle(), sequence(0, 1 / 4, 1 / 2, 3 / 4).firstCycle(),
); );
expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual( expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(-1, -0.5, 0, 0.5).firstCycle());
sequence(-3 / 4, -1 / 4, 1 / 4, 3 / 4).firstCycle(),
);
}); });
it('Can make isaw/isaw2', () => { it('Can make isaw/isaw2', () => {
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual( expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.75, 0.5, 0.25).firstCycle());
sequence(7 / 8, 5 / 8, 3 / 8, 1 / 8).firstCycle(),
);
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual( expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.5, 0, -0.5).firstCycle());
sequence(3 / 4, 1 / 4, -1 / 4, -3 / 4).firstCycle(),
);
}); });
}); });
describe('_setContext()', () => { describe('_setContext()', () => {
@@ -887,7 +874,7 @@ describe('Pattern', () => {
.squeezeJoin() .squeezeJoin()
.queryArc(3, 4) .queryArc(3, 4)
.map((x) => x.value), .map((x) => x.value),
).toStrictEqual([Fraction(3.5)]); ).toStrictEqual([Fraction(3)]);
}); });
}); });
describe('ply', () => { describe('ply', () => {
@@ -937,6 +924,9 @@ describe('Pattern', () => {
.firstCycle(), .firstCycle(),
); );
}); });
it('Can chop chops', () => {
expect(pure({ s: 'bev' }).chop(2).chop(2).firstCycle()).toStrictEqual(pure({ s: 'bev' }).chop(4).firstCycle());
});
}); });
describe('range', () => { describe('range', () => {
it('Can be patterned', () => { it('Can be patterned', () => {
@@ -955,6 +945,18 @@ describe('Pattern', () => {
expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle()); expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle());
}); });
}); });
describe('binaryN', () => {
it('Can make a binary pattern from a decimal', () => {
expect(binaryN(55532).firstCycle()).toStrictEqual(
sequence(1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0).firstCycle(),
);
});
it('Can make a binary pattern from patterned inputs', () => {
expect(binaryN(pure(0x1337), pure(14)).firstCycle()).toStrictEqual(
sequence(0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1).firstCycle(),
);
});
});
describe('ribbon', () => { describe('ribbon', () => {
it('Can ribbon', () => { it('Can ribbon', () => {
expect(cat(0, 1, 2, 3, 4, 5, 6, 7).ribbon(2, 4).fast(4).firstCycle()).toStrictEqual( expect(cat(0, 1, 2, 3, 4, 5, 6, 7).ribbon(2, 4).fast(4).firstCycle()).toStrictEqual(
@@ -1124,89 +1126,145 @@ describe('Pattern', () => {
); );
}); });
}); });
describe('tactus', () => { describe('_steps', () => {
it('Is correctly preserved/calculated through transformations', () => { 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).linger(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).iter(4).tactus).toStrictEqual(Fraction(4)); expect(sequence(0, 1, 2, 3).iter(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).fast(4).tactus).toStrictEqual(Fraction(4)); expect(sequence(0, 1, 2, 3).fast(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).hurry(4).tactus).toStrictEqual(Fraction(4)); expect(sequence(0, 1, 2, 3).hurry(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).rev().tactus).toStrictEqual(Fraction(4)); expect(sequence(0, 1, 2, 3).rev()._steps).toStrictEqual(Fraction(4));
expect(sequence(1).segment(10).tactus).toStrictEqual(Fraction(10)); expect(sequence(1).segment(10)._steps).toStrictEqual(Fraction(10));
expect(sequence(1, 0, 1).invert().tactus).toStrictEqual(Fraction(3)); expect(sequence(1, 0, 1).invert()._steps).toStrictEqual(Fraction(3));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4).tactus).toStrictEqual(Fraction(8)); expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4)._steps).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4).tactus).toStrictEqual(Fraction(8)); expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4)._steps).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual( expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3))._steps).toStrictEqual(
Fraction(4), Fraction(4),
); );
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).splice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual( expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).splice(4, sequence(0, 1, 2, 3))._steps).toStrictEqual(
Fraction(4), Fraction(4),
); );
expect(sequence({ n: 0 }, { n: 1 }, { n: 2 }).chop(4).tactus).toStrictEqual(Fraction(12)); expect(sequence({ n: 0 }, { n: 1 }, { n: 2 }).chop(4)._steps).toStrictEqual(Fraction(12));
expect( expect(
pure((x) => x + 1) pure((x) => x + 1)
.setTactus(3) .setSteps(3)
.appBoth(pure(1).setTactus(2)).tactus, .appBoth(pure(1).setSteps(2))._steps,
).toStrictEqual(Fraction(6)); ).toStrictEqual(Fraction(6));
expect( expect(
pure((x) => x + 1) pure((x) => x + 1)
.setTactus(undefined) .setSteps(undefined)
.appBoth(pure(1).setTactus(2)).tactus, .appBoth(pure(1).setSteps(2))._steps,
).toStrictEqual(Fraction(2)); ).toStrictEqual(Fraction(2));
expect( expect(
pure((x) => x + 1) pure((x) => x + 1)
.setTactus(3) .setSteps(3)
.appBoth(pure(1).setTactus(undefined)).tactus, .appBoth(pure(1).setSteps(undefined))._steps,
).toStrictEqual(Fraction(3)); ).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))._steps).toStrictEqual(Fraction(6));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setTactus(undefined)).tactus).toStrictEqual(Fraction(3)); expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setSteps(undefined))._steps).toStrictEqual(Fraction(3));
expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4)).tactus).toStrictEqual(Fraction(4)); expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4))._steps).toStrictEqual(Fraction(4));
expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3)); expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3));
// maybe this should double when they are either all even or all odd // 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(stackCentre(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3));
expect(fastcat(0, 1).ply(3).tactus).toStrictEqual(Fraction(6)); expect(fastcat(0, 1).ply(3)._steps).toStrictEqual(Fraction(6));
expect(fastcat(0, 1).setTactus(undefined).ply(3).tactus).toStrictEqual(undefined); expect(fastcat(0, 1).setSteps(undefined).ply(3)._steps).toStrictEqual(undefined);
expect(fastcat(0, 1).fast(3).tactus).toStrictEqual(Fraction(2)); expect(fastcat(0, 1).fast(3)._steps).toStrictEqual(Fraction(2));
expect(fastcat(0, 1).setTactus(undefined).fast(3).tactus).toStrictEqual(undefined); expect(fastcat(0, 1).setSteps(undefined).fast(3)._steps).toStrictEqual(undefined);
}); });
}); });
describe('s_cat', () => { describe('stepcat', () => {
it('can 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(stepcat(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))); expect(sameFirst(stepcat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3)));
}); });
it('calculates undefined tactuses as the average', () => { it('calculates undefined steps as the average', () => {
expect(sameFirst(s_cat(pure(1), pure(2), pure(3).setTactus(undefined)), fastcat(1, 2, 3))); expect(sameFirst(stepcat(pure(1), pure(2), pure(3).setSteps(undefined)), fastcat(1, 2, 3)));
}); });
}); });
describe('s_taper', () => { describe('shrink', () => {
it('can taper', () => { it('can shrink', () => {
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))); expect(sameFirst(sequence(0, 1, 2, 3, 4).shrink(1), sequence(0, 1, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 3, 4, 4)));
}); });
it('can taper backwards', () => { it('can shrink 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))); expect(sameFirst(sequence(0, 1, 2, 3, 4).shrink(-1), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0)));
}); });
}); });
describe('s_add and s_sub', () => { describe('grow', () => {
it('can add from the left', () => { it('can grow', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(2), sequence(0, 1))); expect(sameFirst(sequence(0, 1, 2, 3, 4).grow(1), sequence(0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4)));
}); });
it('can sub to the left', () => { it('can grow backwards', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(2), sequence(0, 1, 2))); expect(sameFirst(sequence(0, 1, 2, 3, 4).grow(-1), sequence(4, 3, 4, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3, 4)));
}); });
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', () => { describe('take and drop', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(-2), sequence(2, 3, 4))); it('can take from the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).take(2), sequence(0, 1)));
}); });
it('can subtract nothing', () => { it('can drop from the left', () => {
expect(sameFirst(pure('a').s_sub(0), pure('a'))); expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(2), sequence(2, 3, 4)));
}); });
it('can subtract nothing, repeatedly', () => { it('can take from the right', () => {
expect(sameFirst(pure('a').s_sub(0, 0), fastcat('a', 'a'))); expect(sameFirst(sequence(0, 1, 2, 3, 4).take(-2), sequence(3, 4)));
});
it('can drop from the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(-2), sequence(0, 1, 2)));
});
it('can drop nothing', () => {
expect(sameFirst(pure('a').drop(0), pure('a')));
});
it('can drop nothing, repeatedly', () => {
expect(sameFirst(pure('a').drop(0, 0), fastcat('a', 'a')));
for (var i = 0; i < 100; ++i) { for (var i = 0; i < 100; ++i) {
expect(sameFirst(pure('a').s_sub(...Array(i).fill(0)), fastcat(...Array(i).fill('a')))); expect(sameFirst(pure('a').drop(...Array(i).fill(0)), fastcat(...Array(i).fill('a'))));
} }
}); });
}); });
describe('expand', () => {
it('can expand four things in half', () => {
expect(
sameFirst(sequence(0, 1, 2, 3).expand(1, 0.5), stepcat(sequence(0, 1, 2, 3), sequence(0, 1, 2, 3).expand(0.5))),
);
});
it('can expand five things in half', () => {
expect(
sameFirst(
sequence(0, 1, 2, 3, 4).expand(1, 0.5),
stepcat(sequence(0, 1, 2, 3, 4), sequence(0, 1, 2, 3, 4).expand(0.5)),
),
);
});
});
describe('stepJoin', () => {
it('can join a pattern with steps of 2', () => {
expect(
sameFirst(
sequence(pure(pure('a')), pure(pure('b').setSteps(2))).stepJoin(),
stepcat(pure('a'), pure('b').setSteps(2)),
),
);
});
it('can join a pattern with steps of 0.5', () => {
expect(
sameFirst(
sequence(pure(pure('a')), pure(pure('b').setSteps(0.5))).stepJoin(),
stepcat(pure('a'), pure('b').setSteps(0.5)),
),
);
});
});
describe('loopAt', () => {
it('maintains steps', () => {
expect(s('bev').chop(8).loopAt(2)._steps).toStrictEqual(Fraction(4));
});
});
describe('bite', () => {
it('works with uneven patterns', () => {
sameFirst(
fastcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5))
.bite(2, stepcat(pure(0), pure(1).expand(2)))
.fast(5),
stepcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5).expand(2)).fast(5),
);
});
});
}); });
+105
View File
@@ -91,6 +91,9 @@ 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) // 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 const _mod = (n, m) => ((n % m) + m) % m;
// average numbers in an array
export const averageArray = (arr) => arr.reduce((a, b) => a + b) / arr.length;
export function nanFallback(value, fallback = 0) { export function nanFallback(value, fallback = 0) {
if (isNaN(Number(value))) { if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning'); logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
@@ -360,6 +363,108 @@ export function objectMap(obj, fn) {
} }
return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)])); return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]));
} }
export function cycleToSeconds(cycle, cps) {
return cycle / cps;
}
// utility for averaging two clocks together to account for drift
export class ClockCollator {
constructor({
getTargetClockTime = getUnixTimeSeconds,
weight = 16,
offsetDelta = 0.005,
checkAfterTime = 2,
resetAfterTime = 8,
}) {
this.offsetTime;
this.timeAtPrevOffsetSample;
this.prevOffsetTimes = [];
this.getTargetClockTime = getTargetClockTime;
this.weight = weight;
this.offsetDelta = offsetDelta;
this.checkAfterTime = checkAfterTime;
this.resetAfterTime = resetAfterTime;
this.reset = () => {
this.prevOffsetTimes = [];
this.offsetTime = null;
this.timeAtPrevOffsetSample = null;
};
}
calculateOffset(currentTime) {
const targetClockTime = this.getTargetClockTime();
const diffBetweenTimeSamples = targetClockTime - this.timeAtPrevOffsetSample;
const newOffsetTime = targetClockTime - currentTime;
// recalcuate the diff from scratch if the clock has been paused for some time.
if (diffBetweenTimeSamples > this.resetAfterTime) {
this.reset();
}
if (this.offsetTime == null) {
this.offsetTime = newOffsetTime;
}
this.prevOffsetTimes.push(newOffsetTime);
if (this.prevOffsetTimes.length > this.weight) {
this.prevOffsetTimes.shift();
}
// after X time has passed, the average of the previous weight offset times is calculated and used as a stable reference
// for calculating the timestamp
if (this.timeAtPrevOffsetSample == null || diffBetweenTimeSamples > this.checkAfterTime) {
this.timeAtPrevOffsetSample = targetClockTime;
const rollingOffsetTime = averageArray(this.prevOffsetTimes);
//when the clock offsets surpass the delta, set the new reference time
if (Math.abs(rollingOffsetTime - this.offsetTime) > this.offsetDelta) {
this.offsetTime = rollingOffsetTime;
}
}
return this.offsetTime;
}
calculateTimestamp(currentTime, targetTime) {
return this.calculateOffset(currentTime) + targetTime;
}
}
export function getPerformanceTimeSeconds() {
return performance.now() * 0.001;
}
function getUnixTimeSeconds() {
return Date.now() * 0.001;
}
export const keyAlias = new Map([
['control', 'Control'],
['ctrl', 'Control'],
['alt', 'Alt'],
['shift', 'Shift'],
['down', 'ArrowDown'],
['up', 'ArrowUp'],
['left', 'ArrowLeft'],
['right', 'ArrowRight'],
]);
let keyState;
export function getCurrentKeyboardState() {
if (keyState == null) {
if (typeof window === 'undefined') {
return;
}
keyState = {};
// Listen for the keydown event to mark the key as pressed
window.addEventListener('keydown', (event) => {
keyState[event.key] = true; // Mark the key as pressed
});
// Listen for the keyup event to mark the key as released
window.addEventListener('keyup', (event) => {
keyState[event.key] = false; // Mark the key as released
});
}
return { ...keyState }; // Return a shallow copy of the key state object
}
// Floating point versions, see Fraction for rational versions // Floating point versions, see Fraction for rational versions
// // greatest common divisor // // greatest common divisor
+1
View File
@@ -4,6 +4,7 @@ import { resolve } from 'path';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
base: './',
plugins: [], plugins: [],
build: { build: {
lib: { lib: {
+1 -1
View File
@@ -37,6 +37,6 @@
"@strudel/webaudio": "workspace:*" "@strudel/webaudio": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^6.0.11"
} }
} }
+12 -19
View File
@@ -1,19 +1,13 @@
import { parseNumeral, Pattern, getEventOffsetMs } from '@strudel/core'; import { Pattern, ClockCollator } from '@strudel/core';
import { parseControlsFromHap } from 'node_modules/@strudel/osc/osc.mjs';
import { Invoke } from './utils.mjs'; import { Invoke } from './utils.mjs';
Pattern.prototype.osc = function () { const collator = new ClockCollator({});
return this.onTrigger(async (time, hap, currentTime, cps = 1, targetTime) => {
hap.ensureObjectValue();
const cycle = hap.wholeOrPart().begin.valueOf();
const delta = hap.duration.valueOf();
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
// make sure n and note are numbers
controls.n && (controls.n = parseNumeral(controls.n));
controls.note && (controls.note = parseNumeral(controls.note));
export async function oscTriggerTauri(t_deprecate, hap, currentTime, cps = 1, targetTime) {
const controls = parseControlsFromHap(hap, cps);
const params = []; const params = [];
const timestamp = collator.calculateTimestamp(currentTime, targetTime);
const timestamp = Math.round(Date.now() + getEventOffsetMs(targetTime, currentTime));
Object.keys(controls).forEach((key) => { Object.keys(controls).forEach((key) => {
const val = controls[key]; const val = controls[key];
@@ -29,15 +23,14 @@ Pattern.prototype.osc = function () {
}); });
}); });
const messagesfromjs = []; if (params.length === 0) {
if (params.length) { return;
messagesfromjs.push({ target: '/dirt/play', timestamp, params });
} }
const message = { target: '/dirt/play', timestamp, params };
if (messagesfromjs.length) {
setTimeout(() => { setTimeout(() => {
Invoke('sendosc', { messagesfromjs }); Invoke('sendosc', { messagesfromjs: [message] });
}); });
} }
}); Pattern.prototype.osc = function () {
return this.onTrigger(oscTriggerTauri);
}; };
+1 -1
View File
@@ -23,7 +23,7 @@
}, },
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel/core": "workspace:*",
"@tauri-apps/api": "^1.5.3" "@tauri-apps/api": "^2.2.0"
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme" "homepage": "https://github.com/tidalcycles/strudel#readme"
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api/tauri'; import { invoke } from '@tauri-apps/api/core';
export const Invoke = invoke; export const Invoke = invoke;
export const isTauri = () => window.__TAURI_IPC__ != null; export const isTauri = () => window.__TAURI_IPC__ != null;
+24 -14
View File
@@ -26,7 +26,7 @@ export const getDrawContext = (id = 'test-canvas', options) => {
}, 200); }, 200);
}); });
} }
return canvas.getContext(contextType); return canvas.getContext(contextType, { willReadFrequently: true });
}; };
let animationFrames = {}; let animationFrames = {};
@@ -36,8 +36,8 @@ function stopAnimationFrame(id) {
delete animationFrames[id]; delete animationFrames[id];
} }
} }
function stopAllAnimations() { function stopAllAnimations(replID) {
Object.keys(animationFrames).forEach((id) => stopAnimationFrame(id)); Object.keys(animationFrames).forEach((id) => (!replID || id.startsWith(replID)) && stopAnimationFrame(id));
} }
let memory = {}; let memory = {};
@@ -72,18 +72,25 @@ Pattern.prototype.draw = function (fn, options) {
return this; return this;
}; };
export const cleanupDraw = (clearScreen = true) => { export const cleanupDraw = (clearScreen = true, id) => {
const ctx = getDrawContext(); const ctx = getDrawContext();
clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.width); clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
stopAllAnimations(); stopAllAnimations(id);
if (window.strudelScheduler) {
clearInterval(window.strudelScheduler);
}
}; };
Pattern.prototype.onPaint = function () { Pattern.prototype.onPaint = function (painter) {
console.warn('[draw] onPaint was not overloaded. Some drawings might not work'); return this.withState((state) => {
return this; if (!state.controls.painters) {
state.controls.painters = [];
}
state.controls.painters.push(painter);
});
};
Pattern.prototype.getPainters = function () {
let painters = [];
this.queryArc(0, 0, { painters });
return painters;
}; };
// const round = (x) => Math.round(x * 1000) / 1000; // const round = (x) => Math.round(x * 1000) / 1000;
@@ -122,6 +129,7 @@ export class Drawer {
this.visibleHaps = []; this.visibleHaps = [];
this.lastFrame = null; this.lastFrame = null;
this.drawTime = drawTime; this.drawTime = drawTime;
this.painters = [];
this.framer = new Framer( this.framer = new Framer(
() => { () => {
if (!this.scheduler) { if (!this.scheduler) {
@@ -146,7 +154,7 @@ export class Drawer {
// add new haps with onset (think right edge bars scrolling in) // add new haps with onset (think right edge bars scrolling in)
.concat(haps.filter((h) => h.hasOnset())); .concat(haps.filter((h) => h.hasOnset()));
const time = phase - lookahead; const time = phase - lookahead;
onDraw(this.visibleHaps, time, this); onDraw(this.visibleHaps, time, this, this.painters);
}, },
(err) => { (err) => {
console.warn('draw error', err); console.warn('draw error', err);
@@ -164,11 +172,13 @@ export class Drawer {
t = t ?? scheduler.now(); t = t ?? scheduler.now();
this.scheduler = scheduler; this.scheduler = scheduler;
let [_, lookahead] = this.drawTime; let [_, lookahead] = this.drawTime;
// +0.1 = workaround for weird holes in query..
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1]; const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
// remove all future haps // remove all future haps
this.visibleHaps = this.visibleHaps.filter((h) => h.whole.begin < t); this.visibleHaps = this.visibleHaps.filter((h) => h.whole.begin < t);
this.painters = []; // will get populated by .onPaint calls attached to the pattern
// query future haps // query future haps
const futureHaps = scheduler.pattern.queryArc(begin, end); // +0.1 = workaround for weird holes in query.. const futureHaps = scheduler.pattern.queryArc(begin, end, { painters: this.painters });
// append future haps // append future haps
this.visibleHaps = this.visibleHaps.concat(futureHaps); this.visibleHaps = this.visibleHaps.concat(futureHaps);
} }
+1 -1
View File
@@ -32,6 +32,6 @@
"@strudel/core": "workspace:*" "@strudel/core": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^6.0.11"
} }
} }
+51 -36
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Pattern, noteToMidi, freqToMidi } from '@strudel/core'; import { Pattern, noteToMidi, freqToMidi, isPattern } from '@strudel/core';
import { getTheme, getDrawContext } from './draw.mjs'; import { getTheme, getDrawContext } from './draw.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min; const scale = (normalized, min, max) => normalized * (max - min) + min;
@@ -36,37 +36,12 @@ const getValue = (e) => {
return value; return value;
}; };
Pattern.prototype.pianoroll = function (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(
(haps, time) => {
pianoroll({
...options,
time,
ctx,
haps: haps.filter((hap) => inFrame(hap, time)),
});
},
{
lookbehind: from - overscan,
lookahead: to + overscan,
id,
},
);
return this;
};
// this function allows drawing a pianoroll without ties to Pattern.prototype
// it will probably replace the above in the future
/** /**
* Displays a midi-style piano roll * Visualises a pattern as a scrolling 'pianoroll', displayed in the background of the editor. To show a pianoroll for all running patterns, use `all(pianoroll)`. To have a pianoroll appear below
* a pattern instead, prefix with `_`, e.g.: `sound("bd sd")._pianoroll()`.
* *
* @name pianoroll * @name pianoroll
* @synonyms punchcard
* @param {Object} options Object containing all the optional following parameters as key value pairs: * @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4 * @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4
* @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5 * @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5
@@ -92,11 +67,51 @@ Pattern.prototype.pianoroll = function (options = {}) {
* @param {integer} minMidi minimum note value to display on the value axis - defaults to 10 * @param {integer} minMidi minimum note value to display on the value axis - defaults to 10
* @param {integer} maxMidi maximum note value to display on the value axis - defaults to 90 * @param {integer} maxMidi maximum note value to display on the value axis - defaults to 90
* @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default * @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default
* * @see _pianoroll
* @example * @example
* note("C2 A2 G2").euclid(5,8).s('piano').clip(1).color('salmon').pianoroll({vertical:1, labels:1}) * note("c2 a2 eb2")
* .euclid(5,8)
* .s('sawtooth')
* .lpenv(4).lpf(300)
* .pianoroll({ labels: 1 })
*/ */
export function pianoroll({
Pattern.prototype.pianoroll = function (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(
(haps, time) => {
__pianoroll({
...options,
time,
ctx,
haps: haps.filter((hap) => inFrame(hap, time)),
});
},
{
lookbehind: from - overscan,
lookahead: to + overscan,
id,
},
);
return this;
};
export function pianoroll(arg) {
if (isPattern(arg)) {
// Single argument as a pattern
// (to support `all(pianoroll)`)
return arg.pianoroll();
}
// Single argument with option - return function to get the pattern
// (to support `all(pianoroll(options))`)
return (pat) => pat.pianoroll(arg);
}
export function __pianoroll({
time, time,
haps, haps,
cycles = 4, cycles = 4,
@@ -113,7 +128,7 @@ export function pianoroll({
maxMidi = 90, maxMidi = 90,
autorange = 0, autorange = 0,
timeframe: timeframeProp, timeframe: timeframeProp,
fold = 0, fold = 1,
vertical = 0, vertical = 0,
labels = false, labels = false,
fill = 1, fill = 1,
@@ -266,14 +281,14 @@ export function getDrawOptions(drawTime, options = {}) {
let [lookbehind, lookahead] = drawTime; let [lookbehind, lookahead] = drawTime;
lookbehind = Math.abs(lookbehind); lookbehind = Math.abs(lookbehind);
const cycles = lookahead + lookbehind; const cycles = lookahead + lookbehind;
const playhead = lookbehind / cycles; const playhead = cycles !== 0 ? lookbehind / cycles : 0;
return { fold: 1, ...options, cycles, playhead }; return { fold: 1, ...options, cycles, playhead };
} }
export const getPunchcardPainter = export const getPunchcardPainter =
(options = {}) => (options = {}) =>
(ctx, time, haps, drawTime) => (ctx, time, haps, drawTime) =>
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) }); __pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
Pattern.prototype.punchcard = function (options) { Pattern.prototype.punchcard = function (options) {
return this.onPaint(getPunchcardPainter(options)); return this.onPaint(getPunchcardPainter(options));
@@ -297,5 +312,5 @@ Pattern.prototype.wordfall = function (options) {
export function drawPianoroll(options) { export function drawPianoroll(options) {
const { drawTime, ...rest } = options; const { drawTime, ...rest } = options;
pianoroll({ ...getDrawOptions(drawTime), ...rest }); __pianoroll({ ...getDrawOptions(drawTime), ...rest });
} }
+17
View File
@@ -113,6 +113,23 @@ export function pitchwheel({
return; return;
} }
/**
* Renders a pitch circle to visualize frequencies within one octave
* @name pitchwheel
* @param {number} hapcircles
* @param {number} circle
* @param {number} edo
* @param {string} root
* @param {number} thickness
* @param {number} hapRadius
* @param {string} mode
* @param {number} margin
* @example
* n("0 .. 12").scale("C:chromatic")
* .s("sawtooth")
* .lpf(500)
* ._pitchwheel()
*/
Pattern.prototype.pitchwheel = function (options = {}) { Pattern.prototype.pitchwheel = function (options = {}) {
let { ctx = getDrawContext(), id = 1 } = options; let { ctx = getDrawContext(), id = 1 } = options;
return this.tag(id).onPaint((_, time, haps) => return this.tag(id).onPaint((_, time, haps) =>
+27
View File
@@ -125,6 +125,33 @@ function drawSpiral(options) {
}); });
} }
/**
* Displays a spiral visual.
*
* @name spiral
* @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {number} stretch controls the rotations per cycle ratio, where 1 = 1 cycle / 360 degrees
* @param {number} size the diameter of the spiral
* @param {number} thickness line thickness
* @param {string} cap style of line ends: butt (default), round, square
* @param {string} inset number of rotations before spiral starts (default 3)
* @param {string} playheadColor color of playhead, defaults to white
* @param {number} playheadLength length of playhead in rotations, defaults to 0.02
* @param {number} playheadThickness thickness of playheadrotations, defaults to thickness
* @param {number} padding space around spiral
* @param {number} steady steadyness of spiral vs playhead. 1 = spiral doesn't move, playhead does.
* @param {number} activeColor color of active segment. defaults to foreground of theme
* @param {number} inactiveColor color of inactive segments. defaults to gutterForeground of theme
* @param {boolean} colorizeInactive wether or not to colorize inactive segments, defaults to 0
* @param {boolean} fade wether or not past and future should fade out. defaults to 1
* @param {boolean} logSpiral wether or not the spiral should be logarithmic. defaults to 0
* @example
* note("c2 a2 eb2")
* .euclid(5,8)
* .s('sawtooth')
* .lpenv(4).lpf(300)
* ._spiral({ steady: .96 })
*/
Pattern.prototype.spiral = function (options = {}) { Pattern.prototype.spiral = function (options = {}) {
return this.onPaint((ctx, time, haps, drawTime) => drawSpiral({ ctx, time, haps, drawTime, ...options })); return this.onPaint((ctx, time, haps, drawTime) => drawSpiral({ ctx, time, haps, drawTime, ...options }));
}; };
+3 -3
View File
@@ -28,10 +28,10 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel", "homepage": "https://github.com/tidalcycles/strudel",
"dependencies": { "dependencies": {
"web-tree-sitter": "^0.20.8" "web-tree-sitter": "^0.24.7"
}, },
"devDependencies": { "devDependencies": {
"tree-sitter-haskell": "^0.21.0", "tree-sitter-haskell": "^0.23.1",
"vite": "^5.0.10" "vite": "^6.0.11"
} }
} }
+1 -1
View File
@@ -39,6 +39,6 @@
}, },
"devDependencies": { "devDependencies": {
"pkg": "^5.8.1", "pkg": "^5.8.1",
"vite": "^5.0.10" "vite": "^6.0.11"
} }
} }
+140 -12
View File
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import * as _WebMidi from 'webmidi'; import * as _WebMidi from 'webmidi';
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core'; import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
import { noteToMidi } from '@strudel/core'; import { noteToMidi, getControlName } from '@strudel/core';
import { Note } from 'webmidi'; import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance: // if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi; export const { WebMidi } = _WebMidi;
@@ -89,6 +89,113 @@ if (typeof window !== 'undefined') {
}); });
} }
// registry for midi mappings, converting control names to cc messages
export const midicontrolMap = new Map();
// takes midimap and converts each control key to the main control name
function unifyMapping(mapping) {
return Object.fromEntries(
Object.entries(mapping).map(([key, mapping]) => {
if (typeof mapping === 'number') {
mapping = { ccn: mapping };
}
return [getControlName(key), mapping];
}),
);
}
function githubPath(base, subpath = '') {
if (!base.startsWith('github:')) {
throw new Error('expected "github:" at the start of pseudoUrl');
}
let [_, path] = base.split('github:');
path = path.endsWith('/') ? path.slice(0, -1) : path;
if (path.split('/').length === 2) {
// assume main as default branch if none set
path += '/main';
}
return `https://raw.githubusercontent.com/${path}/${subpath}`;
}
/**
* configures the default midimap, which is used when no "midimap" port is set
* @example
* defaultmidimap({ lpf: 74 })
* $: note("c a f e").midi();
* $: lpf(sine.slow(4).segment(16)).midi();
*/
export function defaultmidimap(mapping) {
midicontrolMap.set('default', unifyMapping(mapping));
}
let loadCache = {};
/**
* Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers.
* @example
* midimaps({ mymap: { lpf: 74 } })
* $: note("c a f e")
* .lpf(sine.slow(4))
* .midimap('mymap')
* .midi()
* @example
* midimaps({ mymap: {
* lpf: { ccn: 74, min: 0, max: 20000, exp: 0.5 }
* }})
* $: note("c a f e")
* .lpf(sine.slow(2).range(400,2000))
* .midimap('mymap')
* .midi()
*/
export async function midimaps(map) {
if (typeof map === 'string') {
if (map.startsWith('github:')) {
map = githubPath(map, 'midimap.json');
}
if (!loadCache[map]) {
loadCache[map] = fetch(map).then((res) => res.json());
}
map = await loadCache[map];
}
if (typeof map === 'object') {
Object.entries(map).forEach(([name, mapping]) => midicontrolMap.set(name, unifyMapping(mapping)));
}
}
// registry for midi sounds, converting sound names to controls
export const midisoundMap = new Map();
// normalizes the given value from the given range and exponent
function normalize(value = 0, min = 0, max = 1, exp = 1) {
if (min === max) {
throw new Error('min and max cannot be the same value');
}
let normalized = (value - min) / (max - min);
normalized = Math.min(1, Math.max(0, normalized));
return Math.pow(normalized, exp);
}
function mapCC(mapping, value) {
return Object.keys(value)
.filter((key) => !!mapping[getControlName(key)])
.map((key) => {
const { ccn, min = 0, max = 1, exp = 1 } = mapping[key];
const ccv = normalize(value[key], min, max, exp);
return { ccn, ccv };
});
}
// sends a cc message to the given device on the given channel
function sendCC(ccn, ccv, device, midichan, timeOffsetString) {
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
throw new Error('expected ccv to be a number between 0 and 1');
}
if (!['string', 'number'].includes(typeof ccn)) {
throw new Error('expected ccn to be a number or a string');
}
const scaled = Math.round(ccv * 127);
device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString });
}
Pattern.prototype.midi = function (output) { Pattern.prototype.midi = function (output) {
if (isPattern(output)) { if (isPattern(output)) {
throw new Error( throw new Error(
@@ -117,16 +224,39 @@ Pattern.prototype.midi = function (output) {
console.log('not enabled'); console.log('not enabled');
return; return;
} }
const device = getDevice(output, WebMidi.outputs);
hap.ensureObjectValue(); hap.ensureObjectValue();
//magic number to get audio engine to line up, can probably be calculated somehow //magic number to get audio engine to line up, can probably be calculated somehow
const latencyMs = 34; const latencyMs = 34;
// passing a string with a +num into the webmidi api adds an offset to the current time https://webmidijs.org/api/classes/Output // passing a string with a +num into the webmidi api adds an offset to the current time https://webmidijs.org/api/classes/Output
const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}`; const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}`;
// destructure value // destructure value
let { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd, gain = 1, velocity = 0.9 } = hap.value; let {
note,
ccn,
ccv,
midichan = 1,
midicmd,
gain = 1,
velocity = 0.9,
midimap = 'default',
midiport = output,
} = hap.value;
const device = getDevice(midiport, WebMidi.outputs);
if (!device) {
logger(
`[midi] midiport "${midiport}" not found! available: ${WebMidi.outputs.map((output) => `'${output.name}'`).join(', ')}`,
);
return;
}
velocity = gain * velocity; velocity = gain * velocity;
// if midimap is set, send a cc messages from defined controls
if (midicontrolMap.has(midimap)) {
const ccs = mapCC(midicontrolMap.get(midimap), hap.value);
ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString));
}
// note off messages will often a few ms arrive late, try to prevent glitching by subtracting from the duration length // note off messages will often a few ms arrive late, try to prevent glitching by subtracting from the duration length
const duration = (hap.duration.valueOf() / cps) * 1000 - 10; const duration = (hap.duration.valueOf() / cps) * 1000 - 10;
@@ -138,14 +268,7 @@ Pattern.prototype.midi = function (output) {
}); });
} }
if (ccv !== undefined && ccn !== undefined) { if (ccv !== undefined && ccn !== undefined) {
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) { sendCC(ccn, ccv, device, midichan, timeOffsetString);
throw new Error('expected ccv to be a number between 0 and 1');
}
if (!['string', 'number'].includes(typeof ccn)) {
throw new Error('expected ccn to be a number or a string');
}
const scaled = Math.round(ccv * 127);
device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString });
} }
if (hap.whole.begin + 0 === 0) { if (hap.whole.begin + 0 === 0) {
// we need to start here because we have the timing info // we need to start here because we have the timing info
@@ -169,13 +292,18 @@ const refs = {};
export async function midin(input) { export async function midin(input) {
if (isPattern(input)) { if (isPattern(input)) {
throw new Error( throw new Error(
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${ `midin: does not accept Pattern as input. Make sure to pass device name with single quotes. Example: midin('${
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1' WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`, }')`,
); );
} }
const initial = await enableWebMidi(); // only returns on first init const initial = await enableWebMidi(); // only returns on first init
const device = getDevice(input, WebMidi.inputs); const device = getDevice(input, WebMidi.inputs);
if (!device) {
throw new Error(
`midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
);
}
if (initial) { if (initial) {
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
logger( logger(
+2 -2
View File
@@ -31,9 +31,9 @@
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel/webaudio": "workspace:*",
"webmidi": "^3.1.8" "webmidi": "^3.1.12"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^6.0.11"
} }
} }
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -19,10 +19,10 @@ This program is free software: you can redistribute it and/or modify it under th
this.location_ = location(); this.location_ = location();
} }
var PatternStub = function(source, alignment, seed, tactus) var PatternStub = function(source, alignment, seed, _steps)
{ {
this.type_ = "pattern"; this.type_ = "pattern";
this.arguments_ = { alignment: alignment, tactus: tactus }; this.arguments_ = { alignment: alignment, _steps: _steps };
if (seed !== undefined) { if (seed !== undefined) {
this.arguments_.seed = seed; this.arguments_.seed = seed;
} }
@@ -172,8 +172,8 @@ slice_with_ops = s:slice ops:slice_op*
} }
// a sequence is a combination of one or more successive slices (as an array) // a sequence is a combination of one or more successive slices (as an array)
sequence = tactus:'^'? s:(slice_with_ops)+ sequence = _steps:'^'? s:(slice_with_ops)+
{ return new PatternStub(s, 'fastcat', undefined, !!tactus); } { return new PatternStub(s, 'fastcat', undefined, !!_steps); }
// a stack is a series of vertically aligned sequence, separated by a comma // a stack is a series of vertically aligned sequence, separated by a comma
stack_tail = tail:(comma @sequence)+ stack_tail = tail:(comma @sequence)+
+17 -17
View File
@@ -14,7 +14,7 @@ const applyOptions = (parent, enter) => (pat, i) => {
const ast = parent.source_[i]; const ast = parent.source_[i];
const options = ast.options_; const options = ast.options_;
const ops = options?.ops; const ops = options?.ops;
const steps_source = pat.__steps_source;
if (ops) { if (ops) {
for (const op of ops) { for (const op of ops) {
switch (op.type_) { switch (op.type_) {
@@ -69,7 +69,7 @@ const applyOptions = (parent, enter) => (pat, i) => {
} }
} }
} }
pat.__steps_source = pat.__steps_source || steps_source;
return pat; return pat;
}; };
@@ -82,20 +82,20 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
// resolveReplications(ast); // resolveReplications(ast);
const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter)); const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter));
const alignment = ast.arguments_.alignment; const alignment = ast.arguments_.alignment;
const with_tactus = children.filter((child) => child.__tactus_source); const with_steps = children.filter((child) => child.__steps_source);
let pat; let pat;
switch (alignment) { switch (alignment) {
case 'stack': { case 'stack': {
pat = strudel.stack(...children); pat = strudel.stack(...children);
if (with_tactus.length) { if (with_steps.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus))); pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
} }
break; break;
} }
case 'polymeter_slowcat': { case 'polymeter_slowcat': {
pat = strudel.stack(...children.map((child) => child._slow(child.__weight))); pat = strudel.stack(...children.map((child) => child._slow(child.__weight)));
if (with_tactus.length) { if (with_steps.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus))); pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
} }
break; break;
} }
@@ -111,8 +111,8 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
} }
case 'rand': { case 'rand': {
pat = strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children); pat = strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
if (with_tactus.length) { if (with_steps.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus))); pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
} }
break; break;
} }
@@ -131,21 +131,21 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
...ast.source_.map((child, i) => [child.options_?.weight || strudel.Fraction(1), children[i]]), ...ast.source_.map((child, i) => [child.options_?.weight || strudel.Fraction(1), children[i]]),
); );
pat.__weight = weightSum; // for polymeter pat.__weight = weightSum; // for polymeter
pat.tactus = weightSum; pat._steps = weightSum;
if (with_tactus.length) { if (with_steps.length) {
pat.tactus = pat.tactus.mul(lcm(...with_tactus.map((x) => Fraction(x.tactus)))); pat._steps = pat._steps.mul(lcm(...with_steps.map((x) => Fraction(x._steps))));
} }
} else { } else {
pat = strudel.sequence(...children); pat = strudel.sequence(...children);
pat.tactus = children.length; pat._steps = children.length;
} }
if (ast.arguments_.tactus) { if (ast.arguments_._steps) {
pat.__tactus_source = true; pat.__steps_source = true;
} }
} }
} }
if (with_tactus.length) { if (with_steps.length) {
pat.__tactus_source = true; pat.__steps_source = true;
} }
return pat; return pat;
} }
+3 -3
View File
@@ -35,8 +35,8 @@
"@strudel/core": "workspace:*" "@strudel/core": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"peggy": "^3.0.2", "peggy": "^4.2.0",
"vite": "^5.0.10", "vite": "^6.0.11",
"vitest": "^1.1.0" "vitest": "^3.0.4"
} }
} }
+10 -8
View File
@@ -208,14 +208,16 @@ describe('mini', () => {
it('_ and @ are almost interchangeable', () => { it('_ and @ are almost interchangeable', () => {
expect(minS('a @ b @ @')).toEqual(minS('a _2 b _3')); expect(minS('a @ b @ @')).toEqual(minS('a _2 b _3'));
}); });
it('supports ^ tactus marking', () => { it('supports ^ step marking', () => {
expect(mini('a [^b c]').tactus).toEqual(Fraction(4)); expect(mini('a [^b c]')._steps).toEqual(Fraction(4));
expect(mini('[a b c] [d [e f]]').tactus).toEqual(Fraction(2)); expect(mini('[^b c]!3')._steps).toEqual(Fraction(6));
expect(mini('^[a b c] [d [e f]]').tactus).toEqual(Fraction(2)); expect(mini('[a b c] [d [e f]]')._steps).toEqual(Fraction(2));
expect(mini('[a b c] [d [^e f]]').tactus).toEqual(Fraction(8)); expect(mini('^[a b c] [d [e f]]')._steps).toEqual(Fraction(2));
expect(mini('[a b c] [^d [e f]]').tactus).toEqual(Fraction(4)); expect(mini('[a b c] [d [^e f]]')._steps).toEqual(Fraction(8));
expect(mini('[^a b c] [^d [e f]]').tactus).toEqual(Fraction(12)); expect(mini('[a b c] [^d [e f]]')._steps).toEqual(Fraction(4));
expect(mini('[^a b c] [d [^e f]]').tactus).toEqual(Fraction(24)); expect(mini('[^a b c] [^d [e f]]')._steps).toEqual(Fraction(12));
expect(mini('[^a b c] [d [^e f]]')._steps).toEqual(Fraction(24));
expect(mini('[^a b c d e]')._steps).toEqual(Fraction(5));
}); });
}); });
+72
View File
@@ -0,0 +1,72 @@
# @strudel/motion
This package adds device motion sensing functionality to strudel Patterns.
## Install
```sh
npm i @strudel/motion --save
```
## Usage
| Motion | Long Names & Aliases | Description |
|----------------------------|-----------------------------------------------------------|------------------------------------------|
| Acceleration | accelerationX (accX), accelerationY (accY), accelerationZ (accZ) | X, Y, Z-axis acceleration values |
| Gravity | gravityX (gravX), gravityY (gravY), gravityZ (gravZ) | X, Y, Z-axis gravity values |
| Rotation | rotationAlpha (rotA, rotZ), rotationBeta (rotB, rotX), rotationGamma (rotG, rotY) | Rotation around alpha, beta, gamma axes and mapped to X, Y, Z |
| Orientation | orientationAlpha (oriA, oriZ), orientationBeta (oriB, oriX), orientationGamma (oriG, oriY) | Orientation alpha, beta, gamma values and mapped to X, Y, Z |
| Absolute Orientation | absoluteOrientationAlpha (absOriA, absOriZ), absoluteOrientationBeta (absOriB, absOriX), absoluteOrientationGamma (absOriG, absOriY) | Absolute orientation alpha, beta, gamma values and mapped to X, Y, Z |
## Example
```js
enableMotion() //enable DeviceMotion
setcpm(200/4)
$_: accX.segment(16).gain().log()
$:n("0 1 3 1 5 4")
.scale("Bb:lydian")
.sometimesBy(0.5,sub(note(12)))
.lpf(gravityY.range(20,1000))
.lpq(gravityZ.range(1,30))
.lpenv(gravityX.range(2,2))
.gain(oriX.range(0.2,0.8))
.room(oriZ.range(0,0.5))
.attack(oriY.range(0,0.3))
.delay(rotG.range(0,1))
.decay(rotA.range(0,1))
.attack(rotB.range(0,0.1))
.sound("sawtooth")
```
## Setup SSL for Local Development
`DeviceMotionEvent` only works with HTTPS, so you'll need to enable SSL for local development.
Try installing an SSL plugin for Vite.
```sh
cd website
pnpm install -D @vitejs/plugin-basic-ssl
```
add the basicSsl plugin to the defineConfig block in `strudel/website/astro.config.mjs`
```js
vite: {
plugins: [basicSsl()],
server: {
host: '0.0.0.0', // Ensures it binds to all network interfaces
// https: {
// key: '../../key.pem', //
// cert: '../../cert.pem',
// },
},
},
```
generate an SSL certificate to avoid security warnings.
`openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout key.pem -out cert.pem`
+82
View File
@@ -0,0 +1,82 @@
import { MiniRepl } from '../../../website/src/docs/MiniRepl';
import { JsDoc } from '../../../website/src/docs/JsDoc';
# Device Motion
Devicemotion module allows you to use your mobile device's motion sensors (accelerometer, gyroscope, and orientation sensors) to control musical parameters in real-time. This creates opportunities for expressive, movement-based musical interactions.
## Basic Setup
First, you need to enable device motion sensing:
<MiniRepl client:idle tune={`enableMotion()`} />
This will prompt the user for permission to access device motion sensors.
## Available Motion Parameters
You can access different types of motion data:
| Motion | Long Names & Aliases | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Acceleration | accelerationX (accX), accelerationY (accY), accelerationZ (accZ) | Measures linear acceleration of the device, excluding gravity. Raw values are normalized from g-force. |
| Gravity | gravityX (gravX), gravityY (gravY), gravityZ (gravZ) | Indicates device's orientation relative to Earth's gravity. Raw values are normalized from ±9.81 m/s². |
| Rotation | rotationAlpha (rotA, rotZ), rotationBeta (rotB, rotX), rotationGamma (rotG, rotY) | Measures rotation rate around each axis. Raw values (±180°/s) are normalized. |
| Orientation | orientationAlpha (oriA, oriZ), orientationBeta (oriB, oriX), orientationGamma (oriG, oriY) | Relative orientation from its starting device position. Normalized from:<br/>- Alpha: 0° to 360°<br/>- Beta: -180° to 180°<br/>- Gamma: -90° to 90° |
| Absolute Orientation | absoluteOrientationAlpha (absOriA, absOriZ), absoluteOrientationBeta (absOriB, absOriX), absoluteOrientationGamma (absOriG, absOriY) | **Not available for iOS** <br/> Earth-referenced orientation using magnetometer. Same normalization as Orientation. |
Note:
- All motion values are normalized to a range of 0 to 1.
- Not all devices have the same sensors available
Check [DeviceMotionEvent API](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent) for browser compatibility
- Refer to [Oritentation and motion data explained](https://developer.mozilla.org/en-US/docs/Web/API/Device_orientation_events/Orientation_and_motion_data_explained) for more details
### Orientation vs Absolute Orientation
The key difference between regular orientation and absolute orientation is:
- Regular orientation (`oriX/Y/Z`) measures relative changes in device orientation from its starting position
- Absolute orientation (`absOriX/Y/Z`) measures orientation relative to Earth's magnetic field and gravity, providing consistent absolute values regardless of starting position
For example, if you rotate your device 90 degrees clockwise and then back:
- Regular orientation will show a change during rotation but return to initial values
- Absolute orientation will show the actual compass heading throughout
This makes absolute orientation particularly useful for creating direction-based musical interactions - for example, performers facing north could play one melody while those facing south play another, creating spatially-aware ensemble performances. Regular orientation, on the other hand, is better suited for detecting relative motion and gestures regardless of which direction the performer is facing.
## Basic Example
Here's a simple example that uses device motion to control a synthesizer:
<MiniRepl
client:idle
tune={`enableMotion()
// Create a simple melody
$:n("0 1 3 5")
.scale("C:major")
// Use tilt (gravity) to control filter
.lpf(gravityY.range(200, 2000)) // tilt forward/back for filter cutoff
// Use rotation to control effects
.room(rotZ.range(0, 0.8)) // rotate device for reverb amount
.gain(oriX.range(0.2, 0.8)) // tilt left/right for volume
.sound("sawtooth")`}
/>
## Tips for Using Motion Controls
1. Use `.range(min, max)` to map sensor values to musically useful ranges
2. Consider using `.segment()` to smooth out rapid changes in sensor values
## Debugging
You can use `segment(16).log()` to see the raw values from any motion sensor:
```javascript
$_: accX.segment(16).log(); // logs acceleration values to the console
```
This is helpful when calibrating your ranges and understanding how your device responds to different movements.
Remember that device motion works best on mobile devices and may not be available on all desktop browsers. Always test your motion-controlled pieces on the target device type!
+3
View File
@@ -0,0 +1,3 @@
import './motion.mjs';
export * from './motion.mjs';
+371
View File
@@ -0,0 +1,371 @@
// motion.mjs
import { signal } from '../core/signal.mjs';
/**
* The accelerometer's x-axis value ranges from 0 to 1.
* @name accelerationX
* @return {Pattern}
* @synonyms accX
* @example
* n(accelerationX.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The accelerometer's y-axis value ranges from 0 to 1.
* @name accelerationY
* @return {Pattern}
* @synonyms accY
* @example
* n(accelerationY.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The accelerometer's z-axis value ranges from 0 to 1.
* @name accelerationZ
* @return {Pattern}
* @synonyms accZ
* @example
* n(accelerationZ.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's gravity x-axis value ranges from 0 to 1.
* @name gravityX
* @return {Pattern}
* @synonyms gravX
* @example
* n(gravityX.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's gravity y-axis value ranges from 0 to 1.
* @name gravityY
* @return {Pattern}
* @synonyms gravY
* @example
* n(gravityY.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's gravity z-axis value ranges from 0 to 1.
* @name gravityZ
* @return {Pattern}
* @synonyms gravZ
* @example
* n(gravityZ.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's rotation around the alpha-axis value ranges from 0 to 1.
* @name rotationAlpha
* @return {Pattern}
* @synonyms rotA, rotZ, rotationZ
* @example
* n(rotationAlpha.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's rotation around the beta-axis value ranges from 0 to 1.
* @name rotationBeta
* @return {Pattern}
* @synonyms rotB, rotX, rotationX
* @example
* n(rotationBeta.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's rotation around the gamma-axis value ranges from 0 to 1.
* @name rotationGamma
* @return {Pattern}
* @synonyms rotG, rotY, rotationY
* @example
* n(rotationGamma.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's orientation alpha value ranges from 0 to 1.
* @name orientationAlpha
* @return {Pattern}
* @synonyms oriA, oriZ, orientationZ
* @example
* n(orientationAlpha.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's orientation beta value ranges from 0 to 1.
* @name orientationBeta
* @return {Pattern}
* @synonyms oriB, oriX, orientationX
* @example
* n(orientationBeta.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's orientation gamma value ranges from 0 to 1.
* @name orientationGamma
* @return {Pattern}
* @synonyms oriG, oriY, orientationY
* @example
* n(orientationGamma.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's absolute orientation alpha value ranges from 0 to 1.
* @name absoluteOrientationAlpha
* @return {Pattern}
* @synonyms absOriA, absOriZ, absoluteOrientationZ
* @example
* n(absoluteOrientationAlpha.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's absolute orientation beta value ranges from 0 to 1.
* @name absoluteOrientationBeta
* @return {Pattern}
* @synonyms absOriB, absOriX, absoluteOrientationX
* @example
* n(absoluteOrientationBeta.segment(4).range(0,7)).scale("C:minor")
*
*/
/**
* The device's absolute orientation gamma value ranges from 0 to 1.
* @name absoluteOrientationGamma
* @return {Pattern}
* @synonyms absOriG, absOriY, absoluteOrientationY
* @example
* n(absoluteOrientationGamma.segment(4).range(0,7)).scale("C:minor")
*
*/
class DeviceMotionHandler {
constructor() {
this.GRAVITY = 9.81;
// Initialize sensor values
this._acceleration = {
x: 0,
y: 0,
z: 0,
};
this._gravity = {
x: 0,
y: 0,
z: 0,
};
this._rotation = {
alpha: 0,
beta: 0,
gamma: 0,
};
this._orientation = {
alpha: 0,
beta: 0,
gamma: 0,
};
this._absoluteOrientation = {
alpha: 0,
beta: 0,
gamma: 0,
};
this._permissionStatus = 'unknown';
}
async requestPermissions() {
if (typeof DeviceMotionEvent?.requestPermission === 'function') {
try {
// iOS requires explicit permission
const motionPermission = await DeviceMotionEvent.requestPermission();
const orientationPermission = await DeviceOrientationEvent.requestPermission();
this._permissionStatus =
motionPermission === 'granted' && orientationPermission === 'granted' ? 'granted' : 'denied';
this.setupEventListeners();
} catch (error) {
console.error('Permission request failed:', error);
this._permissionStatus = 'denied';
}
} else {
this._permissionStatus = 'granted';
this.setupEventListeners();
}
}
setupEventListeners() {
if (this._permissionStatus === 'granted') {
// Device Motion handler
window.addEventListener('devicemotion', this.handleDeviceMotion.bind(this), true);
window.addEventListener('deviceorientation', this.handleDeviceOrientation.bind(this), true);
window.addEventListener('deviceorientationabsolute', this.handleAbsoluteDeviceOrientation.bind(this), true);
}
}
handleDeviceMotion(event) {
//console.log(event);
if (event.acceleration) {
// Normalize acceleration values to 0-1 range
this._acceleration.x = (event.acceleration.x + 1) / 2;
this._acceleration.y = (event.acceleration.y + 1) / 2;
this._acceleration.z = (event.acceleration.z + 1) / 2;
}
if (event.accelerationIncludingGravity) {
// Normalize acceleration values to 0-1 range
this._gravity.x = (event.accelerationIncludingGravity.x + this.GRAVITY) / (2 * this.GRAVITY);
this._gravity.y = (event.accelerationIncludingGravity.y + this.GRAVITY) / (2 * this.GRAVITY);
this._gravity.z = (event.accelerationIncludingGravity.z + this.GRAVITY) / (2 * this.GRAVITY);
}
if (event.rotationRate) {
// Normalize rotation values to 0-1 range
this._rotation.alpha = (event.rotationRate.alpha + 180) / 360;
this._rotation.beta = (event.rotationRate.beta + 180) / 360;
this._rotation.gamma = (event.rotationRate.gamma + 180) / 360;
}
}
handleDeviceOrientation(event) {
this._orientation.alpha = event.alpha / 360; //a(0~360)
this._orientation.beta = (event.beta + 180) / 360; //b(-180~180)
this._orientation.gamma = (event.gamma + 90) / 180; //g(-90~90)
}
handleAbsoluteDeviceOrientation(event) {
this._absoluteOrientation.alpha = event.alpha / 360; //a(0~360)
this._absoluteOrientation.beta = (event.beta + 180) / 360; //b(-180~180)
this._absoluteOrientation.gamma = (event.gamma + 90) / 180; //g(-90~90)
}
// Getter methods for current values
getAcceleration() {
return this._acceleration;
}
getGravity() {
return this._gravity;
}
getRotation() {
return this._rotation;
}
getOrientation() {
return this._orientation;
}
getAbsoluteOrientation() {
return this._absoluteOrientation;
}
}
// Create singleton instance
const deviceMotion = new DeviceMotionHandler();
// Export a function to request permission
export async function enableMotion() {
return deviceMotion.requestPermissions();
}
// Create signals for acceleration
export const accelerationX = signal(() => deviceMotion.getAcceleration().x);
export const accelerationY = signal(() => deviceMotion.getAcceleration().y);
export const accelerationZ = signal(() => deviceMotion.getAcceleration().z);
// Aliases for shorter names
export const accX = accelerationX;
export const accY = accelerationY;
export const accZ = accelerationZ;
// Create signals for gravity
export const gravityX = signal(() => deviceMotion.getGravity().x);
export const gravityY = signal(() => deviceMotion.getGravity().y);
export const gravityZ = signal(() => deviceMotion.getGravity().z);
// Aliases for shorter names
export const gravX = gravityX;
export const gravY = gravityY;
export const gravZ = gravityZ;
// Create signals for orientation
export const orientationAlpha = signal(() => deviceMotion.getOrientation().alpha);
export const orientationBeta = signal(() => deviceMotion.getOrientation().beta);
export const orientationGamma = signal(() => deviceMotion.getOrientation().gamma);
// Aliases for shorter names
export const orientationA = orientationAlpha;
export const orientationB = orientationBeta;
export const orientationG = orientationGamma;
// Aliases mapping to X,Y,Z coordinates
export const orientationX = orientationBeta;
export const orientationY = orientationGamma;
export const orientationZ = orientationAlpha;
// Short aliases for A,B,G,X,Y,Z
export const oriA = orientationAlpha;
export const oriB = orientationBeta;
export const oriG = orientationGamma;
export const oriX = orientationX;
export const oriY = orientationY;
export const oriZ = orientationZ;
// Create signals for absolute orientation
export const absoluteOrientationAlpha = signal(() => deviceMotion.getAbsoluteOrientation().alpha);
export const absoluteOrientationBeta = signal(() => deviceMotion.getAbsoluteOrientation().beta);
export const absoluteOrientationGamma = signal(() => deviceMotion.getAbsoluteOrientation().gamma);
// Aliases for shorter names
export const absOriA = absoluteOrientationAlpha;
export const absOriB = absoluteOrientationBeta;
export const absOriG = absoluteOrientationGamma;
// Aliases mapping to X,Y,Z coordinates
export const absoluteOrientationX = absoluteOrientationBeta;
export const absoluteOrientationY = absoluteOrientationGamma;
export const absoluteOrientationZ = absoluteOrientationAlpha;
// Short aliases for X,Y,Z
export const absOriX = absoluteOrientationX;
export const absOriY = absoluteOrientationY;
export const absOriZ = absoluteOrientationZ;
// Create signals for rotation
export const rotationAlpha = signal(() => deviceMotion.getRotation().alpha);
export const rotationBeta = signal(() => deviceMotion.getRotation().beta);
export const rotationGamma = signal(() => deviceMotion.getRotation().gamma);
export const rotationX = rotationBeta;
export const rotationY = rotationGamma;
export const rotationZ = rotationAlpha;
// Aliases for shorter names
export const rotA = rotationAlpha;
export const rotB = rotationBeta;
export const rotG = rotationGamma;
export const rotX = rotationX;
export const rotY = rotationY;
export const rotZ = rotationZ;
// // Bipolar versions (ranging from -1 to 1 instead of 0 to 1)
// export const accX2 = accX.toBipolar();
// export const accY2 = accY.toBipolar();
// export const accZ2 = accZ.toBipolar();
// export const rotA2 = rotA.toBipolar();
// export const rotB2 = rotB.toBipolar();
// export const rotG2 = rotG.toBipolar();
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@strudel/motion",
"version": "1.1.0",
"description": "DeviceMotion API for 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": "Yuta Nakayama <nkymut@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": "^6.0.11"
}
}
+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',
},
});

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