Compare commits

...

544 Commits

Author SHA1 Message Date
Felix Roos 9bd7b66228 Publish
- @strudel/codemirror@1.2.1
 - @strudel/core@1.2.1
 - @strudel/csound@1.2.2
 - @strudel/draw@1.2.1
 - @strudel/gamepad@1.2.1
 - @strudel/hydra@1.2.1
 - @strudel/midi@1.2.2
 - @strudel/mini@1.2.1
 - @strudel/motion@1.2.1
 - @strudel/mqtt@1.2.1
 - @strudel/osc@1.2.1
 - @strudel/repl@1.2.2
 - @strudel/serial@1.2.1
 - @strudel/soundfonts@1.2.2
 - superdough@1.2.2
 - @strudel/tonal@1.2.1
 - @strudel/transpiler@1.2.1
 - @strudel/web@1.2.2
 - @strudel/webaudio@1.2.2
 - @strudel/xen@1.2.1
2025-05-24 11:30:02 +02:00
Nachito 5c3501caa4 fix: typo on docs causing problems with autocompletion. (#1350)
* fix: scramble and shuffle comments

* fix: comments on themes,docs, tests and more
2025-05-18 20:43:04 +01:00
Jade (Rose) Rowland 1341bd9c92 Merge pull request #1343 from daslyfe/pw
feat: Create pulsewidth (pw) and pulsewidth lfo parameters
2025-05-14 19:12:06 -04:00
Jade (Rose) Rowland 760d1abec0 remove n 2025-05-14 19:10:59 -04:00
Jade (Rose) Rowland 2b44fc48a4 Merge pull request #1344 from daslyfe/multichannelorbits
feat: Multi Channel Orbits
2025-05-14 12:25:16 -04:00
Henré Botha 99779171f5 Fix typo (#1346) 2025-05-13 16:45:29 +01:00
Jade (Rose) Rowland 64ebfb57c3 lint 2025-05-07 11:30:42 -04:00
Jade (Rose) Rowland 099d321dbd format 2025-05-07 11:25:34 -04:00
Jade (Rose) Rowland 59bdfd2cb8 format 2025-05-07 11:12:05 -04:00
Jade (Rose) Rowland 56d3436fda asconst 2025-05-07 01:08:00 -04:00
Jade (Rose) Rowland 440ce0cd3b format 2025-05-06 02:14:35 -04:00
Jade (Rose) Rowland 98346261ed lint 2025-05-06 01:13:50 -04:00
Jade (Rose) Rowland 38e4718826 lint 2025-05-06 01:09:20 -04:00
Jade (Rose) Rowland 38908baccd working 2025-05-06 01:06:37 -04:00
Felix Roos 8d02d4270a Publish
- @strudel/repl@1.2.1
 - @strudel/web@1.2.1
2025-05-02 00:03:18 +02:00
Felix Roos 9cf1cd2656 revert web + repl versions for next try 2025-05-02 00:02:50 +02:00
Felix Roos 4c079b6ebe Merge pull request #1339 from tidalcycles/fix-web-pkg-build
Fix web + repl package builds
2025-05-02 00:01:27 +02:00
Felix Roos e9e2e881e5 fix: repl package build 2025-05-01 23:58:49 +02:00
Felix Roos 685b4f172b fix: duplicate key 2025-05-01 23:57:09 +02:00
Felix Roos 53e50d81b0 also use worklet plugin in web package 2025-05-01 23:56:29 +02:00
Felix Roos 8f58d5788f Publish
- @strudel/csound@1.2.1
 - @strudel/midi@1.2.1
 - @strudel/repl@1.2.1
 - @strudel/soundfonts@1.2.1
 - superdough@1.2.1
 - vite-plugin-bundle-audioworklet@0.1.1
 - @strudel/web@1.2.1
 - @strudel/webaudio@1.2.1
2025-05-01 23:54:13 +02:00
Felix Roos 306a6db8dc hotfix: remove broken tauri workflow 2025-05-01 23:52:36 +02:00
Felix Roos 757d78252b Merge pull request #1338 from tidalcycles/fix-worklet-bundling
fix: superdough worklets bundling
2025-05-01 23:50:56 +02:00
Felix Roos 6f2e026e4a fix: import in vitest config 2025-05-01 23:49:47 +02:00
Felix Roos 22f114be59 update metadata 2025-05-01 23:35:20 +02:00
Felix Roos f993056fb1 fix: tests -> use vite plugin in tests as well 2025-05-01 23:30:14 +02:00
Felix Roos fdbbcd2dd2 add vite-plugin-bundle-audioworklet and use it both from website and superdough 2025-05-01 23:26:04 +02:00
Felix Roos 7bec92ca99 fix: superdough worklets bundling 2025-05-01 23:13:32 +02:00
Felix Roos 5702914661 Publish
- @strudel/reference@1.2.0
2025-05-01 12:52:12 +02:00
Felix Roos f2a9e2d4e6 hotfix: downgrade reference to be able to get 1.2.0 2025-05-01 12:51:54 +02:00
Felix Roos 6398b5fb38 hotfix: make broken hs2js + tidal packages private to opt out of release
- some issue with the build command
2025-05-01 12:48:43 +02:00
Felix Roos 9ceebd4679 hotfix: reference package vite config 2025-05-01 12:47:56 +02:00
Felix Roos 177f5c1431 Publish
- @strudel/codemirror@1.2.0
 - @strudel/core@1.2.0
 - @strudel/csound@1.2.0
 - @strudel/draw@1.2.0
 - @strudel/gamepad@1.2.0
 - hs2js@0.2.0
 - @strudel/hydra@1.2.0
 - @strudel/midi@1.2.0
 - @strudel/mini@1.2.0
 - @strudel/motion@1.2.0
 - @strudel/mqtt@1.2.0
 - @strudel/osc@1.2.0
 - @strudel/reference@1.2.0
 - @strudel/repl@1.2.0
 - @strudel/sampler@0.2.0
 - @strudel/serial@1.2.0
 - @strudel/soundfonts@1.2.0
 - superdough@1.2.0
 - @strudel/tidal@0.2.0
 - @strudel/tonal@1.2.0
 - @strudel/transpiler@1.2.0
 - @strudel/web@1.2.0
 - @strudel/webaudio@1.2.0
 - @strudel/xen@1.2.0
2025-05-01 12:39:24 +02:00
Jade (Rose) Rowland d6c2a88496 Merge pull request #1333 from daslyfe/jade/soundimportorder
FIX: sound import order
2025-04-27 16:25:53 -04:00
Jade (Rose) Rowland 3ee7d0cd4f lint 2025-04-27 16:21:14 -04:00
Jade (Rose) Rowland 53d43dc44c working 2025-04-27 16:19:58 -04:00
Jade (Rose) Rowland 637c619345 Merge pull request #1332 from hpunq/import-sounds-docs
update docs to reflect import sounds tab change
2025-04-27 12:39:49 -04:00
Jade (Rose) Rowland 47fb441feb Merge pull request #1318 from daslyfe/jade/gaincurve
feat: Improve gain curve
2025-04-27 12:38:26 -04:00
hpunq d02b1fc05c update docs to reflect import sounds tab change 2025-04-27 11:05:06 +01:00
Jade (Rose) Rowland 480b5ff58b fix vel 2025-04-27 02:38:10 -04:00
Jade (Rose) Rowland c088c0c9d2 def 2025-04-27 02:21:49 -04:00
Jade (Rose) Rowland f32abac06f reset gaincurve 2025-04-27 02:20:53 -04:00
Jade (Rose) Rowland 697f0a82e6 Merge branch 'main' into jade/gaincurve 2025-04-27 02:10:30 -04:00
Jade (Rose) Rowland 98970417c3 Merge pull request #1331 from tidalcycles/BRANCH_NAME
Add Icon to import sample button
2025-04-27 01:54:49 -04:00
Jade (Rose) Rowland b5d48b2f0b Merge pull request #1329 from hpunq/sample-ui
Add new "import-sounds" tab with explanation on folder import
2025-04-27 01:53:40 -04:00
Jade (Rose) Rowland c48aba50f0 button size and icon 2025-04-27 01:51:41 -04:00
hpunq f539930509 formatted code with prettier 2025-04-26 01:04:11 +01:00
hpunq 759cdb6276 add new import sounds tab with explanation and hyperlinks 2025-04-24 13:53:42 +01:00
hpunq 3dae2a80ab change import sounds label to import sounds folder 2025-04-24 13:51:44 +01:00
Jade (Rose) Rowland 7cc44a8829 Merge pull request #1326 from daslyfe/origin/jade/newthemes
feat: new themes + theme improvements
2025-04-21 18:04:37 -04:00
Jade (Rose) Rowland 90506f9e61 Merge pull request #1321 from daslyfe/jade/slit
feat: Create scrub function for scrubbing an audio file
2025-04-20 12:24:15 -04:00
Jade (Rose) Rowland c852b5fab0 fix lint 2025-04-19 16:21:11 -04:00
Jade (Rose) Rowland bd08b74ebc fixed strudel bool 2025-04-19 16:10:38 -04:00
Jade (Rose) Rowland 8f4dcb6bc6 Merge pull request #1325 from daslyfe/jade/udelsconfig
fix: udels header
2025-04-18 01:17:33 -04:00
Jade (Rose) Rowland 17e1ebeb93 udelsconfig 2025-04-18 01:02:13 -04:00
Jade (Rose) Rowland ad25ce7b21 working 2025-04-18 01:01:10 -04:00
Jade (Rose) Rowland 6e3aa693d5 format 2025-04-17 23:35:10 -04:00
Jade (Rose) Rowland 2968017300 disabled 2025-04-17 23:32:20 -04:00
Alex McLean ab76b4424a send delta in seconds, to match tidal/superdirt (#1323) 2025-04-11 08:18:57 +01:00
Jade (Rose) Rowland b64daf6284 Merge pull request #1322 from daslyfe/jade/fixmultichannelaudio
FIX: Multichannel Audio
2025-04-08 00:08:57 -04:00
Jade (Rose) Rowland 2b69004eb4 lint 2025-04-08 00:04:22 -04:00
Jade (Rose) Rowland a32bb58fb3 rm dependency 2025-04-08 00:01:34 -04:00
Jade (Rose) Rowland 9e233fe587 var name 2025-04-07 23:55:34 -04:00
Jade (Rose) Rowland b1090a1dd8 rm deadcode 2025-04-07 23:52:15 -04:00
Jade (Rose) Rowland 344d9ca1a8 working 2025-04-07 23:43:29 -04:00
Jade (Rose) Rowland b92a28242e Merge pull request #1317 from daslyfe/jade/voicemax
feat: add max polyphony feature for superdough
2025-04-07 22:18:03 -04:00
Jade (Rose) Rowland 695ac6a5d3 var 2025-04-06 16:34:14 -04:00
Jade (Rose) Rowland a468201822 add tests 2025-04-06 16:32:37 -04:00
Jade (Rose) Rowland 05ea9c86fb working 2025-04-06 16:08:42 -04:00
Jade (Rose) Rowland 6e29210bdc Merge pull request #1315 from daslyfe/jade/readableerror
enhancement: make error messages easier to read
2025-04-05 15:12:05 -04:00
Jade (Rose) Rowland e81345ca2f Merge pull request #1319 from daslyfe/jade/bug/replace_white_spaces_in_sounds
fix: replace empty spaces in registered sound keys
2025-04-04 00:26:57 -04:00
Jade (Rose) Rowland 6b8ee8ad11 lint 2025-04-03 01:05:26 -04:00
Jade (Rose) Rowland c2d4b529f6 working 2025-04-03 00:59:46 -04:00
Jade (Rose) Rowland 999c0b2349 lint 2025-03-31 00:21:01 -04:00
Jade (Rose) Rowland 5ed28d6168 gain curve func 2025-03-31 00:17:26 -04:00
Jade (Rose) Rowland bceb37e8ea rm comment 2025-03-30 20:32:19 -04:00
Jade (Rose) Rowland 2f4cba4e94 format 2025-03-30 20:26:44 -04:00
Jade (Rose) Rowland d8e4055c6e settings panel 2025-03-30 20:22:24 -04:00
Jade (Rose) Rowland 6f9bcc53dc ramp cut audio node 2025-03-30 16:33:47 -04:00
Jade (Rose) Rowland 8962e5a4f0 max poly 2025-03-30 01:35:25 -04:00
Jade (Rose) Rowland e55b7c6ce3 mostly working 2025-03-30 00:55:12 -04:00
Jade (Rose) Rowland 9b4574c7b0 Merge pull request #1314 from daslyfe/jade/alias
small feat: Add alias for segment and ribbon
2025-03-26 12:01:04 -04:00
Rene Nyffenegger 6d355a803a Fix typo pattnr (#1316) 2025-03-26 13:54:15 +00:00
Jade (Rose) Rowland dee75bc759 update tests 2025-03-24 01:32:48 -04:00
Jade (Rose) Rowland dc8e5d5394 lint 2025-03-24 01:28:43 -04:00
Jade (Rose) Rowland 282180ef1c style update 2025-03-24 01:28:31 -04:00
Jade (Rose) Rowland 3eea908636 fix doc 2025-03-24 00:33:00 -04:00
Jade (Rose) Rowland bfbd920b1d fix doc 2025-03-24 00:18:26 -04:00
Jade (Rose) Rowland 739fb8562f update test 2025-03-24 00:15:36 -04:00
Jade (Rose) Rowland cbd448e934 update docs 2025-03-24 00:09:46 -04:00
Felix Roos b192c6823c Merge pull request #1312 from daslyfe/jade/bug/as_single_param
bugfix: Allow single param to be used in the as function
2025-03-23 20:05:31 +01:00
Jade (Rose) Rowland dccfefacd7 test 2025-03-22 12:06:43 -04:00
Jade (Rose) Rowland 38876e135b fixed 2025-03-22 11:54:10 -04:00
Felix Roos f652c2ca86 Merge pull request #1244 from nkymut/add-program-change
Add MIDI Program Change, SysEx, NRPN, PitchBend and AfterTouch Output
2025-03-20 23:35:17 +01:00
Felix Roos 4f6c4bb95f Merge pull request #1040 from fnordomat/soundfont-url
WIP: make soundfont base url configurable
2025-03-19 17:32:30 +01:00
Alex McLean f5bb88a696 Add num samples from 0 up to 20 (#1310)
* re-edited numbers
* num samples up to 20
* add freesound license
2025-03-18 19:07:08 +00:00
Alex McLean b27c9dbf37 add num samples (edited numbers) (#1309) 2025-03-17 09:38:54 +00:00
nkymut 5fcb96f73c codeformat 2025-03-11 23:33:45 +08:00
nkymut 17df418935 Update Documentation 2025-03-11 23:30:21 +08:00
nkymut 2ccb95aec0 Refactor: Consolidate configuration variables into midiConfig object
- add options argument to .midi
- add midiConfig object with properties
2025-03-11 23:29:37 +08:00
nkymut 3c2692bdda remove debugging artifacts 2025-03-09 06:06:59 +08:00
nkymut 756a65016d Merge branch 'add-program-change' of https://github.com/nkymut/strudel into add-program-change 2025-03-09 05:37:44 +08:00
nkymut a18d485283 Merge branch 'main' of https://github.com/tidalcycles/strudel into add-program-change 2025-03-09 05:34:18 +08:00
Felix Roos fcf6450ff0 Merge pull request #1288 from tidalcycles/sampler-improvements
@strudel/sampler improvements
2025-03-08 22:17:46 +01:00
Felix Roos 09dd374722 Merge branch 'main' into add-program-change 2025-03-08 22:08:03 +01:00
Felix Roos 9c9ce50b03 Merge pull request #1223 from nkymut/gamepad-pr
Add Gamepad module
2025-03-08 21:56:50 +01:00
Felix Roos edc2903106 fix: relative imports 2025-03-04 21:35:32 +01: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
Felix Roos b016720696 sampler: support serving any file (like strudel.json) 2025-02-20 23:37:59 +01:00
Felix Roos c639e44ca7 fix: dont bail when ip cannot be determined (happens when no wifi) 2025-02-20 23:07:45 +01: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
nkymut 43523b73c4 codeformat 2025-02-09 15:12:14 +08:00
nkymut b98ebc696d remove midimap JSDoc 2025-02-09 12:19:30 +08:00
nkymut f802f18d68 refactor(midi): extract MIDI message handlers into dedicated functions
- Move MIDI API logic into separate functions following #1274 (sendCC, sendProgramChange, sendPitchBend, etc.)
2025-02-09 10:56:51 +08:00
nkymut 919c2854d5 Merge branch 'main' of https://github.com/tidalcycles/strudel into add-program-change 2025-02-09 10:53:03 +08: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
nkymut 451cdcc3a9 fix sendChannelAfterTouch -> sendChannelAftertouch 2025-02-07 04:39:24 +08:00
nkymut 5ff1d35272 'miditouch' change sendKeyAfterTouch to sendChannelAfterTouch 2025-02-07 00:08:31 +08: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
nkymut 54c5454306 edit README.md 2025-02-05 08:17:42 +08:00
nkymut 9b279ff671 update 'midicmd' in README.md 2025-02-05 08:11:39 +08:00
nkymut 20dcae68c1 add sysex handler to midicmd 2025-02-05 07:42:06 +08:00
nkymut 52d1443cf8 Add midicmd JSdoc 2025-02-05 07:41:24 +08: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
nkymut ed7dc4ef6e codeformat 2025-02-04 02:49:47 +08:00
nkymut 4636d824fa remove experimental code 2025-02-03 08:40:39 +08: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 9d5d86bb09 docs: use mask instead of gain, so you see what's active 2025-01-31 14:13:47 +01:00
Felix Roos e2b7c3a62b chore: remove obsolete snapshots 2025-01-31 11:05:12 +01:00
Felix Roos 1fb68af5af chore: pnpm i 2025-01-31 11:01:40 +01:00
Felix Roos b611c9c2b4 chore: revert to main package lock 2025-01-31 11:00:07 +01:00
Felix Roos ceb466ab9c chore: update vite 2025-01-31 10:58:27 +01:00
Felix Roos d09809ed12 Merge branch 'main' into gamepad-pr 2025-01-31 10:55:12 +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
nkymut 23ed342635 prettier! 2025-01-26 02:11:13 +08:00
nkymut 443760c0d1 update documentation 2025-01-26 02:09:37 +08:00
nkymut bb56ed479f Refactor gamepad polling to use Strudel's signal system
- Replace requestAnimationFrame polling with signal
- individual signals for buttons and axes from baseSignal
- Remove window message-based gamepad state handling for toggleButtons
- Add module-level state storage to keep button state across strudel code update
2025-01-26 02:04:13 +08:00
nkymut c5d7f95441 add testsnapshot 2025-01-25 10:01:58 +08:00
nkymut 3ffe3957ba Prettier! 2025-01-25 08:00:49 +08:00
nkymut 3189b365c8 fix midibend and miditouch 2025-01-25 03:27:46 +08:00
nkymut e19d059c0f update documents 2025-01-25 02:29:44 +08:00
nkymut b8b999eab5 add midibend, miditouch 2025-01-25 02:28:56 +08:00
nkymut 393d17a41b fix test error
- add mock 'midin' and 'sysex'
- typo in sysex example
2025-01-25 01:12:38 +08: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
nkymut d06a75a2cd Add cc to midicmd, add API Reference for midi related controls 2025-01-24 05:37:11 +08:00
nkymut 57c48f0c45 Add 'sysex' control
- sysex(id, data) and both arguments are patternable
2025-01-23 08:30:06 +08:00
nkymut a4792e29f5 update ProgramChange from pc to progNum
- add progNum keyword handler
- update midicmd handler to handle 'progNum' case
2025-01-22 06:48:55 +08: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
nkymut f95eadab2f adjust midicmd heading level 2025-01-20 01:31:26 +08:00
nkymut 1349ecb4af add midicmd documentation 2
- MiniRepl for input-output.mdx
- addresses #789 #710
2025-01-20 01:26:41 +08:00
nkymut 268c66cd3d add midicmd documentation
- addresses #789 #710
2025-01-20 01:11:39 +08: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
nkymut a8803784e1 prettier! 2025-01-18 14:42:58 +08:00
nkymut bfb4eee122 Merge branch 'main' of https://github.com/tidalcycles/strudel into add-program-change 2025-01-18 14:42:30 +08:00
nkymut 3325a8dbe9 Add device specific setting folder 1
- add example sysex data for setting NSX-39(Pocket Miku) voice data.
2025-01-18 14:38:22 +08:00
nkymut 13a4512601 Get sysex working
split sysex message into sysexid and sysexdata
sysexid is a device identification number or array
sysexdata is an array of data to be sent to the device
2025-01-18 14:31:11 +08: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
nkymut e085819fe2 add documentation for pc and sysex 2025-01-15 22:43:03 +08:00
nkymut 155ef9e95f register pc and sysex as control keywords 2025-01-14 22:10:16 +08: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
nkymut c242f5f625 midi mapping to handle program change and sysex 2025-01-14 17:11:14 +08:00
nkymut 859f153ec6 Add program change(pc) and sysex to midi 2025-01-14 16:54:53 +08: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
nkymut 0be9f439bc Prettier💄 2024-12-17 19:39:38 +08:00
nkymut 36fe942e24 getting rid of AI-smell 2024-12-17 19:35:09 +08:00
Yuta Nakayama 06958a5281 Merge branch 'main' into gamepad-pr 2024-12-17 19:22:42 +08:00
nkymut 033b123ae0 Add DOCS for gamepad module 2024-12-17 19:22:03 +08: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 f01d51f76a fix errors in README.md 2024-11-27 01:16:06 +08:00
nkymut d741b4d17a Add GamePad Module 2024-11-27 01:03:09 +08: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
Matthew Kaney bd69ffb4b7 Add high-resolution CC option to midi 2024-11-16 14:35:18 -05: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
Raphaël Forment 23a4bf6641 Getting rid of second argument 2024-11-09 02:33:56 +01:00
Raphaël Forment ec470aa2c6 Feat: midi() command support external instrument parameter mapping
This commit adds a second argument to the midi() command: mapping.
This argument should be an object containing a key-value map of
MIDI controls used by an external synthesizer. If any control is
used that matches the mapping, a CC message is sent.
2024-11-09 01:41:59 +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 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
fnordomat b233c33f56 (squash me) save setting in variable, not store 2024-04-01 17:18:32 +02:00
fnordomat e3b6884b86 make soundfont base url configurable 2024-04-01 10:46:32 +02:00
Felix Roos cdba9efb59 add filter + filterWhen + within 2024-04-01 03:17:15 +02:00
245 changed files with 31787 additions and 20907 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": {
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }],
"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 }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v4
with:
version: 8.11.0
- uses: actions/setup-node@v3
version: 9.12.2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
@@ -39,11 +39,11 @@ jobs:
uses: actions/configure-pages@v2
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: "./website/dist"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v4
-62
View File
@@ -1,62 +0,0 @@
name: Tauri Builder
on:
push:
tags:
- "v*"
workflow_dispatch:
jobs:
release:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, ubuntu-latest, windows-latest]
include:
- os: ubuntu-latest
rust_target: x86_64-unknown-linux-gnu
- os: macos-latest
rust_target: x86_64-apple-darwin
- os: macos-latest
rust_target: aarch64-apple-darwin
- os: windows-latest
rust_target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8.6.2
- name: Node.js setup
uses: actions/setup-node@v3
with:
node-version: latest
# node-version-file: '.nvmrc'
- name: Install Rust (Stable)
run:
curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf libasound2-dev
- name: Install app dependencies from lockfile and build web
run: pnpm install
- name: Build the app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# tauri-action replaces \_\_VERSION\_\_ with the app version
tagName: ${{ github.ref_name }}
releaseName: "Strudel v__VERSION__"
releaseBody: |
See the assets to download this version and install.
releaseDraft: true
prerelease: false
+3 -3
View File
@@ -11,10 +11,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v4
with:
version: 8.11.0
- uses: actions/setup-node@v3
version: 9.12.2
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
+2
View File
@@ -11,3 +11,5 @@ pnpm-lock.yaml
pnpm-workspace.yaml
**/dev-dist
website/.astro
!tidal-drum-machines.json
!tidal-drum-machines-alias.json
+12
View File
@@ -138,6 +138,18 @@ pnpm --filter "./packages/**" publish --access public
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.
## useful commands
```sh
#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
Remember to have fun, and that this project is driven by the passion of volunteers!
+15 -5
View File
@@ -1,6 +1,6 @@
# 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.
@@ -14,10 +14,16 @@ An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using w
After cloning the project, you can run the REPL locally:
```bash
pnpm i
pnpm dev
```
1. Install [Node.js](https://nodejs.org/)
2. Install [pnpm](https://pnpm.io/installation)
3. Install dependencies by running the following command:
```bash
pnpm i
```
4. Run the development server:
```bash
pnpm dev
```
## Using Strudel In Your Project
@@ -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).
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
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 * as tunes from '../website/src/repl/tunes.mjs';
import { describe, bench } from 'vitest';
import { calculateTactus } from '../packages/core/index.mjs';
import { calculateSteps } from '../packages/core/index.mjs';
const tuneKeys = Object.keys(tunes);
describe('renders tunes', () => {
tuneKeys.forEach((key) => {
describe(key, () => {
calculateTactus(true);
bench(`+tactus`, async () => {
calculateSteps(true);
bench(`+steps`, async () => {
await queryCode(tunes[key], testCycles[key] || 1);
});
calculateTactus(false);
bench(`-tactus`, async () => {
calculateSteps(false);
bench(`-steps`, async () => {
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"
},
"devDependencies": {
"vite": "^5.0.10"
"vite": "^6.0.11"
},
"dependencies": {
"@strudel/codemirror": "workspace:*",
+1 -1
View File
@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"devDependencies": {
"vite": "^5.0.10"
"vite": "^6.0.11"
},
"dependencies": {
"@strudel/web": "workspace:*"
+1 -1
View File
@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"devDependencies": {
"vite": "^5.0.10"
"vite": "^6.0.11"
},
"dependencies": {
"@strudel/core": "workspace:*",
+1 -1
View File
@@ -12,6 +12,6 @@
"superdough": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.10"
"vite": "^6.0.11"
}
}
+1 -1
View File
@@ -31,6 +31,6 @@
"hs2js": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.8"
"vite": "^6.0.11"
}
}
+17 -13
View File
@@ -21,7 +21,7 @@
"osc": "cd packages/osc && npm run server",
"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",
"lint": "eslint . --ext mjs,js --quiet",
"lint": "eslint . --quiet",
"codeformat": "prettier --write .",
"format-check": "prettier --check .",
"report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json",
@@ -55,19 +55,23 @@
"@strudel/xen": "workspace:*"
},
"devDependencies": {
"@tauri-apps/cli": "^1.5.9",
"@vitest/ui": "^1.1.0",
"acorn": "^8.11.3",
"dependency-tree": "^10.0.9",
"eslint": "^8.56.0",
"eslint-plugin-import": "^2.29.1",
"@eslint/compat": "^1.2.5",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@tauri-apps/cli": "^2.2.7",
"@vitest/coverage-v8": "3.0.4",
"@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",
"jsdoc": "^4.0.2",
"globals": "^15.14.0",
"jsdoc": "^4.0.4",
"jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^8.0.0",
"lerna": "^8.0.1",
"prettier": "^3.1.1",
"rollup-plugin-visualizer": "^5.12.0",
"vitest": "^1.1.0"
"lerna": "^8.1.9",
"prettier": "^3.4.2",
"vitest": "^3.0.4",
"vite-plugin-bundle-audioworklet": "workspace:*"
}
}
+25 -12
View File
@@ -1,4 +1,5 @@
import { closeBrackets } from '@codemirror/autocomplete';
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
// import { search, highlightSelectionMatches } from '@codemirror/search';
import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
@@ -12,7 +13,7 @@ import {
lineNumbers,
drawSelection,
} from '@codemirror/view';
import { Pattern, repl } from '@strudel/core';
import { repl, registerControl } from '@strudel/core';
import { Drawer, cleanupDraw } from '@strudel/draw';
import { isAutoCompletionEnabled } from './autocomplete.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) =>
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
);
initTheme(settings.theme);
let state = EditorState.create({
doc: initialCode,
@@ -98,10 +100,8 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
},
{
key: 'Alt-.',
run: (_, e) => {
e.preventDefault();
onStop?.();
},
preventDefault: true,
run: () => onStop?.(),
},
/* {
key: 'Ctrl-Shift-.',
@@ -134,6 +134,7 @@ export class StrudelMirror {
autodraw,
prebake,
bgFill = true,
solo = true,
...replOptions
} = options;
this.code = initialCode;
@@ -144,6 +145,7 @@ export class StrudelMirror {
this.drawContext = drawContext;
this.onDraw = onDraw || this.draw;
this.id = id || s4();
this.solo = solo;
this.drawer = new Drawer((haps, time, _, painters) => {
const currentFrame = haps.filter((hap) => hap.isActive(time));
@@ -160,12 +162,14 @@ export class StrudelMirror {
replOptions?.onToggle?.(started);
if (started) {
this.drawer.start(this.repl.scheduler);
// stop other repls when this one is started
document.dispatchEvent(
new CustomEvent('start-repl', {
detail: this.id,
}),
);
if (this.solo) {
// stop other repls when this one is started
document.dispatchEvent(
new CustomEvent('start-repl', {
detail: this.id,
}),
);
}
} else {
this.drawer.stop();
updateMiniLocations(this.editor, []);
@@ -220,7 +224,7 @@ export class StrudelMirror {
// stop this repl when another repl is started
this.onStartRepl = (e) => {
if (e.detail !== this.id) {
if (this.solo && e.detail !== this.id) {
this.stop();
}
};
@@ -355,3 +359,12 @@ function s4() {
.toString(16)
.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');
+3 -1
View File
@@ -11,7 +11,9 @@ export const flashField = StateField.define({
for (let e of tr.effects) {
if (e.is(setFlash)) {
if (e.value && tr.newDoc.length > 0) {
const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } });
const mark = Decoration.mark({
attributes: { style: `background-color: rgba(255,255,255, .4); filter: invert(10%)` },
});
flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
} else {
flash = Decoration.set([]);
+2 -1
View File
@@ -93,6 +93,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
if (haps.has(id)) {
const hap = haps.get(id);
const color = hap.value?.color ?? 'var(--foreground)';
const style = hap.value?.markcss || `outline: solid 2px ${color}`;
// Get explicit channels for color values
/*
const swatch = document.createElement('div');
@@ -114,7 +115,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
to,
Decoration.mark({
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
attributes: { style: `outline: solid 2px ${color}` },
attributes: { style },
}),
);
}
+14 -16
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "1.1.0",
"version": "1.2.1",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"publishConfig": {
@@ -32,26 +32,24 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@codemirror/autocomplete": "^6.11.1",
"@codemirror/commands": "^6.3.3",
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/language": "^6.10.0",
"@codemirror/search": "^6.5.5",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.23.0",
"@lezer/highlight": "^1.2.0",
"@nanostores/persistent": "^0.9.1",
"@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.1.0",
"@codemirror/autocomplete": "^6.18.4",
"@codemirror/commands": "^6.8.0",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/language": "^6.10.8",
"@codemirror/search": "^6.5.8",
"@codemirror/state": "^6.5.1",
"@codemirror/view": "^6.36.2",
"@lezer/highlight": "^1.2.1",
"@nanostores/persistent": "^0.10.2",
"@replit/codemirror-emacs": "^6.1.0",
"@replit/codemirror-vim": "^6.2.1",
"@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel/core": "workspace:*",
"@strudel/draw": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@uiw/codemirror-themes": "^4.21.21",
"@uiw/codemirror-themes-all": "^4.21.21",
"nanostores": "^0.9.5"
"nanostores": "^0.11.3"
},
"devDependencies": {
"vite": "^5.0.10"
"vite": "^6.0.11"
}
}
+84 -407
View File
@@ -1,440 +1,117 @@
import {
abcdef,
androidstudio,
atomone,
aura,
bespin,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
gruvboxDark,
materialDark,
nord,
okaidia,
solarizedDark,
sublime,
tokyoNight,
tokyoNightStorm,
vscodeDark,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
xcodeLight,
} from '@uiw/codemirror-themes-all';
import strudelTheme, { settings as strudelThemeSettings } from './themes/strudel-theme.mjs';
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen.mjs';
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen.mjs';
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen.mjs';
import teletext, { settings as teletextSettings } from './themes/teletext.mjs';
import algoboy, { settings as algoboySettings } from './themes/algoboy.mjs';
import CutiePi, { settings as CutiePiSettings } from './themes/CutiePi.mjs';
import sonicPink, { settings as sonicPinkSettings } from './themes/sonic-pink.mjs';
import redText, { settings as redTextSettings } from './themes/red-text.mjs';
import greenText, { settings as greenTextSettings } from './themes/green-text.mjs';
import androidstudio, { settings as androidstudioSettings } from './themes/androidstudio.mjs';
import atomone, { settings as atomOneSettings } from './themes/atomone.mjs';
import aura, { settings as auraSettings } from './themes/aura.mjs';
import darcula, { settings as darculaSettings } from './themes/darcula.mjs';
import dracula, { settings as draculaSettings } from './themes/dracula.mjs';
import duotoneDark, { settings as duotoneDarkSettings } from './themes/duotoneDark.mjs';
import eclipse, { settings as eclipseSettings } from './themes/eclipse.mjs';
import githubDark, { settings as githubDarkSettings } from './themes/githubDark.mjs';
import githubLight, { settings as githubLightSettings } from './themes/githubLight.mjs';
import gruvboxDark, { settings as gruvboxDarkSettings } from './themes/gruvboxDark.mjs';
import gruvboxLight, { settings as gruvboxLightSettings } from './themes/gruvboxLight.mjs';
import materialDark, { settings as materialDarkSettings } from './themes/materialDark.mjs';
import materialLight, { settings as materialLightSettings } from './themes/materialLight.mjs';
import nord, { settings as nordSettings } from './themes/nord.mjs';
import monokai, { settings as monokaiSettings } from './themes/monokai.mjs';
import solarizedDark, { settings as solarizedDarkSettings } from './themes/solarizedDark.mjs';
import solarizedLight, { settings as solarizedLightSettings } from './themes/solarizedLight.mjs';
import sublime, { settings as sublimeSettings } from './themes/sublime.mjs';
import tokyoNight, { settings as tokyoNightSettings } from './themes/tokyoNight.mjs';
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 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';
export const themes = {
strudelTheme,
bluescreen,
blackscreen,
whitescreen,
teletext,
algoboy,
terminal,
abcdef,
androidstudio,
atomone,
aura,
bespin,
bbedit,
blackscreen,
bluescreen,
CutiePi,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
githubLight,
greenText,
gruvboxDark,
gruvboxLight,
sonicPink,
materialDark,
materialLight,
monokai,
noctisLilac,
nord,
okaidia,
redText,
solarizedDark,
solarizedLight,
sublime,
teletext,
tokyoNight,
tokyoNightDay,
tokyoNightStorm,
vscodeDark,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
vscodeLight,
whitescreen,
xcodeLight,
};
// lineBackground is background with 50% opacity, to make sure the selection below is visible
export const settings = {
strudelTheme: {
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',
},
strudelTheme: strudelThemeSettings,
bluescreen: bluescreenSettings,
blackscreen: blackscreenSettings,
whitescreen: whitescreenSettings,
teletext: teletextSettings,
algoboy: algoboySettings,
terminal: terminalSettings,
abcdef: {
background: '#0f0f0f',
lineBackground: '#0f0f0f99',
foreground: '#defdef',
caret: '#00FF00',
selection: '#515151',
selectionMatch: '#515151',
gutterBackground: '#555',
gutterForeground: '#FFFFFF',
lineHighlight: '#314151',
},
androidstudio: {
background: '#282b2e',
lineBackground: '#282b2e99',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#343739',
selectionMatch: '#343739',
lineHighlight: '#343739',
},
atomone: {
background: '#272C35',
lineBackground: '#272C3599',
foreground: '#9d9b97',
caret: '#797977',
selection: '#ffffff30',
selectionMatch: '#2B323D',
gutterBackground: '#272C35',
gutterForeground: '#465063',
gutterBorder: 'transparent',
lineHighlight: '#2B323D',
},
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',
},
androidstudio: androidstudioSettings,
atomone: atomOneSettings,
aura: auraSettings,
bbedit: bbeditSettings,
darcula: darculaSettings,
dracula: draculaSettings,
duotoneDark: duotoneDarkSettings,
eclipse: eclipseSettings,
CutiePi: CutiePiSettings,
sonicPink: sonicPinkSettings,
githubLight: githubLightSettings,
githubDark: githubDarkSettings,
greenText: greenTextSettings,
gruvboxDark: gruvboxDarkSettings,
gruvboxLight: gruvboxLightSettings,
materialDark: materialDarkSettings,
materialLight: materialLightSettings,
noctisLilac: noctisLilacSettings,
nord: nordSettings,
monokai: monokaiSettings,
redText: redTextSettings,
solarizedLight: solarizedLightSettings,
solarizedDark: solarizedDarkSettings,
sublime: sublimeSettings,
tokyoNight: tokyoNightSettings,
tokyoNightStorm: tokyoNightStormSettings,
vscodeDark: vscodeDarkSettings,
vscodeLight: vscodeLightSettings,
xcodeLight: xcodeLightSettings,
tokyoNightDay: tokyoNightDaySettings,
};
function getColors(str) {
+45
View File
@@ -0,0 +1,45 @@
/*
* 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 },
],
});
+50 -31
View File
@@ -1,42 +1,61 @@
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 = {
background: '#9bbc0f',
foreground: '#0f380f', // whats that?
caret: '#0f380f',
selection: '#306230',
selectionMatch: '#ffffff26',
lineHighlight: '#8bac0f',
lineBackground: '#9bbc0f50',
background: palette[3],
foreground: palette[1],
caret: palette[0],
selection: palette[0],
selectionMatch: palette[1],
lineHighlight: palette[3],
lineBackground: palette[3] + '90',
//lineBackground: 'transparent',
gutterBackground: 'transparent',
gutterForeground: '#0f380f',
light: true,
gutterForeground: palette[0],
light: false,
// customStyle: '.cm-line { line-height: 1 }',
};
export default createTheme({
theme: 'light',
theme: 'dark',
settings,
styles: [
{ tag: t.labelName, color: '#0f380f' },
{ tag: t.keyword, color: '#0f380f' },
{ tag: t.operator, color: '#0f380f' },
{ tag: t.special(t.variableName), color: '#0f380f' },
{ tag: t.typeName, color: '#0f380f' },
{ tag: t.atom, color: '#0f380f' },
{ tag: t.number, color: '#0f380f' },
{ tag: t.definition(t.variableName), color: '#0f380f' },
{ tag: t.string, color: '#0f380f' },
{ tag: t.special(t.string), color: '#0f380f' },
{ 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' },
{ tag: t.comment, color: palette[2] },
{ tag: t.string, color: palette[1] },
{ tag: [t.atom, t.number], color: palette[1] },
{ tag: [t.meta, t.labelName, t.variableName], color: palette[0] },
{
tag: [t.keyword, t.tagName, t.arithmeticOperator],
color: palette[1],
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: palette[0] },
{ tag: [t.function(t.variableName), t.propertyName], color: palette[0] },
{ tag: t.atom, color: palette[1] },
],
});
+43
View File
@@ -0,0 +1,43 @@
/*
* 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' },
],
});
+51
View File
@@ -0,0 +1,51 @@
/*
* 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.atom, t.bool, t.special(t.variableName)], color: 'hsl( 29, 54%, 61%)' },
{ 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' },
],
});
+21 -21
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: 'black',
foreground: 'white', // whats that?
@@ -15,25 +15,25 @@ export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.labelName, color: 'white' },
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
{ tag: t.typeName, color: 'white' },
{ tag: t.atom, color: 'white' },
{ tag: t.number, color: 'white' },
{ tag: t.definition(t.variableName), color: 'white' },
{ tag: t.string, color: 'white' },
{ tag: t.special(t.string), color: 'white' },
{ tag: t.comment, color: 'white' },
{ tag: t.variableName, color: 'white' },
{ tag: t.tagName, color: 'white' },
{ tag: t.bracket, color: 'white' },
{ tag: t.meta, color: 'white' },
{ tag: t.attributeName, color: 'white' },
{ tag: t.propertyName, color: 'white' },
{ tag: t.className, color: 'white' },
{ tag: t.invalid, color: 'white' },
{ tag: [t.unit, t.punctuation], color: 'white' },
{ tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: 'inherit' },
{ tag: t.operator, color: 'inherit' },
{ tag: t.special(t.variableName), color: 'inherit' },
{ tag: t.typeName, color: 'inherit' },
{ tag: t.atom, color: 'inherit' },
{ tag: t.number, color: 'inherit' },
{ tag: t.definition(t.variableName), color: 'inherit' },
{ tag: t.string, color: 'inherit' },
{ tag: t.special(t.string), color: 'inherit' },
{ tag: t.comment, color: 'inherit' },
{ tag: t.variableName, color: 'inherit' },
{ tag: t.tagName, color: 'inherit' },
{ tag: t.bracket, color: 'inherit' },
{ tag: t.meta, color: 'inherit' },
{ tag: t.attributeName, color: 'inherit' },
{ tag: t.propertyName, color: 'inherit' },
{ tag: t.className, color: 'inherit' },
{ tag: t.invalid, color: 'inherit' },
{ tag: [t.unit, t.punctuation], color: 'inherit' },
],
});
+21 -21
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#051DB5',
lineBackground: '#051DB550',
@@ -18,25 +18,25 @@ export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.labelName, color: 'white' },
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
{ tag: t.typeName, color: 'white' },
{ tag: t.atom, color: 'white' },
{ tag: t.number, color: 'white' },
{ tag: t.definition(t.variableName), color: 'white' },
{ tag: t.string, color: 'white' },
{ tag: t.special(t.string), color: 'white' },
{ tag: t.comment, color: 'white' },
{ tag: t.variableName, color: 'white' },
{ tag: t.tagName, color: 'white' },
{ tag: t.bracket, color: 'white' },
{ tag: t.meta, color: 'white' },
{ tag: t.attributeName, color: 'white' },
{ tag: t.propertyName, color: 'white' },
{ tag: t.className, color: 'white' },
{ tag: t.invalid, color: 'white' },
{ tag: [t.unit, t.punctuation], color: 'white' },
{ tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: 'inherit' },
{ tag: t.operator, color: 'inherit' },
{ tag: t.special(t.variableName), color: 'inherit' },
{ tag: t.typeName, color: 'inherit' },
{ tag: t.atom, color: 'inherit' },
{ tag: t.number, color: 'inherit' },
{ tag: t.definition(t.variableName), color: 'inherit' },
{ tag: t.string, color: 'inherit' },
{ tag: t.special(t.string), color: 'inherit' },
{ tag: t.comment, color: 'inherit' },
{ tag: t.variableName, color: 'inherit' },
{ tag: t.tagName, color: 'inherit' },
{ tag: t.bracket, color: 'inherit' },
{ tag: t.meta, color: 'inherit' },
{ tag: t.attributeName, color: 'inherit' },
{ tag: t.propertyName, color: 'inherit' },
{ tag: t.className, color: 'inherit' },
{ tag: t.invalid, color: 'inherit' },
{ tag: [t.unit, t.punctuation], color: 'inherit' },
],
});
+47
View File
@@ -0,0 +1,47 @@
/*
* 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' },
],
});
+50
View File
@@ -0,0 +1,50 @@
/*
* @name 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 @@
/*
* 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 @@
/*
* 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' },
],
});
+42
View File
@@ -0,0 +1,42 @@
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' },
],
});
+42
View File
@@ -0,0 +1,42 @@
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' },
],
});
+39
View File
@@ -0,0 +1,39 @@
/*
* 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';
const hex = ['#000000', '#8ed675', '#56bd2a', '#54636D', '#171717'];
export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[2],
selection: hex[4],
selectionMatch: hex[0],
gutterBackground: hex[0],
gutterForeground: hex[3],
gutterBorder: 'transparent',
lineHighlight: hex[0],
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: hex[2],
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
{ tag: t.comment, color: hex[3] },
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
{ tag: [t.attributeName, t.number], color: hex[1] },
{ tag: t.keyword, color: hex[2] },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
],
});
+82
View File
@@ -0,0 +1,82 @@
/*
* gruvbox-dark
* author morhetz
* 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` },
],
});
+130
View File
@@ -0,0 +1,130 @@
/*
* gruvbox-light
* author morhetz
* 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` },
],
});
+39
View File
@@ -0,0 +1,39 @@
/*
* 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';
const hex = ['#000000', '#ff5356', '#bd312a', '#54636D', '#171717'];
export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[2],
selection: hex[4],
selectionMatch: hex[0],
gutterBackground: hex[0],
gutterForeground: hex[3],
gutterBorder: 'transparent',
lineHighlight: hex[0],
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: hex[2],
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
{ tag: t.comment, color: hex[3] },
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
{ tag: [t.attributeName, t.number], color: hex[1] },
{ tag: t.keyword, color: hex[2] },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
],
});
+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' },
],
});
+39
View File
@@ -0,0 +1,39 @@
/*
* 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';
const hex = ['#1e1e1e', '#fbde2d', '#ff1493', '#4c83ff', '#ededed', '#cccccc', '#ffffff30', '#dc2f8c'];
export const settings = {
background: '#000000',
lineBackground: 'transparent',
foreground: hex[4],
selection: hex[6],
gutterBackground: hex[0],
gutterForeground: hex[5],
gutterBorder: 'transparent',
lineHighlight: hex[0],
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: hex[4],
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[3] },
{ tag: t.comment, color: '#54636D' },
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[4] },
{ tag: [t.attributeName, t.number], color: hex[3] },
{ tag: t.keyword, color: hex[1] },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] },
],
});
+16 -13
View File
@@ -1,20 +1,23 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#222',
lineBackground: '#22222299',
foreground: '#fff',
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
lineHighlight: '#00000050',
gutterBackground: 'transparent',
gutterForeground: '#8a919966',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#222',
foreground: '#75baff', // whats that?
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
},
settings,
styles: [
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#89ddff' },
{ tag: t.labelName, color: '#89ddff' },
{ tag: t.keyword, color: '#c792ea' },
{ tag: t.operator, color: '#89ddff' },
+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' },
],
});
+1 -1
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import { createTheme } from './theme-helper.mjs';
let colorA = '#6edee4';
//let colorB = 'magenta';
+20 -20
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: 'black',
foreground: '#41FF00', // whats that?
@@ -14,24 +14,24 @@ export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.labelName, color: '#41FF00' },
{ tag: t.keyword, color: '#41FF00' },
{ tag: t.operator, color: '#41FF00' },
{ tag: t.special(t.variableName), color: '#41FF00' },
{ tag: t.typeName, color: '#41FF00' },
{ tag: t.atom, color: '#41FF00' },
{ tag: t.number, color: '#41FF00' },
{ tag: t.definition(t.variableName), color: '#41FF00' },
{ tag: t.string, color: '#41FF00' },
{ tag: t.special(t.string), color: '#41FF00' },
{ tag: t.comment, color: '#41FF00' },
{ tag: t.variableName, color: '#41FF00' },
{ tag: t.tagName, color: '#41FF00' },
{ tag: t.bracket, color: '#41FF00' },
{ tag: t.meta, color: '#41FF00' },
{ tag: t.attributeName, color: '#41FF00' },
{ tag: t.propertyName, color: '#41FF00' },
{ tag: t.className, color: '#41FF00' },
{ tag: t.invalid, color: '#41FF00' },
{ tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: 'inherit' },
{ tag: t.operator, color: 'inherit' },
{ tag: t.special(t.variableName), color: 'inherit' },
{ tag: t.typeName, color: 'inherit' },
{ tag: t.atom, color: 'inherit' },
{ tag: t.number, color: 'inherit' },
{ tag: t.definition(t.variableName), color: 'inherit' },
{ tag: t.string, color: 'inherit' },
{ tag: t.special(t.string), color: 'inherit' },
{ tag: t.comment, color: 'inherit' },
{ tag: t.variableName, color: 'inherit' },
{ tag: t.tagName, color: 'inherit' },
{ tag: t.bracket, color: 'inherit' },
{ tag: t.meta, color: 'inherit' },
{ tag: t.attributeName, color: 'inherit' },
{ tag: t.propertyName, color: 'inherit' },
{ tag: t.className, color: 'inherit' },
{ 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 { createTheme } from '@uiw/codemirror-themes';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: 'white',
foreground: 'black', // whats that?
@@ -16,24 +16,24 @@ export default createTheme({
theme: 'light',
settings,
styles: [
{ tag: t.labelName, color: 'black' },
{ tag: t.keyword, color: 'black' },
{ tag: t.operator, color: 'black' },
{ tag: t.special(t.variableName), color: 'black' },
{ tag: t.typeName, color: 'black' },
{ tag: t.atom, color: 'black' },
{ tag: t.number, color: 'black' },
{ tag: t.definition(t.variableName), color: 'black' },
{ tag: t.string, color: 'black' },
{ tag: t.special(t.string), color: 'black' },
{ tag: t.comment, color: 'black' },
{ tag: t.variableName, color: 'black' },
{ tag: t.tagName, color: 'black' },
{ tag: t.bracket, color: 'black' },
{ tag: t.meta, color: 'black' },
{ tag: t.attributeName, color: 'black' },
{ tag: t.propertyName, color: 'black' },
{ tag: t.className, color: 'black' },
{ tag: t.invalid, color: 'black' },
{ tag: t.labelName, color: 'inherit' },
{ tag: t.keyword, color: 'inherit' },
{ tag: t.operator, color: 'inherit' },
{ tag: t.special(t.variableName), color: 'inherit' },
{ tag: t.typeName, color: 'inherit' },
{ tag: t.atom, color: 'inherit' },
{ tag: t.number, color: 'inherit' },
{ tag: t.definition(t.variableName), color: 'inherit' },
{ tag: t.string, color: 'inherit' },
{ tag: t.special(t.string), color: 'inherit' },
{ tag: t.comment, color: 'inherit' },
{ tag: t.variableName, color: 'inherit' },
{ tag: t.tagName, color: 'inherit' },
{ tag: t.bracket, color: 'inherit' },
{ tag: t.meta, color: 'inherit' },
{ tag: t.attributeName, color: 'inherit' },
{ tag: t.propertyName, color: 'inherit' },
{ tag: t.className, color: 'inherit' },
{ tag: t.invalid, color: 'inherit' },
],
});
+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');
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());
describe('tactus', () => {
describe('steps', () => {
calculateTactus(true);
bench(
'+tactus',
+4 -11
View File
@@ -4,9 +4,9 @@
// import createClock from './zyklus.mjs';
function getTime() {
const precision = 10 ** 4;
const seconds = performance.now() / 1000;
return Math.round(seconds * precision) / precision;
const seconds = performance.now() * 0.001;
return seconds;
// return Math.round(seconds * precision) / precision;
}
let num_cycles_at_cps_change = 0;
@@ -24,27 +24,20 @@ const sendMessage = (type, payload) => {
const sendTick = (phase, duration, tick, time) => {
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration;
const tickdeadline = phase - time;
const lastTick = time + tickdeadline;
const num_cycles_since_cps_change = num_seconds_since_cps_change * cps;
const begin = num_cycles_at_cps_change + num_cycles_since_cps_change;
const secondsSinceLastTick = time - lastTick - duration;
const eventLength = duration * cps;
const end = begin + eventLength;
const cycle = begin + secondsSinceLastTick * cps;
sendMessage('tick', {
begin,
end,
cps,
tickdeadline,
num_cycles_at_cps_change,
num_seconds_at_cps_change,
num_seconds_since_cps_change,
time,
cycle,
});
num_ticks_since_cps_change++;
+258 -15
View File
@@ -1,5 +1,5 @@
/*
controls.mjs - <short description TODO>
controls.mjs - Registers audio controls for pattern manipulation and effects.
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/controls.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/>.
*/
@@ -47,12 +47,16 @@ export function createParam(names) {
return func;
}
// maps control alias names to the "main" control name
const controlAlias = new Map();
export function registerControl(names, ...aliases) {
const name = Array.isArray(names) ? names[0] : names;
let bag = {};
bag[name] = createParam(names);
aliases.forEach((alias) => {
bag[alias] = bag[name];
controlAlias.set(alias, name);
Pattern.prototype[alias] = Pattern.prototype[name];
});
return bag;
@@ -454,8 +458,40 @@ export const { drive } = registerControl('drive');
*/
export const { channels, ch } = registerControl('channels', 'ch');
// superdirt only
export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
/**
* controls the pulsewidth of the pulse oscillator
*
* @name pw
* @param {number | Pattern} pulsewidth
* @example
* note("{f a c e}%16").s("pulse").pw(".8:1:.2")
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1")
*/
export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']);
/**
* controls the lfo rate for the pulsewidth of the pulse oscillator
*
* @name pwrate
* @param {number | Pattern} rate
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
*
*/
export const { pwrate } = registerControl('pwrate');
/**
* controls the lfo sweep for the pulsewidth of the pulse oscillator
*
* @name pwsweep
* @param {number | Pattern} sweep
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
*
*/
export const { pwsweep } = registerControl('pwsweep');
/**
* Phaser audio effect that approximates popular guitar pedals.
@@ -468,7 +504,11 @@ export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
* .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
@@ -1383,6 +1423,17 @@ export const { compressorRelease } = registerControl('compressorRelease');
*
*/
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`.
*
@@ -1393,6 +1444,7 @@ export const { speed } = registerControl('speed');
* @superdirtOnly
*
*/
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:
@@ -1444,7 +1496,7 @@ export const { vowel } = registerControl('vowel');
* @name waveloss
*/
export const { waveloss } = registerControl('waveloss');
/*
/**
* Noise crackle density
*
* @name density
@@ -1496,20 +1548,11 @@ export const { scram } = registerControl('scram');
export const { binshift } = registerControl('binshift');
export const { hbrick } = registerControl('hbrick');
export const { lbrick } = registerControl('lbrick');
export const { midichan } = registerControl('midichan');
export const { control } = registerControl('control');
export const { ccn } = registerControl('ccn');
export const { ccv } = registerControl('ccv');
export const { polyTouch } = registerControl('polyTouch');
export const { midibend } = registerControl('midibend');
export const { miditouch } = registerControl('miditouch');
export const { ctlNum } = registerControl('ctlNum');
export const { frameRate } = registerControl('frameRate');
export const { frames } = registerControl('frames');
export const { hours } = registerControl('hours');
export const { midicmd } = registerControl('midicmd');
export const { minutes } = registerControl('minutes');
export const { progNum } = registerControl('progNum');
export const { seconds } = registerControl('seconds');
export const { songPtr } = registerControl('songPtr');
export const { uid } = registerControl('uid');
@@ -1601,3 +1644,203 @@ export const ar = register('ar', (t, pat) => {
const [attack, release = attack] = t;
return pat.set({ attack, release });
});
//MIDI
/**
* MIDI channel: Sets the MIDI channel for the event.
*
* @name midichan
* @param {number | Pattern} channel MIDI channel number (0-15)
* @example
* note("c4").midichan(1).midi()
*/
export const { midichan } = registerControl('midichan');
export const { midimap } = registerControl('midimap');
/**
* MIDI port: Sets the MIDI port for the event.
*
* @name midiport
* @param {number | Pattern} port MIDI port
* @example
* note("c a f e").midiport("<0 1 2 3>").midi()
*/
export const { midiport } = registerControl('midiport');
/**
* MIDI command: Sends a MIDI command message.
*
* @name midicmd
* @param {number | Pattern} command MIDI command
* @example
* midicmd("clock*48,<start stop>/2").midi()
*/
export const { midicmd } = registerControl('midicmd');
/**
* MIDI control: Sends a MIDI control change message.
*
* @name control
* @param {number | Pattern} MIDI control number (0-127)
* @param {number | Pattern} MIDI controller value (0-127)
*/
export const control = register('control', (args, pat) => {
if (!Array.isArray(args)) {
throw new Error('control expects an array of [ccn, ccv]');
}
const [_ccn, _ccv] = args;
return pat.ccn(_ccn).ccv(_ccv);
});
/**
* MIDI control number: Sends a MIDI control change message.
*
* @name ccn
* @param {number | Pattern} MIDI control number (0-127)
*/
export const { ccn } = registerControl('ccn');
/**
* MIDI control value: Sends a MIDI control change message.
*
* @name ccv
* @param {number | Pattern} MIDI control value (0-127)
*/
export const { ccv } = registerControl('ccv');
export const { ctlNum } = registerControl('ctlNum');
// TODO: ctlVal?
/**
* MIDI NRPN non-registered parameter number: Sends a MIDI NRPN non-registered parameter number message.
* @name nrpnn
* @param {number | Pattern} nrpnn MIDI NRPN non-registered parameter number (0-127)
* @example
* note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi()
*/
export const { nrpnn } = registerControl('nrpnn');
/**
* MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message.
* @name nrpv
* @param {number | Pattern} nrpv MIDI NRPN non-registered parameter value (0-127)
* @example
* note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi()
*/
export const { nrpv } = registerControl('nrpv');
/**
* MIDI program number: Sends a MIDI program change message.
*
* @name progNum
* @param {number | Pattern} program MIDI program number (0-127)
* @example
* note("c4").progNum(10).midichan(1).midi()
*/
export const { progNum } = registerControl('progNum');
/**
* MIDI sysex: Sends a MIDI sysex message.
* @name sysex
* @param {number | Pattern} id Sysex ID
* @param {number | Pattern} data Sysex data
* @example
* note("c4").sysex(["0x77", "0x01:0x02:0x03:0x04"]).midichan(1).midi()
*/
export const sysex = register('sysex', (args, pat) => {
if (!Array.isArray(args)) {
throw new Error('sysex expects an array of [id, data]');
}
const [id, data] = args;
return pat.sysexid(id).sysexdata(data);
});
/**
* MIDI sysex ID: Sends a MIDI sysex identifier message.
* @name sysexid
* @param {number | Pattern} id Sysex ID
* @example
* note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
*/
export const { sysexid } = registerControl('sysexid');
/**
* MIDI sysex data: Sends a MIDI sysex message.
* @name sysexdata
* @param {number | Pattern} data Sysex data
* @example
* note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
*/
export const { sysexdata } = registerControl('sysexdata');
/**
* MIDI pitch bend: Sends a MIDI pitch bend message.
* @name midibend
* @param {number | Pattern} midibend MIDI pitch bend (-1 - 1)
* @example
* note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi()
*/
export const { midibend } = registerControl('midibend');
/**
* MIDI key after touch: Sends a MIDI key after touch message.
* @name miditouch
* @param {number | Pattern} miditouch MIDI key after touch (0-1)
* @example
* note("c4").miditouch(sine.slow(4).range(0,1)).midi()
*/
export const { miditouch } = registerControl('miditouch');
// TODO: what is this?
export const { polyTouch } = registerControl('polyTouch');
export const getControlName = (alias) => {
if (controlAlias.has(alias)) {
return controlAlias.get(alias);
}
return alias;
};
/**
* Sets properties in a batch.
*
* @name as
* @param {String | Array} mapping the control names that are set
* @example
* "c:.5 a:1 f:.25 e:.8".as("note:clip")
* @example
* "{0@2 0.25 0 0.5 .3 .5}%8".as("begin").s("sax_vib").clip(1)
*/
export const as = register('as', (mapping, pat) => {
mapping = Array.isArray(mapping) ? mapping : [mapping];
return pat.fmap((v) => {
v = Array.isArray(v) ? v : [v];
v = Object.fromEntries(mapping.map((prop, i) => [getControlName(prop), v[i]]));
return v;
});
});
/**
* Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file
* in the optional array syntax ex: "0.5:2", the second value controls the speed of playback
* @name scrub
* @memberof Pattern
* @returns Pattern
* @example
* samples('github:switchangel/pad')
* s("swpad:0").scrub("{0.1!2 .25@3 0.7!2 <0.8:1.5>}%8")
* @example
* samples('github:yaxu/clean-breaks/main');
* s("amen/4").fit().scrub("{0@3 0@2 4@3}%8".div(16))
*/
export const scrub = register(
'scrub',
(beginPat, pat) => {
return beginPat.outerBind((v) => {
if (!Array.isArray(v)) {
v = [v];
}
const [beginVal, speedMultiplier = 1] = v;
return pat.begin(beginVal).mul(speed(speedMultiplier)).clip(1);
});
},
false,
);
+4
View File
@@ -68,6 +68,10 @@ export class Cyclist {
// see https://github.com/tidalcycles/strudel/pull/1004
const deadline = targetTime - phase;
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) {
+1 -2
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/>.
*/
import { isPattern } from './index.mjs';
export const evalScope = async (...args) => {
const results = await Promise.allSettled(args);
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
@@ -39,6 +37,7 @@ function safeEval(str, options = {}) {
export const evaluate = async (code, transpiler, transpilerOptions) => {
let meta = {};
if (transpiler) {
// transform syntactically correct js code to semantically usable code
const transpiled = transpiler(code, transpilerOptions);
+2 -2
View File
@@ -119,10 +119,10 @@ export const lcm = (...fractions) => {
if (fractions.length === 0) {
return undefined;
}
const x = fractions.pop();
return fractions.reduce(
(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 './pattern.mjs';
export * from './signal.mjs';
export * from './pick.mjs';
export * from './state.mjs';
export * from './timespan.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 { ClockCollator, cycleToSeconds } from './util.mjs';
export class NeoCyclist {
constructor({ onTrigger, onToggle, getTime }) {
@@ -13,79 +14,38 @@ export class NeoCyclist {
this.lastTick = 0; // absolute time when last tick (clock callback) happened
this.getTime = getTime; // get absolute time
this.time_at_last_tick_message = 0;
this.num_cycles_at_cps_change = 0;
// 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 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.latency = 0.1; // fixed trigger time offset
this.cycle = 0;
this.id = Math.round(Date.now() * Math.random());
this.worker_time_dif;
this.worker = new SharedWorker(new URL('./clockworker.js', import.meta.url));
this.worker.port.start();
this.channel = new BroadcastChannel('strudeltick');
let weight = 0; // the amount of weight that is applied to the current average when averaging a new time dif
const maxWeight = 20;
const precision = 10 ** 3; //round off time diff to prevent accumulating outliers
// the clock of the worker and the audio context clock can drift apart over time
// aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
// we need to keep a rolling weighted average of the time difference between the worker clock and audio context clock
// in order to schedule events consistently.
const setTimeReference = (num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline) => {
const time_dif = getTime() - (num_seconds_at_cps_change + num_seconds_since_cps_change) + tickdeadline;
if (this.worker_time_dif == null) {
this.worker_time_dif = time_dif;
} else {
const w = 1; //weight of new time diff;
const new_dif =
Math.round(((this.worker_time_dif * weight + time_dif * w) / (weight + w)) * precision) / precision;
if (new_dif != this.worker_time_dif) {
// reset the weight so the clock recovers faster from an audio context freeze/dropout if it happens
weight = 4;
}
this.worker_time_dif = new_dif;
}
weight = Math.min(weight + 1, maxWeight);
};
const tickCallback = (payload) => {
const {
num_cycles_at_cps_change,
cps,
num_seconds_at_cps_change,
num_seconds_since_cps_change,
begin,
end,
tickdeadline,
cycle,
} = payload;
const { cps, begin, end, cycle, time } = payload;
this.cps = cps;
this.cycle = cycle;
setTimeReference(num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline);
processHaps(begin, end, num_cycles_at_cps_change, num_seconds_at_cps_change);
this.time_at_last_tick_message = this.getTime();
const currentTime = this.collator.calculateOffset(time) + time;
processHaps(begin, end, currentTime);
this.time_at_last_tick_message = currentTime;
};
const processHaps = (begin, end, num_cycles_at_cps_change, seconds_at_cps_change) => {
const processHaps = (begin, end, currentTime) => {
if (this.started === false) {
return;
}
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
haps.forEach((hap) => {
if (hap.hasOnset()) {
const targetTime =
(hap.whole.begin - num_cycles_at_cps_change) / this.cps +
seconds_at_cps_change +
this.latency +
this.worker_time_dif;
const duration = hap.duration / this.cps;
const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps);
const targetTime = timeUntilTrigger + currentTime + this.latency;
const duration = cycleToSeconds(hap.duration, this.cps);
onTrigger?.(hap, 0, duration, this.cps, targetTime);
}
});
@@ -129,8 +89,8 @@ export class NeoCyclist {
this.setStarted(true);
}
stop() {
this.worker_time_dif = null;
logger('[cyclist] stop');
this.collator.reset();
this.setStarted(false);
}
setPattern(pat, autostart = false) {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/core",
"version": "1.1.0",
"version": "1.2.1",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
@@ -31,11 +31,11 @@
},
"homepage": "https://strudel.cc",
"dependencies": {
"fraction.js": "^4.3.7"
"fraction.js": "^5.2.1"
},
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
"devDependencies": {
"vite": "^5.0.10",
"vitest": "^1.1.0"
"vite": "^6.0.11",
"vitest": "^3.0.4"
}
}
+562 -238
View File
File diff suppressed because it is too large Load Diff
+213
View File
@@ -0,0 +1,213 @@
/*
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();
};
+39 -2
View File
@@ -63,11 +63,13 @@ export function repl({
let pPatterns = {};
let anonymousIndex = 0;
let allTransform;
let eachTransform;
const hush = function () {
pPatterns = {};
anonymousIndex = 0;
allTransform = undefined;
eachTransform = undefined;
return silence;
};
@@ -84,15 +86,42 @@ export function repl({
const toggle = () => scheduler.toggle();
const setCps = (cps) => scheduler.setCps(cps);
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) {
allTransform = transform;
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
const injectPatternMethods = () => {
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
return silence;
}
@@ -131,6 +160,7 @@ export function repl({
});
return evalScope({
all,
each,
hush,
cpm,
setCps,
@@ -152,7 +182,14 @@ export function repl({
shouldHush && hush();
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
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) {
pattern = allTransform(pattern);
+252 -230
View File
@@ -1,13 +1,14 @@
/*
signal.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
signal.mjs - continuous 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 { 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 { id, _mod, clamp, objectMap } from './util.mjs';
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
export function steady(value) {
// A continuous value
@@ -15,13 +16,10 @@ export function steady(value) {
}
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);
};
export const isaw = signal((t) => 1 - (t % 1));
export const isaw2 = isaw.toBipolar();
/**
* A sawtooth signal between 0 and 1.
*
@@ -35,8 +33,40 @@ export const isaw2 = isaw.toBipolar();
*
*/
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();
/**
* 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));
/**
@@ -60,6 +90,12 @@ export const sine = sine2.fromBipolar();
*
*/
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));
/**
@@ -71,6 +107,12 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
*
*/
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();
/**
@@ -81,9 +123,37 @@ export const square2 = square.toBipolar();
* n(tri.segment(8).range(0,7)).scale("C:minor")
*
*/
export const tri = fastcat(isaw, saw);
export const tri2 = fastcat(isaw2, saw2);
export const tri = fastcat(saw, isaw);
/**
* 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);
/**
@@ -138,7 +208,7 @@ const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x)));
const timeToRandsPrime = (seed, n) => {
const result = [];
// eslint-disable-next-line
for (let i = 0; i < n; ++n) {
for (let i = 0; i < n; ++i) {
result.push(intSeedToRand(seed));
seed = xorwise(seed);
}
@@ -157,7 +227,82 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
* n(run(4)).scale("C4:pentatonic")
* // n("0 1 2 3").scale("C4:pentatonic")
*/
export const run = (n) => saw.range(0, n).floor().segment(n);
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();
};
/**
* 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.
* @name shuffle
* @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);
});
/**
* 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.
* @name scramble
* @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.
@@ -209,204 +354,6 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
*/
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) => {
xs = xs.map(reify);
if (xs.length == 0) {
@@ -486,19 +433,30 @@ export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
export const randcat = chooseCycles;
const _wchooseWith = function (pat, ...pairs) {
// A list of patterns of values
const values = pairs.map((pair) => reify(pair[0]));
// A list of weight patterns
const weights = [];
let accum = 0;
let total = pure(0);
for (const pair of pairs) {
accum += pair[1];
weights.push(accum);
// 'add' accepts either values or patterns of values here, so no need
// 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 find = r * total;
return values[weights.findIndex((x) => x > find, weights)];
const findpat = total.mul(r);
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();
@@ -520,6 +478,9 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
* @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();
@@ -545,8 +506,11 @@ export const perlinWith = (pat) => {
*/
export const perlin = perlinWith(time.fmap((v) => Number(v)));
export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
export const degradeByWith = register(
'degradeByWith',
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
true,
true,
);
/**
@@ -562,10 +526,18 @@ export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
* s("hh*8").degradeBy(0.2)
* @example
* s("[hh?0.2]*8")
* @example
* //beat generator
* s("bd").segment(16).degradeBy(.5).ribbon(16,1)
*/
export const degradeBy = register('degradeBy', function (x, pat) {
return pat._degradeByWith(rand, x);
});
export const degradeBy = register(
'degradeBy',
function (x, pat) {
return pat._degradeByWith(rand, x);
},
true,
true,
);
/**
*
@@ -579,7 +551,7 @@ export const degradeBy = register('degradeBy', function (x, pat) {
* @example
* 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.
@@ -599,12 +571,17 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
* x => x.undegradeBy(0.8).pan(1)
* )
*/
export const undegradeBy = register('undegradeBy', function (x, pat) {
return pat._degradeByWith(
rand.fmap((r) => 1 - r),
x,
);
});
export const undegradeBy = register(
'undegradeBy',
function (x, pat) {
return pat._degradeByWith(
rand.fmap((r) => 1 - r),
x,
);
},
true,
true,
);
/**
* Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)`
@@ -621,7 +598,7 @@ export const undegradeBy = register('undegradeBy', function (x, pat) {
* 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 +757,48 @@ export const never = register('never', function (_, pat) {
export const always = register('always', function (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/>.
*/
import { s, pan } from '../controls.mjs';
import { s, pan, getControlName } from '../controls.mjs';
import { mini } from '../../mini/mini.mjs';
import { describe, it, expect } from 'vitest';
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:1').pan(1)).firstCycleValues).toEqual([{ s: 'bd', n: 1, pan: 1 }]);
});
it('preserves tactus of the left pattern', () => {
expect(s(mini('bd cp mt').pan(mini('1 2 3 4'))).tactus).toEqual(Fraction(3));
it('preserves step count of the left pattern', () => {
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', () => {
expect(s(mini('bd cp mt').set.out(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(4));
it('preserves step count of the right pattern for .out', () => {
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', () => {
expect(s(mini('bd cp mt').set.mix(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(12));
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'))))._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,
sequence,
palindrome,
s_polymeter,
s_polymeterSteps,
polymeter,
polyrhythm,
silence,
fast,
@@ -46,12 +45,13 @@ import {
rev,
time,
run,
binaryN,
pick,
stackLeft,
stackRight,
stackCentre,
s_cat,
calculateTactus,
stepcat,
sometimes,
} from '../index.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', () => {
expect(s_polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual(
fastcat(pure('d'), pure('e'), pure('d')).firstCycle(),
);
expect(s_polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
stack(sequence('a', 'b', 'c', 'a', 'b', 'c'), sequence('d', 'e', 'd', 'e', 'd', 'e')).firstCycle(),
);
});
it('Can layer up cycles, stepwise, with weighted patterns', () => {
sameFirst(s_polymeterSteps(3, sequence('a', 'b')).fast(2), sequence('a', 'b', 'a', 'b', 'a', 'b'));
});
});
describe('firstOf()', () => {
@@ -735,21 +728,15 @@ describe('Pattern', () => {
describe('signal()', () => {
it('Can make saw/saw2', () => {
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(
sequence(-3 / 4, -1 / 4, 1 / 4, 3 / 4).firstCycle(),
);
expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(-1, -0.5, 0, 0.5).firstCycle());
});
it('Can make isaw/isaw2', () => {
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(
sequence(7 / 8, 5 / 8, 3 / 8, 1 / 8).firstCycle(),
);
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.75, 0.5, 0.25).firstCycle());
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(
sequence(3 / 4, 1 / 4, -1 / 4, -3 / 4).firstCycle(),
);
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.5, 0, -0.5).firstCycle());
});
});
describe('_setContext()', () => {
@@ -887,7 +874,7 @@ describe('Pattern', () => {
.squeezeJoin()
.queryArc(3, 4)
.map((x) => x.value),
).toStrictEqual([Fraction(3.5)]);
).toStrictEqual([Fraction(3)]);
});
});
describe('ply', () => {
@@ -937,6 +924,9 @@ describe('Pattern', () => {
.firstCycle(),
);
});
it('Can chop chops', () => {
expect(pure({ s: 'bev' }).chop(2).chop(2).firstCycle()).toStrictEqual(pure({ s: 'bev' }).chop(4).firstCycle());
});
});
describe('range', () => {
it('Can be patterned', () => {
@@ -955,6 +945,18 @@ describe('Pattern', () => {
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', () => {
it('Can ribbon', () => {
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', () => {
expect(sequence(0, 1, 2, 3).linger(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).iter(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).fast(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).hurry(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).rev().tactus).toStrictEqual(Fraction(4));
expect(sequence(1).segment(10).tactus).toStrictEqual(Fraction(10));
expect(sequence(1, 0, 1).invert().tactus).toStrictEqual(Fraction(3));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4).tactus).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4).tactus).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual(
expect(sequence(0, 1, 2, 3).linger(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).iter(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).fast(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).hurry(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).rev()._steps).toStrictEqual(Fraction(4));
expect(sequence(1).segment(10)._steps).toStrictEqual(Fraction(10));
expect(sequence(1, 0, 1).invert()._steps).toStrictEqual(Fraction(3));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4)._steps).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))._steps).toStrictEqual(
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),
);
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(
pure((x) => x + 1)
.setTactus(3)
.appBoth(pure(1).setTactus(2)).tactus,
.setSteps(3)
.appBoth(pure(1).setSteps(2))._steps,
).toStrictEqual(Fraction(6));
expect(
pure((x) => x + 1)
.setTactus(undefined)
.appBoth(pure(1).setTactus(2)).tactus,
.setSteps(undefined)
.appBoth(pure(1).setSteps(2))._steps,
).toStrictEqual(Fraction(2));
expect(
pure((x) => x + 1)
.setTactus(3)
.appBoth(pure(1).setTactus(undefined)).tactus,
.setSteps(3)
.appBoth(pure(1).setSteps(undefined))._steps,
).toStrictEqual(Fraction(3));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(6));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setTactus(undefined)).tactus).toStrictEqual(Fraction(3));
expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4)).tactus).toStrictEqual(Fraction(4));
expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(6));
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))._steps).toStrictEqual(Fraction(4));
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
expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
expect(fastcat(0, 1).ply(3).tactus).toStrictEqual(Fraction(6));
expect(fastcat(0, 1).setTactus(undefined).ply(3).tactus).toStrictEqual(undefined);
expect(fastcat(0, 1).fast(3).tactus).toStrictEqual(Fraction(2));
expect(fastcat(0, 1).setTactus(undefined).fast(3).tactus).toStrictEqual(undefined);
expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3));
expect(fastcat(0, 1).ply(3)._steps).toStrictEqual(Fraction(6));
expect(fastcat(0, 1).setSteps(undefined).ply(3)._steps).toStrictEqual(undefined);
expect(fastcat(0, 1).fast(3)._steps).toStrictEqual(Fraction(2));
expect(fastcat(0, 1).setSteps(undefined).fast(3)._steps).toStrictEqual(undefined);
});
});
describe('s_cat', () => {
describe('stepcat', () => {
it('can cat', () => {
expect(sameFirst(s_cat(fastcat(0, 1, 2, 3), fastcat(4, 5)), fastcat(0, 1, 2, 3, 4, 5)));
expect(sameFirst(s_cat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3)));
expect(sameFirst(stepcat(fastcat(0, 1, 2, 3), fastcat(4, 5)), fastcat(0, 1, 2, 3, 4, 5)));
expect(sameFirst(stepcat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3)));
});
it('calculates undefined tactuses as the average', () => {
expect(sameFirst(s_cat(pure(1), pure(2), pure(3).setTactus(undefined)), fastcat(1, 2, 3)));
it('calculates undefined steps as the average', () => {
expect(sameFirst(stepcat(pure(1), pure(2), pure(3).setSteps(undefined)), fastcat(1, 2, 3)));
});
});
describe('s_taper', () => {
it('can taper', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(1, 5), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0)));
describe('shrink', () => {
it('can shrink', () => {
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', () => {
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)));
it('can shrink backwards', () => {
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', () => {
it('can add from the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(2), sequence(0, 1)));
describe('grow', () => {
it('can grow', () => {
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', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(2), sequence(0, 1, 2)));
it('can grow backwards', () => {
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)));
});
describe('take and drop', () => {
it('can take from the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).take(2), sequence(0, 1)));
});
it('can sub to the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(-2), sequence(2, 3, 4)));
it('can drop from the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(2), sequence(2, 3, 4)));
});
it('can subtract nothing', () => {
expect(sameFirst(pure('a').s_sub(0), pure('a')));
it('can take from the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).take(-2), sequence(3, 4)));
});
it('can subtract nothing, repeatedly', () => {
expect(sameFirst(pure('a').s_sub(0, 0), fastcat('a', 'a')));
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) {
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)
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) {
if (isNaN(Number(value))) {
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)]));
}
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
// // greatest common divisor
+1
View File
@@ -4,6 +4,7 @@ import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [],
build: {
lib: {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/csound",
"version": "1.1.0",
"version": "1.2.2",
"description": "csound bindings for strudel",
"main": "index.mjs",
"type": "module",
@@ -37,6 +37,6 @@
"@strudel/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.10"
"vite": "^6.0.11"
}
}
+28 -35
View File
@@ -1,43 +1,36 @@
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';
Pattern.prototype.osc = function () {
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));
const collator = new ClockCollator({});
const params = [];
export async function oscTriggerTauri(t_deprecate, hap, currentTime, cps = 1, targetTime) {
const controls = parseControlsFromHap(hap, cps);
const params = [];
const timestamp = collator.calculateTimestamp(currentTime, targetTime);
const timestamp = Math.round(Date.now() + getEventOffsetMs(targetTime, currentTime));
Object.keys(controls).forEach((key) => {
const val = controls[key];
const value = typeof val === 'number' ? val.toString() : val;
Object.keys(controls).forEach((key) => {
const val = controls[key];
const value = typeof val === 'number' ? val.toString() : val;
if (value == null) {
return;
}
params.push({
name: key,
value,
valueisnumber: typeof val === 'number',
});
if (value == null) {
return;
}
params.push({
name: key,
value,
valueisnumber: typeof val === 'number',
});
const messagesfromjs = [];
if (params.length) {
messagesfromjs.push({ target: '/dirt/play', timestamp, params });
}
if (messagesfromjs.length) {
setTimeout(() => {
Invoke('sendosc', { messagesfromjs });
});
}
});
if (params.length === 0) {
return;
}
const message = { target: '/dirt/play', timestamp, params };
setTimeout(() => {
Invoke('sendosc', { messagesfromjs: [message] });
});
}
Pattern.prototype.osc = function () {
return this.onTrigger(oscTriggerTauri);
};
+28 -28
View File
@@ -1,29 +1,29 @@
{
"name": "@strudel/desktopbridge",
"version": "0.1.0",
"private": true,
"description": "tools/shims for communicating between the JS and Tauri (Rust) sides of the Studel desktop app",
"main": "index.mjs",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Jade Rowland <jaderowlanddev@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"dependencies": {
"@strudel/core": "workspace:*",
"@tauri-apps/api": "^1.5.3"
},
"homepage": "https://github.com/tidalcycles/strudel#readme"
}
"name": "@strudel/desktopbridge",
"version": "0.1.0",
"private": true,
"description": "tools/shims for communicating between the JS and Tauri (Rust) sides of the Studel desktop app",
"main": "index.mjs",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Jade Rowland <jaderowlanddev@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"dependencies": {
"@strudel/core": "workspace:*",
"@tauri-apps/api": "^2.2.0"
},
"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 isTauri = () => window.__TAURI_IPC__ != null;
+1 -1
View File
@@ -26,7 +26,7 @@ export const getDrawContext = (id = 'test-canvas', options) => {
}, 200);
});
}
return canvas.getContext(contextType);
return canvas.getContext(contextType, { willReadFrequently: true });
};
let animationFrames = {};
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/draw",
"version": "1.1.0",
"version": "1.2.1",
"description": "Helpers for drawing with Strudel",
"main": "index.mjs",
"type": "module",
@@ -32,6 +32,6 @@
"@strudel/core": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.10"
"vite": "^6.0.11"
}
}
+44 -34
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/>.
*/
import { Pattern, noteToMidi, freqToMidi } from '@strudel/core';
import { Pattern, noteToMidi, freqToMidi, isPattern } from '@strudel/core';
import { getTheme, getDrawContext } from './draw.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min;
@@ -36,35 +36,9 @@ const getValue = (e) => {
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
* @synonyms punchcard
@@ -93,15 +67,51 @@ Pattern.prototype.pianoroll = function (options = {}) {
* @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 {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default
*
* @see _pianoroll
* @example
* note("c2 a2 eb2")
* .euclid(5,8)
* .s('sawtooth')
* .lpenv(4).lpf(300)
* ._pianoroll({ labels: 1 })
* .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,
haps,
cycles = 4,
@@ -278,7 +288,7 @@ export function getDrawOptions(drawTime, options = {}) {
export const getPunchcardPainter =
(options = {}) =>
(ctx, time, haps, drawTime) =>
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
__pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
Pattern.prototype.punchcard = function (options) {
return this.onPaint(getPunchcardPainter(options));
@@ -302,5 +312,5 @@ Pattern.prototype.wordfall = function (options) {
export function drawPianoroll(options) {
const { drawTime, ...rest } = options;
pianoroll({ ...getDrawOptions(drawTime), ...rest });
__pianoroll({ ...getDrawOptions(drawTime), ...rest });
}
+93
View File
@@ -0,0 +1,93 @@
# @strudel/gamepad
This package adds gamepad input functionality to strudel Patterns.
## Install
```sh
npm i @strudel/gamepad --save
```
## Usage
```javascript
import { gamepad } from '@strudel/gamepad';
// Initialize gamepad (optional index parameter, defaults to 0)
const pad = gamepad(0);
// Use gamepad inputs in patterns
const pattern = sequence([
// Button inputs
pad.a, // A button value (0-1)
pad.tglA, // A button toggle (0 or 1)
// Analog stick inputs
pad.x1, // Left stick X (0-1)
pad.x1_2, // Left stick X (-1 to 1)
]);
```
## Available Controls
### Buttons
- Face Buttons
- `a`, `b`, `x`, `y` (or uppercase `A`, `B`, `X`, `Y`)
- Toggle versions: `tglA`, `tglB`, `tglX`, `tglY`
- Shoulder Buttons
- `lb`, `rb`, `lt`, `rt` (or uppercase `LB`, `RB`, `LT`, `RT`)
- Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT`
- D-Pad
- `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase)
- Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight`(or `tglU`, `tglD`, `tglL`, `tglR`)
### Analog Sticks
- Left Stick
- `x1`, `y1` (0 to 1 range)
- `x1_2`, `y1_2` (-1 to 1 range)
- Right Stick
- `x2`, `y2` (0 to 1 range)
- `x2_2`, `y2_2` (-1 to 1 range)
## Examples
```javascript
// Use button values to control amplitude
$: sequence([
s("bd").gain(pad.X), // X button controls gain
s("[hh oh]").gain(pad.tglY), // Y button toggles gain
]);
// Use analog stick for continuous control
$: note("c4*4".add(pad.y1_2.range(-24,24))) // Left stick Y controls pitch shift
.pan(pad.x1_2); // Left stick X controls panning
// Use toggle buttons to switch patterns on/off
// Define button sequences
const HADOKEN = [
'd', // Down
'r', // Right
'a', // A
];
const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA
// Add these lines to enable buttons(but why?)
$:pad.D.segment(16).gain(0)
$:pad.R.segment(16).gain(0)
$:pad.A.segment(16).gain(0)
// Check button sequence (returns 1 when detected, 0 when not within last 1 second)
$: sound("hadoken").gain(pad.checkSequence(HADOKEN))
```
## Multiple Gamepads
You can connect multiple gamepads by specifying the gamepad index:
```javascript
const pad1 = gamepad(0); // First gamepad
const pad2 = gamepad(1); // Second gamepad
```
+117
View File
@@ -0,0 +1,117 @@
import { MiniRepl } from '../../../website/src/docs/MiniRepl';
# Gamepad
The Gamepad module allows you to integrate gamepad input functionality into your musical patterns. This can be particularly useful for live performances or interactive installations where you want to manipulate sounds using a game controller.
## Getting Started
Initialize a gamepad by calling the gamepad() function with an optional index parameter.
<MiniRepl
client:idle
tune={`// Initialize gamepad (optional index parameter, defaults to 0)
const gp = gamepad(0)
note("c a f e").mask(gp.a)`}
/>
## Available Controls
The gamepad module provides access to buttons and analog sticks as normalized signals (0-1) that can modulate your patterns.
### Buttons
| Type | Controls |
| ---------------- | ---------------------------------------------------------------------------------------------- |
| Face Buttons | `a`, `b`, `x`, `y` (or uppercase `A`, `B`, `X`, `Y`) |
| | Toggle versions: `tglA`, `tglB`, `tglX`, `tglY` |
| Shoulder Buttons | `lb`, `rb`, `lt`, `rt` (or uppercase `LB`, `RB`, `LT`, `RT`) |
| | Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT` |
| D-Pad | `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase) |
| | Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight` (or `tglU`, `tglD`, `tglL`, `tglR`) |
### Analog Sticks
| Stick | Controls |
| ----------- | ------------------------------ |
| Left Stick | `x1`, `y1` (0 to 1 range) |
| | `x1_2`, `y1_2` (-1 to 1 range) |
| Right Stick | `x2`, `y2` (0 to 1 range) |
| | `x2_2`, `y2_2` (-1 to 1 range) |
### Button Sequence
| Stick | Controls |
| --------------- | --------------------------------------- |
| Button Sequence | `btnSequence()`, `btnSeq()`, `btnseq()` |
## Using Gamepad Inputs
Once initialized, you can use various gamepad inputs in your patterns. Here are some examples:
### Button Inputs
You can use button inputs to control different aspects of your music, such as gain or triggering events.
<MiniRepl
client:idle
tune={`const gp = gamepad(0)
// Use button values to control amplitude
$: stack(
s("[[hh hh] oh hh oh]/2").mask(gp.tglX).bank("RolandTR909"), // X btn for HH
s("cr*1").mask(gp.Y).bank("RolandTR909"), // LB btn for CR
s("bd").mask(gp.tglA).bank("RolandTR909"), // A btn for BD
s("[ht - - mt - - lt - ]/2").mask(gp.tglB).bank("RolandTR909"), // B btn for Toms
s("sd*4").mask(gp.RB).bank("RolandTR909"), // RB btn for SD
).cpm(120)
`}
/>
### Analog Stick Inputs
Analog sticks can be used for continuous control, such as pitch shifting or panning.
<MiniRepl
client:idle
tune={`const gp = gamepad(0)
// Use analog stick for continuous control
$: note("c4 d3 a3 e3").sound("sawtooth")
.lpf(gp.x1.range(100,4000))
.lpq(gp.y1.range(5,30))
.decay(gp.y2.range(0.1,2))
.lpenv(gp.x2.range(-5,5))
.cpm(120)
`}
/>
### Button Sequences
You can define button sequences to trigger specific actions, like playing a sound when a sequence is detected.
<MiniRepl client:idle tune={`const gp = gamepad(0)
// Define button sequences
const HADOUKEN = [
'd', // Down
'r', // Right
'a', // A
]
const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA
// Check butto-n sequence (returns 1 while detected, 0 when not within last 1 second)
$: s("free_hadouken -").slow(2)
.mask(gp.btnSequence(HADOUKEN)).room(1).cpm(120)
// hadouken.wav by Syna-Max
//https://freesound.org/people/Syna-Max/sounds/67674/
samples({free_hadouken: 'https://cdn.freesound.org/previews/67/67674_111920-lq.mp3'})
`} />
## Multiple Gamepads
Strudel supports multiple gamepads. You can specify the gamepad index to connect to different devices.
<MiniRepl
client:idle
tune={`const pad1 = gamepad(0); // First gamepad
const pad2 = gamepad(1); // Second gamepad`}
/>
+246
View File
@@ -0,0 +1,246 @@
// @strudel/gamepad/index.mjs
import { signal } from '@strudel/core';
// Button mapping for Logitech Dual Action (STANDARD GAMEPAD Vendor: 046d Product: c216)
export const buttonMap = {
a: 0,
b: 1,
x: 2,
y: 3,
lb: 4,
rb: 5,
lt: 6,
rt: 7,
back: 8,
start: 9,
u: 12,
up: 12,
d: 13,
down: 13,
l: 14,
left: 14,
r: 15,
right: 15,
};
class ButtonSequenceDetector {
constructor(timeWindow = 1000) {
this.sequence = [];
this.timeWindow = timeWindow;
this.lastInputTime = 0;
this.buttonStates = Array(16).fill(0); // Track previous state of each button
// Button mapping for character inputs
}
addInput(buttonIndex, buttonValue) {
const currentTime = Date.now();
// Only add input on button press (rising edge)
if (buttonValue === 1 && this.buttonStates[buttonIndex] === 0) {
// Clear sequence if too much time has passed
if (currentTime - this.lastInputTime > this.timeWindow) {
this.sequence = [];
}
// Store the button name instead of index
const buttonName = Object.keys(buttonMap).find((key) => buttonMap[key] === buttonIndex) || buttonIndex.toString();
this.sequence.push({
input: buttonName,
timestamp: currentTime,
});
this.lastInputTime = currentTime;
//console.log(this.sequence);
// Keep only inputs within the time window
this.sequence = this.sequence.filter((entry) => currentTime - entry.timestamp <= this.timeWindow);
}
// Update button state
this.buttonStates[buttonIndex] = buttonValue;
}
checkSequence(targetSequence) {
if (!Array.isArray(targetSequence) && typeof targetSequence !== 'string') {
console.error('ButtonSequenceDetector: targetSequence must be an array or string');
return 0;
}
if (this.sequence.length < targetSequence.length) return 0;
// Convert string input to array if needed
const sequence =
typeof targetSequence === 'string'
? targetSequence.toLowerCase().split('')
: targetSequence.map((s) => s.toString().toLowerCase());
//console.log(this.sequence);
// Get the last n inputs where n is the target sequence length
const lastInputs = this.sequence.slice(-targetSequence.length).map((entry) => entry.input);
// Compare sequences
return lastInputs.every((input, index) => {
const target = sequence[index];
// Check if either the input matches directly or they refer to the same button in the map
return (
input === target ||
buttonMap[input] === buttonMap[target] ||
// Also check if the numerical index matches
buttonMap[input] === parseInt(target)
);
})
? 1
: 0;
}
}
class GamepadHandler {
constructor(index = 0) {
// Add index parameter
this._gamepads = {};
this._activeGamepad = index; // Use provided index
this._axes = [0, 0, 0, 0];
this._buttons = Array(16).fill(0);
this.setupEventListeners();
}
setupEventListeners() {
window.addEventListener('gamepadconnected', (e) => {
this._gamepads[e.gamepad.index] = e.gamepad;
if (!this._activeGamepad) {
this._activeGamepad = e.gamepad.index;
}
});
window.addEventListener('gamepaddisconnected', (e) => {
delete this._gamepads[e.gamepad.index];
if (this._activeGamepad === e.gamepad.index) {
this._activeGamepad = Object.keys(this._gamepads)[0] || null;
}
});
}
poll() {
if (this._activeGamepad !== null) {
const gamepad = navigator.getGamepads()[this._activeGamepad];
if (gamepad) {
// Update axes (normalized to 0-1 range)
this._axes = gamepad.axes.map((axis) => (axis + 1) / 2);
// Update buttons
this._buttons = gamepad.buttons.map((button) => button.value);
}
}
}
getAxes() {
return this._axes;
}
getButtons() {
return this._buttons;
}
}
// Module-level state store for toggle states
const gamepadStates = new Map();
export const gamepad = (index = 0) => {
const handler = new GamepadHandler(index);
const sequenceDetector = new ButtonSequenceDetector(2000);
// Base signal that polls gamepad state and handles sequence detection
const baseSignal = signal((t) => {
handler.poll();
const axes = handler.getAxes();
const buttons = handler.getButtons();
// Add all button inputs to sequence detector
buttons.forEach((value, i) => {
sequenceDetector.addInput(i, value);
});
return { axes, buttons, t };
});
// Create axes patterns
const axes = {
x1: baseSignal.fmap((state) => state.axes[0]),
y1: baseSignal.fmap((state) => state.axes[1]),
x2: baseSignal.fmap((state) => state.axes[2]),
y2: baseSignal.fmap((state) => state.axes[3]),
};
// Add bipolar versions
axes.x1_2 = axes.x1.toBipolar();
axes.y1_2 = axes.y1.toBipolar();
axes.x2_2 = axes.x2.toBipolar();
axes.y2_2 = axes.y2.toBipolar();
// Create button patterns
const buttons = Array(16)
.fill(null)
.map((_, i) => {
// Create unique key for this gamepad+button combination
const stateKey = `gamepad${index}_btn${i}`;
// Initialize toggle state if it doesn't exist
if (!gamepadStates.has(stateKey)) {
gamepadStates.set(stateKey, {
lastButtonState: 0,
toggleState: 0,
});
}
// Direct button value pattern (no longer needs to call addInput)
const btn = baseSignal.fmap((state) => state.buttons[i]);
// Button toggle pattern with persistent state
const toggle = baseSignal.fmap((state) => {
const currentState = state.buttons[i];
const buttonState = gamepadStates.get(stateKey);
if (currentState === 1 && buttonState.lastButtonState === 0) {
// Toggle the state on rising edge
buttonState.toggleState = buttonState.toggleState === 0 ? 1 : 0;
}
buttonState.lastButtonState = currentState;
return buttonState.toggleState;
});
return { value: btn, toggle };
});
// Create sequence checker pattern
const btnSequence = (sequence) => {
return baseSignal.fmap(() => sequenceDetector.checkSequence(sequence));
};
const checkSequence = btnSequence;
const btnSeq = btnSequence;
const btnseq = btnSeq;
// Return an object with all controls
return {
...axes,
buttons,
...Object.fromEntries(
Object.entries(buttonMap).flatMap(([key, index]) => [
[key.toLowerCase(), buttons[index].value],
[key.toUpperCase(), buttons[index].value],
[`tgl${key.toLowerCase()}`, buttons[index].toggle],
[`tgl${key.toUpperCase()}`, buttons[index].toggle],
]),
),
checkSequence,
btnSequence,
btnSeq,
btnseq,
raw: baseSignal,
};
};
// Optional: Export for debugging or state management
export const getGamepadStates = () => Object.fromEntries(gamepadStates);
export const clearGamepadStates = () => gamepadStates.clear();
+3
View File
@@ -0,0 +1,3 @@
import './gamepad.mjs';
export * from './gamepad.mjs';
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@strudel/gamepad",
"version": "1.2.1",
"description": "Gamepad Inputs 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',
},
});
+5 -4
View File
@@ -1,6 +1,7 @@
{
"name": "hs2js",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"description": "Experimental Haskell in JavaScript interpreter",
"main": "src/index.mjs",
"type": "module",
@@ -28,10 +29,10 @@
},
"homepage": "https://github.com/tidalcycles/strudel",
"dependencies": {
"web-tree-sitter": "^0.20.8"
"web-tree-sitter": "^0.24.7"
},
"devDependencies": {
"tree-sitter-haskell": "^0.21.0",
"vite": "^5.0.10"
"tree-sitter-haskell": "^0.23.1",
"vite": "^6.0.11"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/hydra",
"version": "1.1.0",
"version": "1.2.1",
"description": "Hydra integration for strudel",
"main": "hydra.mjs",
"type": "module",
@@ -39,6 +39,6 @@
},
"devDependencies": {
"pkg": "^5.8.1",
"vite": "^5.0.10"
"vite": "^6.0.11"
}
}
+184
View File
@@ -7,3 +7,187 @@ This package adds midi functionality to strudel Patterns.
```sh
npm i @strudel/midi --save
```
## Available Controls
The following MIDI controls are available:
OUTPUT:
- `midi` - opens a midi output device.
- `note` - Sends MIDI note messages. Can accept note names (e.g. "c4") or MIDI note numbers (0-127)
- `midichan` - Sets the MIDI channel (1-16, defaults to 1)
- `velocity` - Sets note velocity (0-1, defaults to 0.9)
- `gain` - Modifies velocity by multiplying with it (0-1, defaults to 1)
- `control` - Sets MIDI control change messages
- `ccn` - Sets MIDI CC controller number (0-127)
- `ccv` - Sets MIDI CC value (0-1)
- `progNum` - Sends MIDI program change messages (0-127)
- `sysex` - Sends MIDI System Exclusive messages (id: number 0-127 or array of bytes 0-127, data: array of bytes 0-127)
- `sysexid` - Sets MIDI System Exclusive ID (number 0-127 or array of bytes 0-127)
- `sysexdata` - Sets MIDI System Exclusive data (array of bytes 0-127)
- `midibend` - Sets MIDI pitch bend (-1 - 1)
- `miditouch` - Sets MIDI key after touch (0-1)
- `midicmd` - Sends MIDI system real-time messages to control timing and transport on MIDI devices.
- `nrpnn` - Sets MIDI NRPN non-registered parameter number (array of bytes 0-127)
- `nrpv` - Sets MIDI NRPN non-registered parameter value (0-127)
INPUT:
- `midin` - Opens a MIDI input port to receive MIDI control change messages.
Additional controls can be mapped using the mapping object passed to `.midi()`:
## Examples
### midi(outputName?, options?)
Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (Linux) for internal midi messages.
If no outputName is given, it uses the first midi output it finds.
```javascript
$: chord("<C^7 A7 Dm7 G7>").voicing().midi('IAC Driver')
```
In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
### Options
The `.midi()` function accepts an options object with the following properties:
```javascript
$: note("c a f e").midi('IAC Driver', { isController: true, midimap: 'default'})
```
<details>
<summary>Available Options</summary>
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| isController | boolean | false | When true, disables sending note messages. Useful for MIDI controllers |
| latencyMs | number | 34 | Latency in milliseconds to align MIDI with audio engine |
| noteOffsetMs | number | 10 | Offset in milliseconds for note-off messages to prevent glitching |
| midichannel | number | 1 | Default MIDI channel (1-16) |
| velocity | number | 0.9 | Default note velocity (0-1) |
| gain | number | 1 | Default gain multiplier for velocity (0-1) |
| midimap | string | 'default' | Name of MIDI mapping to use for control changes |
| midiport | string/number | - | MIDI device name or index |
</details>
### midiport(outputName)
Selects the MIDI output device to use, pattern can be used to switch between devices.
```javascript
$: midiport('IAC Driver')
$: note("c a f e").midiport("<0 1 2 3>").midi()
```
### midichan(number)
Selects the MIDI channel to use. If not used, `.midi` will use channel 1 by default.
### control, ccn && ccv
`control` sends MIDI control change messages to your MIDI device.
- `ccn` sets the cc number. Depends on your synths midi mapping
- `ccv` sets the cc value. normalized from 0 to 1.
```javascript
$: note("c a f e").control([74, sine.slow(4)]).midi()
$: note("c a f e").ccn(74).ccv(sine.slow(4)).midi()
```
In the above snippet, `ccn` is set to 74, which is the filter cutoff for many synths. `ccv` is controlled by a saw pattern.
Having everything in one pattern, the `ccv` pattern will be aligned to the note pattern, because the structure comes from the left by default.
But you can also control cc messages separately like this:
```javascript
$: note("c a f e").midi()
$: ccv(sine.segment(16).slow(4)).ccn(74).midi()
```
### progNum (Program Change)
`progNum` control sends MIDI program change messages to switch between different presets/patches on your MIDI device.
Program change values should be numbers between 0 and 127.
```javascript
// Play notes while changing programs
note("c3 e3 g3").progNum("<0 1 2>").midi()
```
Program change messages are useful for switching between different instrument sounds or presets during a performance.
The exact sound that each program number maps to depends on your MIDI device's configuration.
## sysex, sysexid && sysexdata (System Exclusive Message)
`sysex`, `sysexid` and `sysexdata` control sends MIDI System Exclusive (SysEx) messages to your MIDI device.
sysEx messages are device-specific commands that allow deeper control over synthesizer parameters.
The value should be an array of numbers between 0-255 representing the SysEx data bytes.
```javascript
// Send a simple SysEx message
let id = 0x43; //Yamaha
//let id = "0x00:0x20:0x32"; //Behringer ID can be an array of numbers
let data = "0x79:0x09:0x11:0x0A:0x00:0x00"; // Set NSX-39 voice to say "Aa"
$: note("c d e f e d c").sysex(id, data).midi();
$: note("c d e f e d c").sysexid(id).sysexdata(data).midi();
```
The exact format of SysEx messages depends on your MIDI device's specification.
Consult your device's MIDI implementation guide for details on supported SysEx messages.
### midibend && miditouch
`midibend` sets MIDI pitch bend (-1 - 1)
`miditouch` sets MIDI key after touch (0-1)
```javascript
$: note("c d e f e d c").midibend(sine.slow(4).range(-0.4,0.4)).midi();
$: note("c d e f e d c").miditouch(sine.slow(4).range(0,1)).midi();
```
### midicmd
`midicmd` sends MIDI system real-time messages to control timing and transport on MIDI devices.
It supports the following commands:
- `clock`/`midiClock` - Sends MIDI timing clock messages
- `start` - Sends MIDI start message
- `stop` - Sends MIDI stop message
- `continue` - Sends MIDI continue message
```javascript
// You can control the clock with a pattern and ensure it starts in sync when the repl begins.
// Note: It might act unexpectedly if MIDI isn't set up initially.
stack(
midicmd("clock*48,<start stop>/2").midi('IAC Driver')
)
```
`midicmd` also supports sending control change, program change and sysex messages.
- `cc` - sends MIDI control change messages.
- `progNum` - sends MIDI program change messages.
- `sysex` - sends MIDI system exclusive messages.
```javascript
stack(
// "cc:ccn:ccv"
midicmd("cc:74:1").midi('IAC Driver'),
// "progNum:progNum"
midicmd("progNum:1").midi('IAC Driver'),
// "sysex:[sysexid]:[sysexdata]"
midicmd("sysex:[0x43]:[0x79:0x09:0x11:0x0A:0x00:0x00]").midi('IAC Driver')
)
```
+344 -34
View File
@@ -6,8 +6,9 @@ This program is free software: you can redistribute it and/or modify it under th
import * as _WebMidi from 'webmidi';
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
import { noteToMidi } from '@strudel/core';
import { noteToMidi, getControlName } from '@strudel/core';
import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi;
@@ -43,13 +44,16 @@ export function enableWebMidi(options = {}) {
resolve(WebMidi);
return;
}
WebMidi.enable((err) => {
if (err) {
reject(err);
}
onReady?.(WebMidi);
resolve(WebMidi);
});
WebMidi.enable(
(err) => {
if (err) {
reject(err);
}
onReady?.(WebMidi);
resolve(WebMidi);
},
{ sysex: true },
);
});
}
@@ -89,18 +93,235 @@ if (typeof window !== 'undefined') {
});
}
Pattern.prototype.midi = function (output) {
if (isPattern(output)) {
// 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 });
}
// sends a program change message to the given device on the given channel
function sendProgramChange(progNum, device, midichan, timeOffsetString) {
if (typeof progNum !== 'number' || progNum < 0 || progNum > 127) {
throw new Error('expected progNum (program change) to be a number between 0 and 127');
}
device.sendProgramChange(progNum, midichan, { time: timeOffsetString });
}
// sends a sysex message to the given device on the given channel
function sendSysex(sysexid, sysexdata, device, timeOffsetString) {
if (Array.isArray(sysexid)) {
if (!sysexid.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error('all sysexid bytes must be integers between 0 and 255');
}
} else if (!Number.isInteger(sysexid) || sysexid < 0 || sysexid > 255) {
throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers');
}
if (!Array.isArray(sysexdata)) {
throw new Error('expected sysex to be an array of numbers (0-255)');
}
if (!sysexdata.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error('all sysex bytes must be integers between 0 and 255');
}
device.sendSysex(sysexid, sysexdata, { time: timeOffsetString });
}
// sends a NRPN message to the given device on the given channel
function sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString) {
if (Array.isArray(nrpnn)) {
if (!nrpnn.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error('all nrpnn bytes must be integers between 0 and 255');
}
} else if (!Number.isInteger(nrpv) || nrpv < 0 || nrpv > 255) {
throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers');
}
device.sendNRPN(nrpnn, nrpv, midichan, { time: timeOffsetString });
}
// sends a pitch bend message to the given device on the given channel
function sendPitchBend(midibend, device, midichan, timeOffsetString) {
if (typeof midibend !== 'number' || midibend < -1 || midibend > 1) {
throw new Error('expected midibend to be a number between -1 and 1');
}
device.sendPitchBend(midibend, midichan, { time: timeOffsetString });
}
// sends a channel aftertouch message to the given device on the given channel
function sendAftertouch(miditouch, device, midichan, timeOffsetString) {
if (typeof miditouch !== 'number' || miditouch < 0 || miditouch > 1) {
throw new Error('expected miditouch to be a number between 0 and 1');
}
device.sendChannelAftertouch(miditouch, midichan, { time: timeOffsetString });
}
// sends a note message to the given device on the given channel
function sendNote(note, velocity, duration, device, midichan, timeOffsetString) {
if (note == null || note === '') {
throw new Error('note cannot be null or empty');
}
if (velocity != null && (typeof velocity !== 'number' || velocity < 0 || velocity > 1)) {
throw new Error('velocity must be a number between 0 and 1');
}
if (duration != null && (typeof duration !== 'number' || duration < 0)) {
throw new Error('duration must be a positive number');
}
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
const midiNote = new Note(midiNumber, { attack: velocity, duration });
device.playNote(midiNote, midichan, {
time: timeOffsetString,
});
}
/**
* MIDI output: Opens a MIDI output port.
* @param {string | number} midiport MIDI device name or index defaulting to 0
* @param {object} options Additional MIDI configuration options
* @example
* note("c4").midichan(1).midi('IAC Driver Bus 1')
* @example
* note("c4").midichan(1).midi('IAC Driver Bus 1', { controller: true, latency: 50 })
*/
Pattern.prototype.midi = function (midiport, options = {}) {
if (isPattern(midiport)) {
throw new Error(
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
`.midi does not accept Pattern input for midiport. Make sure to pass device name with single quotes. Example: .midi('${
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`,
);
}
// For backward compatibility
if (typeof midiport === 'object') {
const { port, isController = false, ...configOptions } = midiport;
options = {
isController,
...configOptions,
...options, // Keep any options passed separately
};
midiport = port;
}
let midiConfig = {
// Default configuration values
isController: false, // Disable sending notes for midi controllers
latencyMs: 34, // Default latency to get audio engine to line up in ms
noteOffsetMs: 10, // Default note-off offset to prevent glitching in ms
midichannel: 1, // Default MIDI channel
velocity: 0.9, // Default velocity
gain: 1, // Default gain
midimap: 'default', // Default MIDI map
midiport: midiport, // Store the port in the config
...options, // Override defaults with provided options
};
enableWebMidi({
onEnabled: ({ outputs }) => {
const device = getDevice(output, outputs);
const device = getDevice(midiConfig.midiport, outputs);
const otherOutputs = outputs.filter((o) => o.name !== device.name);
logger(
`Midi enabled! Using "${device.name}". ${
@@ -114,39 +335,102 @@ Pattern.prototype.midi = function (output) {
return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
if (!WebMidi.enabled) {
console.log('not enabled');
logger('Midi not enabled');
return;
}
const device = getDevice(output, WebMidi.outputs);
hap.ensureObjectValue();
//magic number to get audio engine to line up, can probably be calculated somehow
const latencyMs = 34;
const latencyMs = midiConfig.latencyMs;
// 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}`;
// destructure value
let { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd, gain = 1, velocity = 0.9 } = hap.value;
// midi event values from hap with configurable defaults
let {
note,
nrpnn,
nrpv,
ccn,
ccv,
midichan = midiConfig.midichannel,
midicmd,
midibend,
miditouch,
polyTouch,
gain = midiConfig.gain,
velocity = midiConfig.velocity,
progNum,
sysexid,
sysexdata,
midimap = midiConfig.midimap,
midiport = midiConfig.midiport,
} = 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;
// 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;
if (note != null) {
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
const midiNote = new Note(midiNumber, { attack: velocity, duration });
device.playNote(midiNote, midichan, {
time: timeOffsetString,
});
// Handle midimap
// 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));
} else if (midimap !== 'default') {
// Add warning when a non-existent midimap is specified
logger(`[midi] midimap "${midimap}" not found! Available maps: ${[...midicontrolMap.keys()].join(', ')}`);
}
// Handle note
if (note !== undefined && !midiConfig.isController) {
// note off messages will often a few ms arrive late,
// try to prevent glitching by subtracting noteOffsetMs from the duration length
const duration = (hap.duration.valueOf() / cps) * 1000 - midiConfig.noteOffsetMs;
sendNote(note, velocity, duration, device, midichan, timeOffsetString);
}
// Handle program change
if (progNum !== undefined) {
sendProgramChange(progNum, device, midichan, timeOffsetString);
}
// Handle sysex
// sysex data is consist of 2 arrays, first is sysexid, second is sysexdata
// sysexid is a manufacturer id it is either a number or an array of 3 numbers.
// list of manufacturer ids can be found here : https://midi.org/sysexidtable
// if sysexid is an array the first byte is 0x00
if (sysexid !== undefined && sysexdata !== undefined) {
sendSysex(sysexid, sysexdata, device, timeOffsetString);
}
// Handle control change
if (ccv !== undefined && ccn !== undefined) {
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 });
sendCC(ccn, ccv, device, midichan, timeOffsetString);
}
// Handle NRPN non-registered parameter number
if (nrpnn !== undefined && nrpv !== undefined) {
sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString);
}
// Handle midibend
if (midibend !== undefined) {
sendPitchBend(midibend, device, midichan, timeOffsetString);
}
// Handle miditouch
if (miditouch !== undefined) {
sendAftertouch(miditouch, device, midichan, timeOffsetString);
}
// Handle midicmd
if (hap.whole.begin + 0 === 0) {
// we need to start here because we have the timing info
device.sendStart({ time: timeOffsetString });
@@ -159,6 +443,19 @@ Pattern.prototype.midi = function (output) {
device.sendStop({ time: timeOffsetString });
} else if (['continue'].includes(midicmd)) {
device.sendContinue({ time: timeOffsetString });
} else if (Array.isArray(midicmd)) {
if (midicmd[0] === 'progNum') {
sendProgramChange(midicmd[1], device, midichan, timeOffsetString);
} else if (midicmd[0] === 'cc') {
if (midicmd.length === 2) {
sendCC(midicmd[0], midicmd[1] / 127, device, midichan, timeOffsetString);
}
} else if (midicmd[0] === 'sysex') {
if (midicmd.length === 3) {
const [_, id, data] = midicmd;
sendSysex(id, data, device, timeOffsetString);
}
}
}
});
};
@@ -166,16 +463,29 @@ Pattern.prototype.midi = function (output) {
let listeners = {};
const refs = {};
/**
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
* @param {string | number} input MIDI device name or index defaulting to 0
* @returns {Function}
* @example
* let cc = await midin('IAC Driver Bus 1')
* note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth")
*/
export async function midin(input) {
if (isPattern(input)) {
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'
}')`,
);
}
const initial = await enableWebMidi(); // only returns on first init
const device = getDevice(input, WebMidi.inputs);
if (!device) {
throw new Error(
`midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
);
}
if (initial) {
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
logger(
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/midi",
"version": "1.1.0",
"version": "1.2.2",
"description": "Midi API for strudel",
"main": "index.mjs",
"type": "module",
@@ -31,9 +31,9 @@
"dependencies": {
"@strudel/core": "workspace:*",
"@strudel/webaudio": "workspace:*",
"webmidi": "^3.1.8"
"webmidi": "^3.1.12"
},
"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();
}
var PatternStub = function(source, alignment, seed, tactus)
var PatternStub = function(source, alignment, seed, _steps)
{
this.type_ = "pattern";
this.arguments_ = { alignment: alignment, tactus: tactus };
this.arguments_ = { alignment: alignment, _steps: _steps };
if (seed !== undefined) {
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)
sequence = tactus:'^'? s:(slice_with_ops)+
{ return new PatternStub(s, 'fastcat', undefined, !!tactus); }
sequence = _steps:'^'? s:(slice_with_ops)+
{ return new PatternStub(s, 'fastcat', undefined, !!_steps); }
// a stack is a series of vertically aligned sequence, separated by a comma
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 options = ast.options_;
const ops = options?.ops;
const steps_source = pat.__steps_source;
if (ops) {
for (const op of ops) {
switch (op.type_) {
@@ -69,7 +69,7 @@ const applyOptions = (parent, enter) => (pat, i) => {
}
}
}
pat.__steps_source = pat.__steps_source || steps_source;
return pat;
};
@@ -82,20 +82,20 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
// resolveReplications(ast);
const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter));
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;
switch (alignment) {
case 'stack': {
pat = strudel.stack(...children);
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
if (with_steps.length) {
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
}
break;
}
case 'polymeter_slowcat': {
pat = strudel.stack(...children.map((child) => child._slow(child.__weight)));
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
if (with_steps.length) {
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
}
break;
}
@@ -111,8 +111,8 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
}
case 'rand': {
pat = strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
if (with_steps.length) {
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
}
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]]),
);
pat.__weight = weightSum; // for polymeter
pat.tactus = weightSum;
if (with_tactus.length) {
pat.tactus = pat.tactus.mul(lcm(...with_tactus.map((x) => Fraction(x.tactus))));
pat._steps = weightSum;
if (with_steps.length) {
pat._steps = pat._steps.mul(lcm(...with_steps.map((x) => Fraction(x._steps))));
}
} else {
pat = strudel.sequence(...children);
pat.tactus = children.length;
pat._steps = children.length;
}
if (ast.arguments_.tactus) {
pat.__tactus_source = true;
if (ast.arguments_._steps) {
pat.__steps_source = true;
}
}
}
if (with_tactus.length) {
pat.__tactus_source = true;
if (with_steps.length) {
pat.__steps_source = true;
}
return pat;
}
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mini",
"version": "1.1.0",
"version": "1.2.1",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
@@ -35,8 +35,8 @@
"@strudel/core": "workspace:*"
},
"devDependencies": {
"peggy": "^3.0.2",
"vite": "^5.0.10",
"vitest": "^1.1.0"
"peggy": "^4.2.0",
"vite": "^6.0.11",
"vitest": "^3.0.4"
}
}

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