Compare commits

..

198 Commits

Author SHA1 Message Date
Aria 0001d9c27b Change name to setDefaultJoin 2026-01-22 14:24:45 -06:00
Aria d79d122f68 Add docstring and example 2026-01-22 14:08:26 -06:00
Aria 6e61f3cdb6 Make default alignment configurable 2026-01-22 13:52:57 -06:00
froos 7f2860eb14 Merge pull request 'crunch up layout' (#1938) from stylez into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1938
2026-01-22 10:28:08 +01:00
Felix Roos 3364b9f05f keep it simple stupid 2026-01-22 10:12:16 +01:00
Felix Roos f9ed71fbf8 fix: import sounds font size 2026-01-22 01:35:19 +01:00
Felix Roos e9ea0bc7cc fix: typo 2026-01-22 01:32:04 +01:00
Felix Roos a87daf3d21 fix: patterns tab performance 2026-01-22 01:31:23 +01:00
Felix Roos 784b9c08ec copmuted settings 2026-01-22 01:16:57 +01:00
Felix Roos 139b5376fc rebrand hide button row to hide footer 2026-01-22 01:16:22 +01:00
Felix Roos a052def314 piping hot (main menu in footer) + border around panel toggle 2026-01-22 00:35:04 +01:00
Felix Roos bcce1fa68a make move sidepanel one layer up, so tabs are at the top and the toggle doesn't float 2026-01-21 23:38:25 +01:00
Felix Roos 9a10cac8e9 fix bottom panel 2026-01-21 23:07:49 +01:00
Felix Roos cf999462b7 fix: export tab placeholder color 2026-01-21 23:02:14 +01:00
Felix Roos 9fc084b291 tweaks: consistent font sizes and row heights, persist selected reference tag, add wrap prop to ButtonGroup 2026-01-21 22:47:15 +01:00
Felix Roos cb46dd7840 make it crunchier: border borders, less padding 2026-01-21 01:31:25 +01:00
froos b684e183a0 Merge pull request 'add muted theme color + tweak pane styles' (#1936) from stylez into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1936
2026-01-21 00:27:43 +01:00
Felix Roos 84e4dde3d7 mini tweaks 2026-01-21 00:15:00 +01:00
Felix Roos 5f8d028d4f cleanup 2026-01-21 00:04:18 +01:00
Felix Roos e3abdd41c0 make panel toggle floating 2026-01-21 00:03:19 +01:00
Felix Roos bce6ffde08 cleanup + tweak bottom panel style 2026-01-20 23:36:09 +01:00
Felix Roos 35a3e32238 no bg on button group 2026-01-20 23:08:31 +01:00
Felix Roos 60b5024f63 remove panel hover + optimize bottom panel 2026-01-20 23:06:04 +01:00
Felix Roos 4f14ff00d5 responsiveness 2026-01-20 22:29:16 +01:00
Felix Roos 3d3130e880 fine tune 2026-01-20 22:19:32 +01:00
Felix Roos 83d542d06a proper strudel icon + toggle menu from top bar 2026-01-20 22:13:03 +01:00
Aria 6105d5027d Merge pull request 'Feat: Add retrig option for LFOs' (#1934) from glossing/lfo-retrig into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1934
2026-01-20 04:37:19 +01:00
Felix Roos 7e34069021 fix more font sizes 2026-01-20 01:06:07 +01:00
Felix Roos b75ca193a2 fix: font sizes 2026-01-20 01:01:20 +01:00
Felix Roos afac1f62ca fix: adapt color-theme attribute on root based on theme 2026-01-20 00:59:43 +01:00
Felix Roos de83805fd9 add muted theme color + use it for inputs and tags 2026-01-20 00:06:44 +01:00
Felix Roos 38b7bcb3d2 style tweaks 2026-01-19 23:00:14 +01:00
Aria 62bf9abed3 Add retrig option for LFOs 2026-01-19 09:52:49 -08:00
froos 8ec1d4d034 Merge pull request 'fix: move kabelsalat web to superdough' (#1932) from fix-kabelsalat-dependency into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1932
2026-01-18 22:27:45 +01:00
Felix Roos 6981638f70 fix: move kabelsalat web to superdough 2026-01-18 22:04:49 +01:00
froos f5823ce981 Merge pull request 'Add tags to reference' (#1645) from vvolhejn/strudel:radical-new-docs into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1645
2026-01-18 12:36:49 +01:00
Václav Volhejn f8750901f0 Tag the remaining functions 2026-01-18 12:16:50 +01:00
Václav Volhejn c1fd8c82c6 Merge branch 'radical-new-docs' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2026-01-18 12:08:17 +01:00
Václav Volhejn 6e36bdfff7 Add tags for some of the new functions 2026-01-18 12:08:12 +01:00
Felix Roos 8dbdd0a9b8 Merge branch 'main' into radical-new-docs 2026-01-18 10:39:53 +01:00
froos f4c0281613 Merge pull request 'fix: performance issues when reference is open' (#1926) from robojumper/strudel:fix/reference-performance-issue into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1926
2026-01-18 10:36:00 +01:00
Jade (Rose) Rowland f610965f43 Publish
- @strudel/codemirror@1.3.0
 - @strudel/csound@1.3.0
 - @strudel/midi@1.3.0
 - @strudel/repl@1.3.0
 - @strudel/soundfonts@1.3.0
 - superdough@1.3.0
 - @strudel/web@1.3.0
 - @strudel/webaudio@1.3.0
2026-01-17 19:12:20 -08:00
Jade (Rose) Rowland 17ef84ed70 sp 2026-01-17 19:11:08 -08:00
Jade (Rose) Rowland f0cb96dcad patch 2026-01-17 19:02:25 -08:00
Jade (Rose) Rowland f4f04a7d57 Publish
- @strudel/codemirror@1.2.8
 - @strudel/csound@1.2.8
 - @strudel/midi@1.2.8
 - @strudel/osc@1.3.2
 - @strudel/repl@1.2.9
 - @strudel/soundfonts@1.2.8
 - superdough@1.2.5
 - supradough@1.2.4
 - @strudel/web@1.2.8
 - @strudel/webaudio@1.2.9
2026-01-17 19:01:10 -08:00
Jade (Rose) Rowland a3ac9646f5 inc webaudio 2026-01-17 18:54:39 -08:00
Jade (Rose) Rowland 12b89f9f88 rollback supradough 2026-01-17 16:12:51 -08:00
Jade (Rose) Rowland 166985a055 spr 2026-01-17 16:00:34 -08:00
Jade (Rose) Rowland 85e787214b rollback_supradough 2026-01-17 15:59:38 -08:00
Jade (Rose) Rowland d198910a86 Publish
- @strudel/codemirror@1.2.7
 - @strudel/core@1.2.6
 - @strudel/csound@1.2.7
 - @strudel/draw@1.2.6
 - @strudel/embed@1.1.2
 - @strudel/gamepad@1.2.6
 - @strudel/hydra@1.2.6
 - @strudel/midi@1.2.7
 - @strudel/mini@1.2.6
 - mondolang@1.1.2
 - @strudel/mondo@1.1.6
 - @strudel/motion@1.2.6
 - @strudel/mqtt@1.2.6
 - @strudel/osc@1.3.1
 - @strudel/reference@1.2.2
 - @strudel/repl@1.2.8
 - @strudel/sampler@0.2.4
 - @strudel/serial@1.2.6
 - @strudel/soundfonts@1.2.7
 - superdough@1.2.7
 - supradough@1.2.5
 - @strudel/tonal@1.2.6
 - @strudel/transpiler@1.2.6
 - vite-plugin-bundle-audioworklet@0.1.2
 - @strudel/web@1.2.7
 - @strudel/webaudio@1.2.7
 - @strudel/xen@1.2.6
2026-01-17 15:26:28 -08:00
Aria 8ce6a881e6 Merge pull request 'Make kabelsalat stereo out' (#1927) from glossing/ks-stereo-out into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1927
2026-01-17 21:00:42 +01:00
Aria 7bc8576a68 Make kabelsalat stereo out 2026-01-17 11:16:58 -08:00
Václav Volhejn 99cf256ee2 Clean up 2 2026-01-17 20:11:26 +01:00
Václav Volhejn 42d0bfadce Clean up 2026-01-17 20:08:23 +01:00
Václav Volhejn 22f78fbb2f Merge branch 'radical-new-docs' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2026-01-17 19:24:41 +01:00
Václav Volhejn 77076a1c3e Improve reference search/filtering semantics 2026-01-17 19:12:23 +01:00
Václav Volhejn a1d31df556 Merge branch 'main' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2026-01-17 18:22:56 +01:00
robojumper f9d0e154e8 fix: performance issues when reference is open 2026-01-17 17:51:32 +01:00
froos bbe95b3995 Merge pull request 'update changelog + fix script to not miss entries' (#1916) from changelog-update into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1916
2026-01-16 10:38:03 +01:00
Felix Roos 2dc921e375 fix: add fun fact 2026-01-16 10:14:17 +01:00
Felix Roos 3d13955c41 fix: prefix usernames with @ 2026-01-16 10:08:41 +01:00
Felix Roos ae9638c353 update changelog + fix script to not miss entries 2026-01-16 09:58:14 +01:00
froos 97cd3a5946 Merge pull request 'mondo fix: add registered functions to scope automatically' (#1896) from register_to_scope into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1896
2026-01-15 16:59:13 +01:00
froos ca3452bf52 Merge pull request 'Added docs for pattern search' (#1905) from JohnBjrk/strudel:pattern-search-docs into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1905
2026-01-15 16:58:13 +01:00
froos 7f528870b7 Merge pull request 'Update kabelsalat dependency' (#1911) from jeromew/strudel:kabelsalat into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1911
2026-01-15 16:57:18 +01:00
jeromew 60e5682829 Update kabelsalat dependency 2026-01-15 15:42:34 +00:00
Aria d605fbbde8 Merge pull request 'Bug Fix: Set pooled values immediately instead of scheduling' (#1907) from glossing/set-pooled-vals-immediately into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1907
2026-01-15 00:24:38 +01:00
Aria 21e6d81d34 Also set values immediately for supersaw and wavetable 2026-01-14 17:00:14 -06:00
Aria 85e79d9932 Set pooled values immediately instead of scheduling 2026-01-14 16:53:11 -06:00
John Björk 71dfbeea88 Fix markdown format 2026-01-14 22:35:10 +01:00
John Björk ffcab7bf6c Added docs for pattern search 2026-01-14 22:25:27 +01:00
Aria c3f6a1d6ba Merge pull request 'Feat: Kabelsalat integration' (#1876) from glossing/kabelsalat into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1876
2026-01-14 20:29:15 +01:00
Aria eb96d3ad63 Merge branch 'main' into glossing/kabelsalat 2026-01-14 19:08:20 +01:00
Aria 06e10cd693 Merge pull request 'Feat: MIDI Keyboard 🎹🐈' (#1828) from glossing/keyboard into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1828
2026-01-14 19:07:48 +01:00
Aria 80875cca95 Expose scheduler state and don't enqueue/trigger midi keys when repl stopped 2026-01-14 11:44:48 -06:00
Aria b954a25b2b Merge branch 'main' into glossing/keyboard 2026-01-14 18:15:31 +01:00
Aria cca346d2c2 Merge pull request 'Bug Fix: Fix race condition between worklet termination and port messages' (#1897) from glossing/diedack into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1897
2026-01-14 18:14:51 +01:00
Aria 3343c7bcf0 Merge branch 'main' into glossing/keyboard 2026-01-13 23:00:59 +01:00
Aria 6e3c5b23dd Merge branch 'main' into glossing/diedack 2026-01-13 21:30:58 +01:00
froos c12a77a0a1 Merge pull request 'Add search/filter in patterns tab' (#1842) from JohnBjrk/strudel:patterns-search into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1842
2026-01-13 04:06:50 +01:00
Felix Roos e5ab6b3c93 fix: short circuit 2026-01-13 03:47:33 +01:00
Felix Roos e82bb6c410 fix: use pattern.id as title fallback 2026-01-13 03:38:09 +01:00
Felix Roos 8b1fb12388 fix: class -> className 2026-01-13 03:32:19 +01:00
Aria 1f1f3288a6 Switch to tracking grace period in node pool; add negative begin and ends 2026-01-12 17:59:33 -06:00
Switch Angel AKA Jade Rose d642c0e12f Merge pull request 'Add shortcut for navigating through labels!' (#1807) from jade/dollar_shortcut into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1807
2026-01-13 00:18:08 +01:00
Aria 1eb657e6fe Merge branch 'main' into glossing/diedack 2026-01-12 20:58:34 +01:00
Aria 7c5f53f9ae Add ack message for worklet death; remove max pool size 2026-01-12 13:51:21 -06:00
Switch Angel AKA Jade Rose efe7c9394f Merge branch 'main' into jade/dollar_shortcut 2026-01-12 20:30:14 +01:00
Jade (Rose) Rowland 35bfd8b3ff curried 2026-01-12 11:28:54 -08:00
Jade (Rose) Rowland c2720a5738 working 2026-01-12 11:26:18 -08:00
Aria 5355a0c563 Ignore non-note messages 2026-01-11 23:34:52 -06:00
Alex McLean 75db9ff16e Merge pull request 'add warm.strudel.cc to faq' (#1891) from faq-warm into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1891
2026-01-12 00:17:36 +01:00
Aria 12cc1e9b5e Add immediate triggering; move time to schedulerState 2026-01-11 14:51:47 -06:00
Aria cd48e67675 Merge branch 'main' into glossing/kabelsalat 2026-01-11 19:59:21 +01:00
Aria 3a284696c9 Merge branch 'main' into glossing/keyboard 2026-01-11 19:59:13 +01:00
Felix Roos 753dc48b69 update changelog 2026-01-11 19:12:45 +01:00
froos 5ee5d4f1b1 Merge pull request 'Fix sounds example to work in the REPL' (#1851) from JesCoding/strudel:jescoding-patch-2 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1851
2026-01-11 19:00:24 +01:00
alex 31cacc3c29 add warm to faq 2026-01-11 16:23:17 +00:00
JesCoding 8d2bca59c4 Fix sounds example to work in the REPL
`freq` only takes one argument, so switch this to mini-notation
so this can be used in the REPL as indicated in the paragraph below.
Make it so this can be played by the user.

Fix syntax-type typo.
2026-01-11 14:50:30 +00:00
froos f867cbce92 Merge pull request 'Improve hint text when sound search has no results' (#1883) from floy/strudel:improve-help-text-filtered into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1883
2026-01-11 13:52:01 +01:00
floy a659c89385 fix code formatting 2026-01-11 13:25:44 +01:00
Alex McLean 8290e99cf9 Merge pull request 'New page FAQ in "More"' (#1753) from scrappy_fiddler/strudel:documentation into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1753
2026-01-11 13:19:45 +01:00
froos e648ddd9ff Merge pull request 'fix: remove faulty default readme' (#1889) from trim-website-readme into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1889
2026-01-11 12:45:06 +01:00
Felix Roos 9ffd0e496f fix: all doc 2026-01-11 12:33:03 +01:00
Felix Roos 550d7ff6cb fix: react error 2026-01-11 12:32:54 +01:00
yaxu 85fd567fb2 Format and remove link to open document 2026-01-11 12:21:05 +01:00
scrappy_fiddler 20ea2980a2 Merge branch 'main' into documentation 2026-01-11 12:18:42 +01:00
froos af2d4784c6 Merge pull request 'fix/self-hosted-config' (#1880) from alienmind/strudel:fix/self-hosted-config into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1880
2026-01-11 12:15:32 +01:00
froos 526083d7c9 Merge pull request 'Feat: Support External AudioContext Injection' (#1833) from 1d10t/strudel:main into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1833
2026-01-11 12:07:47 +01:00
Alex McLean 8e792e1bfd Merge branch 'main' into documentation 2026-01-11 11:57:19 +01:00
yaxu f3e6f86857 Update website/src/pages/learn/faq.mdx
Trimmed all the DAW talk a little!
2026-01-11 11:55:40 +01:00
Felix Roos 25dbfe4c7d fix: remove faulty default readme 2026-01-11 11:43:04 +01:00
froos 0a586eddb3 Merge pull request 'fix: add trem to top level' (#1887) from trem-toplevel into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1887
2026-01-11 11:37:36 +01:00
froos b6d81ba2a0 Merge pull request 'fix: export start cycle min 0' (#1888) from fix-export-min-cycle into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1888
2026-01-11 11:37:17 +01:00
yaxu 8f3dc8daea Update website/src/pages/learn/faq.mdx 2026-01-11 11:34:55 +01:00
scrappy_fiddler ced434a1cf Merge branch 'main' into documentation 2026-01-11 11:20:51 +01:00
froos 96626fce25 Merge pull request 'fix: repl package init audio properly' (#1836) from fix-repl-audioworklet into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1836
2026-01-11 11:05:43 +01:00
Felix Roos c74707e804 fix: export start cycle min 0 2026-01-11 10:58:13 +01:00
Felix Roos 0174e08639 fix: add trem to top level 2026-01-11 10:50:44 +01:00
Switch Angel AKA Jade Rose af88471f6b Merge branch 'main' into jade/dollar_shortcut 2026-01-11 06:51:47 +01:00
Switch Angel AKA Jade Rose af855a0c77 Merge pull request 'Fix: show reload dialog when uploading prebake script' (#1886) from jade/reloaddialog into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1886
2026-01-11 06:51:23 +01:00
Switch Angel AKA Jade Rose f9a9993c42 Merge branch 'main' into jade/dollar_shortcut 2026-01-11 06:32:29 +01:00
Jade (Rose) Rowland 604a1b770c dialog 2026-01-11 00:27:07 -05:00
Switch Angel AKA Jade Rose 2aa978ca5b Merge pull request 'fixes Serial onTrigger() params #1633' (#1885) from gueejla/strudel:fix-serial-ontrigger into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1885
2026-01-11 06:01:41 +01:00
gueejla 0571a8dc7d fixes Serial onTrigger() params #1633 2026-01-10 22:11:13 -06:00
Switch Angel AKA Jade Rose a8a919f581 Merge pull request 'Perf: Targeted node pools' (#1810) from glossing/basic-node-pool into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1810
2026-01-10 23:12:47 +01:00
Aria dab1676761 Add license for time file 2026-01-10 11:26:30 -06:00
Aria d753eedb90 Make latency consistent across all cps 2026-01-10 11:20:17 -06:00
Václav Volhejn 726e5ef14a Fix formatting 2026-01-10 07:18:29 -08:00
Václav Volhejn cc15e3cd36 Merge branch 'main' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2026-01-10 06:58:39 -08:00
floy fd0209dbc0 Show explicit help text when search doesn't match 2026-01-10 15:39:02 +01:00
floy b4e00538f8 Don't show 'No sounds loaded' text if search is active 2026-01-10 15:30:07 +01:00
Aria 4743334a17 Reduce latency; change name to midikeys; include in i/o learn page 2026-01-09 22:50:16 -06:00
Aria 7cff2050c8 Merge branch 'main' into glossing/kabelsalat 2026-01-09 23:38:25 +01:00
alienmind eefae851d5 fix malformed script src attributes 2026-01-09 16:26:32 +01:00
alienmind 40f9f4cde0 allow alternative URLs for self-hosted strudel 2026-01-09 16:26:32 +01:00
Aria e6e96c8603 Fix release behavior; add multi-line example 2026-01-08 16:39:10 -06:00
Aria d4b3a3b972 Auto call functions in kabel; clean up end behavior of worklet; move kabel first in chain; write docstring 2026-01-08 12:31:26 -06:00
1d10t a1d1bd0e11 Merge branch 'main' into main 2026-01-08 09:44:32 +01:00
Aria fd2ff255e5 Don't include worklet in autocomplete; use empty object in pure for consistency 2026-01-07 22:59:54 -06:00
Aria 5903070c02 Test cleanup 2026-01-07 13:45:19 -06:00
Aria e92865cecb Handle mini notation without S 2026-01-07 13:32:12 -06:00
Aria a5c4fd5bc1 Merge branch 'main' into glossing/kabelsalat 2026-01-07 13:14:49 -06:00
Aria 5caaf423ff Import kabelsalat, update transpiler to handle K and S, add worklet control 2026-01-07 12:19:16 -06:00
1d10t 8f342262ab Merge branch 'main' into main 2026-01-05 00:37:28 +01:00
1d10t e82602af5a Merge branch 'main' into main 2026-01-04 13:24:39 +01:00
Sergey S Yaglov bfef9ba8d2 Merge remote-tracking branch 'upstream/main' 2026-01-04 00:14:23 +03:00
1d10t aa7ba6ae99 update webaudio to main branch 2026-01-03 22:01:37 +01:00
1d10t a86e5751eb reset audioContext to main branch 2026-01-03 21:55:16 +01:00
scrappy_fiddler 8d79715ce4 Merge branch 'main' into documentation 2026-01-02 00:30:21 +01:00
John Björk aa8209ad4c Refactored handling of trim/lowercase based on PR comments 2026-01-01 12:22:52 +01:00
scrappy_fiddler b648961d30 Merge branch 'main' into documentation 2025-12-29 16:28:57 +01:00
scrappy_fiddler fc2f5accfe Merge branch 'main' into documentation 2025-12-22 14:05:24 +01:00
John Björk 6908ea38e4 Added search/filter in patterns tab
Added pattern as dep to filter memo

Format
2025-12-20 23:10:01 +01:00
Felix Roos 181ebee374 fix: repl package init audio properly 2025-12-19 01:25:58 +01:00
scrappy_fiddler e6c1e97838 Merge branch 'main' into documentation 2025-12-17 20:44:07 +01:00
Sergey S Yaglov adf9596d07 codestyle(webaudio) 2025-12-13 00:28:13 +03:00
1d10t 4e14718e93 feat(webaudio): enable AudioContext sharing for webaudioRepl
- Add support for optional `existingAudioCtx` parameter in `webaudioRepl()` function
- When provided, sets it as the default AudioContext via `setDefaultAudioContext()`
- Allows integration with external systems that manage their own Web Audio API context
- Maintains backward compatibility with existing usage patterns
2025-12-12 21:56:31 +01:00
1d10t 2998de8426 Merge pull request 'feat(superdough/audiocontext): make AudioContext injection optional' (#1) from 1d10t-patch-1 into main
Reviewed-on: https://codeberg.org/1d10t/strudel/pulls/1
2025-12-12 21:28:40 +01:00
1d10t c8b2e1ac9e feat(superdough/audiocontext): make AudioContext injection optional
- Modify `setDefaultAudioContext()` to accept an optional `existingAudioCtx` parameter.
- If provided, it will use the existing context; otherwise, it creates a new one.
- This allows for better integration with external audio systems that manage their own AudioContext.
2025-12-12 21:22:21 +01:00
Aria 103b27c21f Working version of keyboard with variable lengths; tests and cleanup 2025-12-10 13:44:49 -06:00
Aria 597c941991 Add keyboard function 2025-12-10 12:01:04 -06:00
scrappy_fiddler 6402ad5d37 better linebreak in example 2025-12-06 16:55:57 +01:00
scrappy_fiddler ab03ece2b7 prettier 2025-12-06 16:48:40 +01:00
scrappy_fiddler bd282f439a everyone wants that trancegate 2025-12-06 16:37:39 +01:00
scrappy_fiddler 177c3a4a2e some items from review 2025-12-06 16:36:25 +01:00
scrappy_fiddler d4d9b4f543 Merge branch 'main' into documentation 2025-12-05 10:22:52 +01:00
Jade (Rose) Rowland f0aac2098a update_shortcut 2025-12-03 22:59:20 -05:00
Jade (Rose) Rowland c1a185e852 fix null case 2025-12-03 15:25:05 -05:00
Jade (Rose) Rowland b9fce15042 working 2025-12-03 15:18:32 -05:00
scrappy_fiddler 39bff8900f website/src/pages/learn/faq.mdx aktualisiert
updated the section on switch angel's prebake. Now refers to the readme for instructions.
2025-12-01 18:22:26 +01:00
scrappy_fiddler 4ec68fcd1d a little prettier 2025-11-20 00:07:44 +01:00
scrappy_fiddler 7abfcd0f38 Merge branch 'main' into documentation 2025-11-19 22:18:32 +01:00
scrappy_fiddler 4504d1afd0 remove trailing spaces in FAQ 2025-11-19 22:14:23 +01:00
scrappy_fiddler 69c5df67f5 FAQ is now prettier 2025-11-19 21:40:48 +01:00
scrappy_fiddler a21f0d629e Merge branch 'main' into documentation 2025-11-19 20:24:34 +01:00
scrappy_fiddler 371e6c7d6d tidy up FAQ 2025-11-19 20:17:19 +01:00
scrappy_fiddler 0bb89d1a9f New page FAQ in "More" 2025-11-19 20:09:14 +01:00
Václav Volhejn 2fa8ed1424 Revert seq synonym refactor 2025-11-09 20:21:19 +01:00
Václav Volhejn b7827cb89f Tag new functions 2025-11-01 21:11:57 +01:00
Václav Volhejn 29fd541f1f Merge branch 'radical-new-docs' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2025-11-01 20:20:07 +01:00
Václav Volhejn 0830a9df69 Add superdirt tags 2025-11-01 20:19:57 +01:00
froos 4159c8394d Merge branch 'main' into radical-new-docs 2025-10-28 22:02:10 +01:00
Václav Volhejn 466bf819d8 Fix formatting (trailing space) 2025-10-19 21:38:46 +02:00
Václav Volhejn 7e4cbefc8f Tag crossfade as internals 2025-10-19 21:22:57 +02:00
Václav Volhejn 2483c673a4 Effects -> fx, untag "crossfade" 2025-10-19 21:19:42 +02:00
Václav Volhejn 69360b3ec8 Also tag "noise" 2025-10-19 21:17:25 +02:00
Václav Volhejn 96d0f2053e Add supradough controls 2025-10-19 21:13:14 +02:00
Václav Volhejn b952e4c735 Add superdough tags 2025-10-19 20:54:22 +02:00
Václav Volhejn a4212589e5 Recategorize 2025-10-12 16:33:07 +02:00
Václav Volhejn ee1a894f3b Show tags in reference, more compact functions list 2025-10-12 16:28:05 +02:00
Václav Volhejn c62428ca27 Document remaining functions 2025-10-12 16:07:11 +02:00
Václav Volhejn d516d765d5 Redo frontend for tags 2025-10-11 14:12:45 +02:00
Václav Volhejn 5032b4e966 Rename group -> tags 2025-10-11 14:11:50 +02:00
Václav Volhejn a4d7803d43 Annotate controls.mjs 2025-10-02 21:59:45 +02:00
Václav Volhejn 4a395f372d Annote pattern.mjs and signal.mjs 2025-10-01 23:45:48 +02:00
Václav Volhejn c754dab1ef Order groups 2025-10-01 22:31:03 +02:00
Václav Volhejn df54404733 Group by category 2025-10-01 22:21:03 +02:00
154 changed files with 4123 additions and 1073 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"devToolbar": {
"enabled": false
}
}
+1069 -76
View File
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,5 @@
<script src="https://unpkg.com/@strudel/repl@1.0.2"></script>
<script src="https://unpkg.com/@strudel/repl@1.2.7"></script>
<!-- <script src="../../packages/repl/dist/index.js"></script> -->
<strudel-editor>
<!--
// @date 23-08-15
+7
View File
@@ -12,6 +12,13 @@ function defineTags(dictionary) {
doclet.synonyms = doclet.synonyms_text.split(/[ ,]+/);
},
});
dictionary.defineTag('tags', {
mustHaveValue: true,
onTagged: function (doclet, tag) {
doclet.tags = tag.value.split(/[ ,]+/);
},
});
}
module.exports = { defineTags: defineTags };
+211
View File
@@ -0,0 +1,211 @@
0 silly argv {
0 silly argv _: [ 'version' ],
0 silly argv private: false,
0 silly argv lernaVersion: '8.1.9',
0 silly argv '$0': 'node_modules/lerna/dist/cli.js'
0 silly argv }
1 notice cli v8.1.9
2 verbose packageConfigs Explicit "packages" configuration found in lerna.json. Resolving packages using the configured glob(s): ["packages/*"]
3 verbose rootPath /Users/jaderose/Documents/Github/cstrudel/strudel
4 info versioning independent
5 silly isAnythingCommitted
6 verbose isAnythingCommitted 1
7 silly getCurrentBranch
8 verbose currentBranch main
9 silly remoteBranchExists
10 silly isBehindUpstream
11 silly isBehindUpstream main is behind origin/main by 0 commit(s) and ahead by 4
12 silly hasTags
13 verbose hasTags true
14 silly git-describe.sync "@strudel/codemirror@1.2.7-4-ga3ac9646" => {"lastTagName":"@strudel/codemirror@1.2.7","lastVersion":"1.2.7","refCount":"4","sha":"a3ac9646","isDirty":false}
15 info Looking for changed packages since @strudel/codemirror@1.2.7
16 silly checking diff packages/codemirror
17 silly no diff found in @strudel/codemirror
18 silly checking diff packages/core
19 silly no diff found in @strudel/core
20 silly checking diff packages/csound
21 silly no diff found in @strudel/csound
22 silly checking diff packages/desktopbridge
23 silly no diff found in @strudel/desktopbridge
24 silly checking diff packages/draw
25 silly no diff found in @strudel/draw
26 silly checking diff packages/embed
27 silly no diff found in @strudel/embed
28 silly checking diff packages/gamepad
29 silly no diff found in @strudel/gamepad
30 silly checking diff packages/hs2js
31 silly no diff found in hs2js
32 silly checking diff packages/hydra
33 silly no diff found in @strudel/hydra
34 silly checking diff packages/midi
35 silly no diff found in @strudel/midi
36 silly checking diff packages/mini
37 silly no diff found in @strudel/mini
38 silly checking diff packages/mondo
39 silly no diff found in mondolang
40 silly checking diff packages/mondough
41 silly no diff found in @strudel/mondo
42 silly checking diff packages/motion
43 silly no diff found in @strudel/motion
44 silly checking diff packages/mqtt
45 silly no diff found in @strudel/mqtt
46 silly checking diff packages/osc
47 silly found diff in packages/osc/server.js
48 verbose filtered diff [ 'packages/osc/server.js' ]
49 silly checking diff packages/reference
50 silly no diff found in @strudel/reference
51 silly checking diff packages/repl
52 silly no diff found in @strudel/repl
53 silly checking diff packages/sampler
54 silly no diff found in @strudel/sampler
55 silly checking diff packages/serial
56 silly no diff found in @strudel/serial
57 silly checking diff packages/soundfonts
58 silly no diff found in @strudel/soundfonts
59 silly checking diff packages/superdough
60 silly found diff in packages/superdough/package.json
61 verbose filtered diff [ 'packages/superdough/package.json' ]
62 silly checking diff packages/supradough
63 silly found diff in packages/supradough/package.json
64 verbose filtered diff [ 'packages/supradough/package.json' ]
65 silly checking diff packages/tidal
66 silly no diff found in @strudel/tidal
67 silly checking diff packages/tonal
68 silly no diff found in @strudel/tonal
69 silly checking diff packages/transpiler
70 silly no diff found in @strudel/transpiler
71 silly checking diff packages/vite-plugin-bundle-audioworklet
72 silly no diff found in vite-plugin-bundle-audioworklet
73 silly checking diff packages/web
74 silly no diff found in @strudel/web
75 silly checking diff packages/webaudio
76 silly found diff in packages/webaudio/package.json
77 verbose filtered diff [ 'packages/webaudio/package.json' ]
78 silly checking diff packages/xen
79 silly no diff found in @strudel/xen
80 verbose updated @strudel/codemirror
81 verbose updated @strudel/csound
82 verbose updated @strudel/midi
83 verbose updated @strudel/osc
84 verbose updated @strudel/repl
85 verbose updated @strudel/soundfonts
86 verbose updated superdough
87 verbose updated supradough
88 verbose updated @strudel/web
89 verbose updated @strudel/webaudio
90 verbose git-describe undefined => "@strudel/codemirror@1.2.7-4-ga3ac9646"
91 silly git-describe parsed => {"lastTagName":"@strudel/codemirror@1.2.7","lastVersion":"1.2.7","refCount":"4","sha":"a3ac9646","isDirty":false}
92 info execute Skipping releases
93 silly lifecycle No script for "preversion" in "@strudel/monorepo", continuing
94 silly lifecycle No script for "preversion" in "@strudel/osc", continuing
95 silly lifecycle No script for "preversion" in "superdough", continuing
96 silly lifecycle No script for "preversion" in "supradough", continuing
97 verbose version supradough has no lockfile. Skipping lockfile update.
98 verbose version @strudel/osc has no lockfile. Skipping lockfile update.
99 verbose version superdough has no lockfile. Skipping lockfile update.
100 silly lifecycle No script for "version" in "supradough", continuing
101 silly lifecycle No script for "version" in "superdough", continuing
102 silly lifecycle No script for "preversion" in "@strudel/codemirror", continuing
103 silly lifecycle No script for "preversion" in "@strudel/webaudio", continuing
104 silly lifecycle No script for "version" in "@strudel/osc", continuing
105 verbose version @strudel/webaudio has no lockfile. Skipping lockfile update.
106 verbose version @strudel/codemirror has no lockfile. Skipping lockfile update.
107 silly lifecycle No script for "version" in "@strudel/codemirror", continuing
108 silly lifecycle No script for "version" in "@strudel/webaudio", continuing
109 silly lifecycle No script for "preversion" in "@strudel/csound", continuing
110 silly lifecycle No script for "preversion" in "@strudel/midi", continuing
111 silly lifecycle No script for "preversion" in "@strudel/soundfonts", continuing
112 silly lifecycle No script for "preversion" in "@strudel/web", continuing
113 verbose version @strudel/csound has no lockfile. Skipping lockfile update.
114 verbose version @strudel/midi has no lockfile. Skipping lockfile update.
115 verbose version @strudel/soundfonts has no lockfile. Skipping lockfile update.
116 verbose version @strudel/web has no lockfile. Skipping lockfile update.
117 silly lifecycle No script for "version" in "@strudel/midi", continuing
118 silly lifecycle No script for "version" in "@strudel/csound", continuing
119 silly lifecycle No script for "version" in "@strudel/soundfonts", continuing
120 silly lifecycle No script for "preversion" in "@strudel/repl", continuing
121 verbose version @strudel/repl has no lockfile. Skipping lockfile update.
122 silly lifecycle No script for "version" in "@strudel/web", continuing
123 silly lifecycle No script for "version" in "@strudel/repl", continuing
124 silly lifecycle No script for "version" in "@strudel/monorepo", continuing
125 verbose version Updating root pnpm-lock.yaml
126 silly version Skipped applying prettier to ignored file: packages/supradough/package.json
127 silly version Skipped applying prettier to ignored file: packages/superdough/package.json
128 silly version Skipped applying prettier to ignored file: packages/osc/package.json
129 silly version Skipped applying prettier to ignored file: packages/codemirror/package.json
130 silly version Skipped applying prettier to ignored file: packages/webaudio/package.json
131 silly version Skipped applying prettier to ignored file: packages/midi/package.json
132 silly version Skipped applying prettier to ignored file: packages/csound/package.json
133 silly version Skipped applying prettier to ignored file: packages/soundfonts/package.json
134 silly version Skipped applying prettier to ignored file: packages/web/package.json
135 silly version Skipped applying prettier to ignored file: packages/repl/package.json
136 silly version Skipped applying prettier to ignored file: pnpm-lock.yaml
137 silly gitAdd [
137 silly gitAdd 'packages/supradough/package.json',
137 silly gitAdd 'packages/superdough/package.json',
137 silly gitAdd 'packages/osc/package.json',
137 silly gitAdd 'packages/codemirror/package.json',
137 silly gitAdd 'packages/webaudio/package.json',
137 silly gitAdd 'packages/midi/package.json',
137 silly gitAdd 'packages/csound/package.json',
137 silly gitAdd 'packages/soundfonts/package.json',
137 silly gitAdd 'packages/web/package.json',
137 silly gitAdd 'packages/repl/package.json',
137 silly gitAdd 'pnpm-lock.yaml'
137 silly gitAdd ]
138 silly gitCommit Publish
138 silly gitCommit
138 silly gitCommit - @strudel/codemirror@1.2.8
138 silly gitCommit - @strudel/csound@1.2.8
138 silly gitCommit - @strudel/midi@1.2.8
138 silly gitCommit - @strudel/osc@1.3.2
138 silly gitCommit - @strudel/repl@1.2.9
138 silly gitCommit - @strudel/soundfonts@1.2.8
138 silly gitCommit - superdough@1.2.5
138 silly gitCommit - supradough@1.2.4
138 silly gitCommit - @strudel/web@1.2.8
138 silly gitCommit - @strudel/webaudio@1.2.9
139 verbose git [
139 verbose git 'commit',
139 verbose git '-F',
139 verbose git '/private/var/folders/hc/yf_zr55547sbcpz5rj7q85sc0000gn/T/3c85b5d4-56ae-40e9-8df7-db2bc1048402/lerna-commit.txt'
139 verbose git ]
140 silly gitTag @strudel/codemirror@1.2.8 git tag %s -m %s
141 verbose git [
141 verbose git 'tag',
141 verbose git '@strudel/codemirror@1.2.8',
141 verbose git '-m',
141 verbose git '@strudel/codemirror@1.2.8'
141 verbose git ]
142 silly gitTag @strudel/csound@1.2.8 git tag %s -m %s
143 verbose git [ 'tag', '@strudel/csound@1.2.8', '-m', '@strudel/csound@1.2.8' ]
144 silly gitTag @strudel/midi@1.2.8 git tag %s -m %s
145 verbose git [ 'tag', '@strudel/midi@1.2.8', '-m', '@strudel/midi@1.2.8' ]
146 silly gitTag @strudel/osc@1.3.2 git tag %s -m %s
147 verbose git [ 'tag', '@strudel/osc@1.3.2', '-m', '@strudel/osc@1.3.2' ]
148 silly gitTag @strudel/repl@1.2.9 git tag %s -m %s
149 verbose git [ 'tag', '@strudel/repl@1.2.9', '-m', '@strudel/repl@1.2.9' ]
150 silly gitTag @strudel/soundfonts@1.2.8 git tag %s -m %s
151 verbose git [
151 verbose git 'tag',
151 verbose git '@strudel/soundfonts@1.2.8',
151 verbose git '-m',
151 verbose git '@strudel/soundfonts@1.2.8'
151 verbose git ]
152 silly gitTag superdough@1.2.5 git tag %s -m %s
153 verbose git [ 'tag', 'superdough@1.2.5', '-m', 'superdough@1.2.5' ]
154 silly gitTag supradough@1.2.4 git tag %s -m %s
155 verbose git [ 'tag', 'supradough@1.2.4', '-m', 'supradough@1.2.4' ]
156 silly gitTag @strudel/web@1.2.8 git tag %s -m %s
157 verbose git [ 'tag', '@strudel/web@1.2.8', '-m', '@strudel/web@1.2.8' ]
158 silly gitTag @strudel/webaudio@1.2.9 git tag %s -m %s
159 verbose git [ 'tag', '@strudel/webaudio@1.2.9', '-m', '@strudel/webaudio@1.2.9' ]
160 error Error: Command failed with exit code 128: git tag superdough@1.2.5 -m superdough@1.2.5
160 error fatal: tag 'superdough@1.2.5' already exists
160 error at makeError (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/execa@5.0.0/node_modules/execa/lib/error.js:59:11)
160 error at handlePromise (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/execa@5.0.0/node_modules/execa/index.js:114:26)
160 error at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
160 error at async Promise.all (index 6)
160 error at async VersionCommand.gitCommitAndTagVersionForUpdates (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/lerna@8.1.9_encoding@0.1.13/node_modules/lerna/dist/index.js:9957:11)
160 error at async VersionCommand.commitAndTagUpdates (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/lerna@8.1.9_encoding@0.1.13/node_modules/lerna/dist/index.js:9933:18)
160 error at async Promise.all (index 0)
+10
View File
@@ -24,6 +24,7 @@ import { sliderPlugin, updateSliderWidgets } from './slider.mjs';
import { activateTheme, initTheme, theme } from './themes.mjs';
import { isTooltipEnabled } from './tooltip.mjs';
import { updateWidgets, widgetPlugin } from './widget.mjs';
import { jumpToCharacter } from './labelJump.mjs';
export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands';
@@ -119,6 +120,14 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
preventDefault: true,
run: () => onStop?.(),
},
{
key: 'Alt-w',
run: (view) => jumpToCharacter(view, '$', 1),
},
{
key: 'Alt-q',
run: (view) => jumpToCharacter(view, '$', -1),
},
/* {
key: 'Ctrl-Shift-.',
run: () => (onPanic ? onPanic() : onStop?.()),
@@ -425,6 +434,7 @@ function s4() {
/**
* Overrides the css of highlighted events. Make sure to use single quotes!
* @name markcss
* @tag visualization
* @example
* note("c a f e")
* .markcss('text-decoration:underline')
+31
View File
@@ -0,0 +1,31 @@
import { EditorSelection } from '@codemirror/state';
import { SearchCursor } from '@codemirror/search';
export function jumpToCharacter(view, character, direction = 1) {
const { state, dispatch } = view;
const pos = state.selection.main.head;
const cursor = new SearchCursor(state.doc, character);
let characterPositions = [];
let jumpPos;
while (!cursor.next().done) {
characterPositions.push(cursor.value.to);
}
if (!characterPositions.length) {
return false;
}
if (direction > 0) {
jumpPos = characterPositions.find((x) => x > pos + 1) ?? characterPositions.at(0); // Loop back around for convenience
} else {
jumpPos = characterPositions.reverse().find((x) => x < pos + 1) ?? characterPositions.at(0);
}
if (jumpPos == null) {
return false;
}
dispatch({
selection: EditorSelection.cursor(jumpPos - 1),
scrollIntoView: true,
});
return true;
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "1.2.6",
"version": "1.3.0",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"publishConfig": {
+1
View File
@@ -117,6 +117,7 @@ export const sliderPlugin = ViewPlugin.fromClass(
* Displays a slider widget to allow the user manipulate a value
*
* @name slider
* @tags external_io, visualization
* @param {number} value Initial value
* @param {number} min Minimum value - optional, defaults to 0
* @param {number} max Maximum value - optional, defaults to 1
+1
View File
@@ -198,6 +198,7 @@ export function activateTheme(name) {
const themeSettings = settings[name] || settings.strudelTheme;
// set css variables
themeStyle.innerHTML = `:root {
color-scheme: ${themeSettings.light ? 'light' : 'dark'};
${Object.entries(themeSettings)
// important to override fallback
.map(([key, value]) => `--${key}: ${value} !important;`)
+3 -1
View File
@@ -17,13 +17,15 @@ export const settings = {
background: 'white',
lineBackground: 'transparent',
foreground: deepPurple,
muted: deepPurple + '50',
caret: '#797977',
selection: yellowPink,
selectionMatch: '#2B323D',
gutterBackground: grey,
gutterBackground: pinkAccent,
gutterForeground: lightGrey,
gutterBorder: 'transparent',
lineHighlight: pinkAccent,
light: true,
};
export default createTheme({
+1
View File
@@ -31,6 +31,7 @@ const palette = palettes['Sour Watermelon B'];
export const settings = {
background: palette[3],
foreground: palette[1],
muted: palette[1] + '50',
caret: palette[0],
selection: palette[0],
selectionMatch: palette[1],
+2
View File
@@ -8,6 +8,7 @@ export const settings = {
background: '#282b2e',
lineBackground: '#282b2e99',
foreground: '#a9b7c6',
muted: '#a9b7c650',
caret: '#00FF00',
selection: '#343739',
selectionMatch: '#343739',
@@ -19,6 +20,7 @@ export default createTheme({
settings: {
background: '#282b2e',
foreground: '#a9b7c6',
muted: '#a9b7c650',
caret: '#00FF00',
selection: '#4e5254',
selectionMatch: '#4e5254',
+1
View File
@@ -12,6 +12,7 @@ export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[1],
muted: hex[2],
selection: hex[2],
selectionMatch: hex[0],
gutterBackground: hex[0],
+2
View File
@@ -11,6 +11,7 @@ export const settings = {
background: '#272C35',
lineBackground: '#272C3599',
foreground: 'hsl(220, 14%, 71%)',
muted: 'hsl(220, 14%, 41%)',
caret: '#797977',
selection: '#ffffff30',
selectionMatch: '#2B323D',
@@ -25,6 +26,7 @@ export default createTheme({
settings: {
background: '#272C35',
foreground: '#9d9b97',
muted: 'hsl(220, 14%, 41%)',
caret: '#797977',
selection: '#3d4c64',
selectionMatch: '#3d4c64',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#21202e',
lineBackground: '#21202e99',
foreground: '#edecee',
muted: '#edecee50',
caret: '#a277ff',
selection: '#3d375e7f',
selectionMatch: '#3d375e7f',
@@ -18,6 +19,7 @@ export default createTheme({
settings: {
background: '#21202e',
foreground: '#edecee',
muted: '#edecee50',
caret: '#a277ff',
selection: '#5a51898f',
selectionMatch: '#5a51898f',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#FFFFFF',
lineBackground: '#FFFFFF99',
foreground: '#000000',
muted: '#00000050',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
@@ -20,6 +21,7 @@ export default createTheme({
settings: {
background: '#FFFFFF',
foreground: '#000000',
muted: '#00000050',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
+2 -1
View File
@@ -2,7 +2,8 @@ import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: 'black',
foreground: 'white', // whats that?
foreground: 'white',
muted: '#ffffff50',
caret: 'white',
selection: '#ffffff20',
selectionMatch: '#036dd626',
+2 -1
View File
@@ -3,7 +3,8 @@ import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#051DB5',
lineBackground: '#051DB550',
foreground: 'white', // whats that?
foreground: 'white',
muted: '#ffffff50',
caret: 'white',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
+1
View File
@@ -11,6 +11,7 @@ export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[2],
muted: hex[3],
selection: hex[3],
selectionMatch: hex[0],
gutterBackground: hex[0],
+2
View File
@@ -9,6 +9,7 @@ export const settings = {
background: '#242424',
lineBackground: '#24242499',
foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
@@ -23,6 +24,7 @@ export default createTheme({
settings: {
background: '#242424',
foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
+2
View File
@@ -11,6 +11,7 @@ export const settings = {
background: '#282a36',
lineBackground: '#282a3699',
foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
@@ -27,6 +28,7 @@ export default createTheme({
settings: {
background: '#282a36',
foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
+2
View File
@@ -10,6 +10,7 @@ export const settings = {
background: '#2a2734',
lineBackground: '#2a273499',
foreground: '#eeebff',
muted: '#eeebff50',
caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734',
@@ -22,6 +23,7 @@ export default createTheme({
settings: {
background: '#2a2734',
foreground: '#6c6783',
muted: '#eeebff50',
caret: '#ffad5c',
selection: '#9a86fd',
selectionMatch: '#9a86fd',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#000',
muted: '#00000050',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
@@ -20,6 +21,7 @@ export default createTheme({
settings: {
background: '#fff',
foreground: '#000',
muted: '#00000050',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
+1
View File
@@ -23,6 +23,7 @@ export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[10],
muted: hex[7],
selection: hex[8],
selectionMatch: hex[0],
gutterBackground: hex[3],
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#0d1117',
lineBackground: '#0d111799',
foreground: '#c9d1d9',
muted: '#c9d1d950',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
@@ -16,6 +17,7 @@ export default createTheme({
settings: {
background: '#0d1117',
foreground: '#c9d1d9',
muted: '#c9d1d950',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#24292e',
muted: '#24292e50',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
@@ -17,6 +18,7 @@ export default createTheme({
settings: {
background: '#fff',
foreground: '#24292e',
muted: '#24292e50',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
+1
View File
@@ -13,6 +13,7 @@ export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[2],
muted: hex[2] + '50',
selection: hex[4],
selectionMatch: hex[0],
gutterBackground: hex[0],
+2
View File
@@ -10,6 +10,7 @@ export const settings = {
background: '#282828',
lineBackground: '#28282899',
foreground: '#ebdbb2',
muted: '#ebdbb250',
caret: '#ebdbb2',
selection: '#bdae93',
selectionMatch: '#bdae93',
@@ -23,6 +24,7 @@ export default createTheme({
settings: {
background: '#282828',
foreground: '#ebdbb2',
muted: '#ebdbb250',
caret: '#ebdbb2',
selection: '#b99d555c',
selectionMatch: '#b99d555c',
+2
View File
@@ -11,6 +11,7 @@ export const settings = {
background: '#fbf1c7',
lineBackground: '#fbf1c799',
foreground: '#3c3836',
muted: '#3c383650',
caret: '#af3a03',
selection: '#ebdbb2',
selectionMatch: '#bdae93',
@@ -25,6 +26,7 @@ export default createTheme({
settings: {
background: '#fbf1c7',
foreground: '#3c3836',
muted: '#3c383650',
caret: '#af3a03',
selection: '#bdae9391',
selectionMatch: '#bdae9391',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#212121',
lineBackground: '#21212199',
foreground: '#bdbdbd',
muted: '#bdbdbd50',
caret: '#a0a4ae',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
@@ -19,6 +20,7 @@ export default createTheme({
settings: {
background: '#212121',
foreground: '#bdbdbd',
muted: '#bdbdbd50',
caret: '#a0a4ae',
selection: '#d7d4f063',
selectionMatch: '#d7d4f063',
+3 -1
View File
@@ -5,7 +5,8 @@ export const settings = {
light: true,
background: '#FAFAFA',
lineBackground: '#FAFAFA99',
foreground: '#90A4AE',
foreground: '#6182B8',
muted: '#6182B850',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#FAFAFA',
@@ -19,6 +20,7 @@ export default createTheme({
settings: {
background: '#FAFAFA',
foreground: '#90A4AE',
muted: '#6182B850',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#80CBC440',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#272822',
lineBackground: '#27282299',
foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
@@ -18,6 +19,7 @@ export default createTheme({
settings: {
background: '#272822',
foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#f2f1f8',
lineBackground: '#f2f1f899',
foreground: '#0c006b',
muted: '#0c006b50',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
@@ -19,6 +20,7 @@ export default createTheme({
settings: {
background: '#f2f1f8',
foreground: '#0c006b',
muted: '#0c006b50',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#2e3440',
lineBackground: '#2e344099',
foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF',
selection: '#3b4252',
selectionMatch: '#e5e9f0',
@@ -20,6 +21,7 @@ export default createTheme({
settings: {
background: '#2e3440',
foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF',
selection: '#00000073',
selectionMatch: '#00000073',
+1
View File
@@ -13,6 +13,7 @@ export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[2],
muted: hex[2] + '50',
selection: hex[4],
selectionMatch: hex[0],
gutterBackground: hex[0],
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#002b36',
lineBackground: '#002b3699',
foreground: '#93a1a1',
muted: '#93a1a150',
caret: '#839496',
selection: '#173541',
selectionMatch: '#aafe661a',
@@ -45,6 +46,7 @@ export default createTheme({
settings: {
background: c.background,
foreground: c.foreground,
muted: c.muted,
caret: c.cursor,
selection: c.selection,
selectionMatch: c.selection,
+2
View File
@@ -8,6 +8,7 @@ export const settings = {
background: '#fdf6e3',
lineBackground: '#fdf6e399',
foreground: '#657b83',
muted: '#657b8350',
caret: '#586e75',
selection: '#dfd9c8',
selectionMatch: '#dfd9c8',
@@ -19,6 +20,7 @@ export const settings = {
const c = {
background: '#FDF6E3',
foreground: '#657B83',
muted: '#657b8350',
selection: '#EEE8D5',
selectionMatch: '#EEE8D5',
cursor: '#657B83',
+1
View File
@@ -13,6 +13,7 @@ export const settings = {
background: '#000000',
lineBackground: 'transparent',
foreground: hex[4],
muted: hex[6],
selection: hex[6],
gutterBackground: hex[0],
gutterForeground: hex[5],
+1
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#222',
lineBackground: '#22222299',
foreground: '#fff',
muted: '#8a919966',
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#303841',
lineBackground: '#30384199',
foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
@@ -18,6 +19,7 @@ export default createTheme({
settings: {
background: '#303841',
foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
+2 -1
View File
@@ -9,7 +9,8 @@ let colorD = '#f8fc55';
export const settings = {
background: '#000000',
foreground: colorA, // whats that?
foreground: colorA,
muted: colorA + '50',
caret: colorC,
selection: colorD,
selectionMatch: colorA,
+4 -2
View File
@@ -4,7 +4,8 @@ import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#24283b',
lineBackground: '#24283b99',
foreground: '#7982a9',
foreground: '#f9f2f9',
muted: '#f9f2f950',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#1f2335',
@@ -18,7 +19,8 @@ export default createTheme({
theme: 'dark',
settings: {
background: '#24283b',
foreground: '#7982a9',
foreground: '#f9f2f9',
muted: '#f9f2f950',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#343b5f',
+4 -2
View File
@@ -4,7 +4,8 @@ import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#1a1b26',
lineBackground: '#1a1b2699',
foreground: '#787c99',
foreground: '#f8fcf9',
muted: '#f8fcf950',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
@@ -18,7 +19,8 @@ export default createTheme({
theme: 'dark',
settings: {
background: '#1a1b26',
foreground: '#787c99',
foreground: '#f8fcf9',
muted: '#f8fcf950',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#e1e2e7',
lineBackground: '#e1e2e799',
foreground: '#3760bf',
muted: '#3760bf50',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
@@ -20,6 +21,7 @@ export default createTheme({
settings: {
background: '#e1e2e7',
foreground: '#3760bf',
muted: '#3760bf50',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#1e1e1e',
lineBackground: '#1e1e1e99',
foreground: '#fff',
muted: '#ffffff50',
caret: '#c6c6c6',
selection: '#6199ff2f',
selectionMatch: '#72a1ff59',
@@ -19,6 +20,7 @@ export default createTheme({
settings: {
background: '#1e1e1e',
foreground: '#fff',
muted: '#ffffff50',
caret: '#c6c6c6',
selection: '#6199ff2f',
selectionMatch: '#72a1ff59',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#ffffff',
lineBackground: '#ffffff50',
foreground: '#383a42',
muted: '#383a4250',
caret: '#000',
selection: '#add6ff',
selectionMatch: '#a8ac94',
@@ -20,6 +21,7 @@ export default createTheme({
settings: {
background: '#ffffff',
foreground: '#383a42',
muted: '#383a4250',
caret: '#000',
selection: '#add6ff',
selectionMatch: '#a8ac94',
+2 -1
View File
@@ -2,7 +2,8 @@ import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: 'white',
foreground: 'black', // whats that?
foreground: 'black',
muted: '#00000050',
caret: 'black',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#ffffff26',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#3D3D3D',
muted: '#3D3D3D50',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
@@ -18,6 +19,7 @@ export default createTheme({
settings: {
background: '#fff',
foreground: '#3D3D3D',
muted: '#3D3D3D50',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
File diff suppressed because it is too large Load Diff
+1
View File
@@ -15,6 +15,7 @@ import Fraction, { gcd } from './fraction.mjs';
* - "-" hold previous value
* - "." silence
*
* @tags visualization
* @param {Pattern} pattern the pattern to use
* @param {number} chars max number of characters (approximately)
* @returns string
+5
View File
@@ -61,6 +61,7 @@ export const bjorklund = function (ons, steps) {
*
* @memberof Pattern
* @name euclid
* @tags temporal
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @returns Pattern
@@ -73,6 +74,7 @@ export const bjorklund = function (ons, steps) {
* Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
* @memberof Pattern
* @name euclidRot
* @tags temporal
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param {number} rotation offset in steps
@@ -156,6 +158,7 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun
* so there will be no gaps.
* @name euclidLegato
* @memberof Pattern
* @tags temporal
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param rotation offset in steps
@@ -187,6 +190,7 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
* the resulting sequence
* @name euclidLegatoRot
* @memberof Pattern
* @tags temporal
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param {number} rotation offset in steps
@@ -208,6 +212,7 @@ export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, s
* @name euclidish
* @synonyms eish
* @memberof Pattern
* @tags temporal
* @param {number} pulses the number of onsets
* @param {number} steps the number of steps to fill
* @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse)
+1 -1
View File
@@ -22,7 +22,7 @@ export * from './repl.mjs';
export * from './signal.mjs';
export * from './speak.mjs';
export * from './state.mjs';
export * from './time.mjs';
export * from './schedulerState.mjs';
export * from './timespan.mjs';
export * from './ui.mjs';
export * from './util.mjs';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/core",
"version": "1.2.5",
"version": "1.2.6",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
+366 -93
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -28,6 +28,7 @@ const _pick = function (lookup, pat, modulo = true) {
/** * 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.
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -57,6 +58,7 @@ const __pick = register('pick', function (lookup, pat) {
* 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.
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -67,6 +69,7 @@ export const pickmod = register('pickmod', function (lookup, pat) {
});
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
* @tags combiners, functional
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
@@ -83,6 +86,7 @@ export const pickF = register('pickF', function (lookup, funcs, pat) {
/** * 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.
* @tags combiners
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
@@ -93,6 +97,7 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
});
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -103,6 +108,7 @@ export const pickOut = register('pickOut', function (lookup, pat) {
/** * 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.
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -112,6 +118,7 @@ export const pickmodOut = register('pickmodOut', function (lookup, pat) {
});
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -122,6 +129,7 @@ export const pickRestart = register('pickRestart', function (lookup, pat) {
/** * 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.
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -138,6 +146,7 @@ export const pickmodRestart = register('pickmodRestart', function (lookup, pat)
});
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -148,6 +157,7 @@ export const pickReset = register('pickReset', function (lookup, pat) {
/** * 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.
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -159,6 +169,7 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) {
/** 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
* @tags combiners
* @synonyms pickSqueeze
* @param {Pattern} pat
* @param {*} xs
@@ -180,6 +191,7 @@ export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], fun
* second one.
* @name inhabitmod
* @synonyms pickmodSqueeze
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -192,6 +204,7 @@ export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSq
/**
* 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
* @tags combiners
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
+19 -3
View File
@@ -2,7 +2,13 @@ import { NeoCyclist } from './neocyclist.mjs';
import { Cyclist } from './cyclist.mjs';
import { evaluate as _evaluate } from './evaluate.mjs';
import { errorLogger, logger } from './logger.mjs';
import { setTime } from './time.mjs';
import {
setCpsFunc,
setIsStarted,
setPattern as exposeSchedulerPattern,
setTime,
setTriggerFunc,
} from './schedulerState.mjs';
import { evalScope } from './evaluate.mjs';
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
import { reset_state } from './impure.mjs';
@@ -52,6 +58,7 @@ export function repl({
getTime,
onToggle: (started) => {
updateState({ started });
setIsStarted(started);
onToggle?.(started);
if (!started) {
reset_state();
@@ -65,6 +72,8 @@ export function repl({
// NeoCyclist uses a shared worker to communicate between instances, which is not supported on mobile chrome
const scheduler =
sync && typeof SharedWorker != 'undefined' ? new NeoCyclist(schedulerOptions) : new Cyclist(schedulerOptions);
setTriggerFunc(schedulerOptions.onTrigger);
setCpsFunc(() => scheduler.cps);
let pPatterns = {};
let anonymousIndex = 0;
let allTransform;
@@ -89,6 +98,7 @@ export function repl({
const setPattern = async (pattern, autostart = true) => {
pattern = editPattern?.(pattern) || pattern;
await scheduler.setPattern(pattern, autostart);
exposeSchedulerPattern(pattern);
return pattern;
};
setTime(() => scheduler.now()); // TODO: refactor?
@@ -106,6 +116,7 @@ export function repl({
* Changes the global tempo to the given cycles per minute
*
* @name setcpm
* @tags temporal
* @alias setCpm
* @param {number} cpm cycles per minute
* @example
@@ -119,7 +130,9 @@ export function repl({
// 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
let allTransforms = [];
/**
* 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")
@@ -131,18 +144,21 @@ export function repl({
* $: sound("hh*8")
* all(x => x.pianoroll())
* ```
*
* @tags combiners
*/
let allTransforms = [];
const all = function (transform) {
allTransforms.push(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>"))
* ```
* @tags combiners
*/
const each = function (transform) {
eachTransform = transform;
+53
View File
@@ -0,0 +1,53 @@
/*
schedulerState.mjs - Module to pipe out various parameters from the scheduler for global consumption
Copyright (C) 2026 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/schedulerState.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/>.
*/
let time;
let cpsFunc;
let pattern;
let triggerFunc;
let isStarted;
export function getTime() {
if (!time) {
throw new Error('no time set! use setTime to define a time source');
}
return time();
}
export function setTime(func) {
time = func;
}
export function setCpsFunc(func) {
cpsFunc = func;
}
export function getCps() {
return cpsFunc?.();
}
export function setPattern(pat) {
pattern = pat;
}
export function getPattern() {
return pattern;
}
export function setTriggerFunc(func) {
triggerFunc = func;
}
export function getTriggerFunc() {
return triggerFunc;
}
export function setIsStarted(val) {
isStarted = !!val;
}
export function getIsStarted() {
return isStarted;
}
+63
View File
@@ -24,6 +24,7 @@ export const signal = (func) => {
* A sawtooth signal between 0 and 1.
*
* @return {Pattern}
* @tags generators
* @example
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
* .clip(saw.slow(2))
@@ -38,6 +39,7 @@ export const saw = signal((t) => t % 1);
* A sawtooth signal between -1 and 1 (like `saw`, but bipolar).
*
* @return {Pattern}
* @tags generators
*/
export const saw2 = saw.toBipolar();
@@ -45,6 +47,7 @@ export const saw2 = saw.toBipolar();
* A sawtooth signal between 1 and 0 (like `saw`, but flipped).
*
* @return {Pattern}
* @tags generators
* @example
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
* .clip(isaw.slow(2))
@@ -59,6 +62,7 @@ export const isaw = signal((t) => 1 - (t % 1));
* A sawtooth signal between 1 and -1 (like `saw2`, but flipped).
*
* @return {Pattern}
* @tags generators
*/
export const isaw2 = isaw.toBipolar();
@@ -66,12 +70,14 @@ export const isaw2 = isaw.toBipolar();
* A sine signal between -1 and 1 (like `sine`, but bipolar).
*
* @return {Pattern}
* @tags generators
*/
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
/**
* A sine signal between 0 and 1.
* @return {Pattern}
* @tags generators
* @example
* n(sine.segment(16).range(0,15))
* .scale("C:minor")
@@ -83,6 +89,7 @@ export const sine = sine2.fromBipolar();
* A cosine signal between 0 and 1.
*
* @return {Pattern}
* @tags generators
* @example
* n(stack(sine,cosine).segment(16).range(0,15))
* .scale("C:minor")
@@ -94,12 +101,14 @@ export const cosine = sine._early(Fraction(1).div(4));
* A cosine signal between -1 and 1 (like `cosine`, but bipolar).
*
* @return {Pattern}
* @tags generators
*/
export const cosine2 = sine2._early(Fraction(1).div(4));
/**
* A square signal between 0 and 1.
* @return {Pattern}
* @tags generators
* @example
* n(square.segment(4).range(0,7)).scale("C:minor")
*
@@ -110,6 +119,7 @@ export const square = signal((t) => Math.floor((t * 2) % 2));
* A square signal between -1 and 1 (like `square`, but bipolar).
*
* @return {Pattern}
* @tags generators
*/
export const square2 = square.toBipolar();
@@ -117,6 +127,7 @@ export const square2 = square.toBipolar();
* A triangle signal between 0 and 1.
*
* @return {Pattern}
* @tags generators
* @example
* n(tri.segment(8).range(0,7)).scale("C:minor")
*
@@ -127,6 +138,7 @@ export const tri = fastcat(saw, isaw);
* A triangle signal between -1 and 1 (like `tri`, but bipolar).
*
* @return {Pattern}
* @tags generators
*/
export const tri2 = fastcat(saw2, isaw2);
@@ -134,6 +146,7 @@ export const tri2 = fastcat(saw2, isaw2);
* An inverted triangle signal between 1 and 0 (like `tri`, but flipped).
*
* @return {Pattern}
* @tags generators
* @example
* n(itri.segment(8).range(0,7)).scale("C:minor")
*
@@ -144,6 +157,7 @@ export const itri = fastcat(isaw, saw);
* An inverted triangle signal between -1 and 1 (like `itri`, but bipolar).
*
* @return {Pattern}
* @tags generators
*/
export const itri2 = fastcat(isaw2, saw2);
@@ -151,6 +165,7 @@ export const itri2 = fastcat(isaw2, saw2);
* A signal representing the cycle time.
*
* @return {Pattern}
* @tags generators
*/
export const time = signal(id);
@@ -158,6 +173,7 @@ export const time = signal(id);
* The mouse's x position value ranges from 0 to 1.
* @name mousex
* @return {Pattern}
* @tags external_io
* @example
* n(mousex.segment(4).range(0,7)).scale("C:minor")
*
@@ -167,6 +183,7 @@ export const time = signal(id);
* The mouse's y position value ranges from 0 to 1.
* @name mousey
* @return {Pattern}
* @tags external_io
* @example
* n(mousey.segment(4).range(0,7)).scale("C:minor")
*
@@ -269,6 +286,7 @@ export const getRandsAtTime = (t, n = 1, seed = 0) => {
* precise RNG, try `useRNG('precise')`.
*
* @name useRNG
* @tags generators, math
* @param {string} mod - Mode. One of 'legacy', 'precise'
* @example
* useRNG('legacy')
@@ -280,6 +298,7 @@ export const useRNG = (mode = 'legacy') => (RNG_MODE = mode);
/**
* A discrete pattern of numbers from 0 to n-1
* @tags generators
* @example
* n(run(4)).scale("C4:pentatonic")
* // n("0 1 2 3").scale("C4:pentatonic")
@@ -290,6 +309,7 @@ export const run = (n) => saw.range(0, n).round().segment(n);
* Creates a binary pattern from a number.
*
* @name binary
* @tags generators
* @param {number} n - input number to convert to binary
* @example
* "hh".s().struct(binary(5))
@@ -304,6 +324,7 @@ export const binary = (n) => {
* Creates a binary pattern from a number, padded to n bits long.
*
* @name binaryN
* @tags generators
* @param {number} n - input number to convert to binary
* @param {number} nBits - pattern length, defaults to 16
* @example
@@ -321,6 +342,7 @@ export const binaryN = (n, nBits = 16) => {
* Creates a binary list pattern from a number.
*
* @name binaryL
* @tags generators
* @param {number} n - input number to convert to binary
* s("saw").seg(8)
* .partials(binaryL(irand(4096).add(1)))
@@ -334,6 +356,7 @@ export const binaryL = (n) => {
* Creates a binary list pattern from a number, padded to n bits long.
*
* @name binaryNL
* @tags generators
* @param {number} n - input number to convert to binary
* @param {number} nBits - pattern length, defaults to 16
*/
@@ -353,6 +376,7 @@ export const binaryNL = (n, nBits = 16) => {
* Creates a list of random numbers of the given length
*
* @name randL
* @tags generators
* @param {number} n Number of random numbers to sample
* @example
* s("saw").seg(16).n(irand(12)).scale("F1:minor")
@@ -384,6 +408,7 @@ const _rearrangeWith = (ipat, n, pat) => {
* 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
* @tags temporal
* @example
* note("c d e f").sound("piano").shuffle(4)
* @example
@@ -397,6 +422,7 @@ export const shuffle = register('shuffle', (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
* @tags temporal
* @example
* note("c d e f").sound("piano").scramble(4)
* @example
@@ -409,6 +435,7 @@ export const scramble = register('scramble', (n, pat) => {
/**
* Modify a pattern by applying a function to the `randomSeed` control if present
*
* @tags math
* @param {Function} func Function from seed (or undefined) to seed (or undefined)
* @param {Pattern} pat Pattern to update
* @returns Pattern
@@ -428,6 +455,7 @@ export const withSeed = (func, pat) => {
* that use randomness, like `shuffle` and `sometimes`.
*
* @name seed
* @tags math
* @param {number} n A new seed. Can be any number.
* @example
* $: s("hh*4").degrade();
@@ -441,6 +469,7 @@ export const seed = register('seed', (n, pat) => {
* A continuous pattern of random numbers, between 0 and 1.
*
* @name rand
* @tags generators
* @example
* // randomly change the cutoff
* s("bd*4,hh*8").cutoff(rand.range(500,8000))
@@ -449,6 +478,7 @@ export const seed = register('seed', (n, pat) => {
export const rand = signal((t, controls) => getRandsAtTime(t, 1, controls.randSeed));
/**
* A continuous pattern of random numbers, between -1 and 1
* @tags generators
*/
export const rand2 = rand.toBipolar();
@@ -458,6 +488,7 @@ export const _brandBy = (p) => rand.fmap((x) => x < p);
* A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1
*
* @name brandBy
* @tags generators
* @param {number} probability - a number between 0 and 1
* @example
* s("hh*10").pan(brandBy(0.2))
@@ -468,6 +499,7 @@ export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
* A continuous pattern of 0 or 1 (binary random)
*
* @name brand
* @tags generators
* @example
* s("hh*10").pan(brand)
*/
@@ -479,6 +511,7 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
* A continuous pattern of random integers, between 0 and n-1.
*
* @name irand
* @tags generators
* @param {number} n max value (exclusive)
* @example
* // randomly select scale notes from 0 - 7 (= C to C)
@@ -501,6 +534,7 @@ export const __chooseWith = (pat, xs) => {
/**
* Choose from the list of values (or patterns of values) using the given
* pattern of numbers, which should be in the range of 0..1
* @tags temporal
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -514,6 +548,7 @@ export const chooseWith = (pat, xs) => {
/**
* As with {chooseWith}, but the structure comes from the chosen values, rather
* than the pattern you're using to choose with.
* @tags temporal
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -524,6 +559,7 @@ export const chooseInWith = (pat, xs) => {
/**
* Chooses randomly from the given list of elements.
* @tags temporal
* @param {...any} xs values / patterns to choose from.
* @returns {Pattern} - a continuous pattern.
* @example
@@ -539,6 +575,7 @@ export const chooseOut = choose;
* Chooses from the given list of values (or patterns of values), according
* to the pattern that the method is called on. The pattern should be in
* the range 0 .. 1.
* @tags temporal
* @param {...any} xs
* @returns {Pattern}
*/
@@ -549,6 +586,7 @@ Pattern.prototype.choose = function (...xs) {
/**
* As with choose, but the pattern that this method is called on should be
* in the range -1 .. 1
* @tags temporal
* @param {...any} xs
* @returns {Pattern}
*/
@@ -558,6 +596,7 @@ Pattern.prototype.choose2 = function (...xs) {
/**
* Picks one of the elements at random each cycle.
* @tags temporal
* @synonyms randcat
* @returns {Pattern}
* @example
@@ -600,6 +639,7 @@ const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
/**
* Chooses randomly from the given list of elements by giving a probability to each element
* @tags temporal
* @param {...any} pairs arrays of value and weight
* @returns {Pattern} - a continuous pattern.
* @example
@@ -609,6 +649,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
/**
* Picks one of the elements at random each cycle by giving a probability to each element
* @tags temporal
* @synonyms wrandcat
* @returns {Pattern}
* @example
@@ -652,6 +693,7 @@ function _berlin(t, seed = 0) {
/**
* Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1.
*
* @tags generators
* @name perlin
* @example
* // randomly change the cutoff
@@ -664,6 +706,7 @@ export const perlin = signal((t, controls) => _perlin(t, controls.randSeed));
* Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful,
* like perlin noise but with sawtooth waves), in the range 0..1.
*
* @tags generators
* @name berlin
* @example
* // ascending arpeggios
@@ -684,6 +727,7 @@ export const degradeByWith = register(
* 0 = 0% chance of removal
* 1 = 100% chance of removal
*
* @tags temporal
* @name degradeBy
* @memberof Pattern
* @param {number} amount - a number between 0 and 1
@@ -709,6 +753,7 @@ export const degradeBy = register(
*
* Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)`
*
* @tags temporal
* @name degrade
* @memberof Pattern
* @returns Pattern
@@ -725,6 +770,7 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, t
* 1 = 0% chance of removal
* Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example).
*
* @tags temporal
* @name undegradeBy
* @memberof Pattern
* @param {number} amount - a number between 0 and 1
@@ -753,6 +799,7 @@ export const undegradeBy = register(
* Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)`
* Events that would be removed by degrade are let through by undegrade and vice versa (see second example).
*
* @tags temporal
* @name undegrade
* @memberof Pattern
* @returns Pattern
@@ -771,6 +818,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t
* Randomly applies the given function by the given probability.
* Similar to `someCyclesBy`
*
* @tags temporal
* @name sometimesBy
* @memberof Pattern
* @param {number | Pattern} probability - a number between 0 and 1
@@ -790,6 +838,7 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
*
* Applies the given function with a 50% chance
*
* @tags temporal
* @name sometimes
* @memberof Pattern
* @param {function} function - the transformation to apply
@@ -811,6 +860,7 @@ export const sometimes = register('sometimes', function (func, pat) {
* @param {number | Pattern} probability - a number between 0 and 1
* @param {function} function - the transformation to apply
* @returns Pattern
* @tags temporal
* @example
* s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5"))
*/
@@ -833,6 +883,7 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat)
* @name someCycles
* @memberof Pattern
* @returns Pattern
* @tags temporal
* @example
* s("bd,hh*8").someCycles(x=>x.speed("0.5"))
*/
@@ -847,6 +898,7 @@ export const someCycles = register('someCycles', function (func, pat) {
* @name often
* @memberof Pattern
* @returns Pattern
* @tags temporal
* @example
* s("hh*8").often(x=>x.speed("0.5"))
*/
@@ -861,6 +913,7 @@ export const often = register('often', function (func, pat) {
* @name rarely
* @memberof Pattern
* @returns Pattern
* @tags temporal
* @example
* s("hh*8").rarely(x=>x.speed("0.5"))
*/
@@ -872,6 +925,7 @@ export const rarely = register('rarely', function (func, pat) {
*
* Shorthand for `.sometimesBy(0.1, fn)`
*
* @tags temporal
* @name almostNever
* @memberof Pattern
* @returns Pattern
@@ -886,6 +940,7 @@ export const almostNever = register('almostNever', function (func, pat) {
*
* Shorthand for `.sometimesBy(0.9, fn)`
*
* @tags temporal
* @name almostAlways
* @memberof Pattern
* @returns Pattern
@@ -900,6 +955,7 @@ export const almostAlways = register('almostAlways', function (func, pat) {
*
* Shorthand for `.sometimesBy(0, fn)` (never calls fn)
*
* @tags temporal
* @name never
* @memberof Pattern
* @returns Pattern
@@ -914,6 +970,7 @@ export const never = register('never', function (_, pat) {
*
* Shorthand for `.sometimesBy(1, fn)` (always calls fn)
*
* @tags temporal
* @name always
* @memberof Pattern
* @returns Pattern
@@ -942,6 +999,7 @@ export function _keyDown(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)
*
* @tags external_io
* @name whenKey
* @memberof Pattern
* @returns Pattern
@@ -958,6 +1016,7 @@ export const whenKey = register('whenKey', function (input, func, pat) {
* 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)
*
* @tags external_io
* @name keyDown
* @memberof Pattern
* @returns Pattern
@@ -975,6 +1034,8 @@ export const keyDown = register('keyDown', function (pat) {
* event durations, from the pattern that it is combined with.
* For example `cyclesPer.struct("1 1 [1 1] 1")` would give the same as `"0.25 0.25 [0.125 0.125] 0.25"`.
* See also its reciprocal, `per`, also known as `perCycle`.
*
* @tags temporal
* @example
* // Shorter events are lower in pitch
* sound("saw saw [saw saw] saw")
@@ -993,6 +1054,7 @@ export const cyclesPer = new Pattern(function (state) {
* event durations, from the pattern that it is combined with.
* For example `per.struct("1 1 [1 1] 1")` would give the same as `"4 4 [8 8] 4"`.
* See also its reciprocal, `cyclesPer`.
* @tags temporal
* @synonyms perCycle
* @example
* // Shorter events are more distorted
@@ -1010,6 +1072,7 @@ export const perCycle = per;
* particular, where the event duration halves, the
* returned value increases by one. `perx.struct("1 1 [1 [1 1]] 1")` would therefore be
* the same as `"3 3 [4 [5 5]] 3"`.
* @tags temporal
*/
export const perx = new Pattern(function (state) {
const n = Fraction(1).div(state.span.duration);
-11
View File
@@ -1,11 +0,0 @@
let time;
export function getTime() {
if (!time) {
throw new Error('no time set! use setTime to define a time source');
}
return time();
}
export function setTime(func) {
time = func;
}
+2
View File
@@ -135,6 +135,8 @@ export async function loadOrc(url) {
* p4 -- MIDI key number (as a real number, not an integer but in [0, 127].
* p5 -- MIDI velocity (as a real number, not an integer but in [0, 127].
* p6 -- Strudel controls, as a string.
*
* @tags external_io
*/
export const csoundm = register('csoundm', (instrument, pat) => {
let p1 = instrument;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/csound",
"version": "1.2.6",
"version": "1.3.0",
"description": "csound bindings for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/draw",
"version": "1.2.5",
"version": "1.2.6",
"description": "Helpers for drawing with Strudel",
"main": "index.mjs",
"type": "module",
+2
View File
@@ -42,6 +42,7 @@ const getValue = (e) => {
*
* @name pianoroll
* @synonyms punchcard
* @tags visualization
* @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4
* @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5
@@ -299,6 +300,7 @@ Pattern.prototype.punchcard = function (options) {
* Supports all the same options as pianoroll.
*
* @name wordfall
* @tags visualization
*/
Pattern.prototype.wordfall = function (options) {
return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options });
+1
View File
@@ -116,6 +116,7 @@ export function pitchwheel({
/**
* Renders a pitch circle to visualize frequencies within one octave
* @name pitchwheel
* @tags visualization
* @param {number} hapcircles
* @param {number} circle
* @param {number} edo
+1
View File
@@ -129,6 +129,7 @@ function drawSpiral(options) {
* Displays a spiral visual.
*
* @name spiral
* @tags visualization
* @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {number} stretch controls the rotations per cycle ratio, where 1 = 1 cycle / 360 degrees
* @param {number} size the diameter of the spiral
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/embed",
"version": "1.1.1",
"version": "1.1.2",
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
"main": "embed.js",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/gamepad",
"version": "1.2.5",
"version": "1.2.6",
"description": "Gamepad Inputs for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/hydra",
"version": "1.2.5",
"version": "1.2.6",
"description": "Hydra integration for strudel",
"main": "hydra.mjs",
"type": "module",
+184 -28
View File
@@ -5,9 +5,23 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import * as _WebMidi from 'webmidi';
import { Pattern, isPattern, logger, ref } from '@strudel/core';
import {
Hap,
Pattern,
TimeSpan,
getCps,
getIsStarted,
getPattern,
getTime,
getTriggerFunc,
isPattern,
logger,
ref,
reify,
} from '@strudel/core';
import { noteToMidi, getControlName } from '@strudel/core';
import { Note } from 'webmidi';
import { getAudioContext } from '@strudel/webaudio';
import { scheduleAtTime } from '../superdough/helpers.mjs';
// if you use WebMidi from outside of this package, make sure to import that instance:
@@ -124,6 +138,7 @@ function githubPath(base, subpath = '') {
/**
* configures the default midimap, which is used when no "midimap" port is set
* @tags external_io
* @example
* defaultmidimap({ lpf: 74 })
* $: note("c a f e").midi();
@@ -137,6 +152,7 @@ let loadCache = {};
/**
* Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers.
* @tags external_io
* @example
* midimaps({ mymap: { lpf: 74 } })
* $: note("c a f e")
@@ -291,6 +307,7 @@ function sendNote(note, velocity, duration, device, midichan, targetTime) {
/**
* MIDI output: Opens a MIDI output port.
* @tags external_io
* @param {string | number} midiport MIDI device name or index defaulting to 0
* @param {object} options Additional MIDI configuration options
* @example
@@ -477,14 +494,42 @@ Pattern.prototype.midi = function (midiport, options = {}) {
});
};
let listeners = {};
const refs = {};
const refsByChan = {};
/**
* Initialize a midi device
*/
async function _initialize(input) {
if (isPattern(input)) {
throw new Error(
`[midi] Midi input cannot be a pattern. 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(
`[midi] Midi device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
);
}
if (initial) {
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
logger(
`[midi] Midi enabled! Using "${device.name}". ${
otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : ''
}`,
);
}
return device;
}
/**
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
*
* The output is a function that accepts a midi cc value to query as well as (optionally) a midi channel
*
* @name midin
* @tags external_io
* @param {string | number} input MIDI device name or index defaulting to 0
* @returns {function(number, number=): Pattern} A function from (cc, channel?) to a pattern.
* When queried, the pattern will produces the most recently received midi value (normalized to 0 to 1)
@@ -498,29 +543,11 @@ const refsByChan = {};
* note("c a f e").s("saw")
* .when(cc(0).gt(0), x => x.postgain(0))
*/
let listeners = {};
const refs = {};
const refsByChan = {};
export async function midin(input) {
if (isPattern(input)) {
throw new Error(
`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(
`Midi enabled! Using "${device.name}". ${
otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : ''
}`,
);
}
const device = await _initialize(input);
refs[input] ??= {};
refsByChan[input] ??= {};
const cc = (cc, chan) => {
@@ -532,8 +559,7 @@ export async function midin(input) {
listeners[input] && device.removeListener('midimessage', listeners[input]);
listeners[input] = (e) => {
const ccNum = e.dataBytes[0];
const v = e.dataBytes[1];
const [ccNum, v] = e.dataBytes;
const chan = e.message.channel;
const scaled = v / 127;
refsByChan[input][ccNum] ??= {};
@@ -543,3 +569,133 @@ export async function midin(input) {
device.addListener('midimessage', listeners[input]);
return cc;
}
/**
* MIDI keyboard: Opens a MIDI input port to receive MIDI keyboard messages.
*
* The note length is fixed as Superdough is not currently set up for undetermined
* note durations
*
* @name midikeys
* @tags external_io
* @param {string | number} input MIDI device name or index defaulting to 0
* @returns {function((number | Pattern)=): Pattern} A function that produces a pattern.
* When queried, the pattern will produces the most recently played midi notes and velocities,
* lasting for the specified duration
* @example
* const kb = await midikeys('Arturia KeyStep 32')
* kb().s("tri").lpf(80).lpe(6).lpd(0.1).room(2).delay(0.35)
* @example
* const kb = await midikeys('Arturia KeyStep 32')
* kb("0.5 1")
* .s("saw")
* .add(note(rand.mul(0.3)))
* .lpf(1000).lpe(2).room(0.5)
*/
const kHaps = {};
const kListeners = {};
function _triggerKeyboard(input, cps, now, latencyCycles) {
const pattern = getPattern();
const trigger = getTriggerFunc();
if (!pattern || !trigger) {
return false;
}
const t = now + latencyCycles;
const eps = 1e-6;
const haps = pattern.queryArc(t - eps, t + eps, { _cps: cps });
// Only keep haps coming from `midikeys`
const kbHaps = haps.filter((hap) => hap.value?.midikey?.startsWith(`${input}_`));
const ctxNow = getAudioContext().currentTime;
if (!kbHaps.length) {
return false;
}
kbHaps.forEach((hap) => {
if (!hap.hasOnset()) {
return;
}
const t = ctxNow + (hap.whole.begin - now) / cps;
const duration = hap.duration / cps;
trigger(hap, t - ctxNow, duration, cps, t);
});
return true;
}
export async function midikeys(input) {
const device = await _initialize(input);
if (!kHaps[input]) {
kHaps[input] = [];
}
kListeners[input] && device.removeListener('midimessage', kListeners[input]);
kListeners[input] = (e) => {
const { dataBytes, message } = e;
const noteon = message.command === 9;
let noteoff = message.command === 8;
// Don't enqueue or trigger midi notes if scheduler is not started
const notStarted = !getIsStarted();
// Ignore non-note messages (e.g. CC, pitchbend, modwheel, etc.)
const notANote = !noteon && !noteoff;
if (notStarted || notANote) {
return;
}
const [note, velocity] = dataBytes;
noteoff ||= noteon && velocity === 0; // handle devices which may use velocity = 0 to signal noteoff
const key = `${input}_${note}`;
const cps = getCps() ?? 0.5;
const triggerAvailable = !!(getPattern() && getTriggerFunc());
const latencySeconds = triggerAvailable ? 0.01 : 0.06; // avoid missing notes due to cyclist / trigger latency
const now = getTime();
const t = now + latencySeconds * cps;
const span = new TimeSpan(t, t);
let value = { midikey: key };
if (noteoff) {
/* TODO: It's a big effort, but we could modify superdough to allow for situations where
we don't know the hap duration in advance. This would mean, for example, that if the hap
is flagged as such a special note-on event, we have all effects be persistent & all ADSR
envelopes stop at the S stage [and store references to them by `midikey`]
If this is implemented, then getting full keyboard functionality should be as simple
as sending the corresponding note-off event below and triggering `release` on each of those
referenced effects/envelopes
value = { ...value, noteoff: true };
If this is achieved, we can remove the noteLength parameter
*/
return;
} else {
value = { ...value, note: Math.round(note), velocity: velocity / 127 };
}
kHaps[input].push(new Hap(span, span, value, {}));
if (!noteoff && triggerAvailable) {
// If we have access to a trigger function, we call it to immediately
// dispatch to the audio engine, rather than waiting for cyclist to catch these haps
const triggered = _triggerKeyboard(input, cps, now, latencySeconds * cps);
if (triggered) {
kHaps[input] = [];
}
}
};
device.addListener('midimessage', kListeners[input]);
const kb = (noteLength = 0.5) => {
const nlPat = reify(noteLength);
const query = (state) => {
const haps = kHaps[input].flatMap((hap) => {
const lenHaps = nlPat.query(state.setSpan(hap.wholeOrPart()));
return lenHaps.map((lenHap) => {
const nl = lenHap.value ?? 0.5;
const whole = new TimeSpan(hap.whole.begin, hap.whole.begin.add(nl));
const part = new TimeSpan(hap.part.begin, hap.part.begin.add(nl));
const context = hap.combineContext(lenHap);
return new Hap(whole, part, hap.value, context);
});
});
if (state.controls.cyclist) {
// Notes have been sent; clear them
kHaps[input] = [];
}
return haps;
};
return new Pattern(query);
};
return kb;
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/midi",
"version": "1.2.6",
"version": "1.3.0",
"description": "Midi API for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mini",
"version": "1.2.5",
"version": "1.2.6",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mondolang",
"version": "1.1.1",
"version": "1.1.2",
"description": "a language for functional composition that translates to js",
"main": "mondo.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mondo",
"version": "1.1.5",
"version": "1.1.6",
"description": "mondo notation for strudel",
"main": "mondough.mjs",
"type": "module",
+15
View File
@@ -7,6 +7,7 @@ import { signal } from '../core/signal.mjs';
* @name accelerationX
* @return {Pattern}
* @synonyms accX
* @tags external_io
* @example
* n(accelerationX.segment(4).range(0,7)).scale("C:minor")
*
@@ -17,6 +18,7 @@ import { signal } from '../core/signal.mjs';
* @name accelerationY
* @return {Pattern}
* @synonyms accY
* @tags external_io
* @example
* n(accelerationY.segment(4).range(0,7)).scale("C:minor")
*
@@ -27,6 +29,7 @@ import { signal } from '../core/signal.mjs';
* @name accelerationZ
* @return {Pattern}
* @synonyms accZ
* @tags external_io
* @example
* n(accelerationZ.segment(4).range(0,7)).scale("C:minor")
*
@@ -37,6 +40,7 @@ import { signal } from '../core/signal.mjs';
* @name gravityX
* @return {Pattern}
* @synonyms gravX
* @tags external_io
* @example
* n(gravityX.segment(4).range(0,7)).scale("C:minor")
*
@@ -47,6 +51,7 @@ import { signal } from '../core/signal.mjs';
* @name gravityY
* @return {Pattern}
* @synonyms gravY
* @tags external_io
* @example
* n(gravityY.segment(4).range(0,7)).scale("C:minor")
*
@@ -57,6 +62,7 @@ import { signal } from '../core/signal.mjs';
* @name gravityZ
* @return {Pattern}
* @synonyms gravZ
* @tags external_io
* @example
* n(gravityZ.segment(4).range(0,7)).scale("C:minor")
*
@@ -67,6 +73,7 @@ import { signal } from '../core/signal.mjs';
* @name rotationAlpha
* @return {Pattern}
* @synonyms rotA, rotZ, rotationZ
* @tags external_io
* @example
* n(rotationAlpha.segment(4).range(0,7)).scale("C:minor")
*
@@ -77,6 +84,7 @@ import { signal } from '../core/signal.mjs';
* @name rotationBeta
* @return {Pattern}
* @synonyms rotB, rotX, rotationX
* @tags external_io
* @example
* n(rotationBeta.segment(4).range(0,7)).scale("C:minor")
*
@@ -87,6 +95,7 @@ import { signal } from '../core/signal.mjs';
* @name rotationGamma
* @return {Pattern}
* @synonyms rotG, rotY, rotationY
* @tags external_io
* @example
* n(rotationGamma.segment(4).range(0,7)).scale("C:minor")
*
@@ -97,6 +106,7 @@ import { signal } from '../core/signal.mjs';
* @name orientationAlpha
* @return {Pattern}
* @synonyms oriA, oriZ, orientationZ
* @tags external_io
* @example
* n(orientationAlpha.segment(4).range(0,7)).scale("C:minor")
*
@@ -107,6 +117,7 @@ import { signal } from '../core/signal.mjs';
* @name orientationBeta
* @return {Pattern}
* @synonyms oriB, oriX, orientationX
* @tags external_io
* @example
* n(orientationBeta.segment(4).range(0,7)).scale("C:minor")
*
@@ -117,6 +128,7 @@ import { signal } from '../core/signal.mjs';
* @name orientationGamma
* @return {Pattern}
* @synonyms oriG, oriY, orientationY
* @tags external_io
* @example
* n(orientationGamma.segment(4).range(0,7)).scale("C:minor")
*
@@ -127,6 +139,7 @@ import { signal } from '../core/signal.mjs';
* @name absoluteOrientationAlpha
* @return {Pattern}
* @synonyms absOriA, absOriZ, absoluteOrientationZ
* @tags external_io
* @example
* n(absoluteOrientationAlpha.segment(4).range(0,7)).scale("C:minor")
*
@@ -137,6 +150,7 @@ import { signal } from '../core/signal.mjs';
* @name absoluteOrientationBeta
* @return {Pattern}
* @synonyms absOriB, absOriX, absoluteOrientationX
* @tags external_io
* @example
* n(absoluteOrientationBeta.segment(4).range(0,7)).scale("C:minor")
*
@@ -147,6 +161,7 @@ import { signal } from '../core/signal.mjs';
* @name absoluteOrientationGamma
* @return {Pattern}
* @synonyms absOriG, absOriY, absoluteOrientationY
* @tags external_io
* @example
* n(absoluteOrientationGamma.segment(4).range(0,7)).scale("C:minor")
*
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/motion",
"version": "1.2.5",
"version": "1.2.6",
"description": "DeviceMotion API for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mqtt",
"version": "1.2.5",
"version": "1.2.6",
"description": "MQTT API for strudel",
"main": "mqtt.mjs",
"type": "module",
+1
View File
@@ -79,6 +79,7 @@ export async function oscTrigger(hap, currentTime, cps = 1, targetTime) {
* For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output/)
*
* @name osc
* @tags external_io
* @memberof Pattern
* @returns Pattern
*/
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/osc",
"version": "1.3.0",
"version": "1.3.2",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"bin": "./server.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/reference",
"version": "1.2.1",
"version": "1.2.2",
"description": "Headless reference of all strudel functions",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/repl",
"version": "1.2.7",
"version": "1.3.0",
"description": "Strudel REPL as a Web Component",
"module": "index.mjs",
"publishConfig": {
+2 -1
View File
@@ -1,11 +1,12 @@
import { silence } from '@strudel/core';
import { getDrawContext } from '@strudel/draw';
import { transpiler } from '@strudel/transpiler';
import { getAudioContext, webaudioOutput } from '@strudel/webaudio';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
import { StrudelMirror, codemirrorSettings } from '@strudel/codemirror';
import { prebake } from './prebake.mjs';
if (typeof HTMLElement !== 'undefined') {
initAudioOnFirstClick();
class StrudelRepl extends HTMLElement {
static observedAttributes = ['code'];
settings = codemirrorSettings.get();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/sampler",
"version": "0.2.3",
"version": "0.2.4",
"description": "",
"keywords": [
"tidalcycles",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/serial",
"version": "1.2.5",
"version": "1.2.6",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"type": "module",
+1 -1
View File
@@ -68,7 +68,7 @@ Pattern.prototype.serial = function (br = 115200, sendcrc = false, singlecharids
if (!(name in writeMessagers)) {
getWriter(name, br);
}
const onTrigger = (t_deprecate, hap, currentTime, cps, targetTime) => {
const onTrigger = (hap, currentTime, _cps, targetTime) => {
var message = '';
var chk = 0;
if (typeof hap.value === 'object') {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/soundfonts",
"version": "1.2.6",
"version": "1.3.0",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"publishConfig": {
+3 -4
View File
@@ -154,9 +154,8 @@ export function getCompressor(ac, threshold, ratio, knee, attack, release) {
attack: attack ?? 0.005,
release: release ?? 0.05,
};
const now = ac.currentTime;
Object.entries(options).forEach(([key, value]) => {
node[key].setValueAtTime(value, now);
node[key].value = value;
});
return node;
}
@@ -173,6 +172,7 @@ export const getADSRValues = (params, curve = 'linear', defaultValues) => {
if (a == null && d == null && s == null && r == null) {
return defaultValues ?? [envmin, envmin, envmax, releaseMin];
}
const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin;
return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)];
};
@@ -242,9 +242,8 @@ export function createFilter(context, start, end, params, cps, cycle) {
const factory = () => context.createBiquadFilter();
filter = getNodeFromPool('filter', factory);
filter.type = type;
const now = context.currentTime;
Object.entries({ Q: q, frequency }).forEach(([key, value]) => {
filter[key].setValueAtTime(value, now);
filter[key].value = value;
});
frequencyParam = filter.frequency;
}
+2 -1
View File
@@ -98,6 +98,7 @@ export const connectLFO = (id, params, nodeTracker) => {
fxi = 'main',
depth = 1,
depthabs,
retrig = 0,
...filteredParams
} = params;
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl);
@@ -109,7 +110,7 @@ export const connectLFO = (id, params, nodeTracker) => {
const modParams = {
...filteredParams,
frequency: sync !== undefined ? sync * cps : rate,
time: cycle / cps,
time: retrig > 0.5 ? 0 : cycle / cps,
depth: depthValue,
min,
max,
+25 -14
View File
@@ -7,11 +7,13 @@ This program is free software: you can redistribute it and/or modify it under th
const nodePools = new Map();
const POOL_KEY = Symbol('nodePoolKey');
const IS_WORKLET_DEAD = Symbol('nodePoolIsWorkletDead');
const MAX_POOL_SIZE = 64;
export const isPoolable = (node) => !!node[POOL_KEY];
const getNodeTime = (node) => {
return node.context?.currentTime ?? 0;
};
const getParams = (node) => {
const params = new Set();
node.parameters?.forEach((param) => params.add(param));
@@ -38,34 +40,43 @@ export const releaseNodeToPool = (node) => {
// not reusable
return;
}
if (node[IS_WORKLET_DEAD]) {
// Worklet already terminated, don't pool it
return;
}
const key = node[POOL_KEY];
if (key == null) return;
const now = node.context?.currentTime ?? 0;
const now = getNodeTime(node);
getParams(node).forEach((param) => param.cancelScheduledValues(now));
const pool = nodePools.get(key) ?? [];
if (pool.length < MAX_POOL_SIZE) {
pool.push(new WeakRef(node));
nodePools.set(key, pool);
}
pool.push(new WeakRef(node));
nodePools.set(key, pool);
};
export const markWorkletAsDead = (worklet) => (worklet[IS_WORKLET_DEAD] = true);
// Audio worklets are given a grace period to survive (`return true`) after
// being released. This concludes at time `end + 0.5`. We test here whether we are
// within some safe distance of that (`end + 0.45`) and if so, permit the node to be
// released. This helps to prevent race conditions between node termination and node
// re-use
const isNodeAlive = (node) => {
// Skip check if node is not a worklet
if (!(node instanceof AudioWorkletNode)) return true;
const now = getNodeTime(node);
const end = node?.parameters?.get('end').value ?? 0;
return now < end + 0.45;
};
// Attempt to get node from the pool. If this fails, fall back
// to building it with the factory
export const getNodeFromPool = (key, factory) => {
const pool = nodePools.get(key) ?? [];
let node;
let found = false;
while (pool.length) {
const ref = pool.pop();
node = ref?.deref();
if (node != null && !node[IS_WORKLET_DEAD]) break;
if (node != null && isNodeAlive(node)) {
found = true;
break;
}
}
if (node == null || node[IS_WORKLET_DEAD]) {
if (!found) {
node = factory();
}
node[POOL_KEY] = key;
+22 -7
View File
@@ -35,7 +35,9 @@ class OLAProcessor extends AudioWorkletProcessor {
}
/** Handles dynamic reallocation of input/output channels buffer
(channel numbers may lety during lifecycle) **/
* (channel numbers may vary during lifecycle)
* @tags internals
**/
reallocateChannelsIfNeeded(inputs, outputs) {
for (let i = 0; i < this.nbInputs; i++) {
let nbChannels = inputs[i].length;
@@ -88,7 +90,10 @@ class OLAProcessor extends AudioWorkletProcessor {
}
}
/** Read next web audio block to input buffers **/
/**
* Read next web audio block to input buffers
* @tags internals
**/
readInputs(inputs) {
// when playback is paused, we may stop receiving new samples
if (inputs[0].length && inputs[0][0].length == 0) {
@@ -108,7 +113,9 @@ class OLAProcessor extends AudioWorkletProcessor {
}
}
/** Write next web audio block from output buffers **/
/** Write next web audio block from output buffers
* @tags internals
**/
writeOutputs(outputs) {
for (let i = 0; i < this.nbInputs; i++) {
for (let j = 0; j < this.inputBuffers[i].length; j++) {
@@ -118,7 +125,9 @@ class OLAProcessor extends AudioWorkletProcessor {
}
}
/** Shift left content of input buffers to receive new web audio block **/
/** Shift left content of input buffers to receive new web audio block
* @tags internals
**/
shiftInputBuffers() {
for (let i = 0; i < this.nbInputs; i++) {
for (let j = 0; j < this.inputBuffers[i].length; j++) {
@@ -127,7 +136,9 @@ class OLAProcessor extends AudioWorkletProcessor {
}
}
/** Shift left content of output buffers to receive new web audio block **/
/** Shift left content of output buffers to receive new web audio block
* @tags internals
**/
shiftOutputBuffers() {
for (let i = 0; i < this.nbOutputs; i++) {
for (let j = 0; j < this.outputBuffers[i].length; j++) {
@@ -137,7 +148,9 @@ class OLAProcessor extends AudioWorkletProcessor {
}
}
/** Copy contents of input buffers to buffer actually sent to process **/
/** Copy contents of input buffers to buffer actually sent to process
* @tags internals
**/
prepareInputBuffersToSend() {
for (let i = 0; i < this.nbInputs; i++) {
for (let j = 0; j < this.inputBuffers[i].length; j++) {
@@ -146,7 +159,9 @@ class OLAProcessor extends AudioWorkletProcessor {
}
}
/** Add contents of output buffers just processed to output buffers **/
/** Add contents of output buffers just processed to output buffers
* @tags internals
**/
handleOutputBuffersToRetrieve() {
for (let i = 0; i < this.nbOutputs; i++) {
for (let j = 0; j < this.outputBuffers[i].length; j++) {
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "superdough",
"version": "1.2.6",
"version": "1.3.0",
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
"main": "index.mjs",
"type": "module",
@@ -36,6 +36,8 @@
"vite-plugin-bundle-audioworklet": "workspace:*"
},
"dependencies": {
"@kabelsalat/lib": "^0.4.1",
"@kabelsalat/web": "^0.4.1",
"nanostores": "^0.11.3"
},
"engines": {
+5 -2
View File
@@ -18,6 +18,7 @@ var reverbGen = {};
/** Generates a reverb impulse response.
@tags internals
@param {!Object} params TODO: Document the properties.
@param {!function(!AudioBuffer)} callback Function to call when
the impulse response has been generated. The impulse response
@@ -50,7 +51,7 @@ reverbGen.generateReverb = function (params, callback) {
/** Creates a canvas element showing a graph of the given data.
@tags internals
@param {!Float32Array} data An array of numbers, or a Float32Array.
@param {number} width Width in pixels of the canvas.
@param {number} height Height in pixels of the canvas.
@@ -81,7 +82,9 @@ reverbGen.generateGraph = function (data, width, height, min, max) {
@param {number} lpFreqEnd
@param {number} lpFreqEndAt
@param {!function(!AudioBuffer)} callback May be called
immediately within the current execution context, or later.*/
immediately within the current execution context, or later.
@tags internals
*/
var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) {
if (lpFreqStart == 0) {
callback(input);
+1
View File
@@ -235,6 +235,7 @@ export async function fetchSampleMap(url) {
/**
* Loads a collection of samples to use with `s`
* @tags samples
* @example
* samples('github:tidalcycles/dirt-samples');
* s("[bd ~]*2, [~ hh]*2, ~ sd")
+45 -7
View File
@@ -16,6 +16,7 @@ import {
gainNode,
getCompressor,
getDistortion,
getFrequencyFromValue,
getLfo,
getWorklet,
releaseAudioNode,
@@ -39,6 +40,7 @@ export let maxPolyphony = DEFAULT_MAX_POLYPHONY;
* start to die out in first-in-first-out order once the max polyphony has been hit
*
* @name setMaxPolyphony
* @tags fx, superdough
* @param {number} Max polyphony. Defaults to 128
* @example
* setMaxPolyphony(4)
@@ -72,6 +74,7 @@ export function applyGainCurve(val) {
* quadratic, exponential, etc. rather than linear
*
* @name setGainCurve
* @tags fx, superdough
* @param {Function} function to apply to all gain values
* @example
* setGainCurve((x) => x * x) // quadratic gain
@@ -127,6 +130,8 @@ async function aliasBankPath(path) {
* Optionally accepts a single argument string of a path to a JSON file containing bank aliases.
* @param {string} bank - The bank to alias
* @param {string} alias - The alias to use for the bank
*
* @tags samples
*/
export async function aliasBank(...args) {
switch (args.length) {
@@ -145,6 +150,7 @@ export async function aliasBank(...args) {
/**
* Register an alias for a sound.
* @tags samples
* @param {string} original - The original sound name
* @param {string} alias - The alias to use for the sound
*/
@@ -254,6 +260,14 @@ export function loadWorklets() {
return workletsLoading;
}
let kabel;
async function initKabelsalat() {
const { SalatRepl } = await import('@kabelsalat/web');
logger('[kabelsalat] ready');
kabel = new SalatRepl({ localScope: true });
return kabel;
}
// this function should be called on first user interaction (to avoid console warning)
export async function initAudio(options = {}) {
const {
@@ -300,6 +314,7 @@ export async function initAudio(options = {}) {
} catch (err) {
console.warn('could not load AudioWorklet effects', err);
}
await initKabelsalat();
logger('[superdough] ready');
}
let audioReady;
@@ -408,9 +423,9 @@ function mapChannelNumbers(channels) {
}
class Chain {
constructor(head) {
this.audioNodes = [head];
this.tails = [head];
constructor() {
this.audioNodes = [];
this.tails = [];
}
connect(...nodes) {
nodes.forEach((node) => {
@@ -435,6 +450,14 @@ class Chain {
}
}
const compileKabel = (code) => {
if (!kabel) {
throw new Error('kabelsalat not loaded');
}
const node = kabel.evaluate(code);
return node.compile({ log: false });
};
export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => {
// mapping from main FX and numbered FX chains to nodes
const nodes = { main: {} };
@@ -539,6 +562,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
value.s = s;
}
const chain = new Chain(); // connection manager which tracks audio nodes for releasing
// get source AudioNode
let sourceNode;
if (source) {
@@ -579,7 +604,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
return;
}
const chain = new Chain(sourceNode); // connection manager which tracks audio nodes for releasing
chain.connect(sourceNode);
FX = [...FX, value]; // run through the FX chain and then run through all FX outside of it as well
for (let [idx, fx] of Object.entries(FX)) {
const key = idx == FX.length - 1 ? 'main' : idx;
@@ -597,7 +623,6 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
delayfeedback = getDefaultValue('delayfeedback'),
delaysync = getDefaultValue('delaysync'),
delaytime,
stretch = getDefaultValue('stretch'),
i = getDefaultValue('i'),
} = fx;
gain = applyGainCurve(nanFallback(gain, 1));
@@ -608,8 +633,21 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
if (stretch !== undefined) {
const phaseVocoder = getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch });
// Kabelsalat
if (fx.workletSrc !== undefined) {
const workletNode = getWorklet(ac, 'generic-processor', {}, { outputChannelCount: [2] });
chain.connect(workletNode);
const workletSrc = fx.workletSrc
.replace(/\bpat\[(\d+)\]/g, (_, i) => fx.workletInputs[i])
.replaceAll('sFreq', getFrequencyFromValue(value))
.replaceAll('sGate', `cc('strudel-gate-${chainID}')`);
/* global compileKabel */
const { src, ugens, registers } = compileKabel(workletSrc);
workletNode.port.postMessage({ src, schema: { ugens, registers }, start: t, gateEnd: end, end: endWithRelease });
}
if (fx.stretch !== undefined) {
const phaseVocoder = getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: fx.stretch });
chain.connect(phaseVocoder);
fxNodes['stretch'] = [phaseVocoder];
}
+2 -7
View File
@@ -18,7 +18,7 @@ import {
} from './helpers.mjs';
import { logger } from './logger.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
import { getNodeFromPool, markWorkletAsDead, releaseNodeToPool } from './nodePools.mjs';
import { getNodeFromPool, releaseNodeToPool } from './nodePools.mjs';
const waveforms = ['triangle', 'square', 'sawtooth', 'sine', 'user', 'one'];
const waveformAliases = [
@@ -178,17 +178,12 @@ export function registerSynthSounds() {
};
const factory = () => new AudioWorkletNode(ac, 'supersaw-oscillator', { outputChannelCount: [2] });
const o = getNodeFromPool('supersaw', factory);
const now = ac.currentTime;
Object.entries(params).forEach(([key, value]) => {
const param = o.parameters.get(key);
const target = value !== undefined ? value : param.defaultValue;
param.setValueAtTime(target, now);
param.value = target;
});
o.port.postMessage({ type: 'initialize' });
o.port.onmessage = (e) => {
if (e.data.type === 'died') markWorkletAsDead(o);
o.port.onmessage = null;
};
const gainAdjustment = 1 / Math.sqrt(voices);
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
const vibratoHandle = getVibratoOscillator(o.parameters.get('detune'), value, begin);
+3 -1
View File
@@ -110,7 +110,9 @@ export function getCommonSampleInfo(hapValue, bank) {
return { transpose, url, index, midi, label };
}
/** Selects entries from `source` and renames them via `map` */
/** Selects entries from `source` and renames them via `map`
* @tags internals
*/
export const pickAndRename = (source, map) => {
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
};
+3 -7
View File
@@ -11,7 +11,7 @@ import {
webAudioTimeout,
releaseAudioNode,
} from './helpers.mjs';
import { getNodeFromPool, markWorkletAsDead, releaseNodeToPool } from './nodePools.mjs';
import { getNodeFromPool, releaseNodeToPool } from './nodePools.mjs';
import { logger } from './logger.mjs';
export const Warpmode = Object.freeze({
@@ -186,6 +186,7 @@ export function registerWaveTable(key, tables, params) {
* Loads a collection of wavetables to use with `s`
*
* @name tables
* @tags fx
*/
export const tables = async (url, frameLen, json, options = {}) => {
if (json !== undefined) return _processTables(json, url, frameLen);
@@ -244,17 +245,12 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
};
const factory = () => new AudioWorkletNode(ac, 'wavetable-oscillator-processor', { outputChannelCount: [2] });
const source = getNodeFromPool('wavetable', factory);
const now = ac.currentTime;
Object.entries(params).forEach(([key, value]) => {
const param = source.parameters.get(key);
const target = value !== undefined ? value : param.defaultValue;
param.setValueAtTime(target, now);
param.value = target;
});
source.port.postMessage({ type: 'initialize', payload });
source.port.onmessage = (e) => {
if (e.data.type === 'died') markWorkletAsDead(source);
source.port.onmessage = null;
};
if (ac.currentTime > t) {
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
return;
+122 -31
View File
@@ -5,6 +5,9 @@
import OLAProcessor from './ola-processor';
import FFT from './fft.js';
import { getDistortionAlgorithm } from './helpers.mjs';
import * as ugens from '@kabelsalat/lib/src/ugens.js';
const UGENS = new Map(Object.entries(ugens));
const blockSize = 128;
const PI = Math.PI;
@@ -463,7 +466,6 @@ registerProcessor('distort-processor', DistortProcessor);
class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.isAlive = true; // used internally to prevent multiple death messages
this.port.onmessage = (e) => {
const { type, payload } = e.data || {};
if (type === 'initialize') {
@@ -479,16 +481,16 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
return [
{
name: 'begin',
defaultValue: 0,
defaultValue: -1,
max: Number.POSITIVE_INFINITY,
min: 0,
min: -1,
},
{
name: 'end',
defaultValue: 0,
defaultValue: -1,
max: Number.POSITIVE_INFINITY,
min: 0,
min: -1,
},
{
@@ -523,16 +525,17 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
];
}
process(_input, outputs, params) {
if (currentTime >= params.end[0] + 0.5) {
// Outside of grace period - should terminate
if (this.isAlive) {
this.port.postMessage({ type: 'died' });
this.isAlive = false;
}
const begin = params.begin[0];
const end = params.end[0];
const beginDefined = begin >= 0;
const endDefined = end >= 0;
// We give a 0.5s grace period (for node pooling) before termination
const shouldTerminate = endDefined && currentTime >= end + 0.5;
const ended = endDefined && currentTime >= end;
const notStarted = currentTime <= begin;
if (shouldTerminate) {
return false;
}
if (currentTime >= params.end[0] || currentTime <= params.begin[0]) {
// Inside of grace period or not yet started
} else if (ended || notStarted || !beginDefined) {
return true;
}
const output = outputs[0];
@@ -643,14 +646,18 @@ class PhaseVocoderProcessor extends OLAProcessor {
this.timeCursor += this.hopSize;
}
/** Apply Hann window in-place */
/** Apply Hann window in-place
* @tags internals
*/
applyHannWindow(input) {
for (let i = 0; i < this.blockSize; i++) {
input[i] *= this.hannWindow[i] * 1.62;
}
}
/** Compute squared magnitudes for peak finding **/
/** Compute squared magnitudes for peak finding
* @tags internals
**/
computeMagnitudes() {
let i = 0,
j = 0;
@@ -664,7 +671,9 @@ class PhaseVocoderProcessor extends OLAProcessor {
}
}
/** Find peaks in spectrum magnitudes **/
/** Find peaks in spectrum magnitudes
* @tags internals
**/
findPeaks() {
this.nbPeaks = 0;
let i = 2;
@@ -685,7 +694,9 @@ class PhaseVocoderProcessor extends OLAProcessor {
}
}
/** Shift peaks and regions of influence by pitchFactor into new specturm */
/** Shift peaks and regions of influence by pitchFactor into new specturm
* @tags internals
*/
shiftPeaks(pitchFactor) {
// zero-fill new spectrum
this.freqComplexBufferShifted.fill(0);
@@ -838,7 +849,9 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
registerProcessor('pulse-oscillator', PulseOscillatorProcessor);
/** BYTE BEATS */
/** BYTE BEATS
* @tags internals
*/
const chyx = {
/*bit*/ bitC: function (x, y, z) {
return x & y ? z : 0;
@@ -1148,8 +1161,8 @@ const tablesCache = {};
class WavetableOscillatorProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [
{ name: 'begin', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY },
{ name: 'end', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY },
{ name: 'begin', defaultValue: -1, min: -1, max: Number.POSITIVE_INFINITY },
{ name: 'end', defaultValue: -1, min: -1, max: Number.POSITIVE_INFINITY },
{ name: 'frequency', defaultValue: 440, min: Number.EPSILON },
{ name: 'detune', defaultValue: 0 },
{ name: 'freqspread', defaultValue: 0.18, min: 0 },
@@ -1164,7 +1177,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
constructor(options) {
super(options);
this.isAlive = true; // used internally to prevent multiple death messages
this.port.onmessage = (e) => {
const { type, payload } = e.data || {};
if (type === 'initialize') {
@@ -1332,16 +1344,17 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
}
process(_inputs, outputs, parameters) {
if (currentTime >= parameters.end[0] + 0.5) {
// Outside of grace period - should terminate
if (this.isAlive) {
this.port.postMessage({ type: 'died' });
this.isAlive = false;
}
const begin = parameters.begin[0];
const end = parameters.end[0];
const beginDefined = begin >= 0;
const endDefined = end >= 0;
// We give a 0.5s grace period (for node pooling) before termination
const shouldTerminate = endDefined && currentTime >= end + 0.5;
const ended = endDefined && currentTime >= end;
const notStarted = currentTime <= begin;
if (shouldTerminate) {
return false;
}
if (currentTime >= parameters.end[0] || currentTime <= parameters.begin[0]) {
// Inside of grace period or not yet started
} else if (ended || notStarted || !beginDefined) {
return true;
}
const outL = outputs[0][0];
@@ -1479,3 +1492,81 @@ class TransientProcessor extends AudioWorkletProcessor {
}
registerProcessor('transient-processor', TransientProcessor);
class GenericProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.playPos = 0;
const channels = 16;
this.outputs = new Array(channels).fill(0);
this.sources = new Array(channels).fill(0);
this.gateEnded = false;
this.started = false;
this.port.onmessage = (event) => {
let {
src,
schema: { ugens, registers },
start,
gateEnd,
end,
} = event.data;
this.start = start;
this.gateEnd = gateEnd;
this.end = end;
this.registers = new Array(registers).fill(0);
this.src = `o.fill(0); // reset outputs\n${src}`;
this.nodes = [];
for (let i = 0; i < ugens.length; i++) {
const ugen = ugens[i];
const nodeClass = UGENS.get(ugen.type);
const node = new nodeClass(i, ugen, sampleRate);
if (node.type === 'cc' && ugen.inputs?.[0]?.includes('strudel-gate')) {
node.setValue(1);
this.gateNode = node;
}
this.nodes[i] = node;
}
this.genSample = new Function(
'time',
'nodes',
'input',
'r', // registers
'o', // outputs
's', // sources
this.src,
);
};
}
process(inputs, outputs) {
const input = inputs[0]?.[0];
if (currentTime >= this.end) {
return false;
} else if (this.genSample === undefined || currentTime < this.start) {
// pending
return true;
}
this.started = true;
if (!this.gateEnded && currentTime > this.gateEnd) {
this.gateNode?.setValue(0);
this.gateEnded = true;
}
const output = outputs[0];
const outL = output[0];
const outR = output[1];
for (let n = 0; n < blockSize; n++) {
this.genSample(this.playPos, this.nodes, input ? input[n] : 0, this.registers, this.outputs, this.sources);
const left = this.outputs[0];
const right = this.outputs[1];
// Spread to stereo if possible; else mixdown to mono
if (outR) {
outL[n] = left;
outR[n] = right;
} else {
outL[n] = 0.5 * (left + right);
}
this.playPos += 1 / sampleRate;
}
return true;
}
}
registerProcessor('generic-processor', GenericProcessor);
+1
View File
@@ -19,6 +19,7 @@ function applyGainCurve(val) {
* @param {number} a - Signal A (can be a single value or an array value in buffer processing).
* @param {number} b - Signal B (can be a single value or an array value in buffer processing).
* @param {number} m - Crossfade parameter (0.0 = all A, 1.0 = all B, 0.5 = equal mix).
* @tags internals
* @returns {number} Crossfaded output value.
*/
function crossfade(a, b, m) {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/tonal",
"version": "1.2.5",
"version": "1.2.6",
"description": "Tonal functions for strudel",
"main": "index.mjs",
"publishConfig": {

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