Compare commits

..

567 Commits

Author SHA1 Message Date
Vasilii Milovidov fbcd28f09a path fix 2023-10-16 14:26:58 +04:00
Vasilii Milovidov bfb32d89a9 Various improvements + reverb tests 2023-10-16 14:24:47 +04:00
Vasilii Milovidov 6e18bd8d57 tests: reverb 2023-10-16 11:59:23 +04:00
Vasilii Milovidov 9d22a9f9ba tests: reverb 2023-10-07 19:11:35 +04:00
Vasilii Milovidov c0411c389f tests: reverb 2023-10-06 03:14:56 +04:00
Vasilii Milovidov 163f8b53fd tests: reverb 2023-10-06 01:53:22 +04:00
Vasilii Milovidov d447b9cf95 tests: reverb 2023-10-05 21:41:15 +04:00
Vasilii Milovidov c0c6963112 tests: reverb 2023-10-05 16:16:53 +04:00
Vasilii Milovidov e793cb671a tests: reverb 2023-10-05 02:58:58 +04:00
Vasilii Milovidov f6fc55b43e tests: reverb 2023-10-04 01:30:20 +04:00
Vasilii Milovidov 56d992def9 tests: reverb 2023-10-03 14:20:35 +04:00
Vasilii Milovidov eef51f9228 tests: reverb 2023-10-03 04:52:31 +04:00
Vasilii Milovidov 56dd5bb604 tests: reverb 2023-10-03 00:04:07 +04:00
Vasilii Milovidov c2fc2272c5 tests: reverb 2023-10-02 22:11:25 +04:00
Vasilii Milovidov 18a198df9b tests: reverb 2023-10-02 21:53:08 +04:00
Vasilii Milovidov 870e7e4f32 begin implementing orbits 2023-10-02 15:20:32 +04:00
Vasilii Milovidov 6b219ee11a Structural changes, cpu usage improvements, some other fixes 2023-09-30 01:08:36 +04:00
Vasilii Milovidov 38af4af4ff Adding structs and traits 2023-09-29 13:09:27 +04:00
Vasilii Milovidov 69a3e27eec various fixes 2023-09-26 21:30:58 +04:00
Vasilii Milovidov 9606ce0d51 first go at handling samples from the web 2023-09-26 18:42:18 +04:00
Vasilii Milovidov 229dfe487a first go at handling samples from the web 2023-09-26 18:42:15 +04:00
Vasilii Milovidov 592b982990 wip 2023-09-26 15:54:39 +04:00
Vasilii Milovidov 6633b373bc wip 2023-09-26 14:04:20 +04:00
Vasilii Milovidov 5a8d369fa6 wip 2023-09-26 14:00:58 +04:00
Vasilii Milovidov 747ea8e6a3 wip 2023-09-26 13:56:11 +04:00
Vasilii Milovidov a693f89796 remove orbits for now, more adsr tweaking 2023-09-22 15:28:08 +04:00
Vasilii Milovidov b93ac61a7a add compressor 2023-09-22 11:20:46 +04:00
Vasilii Milovidov 6a066a5304 adsr tests 2023-09-22 05:17:12 +04:00
Vasilii Milovidov 4ca57b308f adsr tests 2023-09-21 14:54:19 +04:00
Vasilii Milovidov 97e2d89e61 implementing delay 2023-09-21 04:32:31 +04:00
Vasilii Milovidov f5ca1ff49d prettier 2023-09-20 16:39:00 +04:00
Vasilii Milovidov c456186f0f make it closer to the js implementation 2023-09-20 16:36:03 +04:00
Vasilii Milovidov ebbbcfb071 looper and reverse tests 2023-09-20 05:35:58 +04:00
Vasilii Milovidov 7492c9383a looper and reverse tests 2023-09-20 05:21:43 +04:00
Vasilii Milovidov 51a16b45a6 more adsr tests 2023-09-20 04:48:59 +04:00
Vasilii Milovidov 89d65ad09d sample reverse 2023-09-20 02:40:43 +04:00
Vasilii Milovidov a92834de42 sample reverse 2023-09-20 02:31:04 +04:00
Vasilii Milovidov c8270a98e9 make filter nodes conditional 2023-09-19 12:26:35 +04:00
Vasilii Milovidov 12d8e22b46 adsr tests 2023-09-19 10:37:34 +04:00
Vasilii Milovidov 78a8ca2b4f organize 2023-09-19 10:12:48 +04:00
Vasilii Milovidov c90bd23a53 Gain and filter ADSR envelopes 2023-09-18 19:11:20 +04:00
Vasilii Milovidov 725ddc805d webaudio for tauri WIP 2023-09-18 14:52:10 +04:00
Vasilii Milovidov 00c0df4bfb webaudio for tauri WIP 2023-09-18 14:41:56 +04:00
Vasilii Milovidov 7a17ec6863 Merge branch 'web_audio_tests' into tauri_webaudio
# Conflicts:
#	packages/desktopbridge/index.mjs
#	packages/desktopbridge/package.json
#	packages/desktopbridge/webaudiobridge.mjs
#	src-tauri/Cargo.lock
#	src-tauri/Cargo.toml
#	src-tauri/src/main.rs
#	src-tauri/src/webaudiobridge.rs
2023-09-18 14:18:55 +04:00
Vasilii Milovidov 081f93cb79 webaudio for tauri 2023-09-18 14:18:00 +04:00
Felix Roos 9e27c0fe2c Merge pull request #705 from vasilymilovidov/main
Update tauri.yml workflow file
2023-09-17 15:53:27 +02:00
vmilovidov 05c09eff19 Update tauri.yml workflow file 2023-09-17 13:26:58 +00:00
vmilovidov 258eb88684 Update tauri.yml workflow 2023-09-17 13:14:13 +00:00
Felix Roos 9c73ef770f snapshot 2023-09-17 13:37:16 +02:00
Felix Roos de9a52366f Publish
- @strudel/codemirror@0.9.0
 - @strudel.cycles/core@0.9.0
 - @strudel.cycles/csound@0.9.0
 - @strudel.cycles/midi@0.9.0
 - @strudel.cycles/mini@0.9.0
 - @strudel.cycles/osc@0.9.0
 - @strudel.cycles/react@0.9.0
 - @strudel.cycles/serial@0.9.0
 - @strudel.cycles/soundfonts@0.9.0
 - superdough@0.9.8
 - @strudel.cycles/tonal@0.9.0
 - @strudel.cycles/transpiler@0.9.0
 - @strudel/web@0.9.0
 - @strudel.cycles/webaudio@0.9.0
 - @strudel.cycles/xen@0.9.0
2023-09-17 12:35:03 +02:00
Felix Roos 57265b9e33 add filter envelopes here and there
+ comment out outroMusic (lame)
2023-09-17 12:34:01 +02:00
Felix Roos b7ef2d97e2 make desktopbridge private for now 2023-09-17 11:50:18 +02:00
Felix Roos 4fb364195c snapshot 2023-09-17 11:18:20 +02:00
Felix Roos 578bede46b fix: tune n -> note 2023-09-17 11:17:37 +02:00
Felix Roos 38a51324b2 Merge pull request #693 from Bubobubobubobubo/vibrato
Adding vibrato to base oscillators
2023-09-17 11:05:05 +02:00
Felix Roos 0e7c9a4001 add vib examples for : notation 2023-09-17 11:01:12 +02:00
Felix Roos 32fee6313b move stuff back 2023-09-17 10:44:15 +02:00
Felix Roos c6a74c040e snapshot 2023-09-17 10:42:51 +02:00
Felix Roos a97384cec1 format 2023-09-17 10:42:21 +02:00
Felix Roos f052adb93a simplify examples 2023-09-17 10:41:22 +02:00
Felix Roos c2560e0cf8 set vib default to .5 2023-09-17 10:41:13 +02:00
Felix Roos cd6d1fb2d2 fix: headings 2023-09-17 10:41:02 +02:00
Felix Roos c354ee32e3 simplify vibrato logic 2023-09-17 10:15:33 +02:00
Felix Roos 5c5df74860 fix: control naming 2023-09-17 10:15:02 +02:00
Felix Roos 887fb6a84c Merge remote-tracking branch 'upstream/main' into vibrato 2023-09-17 09:46:12 +02:00
Felix Roos 80ea9e8de4 Merge pull request #698 from Bubobubobubobubo/sampler
Adding loop points and thus wavetable synthesis
2023-09-17 08:27:36 +02:00
Felix Roos 3879b01ddd snapshots 2023-09-17 08:04:02 +02:00
Felix Roos 4490681fa3 add note about wt_ 2023-09-17 08:03:09 +02:00
Felix Roos b98e24fabf docs: fit + splice 2023-09-17 08:02:02 +02:00
Felix Roos 2deafe214a update loop examples 2023-09-17 07:59:55 +02:00
Felix Roos d0258756e5 Merge remote-tracking branch 'upstream/main' into sampler 2023-09-17 07:31:21 +02:00
Felix Roos 4fecad16e4 superdough 0.9.7 2023-09-16 02:21:10 +02:00
Felix Roos a82eeb44c0 update snapshot 2023-09-16 02:08:25 +02:00
Felix Roos 7ec5ab0c20 fix: tune 2023-09-16 02:05:49 +02:00
Felix Roos 872e10fc95 Merge pull request #692 from Bubobubobubobubo/filters
Adding filter envelopes and filter order selection
2023-09-16 02:03:49 +02:00
Felix Roos e3b7d0f84d drop some acid in there 2023-09-16 02:01:46 +02:00
Felix Roos e0c424eb92 update snapshots 2023-09-16 01:50:45 +02:00
Felix Roos 536427310d add some filter envelopes here and there 2023-09-16 01:50:23 +02:00
Felix Roos a7eb0bb04a snapshot 2023-09-16 01:34:00 +02:00
Felix Roos af6a5b85f5 keep only lp envelopes as examples 2023-09-16 01:33:34 +02:00
Felix Roos b96e8093a0 doc: fenv 2023-09-16 01:33:17 +02:00
Felix Roos 989dc0aabe fix: filters without envelopes 2023-09-16 01:33:09 +02:00
Felix Roos 735f892a08 fix: snapshots 2023-09-16 01:14:53 +02:00
Felix Roos e959c275fd add fanchor 2023-09-16 01:14:14 +02:00
Felix Roos 7dbcc90450 fix: docs after fenv change 2023-09-16 01:07:10 +02:00
Felix Roos 7f86eac8f1 simplify 2023-09-16 00:51:36 +02:00
Felix Roos 40de717cc9 fix: don't add frequency 2023-09-16 00:46:50 +02:00
Felix Roos df211c994d anchored fenv, more logical with negative values
- defaulting to .5 for now...
2023-09-16 00:44:03 +02:00
Felix Roos ee314e9b03 snapshot 2023-09-15 23:38:37 +02:00
Felix Roos 9049a8b964 simplify fenv 2023-09-15 23:37:36 +02:00
Felix Roos 88c029202c simplify fenv examples 2023-09-15 23:37:27 +02:00
Felix Roos 9142fb753a cleaner default filter envelope
- fixes some unexpected envelope glitches
2023-09-15 23:36:55 +02:00
Felix Roos 4688c0bf10 format 2023-09-15 23:13:49 +02:00
Felix Roos 7e55258bbf update snapshots 2023-09-15 23:13:30 +02:00
Felix Roos 828e475921 support negative fenv 2023-09-15 23:12:51 +02:00
Felix Roos 9d5f919023 add all filter envelope examples to doc 2023-09-15 23:12:39 +02:00
Felix Roos b8932390f7 update snapshot 2023-09-15 21:44:24 +02:00
Felix Roos 445881992e slow down examples + simplify 2023-09-15 21:43:03 +02:00
Raphael Forment 7f1918ae8c Merge branch 'sampler' of github.com:Bubobubobubobubo/strudel into sampler 2023-09-11 00:56:50 +02:00
Raphael Forment 78adaaedef fixing minor bugs and adding description 2023-09-11 00:56:43 +02:00
Felix Roos 4a65113bff do not panic when no description 2023-09-11 00:52:49 +02:00
Raphael Forment 843d9d52c3 add documentation but it crashes 2023-09-11 00:46:31 +02:00
Raphael Forment 7a74189771 document wavetable synthesis 2023-09-11 00:41:55 +02:00
Raphael Forment 6de04f7781 turn loop on for samples starting with wt_ 2023-09-10 23:29:09 +02:00
Felix Roos ba27f2ba28 fix: looped samples pitch 2023-09-10 21:05:57 +02:00
Felix Roos f09b89ed63 fix: duration for loops 2023-09-10 20:55:59 +02:00
Raphael Forment bac8594c5f beginning documentation work (breaks) 2023-09-10 20:36:09 +02:00
Raphael Forment 3582ae9163 adding loopBegin and loopEnd 2023-09-10 18:34:21 +02:00
Raphael Forment 7a95bafa54 Updating documentation 2023-09-10 09:59:18 +02:00
Raphael Forment 1383af6208 documenting filters more + tests 2023-09-10 09:42:29 +02:00
Raphael Forment bea9a3c2dc replacing fenv by lpenv, hpenv, bpenv 2023-09-10 09:28:07 +02:00
Raphael Forment 78ccec5d7b adding test run 2023-09-10 09:14:41 +02:00
Felix Roos 534e3f7f81 add snapshots 2023-09-10 00:48:04 +02:00
Felix Roos 6a427d6f89 Merge pull request #697 from daslyfe/add_logging_from_tauri
Add logging from tauri
2023-09-09 11:19:57 +02:00
Jade Rowland b4297580a7 changed warning to error 2023-09-09 01:34:35 -04:00
Jade Rowland 26b0e2ae43 its working 2023-09-09 01:27:49 -04:00
Jade Rowland e75ac7b3c0 working 2023-09-09 01:20:08 -04:00
Raphael Forment 74442b0d76 documenting vibrato and removing broken slide/pitchJump mechanism 2023-09-08 13:43:43 +02:00
Raphael Forment 3431803980 documenting vib and vibmod 2023-09-08 13:08:00 +02:00
Raphael Forment c5fb247de6 Documentation for filter envelope 2023-09-08 12:38:39 +02:00
Felix Roos 18640d59da acid party 2023-09-08 03:00:49 +02:00
Felix Roos 48753b2539 fix last crack? 2023-09-08 02:53:08 +02:00
Felix Roos 606dcc8cc3 use fenv to scale above base cutoff? 2023-09-08 02:27:28 +02:00
Felix Roos 9e4c548c69 fix: filter clicks 2023-09-08 02:06:03 +02:00
Felix Roos 75a974643b add missing controls 2023-09-08 01:41:11 +02:00
Felix Roos e2711ba911 fix: filter envelopes + simplify 2023-09-08 01:40:10 +02:00
Raphael Forment 7605198774 parameter renaming 2023-09-05 11:27:42 +02:00
Raphael Forment fc9525e7d8 saner vibrato default 2023-09-05 11:24:21 +02:00
Raphael Forment e597b6473d remove test console log statement 2023-09-05 10:44:41 +02:00
Raphael Forment 16c8dcf788 stable version 2023-09-05 09:56:49 +02:00
Raphael Forment 37842d4fbd Revert "replace odd envelope"
This reverts commit 4319df0cb6.
2023-09-05 09:01:04 +02:00
Felix Roos 133a1d2e8f Merge pull request #694 from daslyfe/tauri_osc_support
Direct OSC Support in Tauri
2023-09-05 00:33:54 +02:00
Vasilii Milovidov 6d5fb0687c rust webaudio for tauri tests 2023-09-05 01:59:25 +04:00
Jade Rowland 15c15a9c36 remove unused import 2023-09-04 17:09:54 -04:00
Jade Rowland d233d50de3 dont panic if message fails to send 2023-09-04 17:07:50 -04:00
Jade Rowland a9d8e7ca82 cleaning up 2023-09-04 15:33:44 -04:00
Jade Rowland 4001cc9531 cleaning up 2023-09-04 15:33:28 -04:00
Raphael Forment 4319df0cb6 replace odd envelope 2023-09-04 21:24:42 +02:00
Raphael Forment 5be7898522 New technique using ADSR envelope 2023-09-04 21:17:16 +02:00
Jade Rowland 009760c31a correct bit calcuations 2023-09-04 15:00:43 -04:00
Jade Rowland 77349526e4 timetag mishaps 2023-09-04 14:21:30 -04:00
Felix Roos 3ba195c2d9 add new controls
+ rename slide_speed > slidespeed
2023-09-04 18:38:05 +02:00
Raphael Forment 60f5032b12 Implement pitch slide 2023-09-04 06:25:49 +02:00
Raphael Forment fdc201a799 Adding vibrato to synth oscillator 2023-09-04 06:03:05 +02:00
Raphael Forment ec8616a515 Envelope filter and filter order 2023-09-04 04:56:45 +02:00
Jade Rowland 349d11e76d different time strats 2023-09-03 17:36:21 -04:00
Jade Rowland 62e3774ff7 its working 2023-09-03 16:28:52 -04:00
Felix Roos abaeb52b7c Merge pull request #690 from vasilymilovidov/main
fix MIDI CC messages
2023-09-03 22:19:22 +02:00
Jade Rowland 60e600c381 setting up 2023-09-03 13:23:13 -04:00
Vasilii Milovidov 97f84ee87f fix MIDI CC messages 2023-09-03 15:54:34 +04:00
Felix Roos c5738aeed9 Merge pull request #688 from daslyfe/fix_tauri_cpu_midi
add sleep timer + improve message iterating
2023-09-03 10:22:15 +02:00
Jade Rowland 192adccb10 add sleep timer + improve message iterating 2023-09-02 20:59:00 -04:00
Felix Roos defcacb59b bump superdough to 0.9.6 2023-08-31 13:09:16 +02:00
Felix Roos e4ea786956 Merge pull request #684 from Bubobubobubobubo/zzfx
ZZFX Synth support
2023-08-31 13:00:30 +02:00
Felix Roos c9d98bf4d7 fix: tests 2023-08-31 12:58:32 +02:00
Felix Roos 8dd08c360f improve synth docs 2023-08-31 12:56:18 +02:00
Felix Roos d6c4bb0e4e Merge remote-tracking branch 'upstream/main' into zzfx 2023-08-31 12:38:19 +02:00
Felix Roos c54916cdfd Merge pull request #683 from Bubobubobubobubo/betterfmsynth
Wave Selection and Global Envelope on the FM Synth Modulator
2023-08-31 12:36:36 +02:00
Felix Roos e29cf6fcc1 update snapshot 2023-08-31 12:34:49 +02:00
Felix Roos 5b3cd166b0 more clear example 2023-08-31 12:34:16 +02:00
Felix Roos a7246c6518 snapshots 2023-08-31 12:30:01 +02:00
Felix Roos b48fd78cf0 fm envelope docs 2023-08-31 12:29:29 +02:00
Felix Roos 66c895ee3c fix: bad import 2023-08-31 11:52:49 +02:00
Felix Roos 37c450dd83 Merge remote-tracking branch 'upstream/main' into betterfmsynth 2023-08-31 11:52:41 +02:00
Felix Roos 4c744da3e2 add zzfx sound + zzfx control to override params 2023-08-31 10:52:36 +02:00
Felix Roos 388e7b3c2f add doc 2023-08-31 09:46:36 +02:00
Felix Roos 6ec5377381 add lfo alias for repeatTime 2023-08-31 09:46:30 +02:00
Felix Roos 24ec47e4ff fix: comments 2023-08-31 07:58:31 +02:00
Felix Roos 4a122523d9 fix: import 2023-08-31 07:55:09 +02:00
Felix Roos 1241911ec0 rename more stuff + pull out fork 2023-08-31 07:53:41 +02:00
Felix Roos 0f54bb1938 rename some params + use correct duration 2023-08-31 06:57:08 +02:00
Felix Roos a3b55aa5cc Merge pull request #685 from daslyfe/desktop_midi_bridge
Create Midi Integration for Tauri Desktop app
2023-08-31 05:52:17 +02:00
Felix Roos 4fa02a483b Merge pull request #681 from tidalcycles/protracker-font
teletext theme + fonts
2023-08-31 05:32:15 +02:00
Felix Roos a786c642f1 remove weird flags 2023-08-31 05:25:18 +02:00
Felix Roos 1bade7bee8 urgh npm 2023-08-31 05:23:08 +02:00
Felix Roos d399df8798 Merge pull request #674 from tidalcycles/waveformn
control osc partial count with n
2023-08-31 05:20:55 +02:00
Felix Roos 749b11a5ea fix: history 2023-08-31 05:12:33 +02:00
Felix Roos 328af5f560 fix: move desktopbridge dependency to website 2023-08-31 05:10:17 +02:00
Felix Roos 876f97e9a2 fix: exp envelope allow 0 without blowup 2023-08-31 04:35:35 +02:00
Felix Roos 26d7a32f11 remove comments 2023-08-31 04:26:14 +02:00
Felix Roos 3057773009 keep sine as fm default 2023-08-31 04:25:40 +02:00
Felix Roos 6ca89cb907 keep sine as mod default 2023-08-31 04:25:12 +02:00
Felix Roos 88b5b9b21c fix: history 2023-08-31 04:24:44 +02:00
Felix Roos 6bcf8b91cc fix: getExpEnvelope full adsr 2023-08-31 04:22:54 +02:00
Felix Roos 7cf7ebc555 fix: condition flip 2023-08-31 04:17:49 +02:00
Felix Roos ee0aaca2b0 only use fm envelope when needed 2023-08-31 04:13:41 +02:00
Felix Roos b1b084452c fix: format 2023-08-31 03:56:31 +02:00
Felix Roos 213b1a2dae pull out debugging fn 2023-08-31 03:53:52 +02:00
Felix Roos b991cfc493 fix: use custom version of ZZFX
- ZZFX audio context broke tests
- only needed buildSamples function
2023-08-31 03:50:38 +02:00
Felix Roos 90cdea3656 fix: double registering 2023-08-30 15:32:45 +02:00
Felix Roos 1463f27a0f move zzfx stuff to separate file 2023-08-30 15:27:07 +02:00
Felix Roos afb8ab44cf more zzfx wiring 2023-08-30 15:25:23 +02:00
Jade Rowland 282f36c47c fix lock file 2023-08-30 09:17:12 -04:00
Raphael Forment c3f27d1ad9 Boilerplate for ZZFX Synth support 2023-08-30 14:32:20 +02:00
Jade Rowland ad230a8dc7 remove console statement 2023-08-30 08:32:13 -04:00
Jade Rowland c29c4c0a8b configuring package, wiring everything together 2023-08-30 08:24:02 -04:00
Jade Rowland 284f5615cc bridge functionality for midi 2023-08-29 18:14:02 -04:00
Raphael Forment c625096f59 removing comments 2023-08-29 17:29:50 +02:00
Raphael Forment c3cd5d08c9 adding linear and exponential envelope for fm synthesis 2023-08-29 17:25:27 +02:00
Raphael Forment c87c3c6672 Revert "envelopes on fmsynth"
This reverts commit 8fc7688585.
2023-08-29 16:31:47 +02:00
Raphael Forment 8fc7688585 envelopes on fmsynth 2023-08-29 13:46:43 +02:00
Raphael Forment 94bcfb11de removing rogue console.log 2023-08-29 13:16:39 +02:00
Raphael Forment ebeaac3c5f adding fm wave choice 2023-08-29 12:54:17 +02:00
Felix Roos 942ffc038d add font credit 2023-08-29 00:57:41 +02:00
Felix Roos 9de344e48d delete protracker font 2023-08-29 00:45:16 +02:00
Felix Roos 5efea045d2 delete protracker theme 2023-08-29 00:30:16 +02:00
Felix Roos 6bd3274a4b remove protracker theme + font (not good) 2023-08-29 00:27:20 +02:00
Felix Roos a179313db2 teletext: add panel bg 2023-08-29 00:19:57 +02:00
Felix Roos 289376840e add mode7 font
+ can now inject custom theme styles
2023-08-28 23:52:24 +02:00
Felix Roos 0e3d84e5ca add teletext theme + font 2023-08-28 19:21:17 +02:00
Felix Roos cb643f8fd9 add protracker theme
+ fix: make sure all text has a bg
2023-08-28 18:54:33 +02:00
Felix Roos 1faa81099c add protracker3 font 2023-08-28 14:50:06 +02:00
Felix Roos 2dc079b950 Merge pull request #680 from tidalcycles/emoji-support
add emoji support
2023-08-27 22:18:06 +02:00
Felix Roos f6cf740507 add emoji support 2023-08-27 22:11:22 +02:00
Felix Roos 338c866d83 Merge pull request #679 from tidalcycles/pianoroll-improvements
Pianoroll improvements
2023-08-27 16:12:32 +02:00
Felix Roos 038e6c312b dedupe .pianoroll 2023-08-27 16:05:58 +02:00
Felix Roos 6f6def34f7 fix: improve performance of setting patterning 2023-08-27 15:35:44 +02:00
Felix Roos 88651149d3 fix: don't import emoji pkg 2023-08-27 15:28:46 +02:00
Felix Roos e301090704 pianoroll improvements:
- add label / activeLabel controls
- add new pianoroll options:
  - fill
  - fillActive
  - strokeActive
  - stroke
  - hideInactive
  - colorizeInactive
  - fontFamily
- add wordfall method
- fix: some haps were drawn with wrong length
- pianoroll labels now use set fontFamily
- hide fold gutter
2023-08-27 15:27:30 +02:00
Felix Roos d0f8a612ad Merge pull request #677 from tidalcycles/scope
Scope
2023-08-26 21:22:16 +02:00
Felix Roos a7728e3d81 comment new controls 2023-08-26 21:16:46 +02:00
Felix Roos 4dadc18a3a fix: import 2023-08-26 21:12:02 +02:00
Felix Roos 43efe6921d move scope to webaudio package 2023-08-26 21:11:24 +02:00
Felix Roos 2e31c8695a use Float32 for higher precision 2023-08-25 16:29:41 +02:00
Felix Roos 988bd8ccdc rename stuff 2023-08-25 12:40:04 +02:00
Felix Roos 965794712e fscope + scaling options 2023-08-25 12:39:47 +02:00
Felix Roos 68ea086e2a improve canvas quality on retina 2023-08-25 12:39:38 +02:00
Felix Roos 7370f41fa0 basic scope feature 2023-08-25 09:45:30 +02:00
Felix Roos 5cbe30095b control osc partial count with n 2023-08-24 12:21:29 +02:00
Felix Roos f20c5a1bcb Merge pull request #668 from daslyfe/midi_time_fixes
Midi time fixes
2023-08-23 21:50:50 +02:00
Felix Roos 02979926fd bump superdough 2023-08-20 23:50:38 +02:00
Felix Roos b08337af45 Merge pull request #669 from tidalcycles/fm
basic fm
2023-08-20 23:19:07 +02:00
Felix Roos 980c1e4812 simplify 2023-08-20 22:45:05 +02:00
Felix Roos f43dd8db1b add doc 2023-08-20 22:41:49 +02:00
Felix Roos badda165fe add snapshot 2023-08-20 22:39:46 +02:00
Felix Roos f7d17875c0 fix: lint 2023-08-20 22:36:44 +02:00
Felix Roos 0c124bcca3 basic fm 2023-08-20 22:32:27 +02:00
Jade Rowland 3eb63f1730 round duration down 2023-08-19 15:10:53 -04:00
Jade Rowland abb0b3b4c0 dont round duration 2023-08-19 15:09:46 -04:00
Jade Rowland 42662748d3 cleaning up changes 2023-08-19 15:06:55 -04:00
Jade Rowland b5b866a894 time clock improvements 2023-08-19 01:08:41 -04:00
Felix Roos 01cccc6462 hotfix: panel z-index < autocomplete 2023-08-19 00:06:44 +02:00
Felix Roos 9287b8ea42 Merge pull request #667 from tidalcycles/toggle-sidebar
togglable panel position
2023-08-18 23:56:20 +02:00
Felix Roos 5fabc5c7f9 let to const 2023-08-18 23:53:37 +02:00
Felix Roos 61dc6f980d ignore tauri dist for format-check 2023-08-18 23:48:00 +02:00
Felix Roos 84328cfb90 fix: format 2023-08-18 23:46:26 +02:00
Felix Roos 37a35022f5 fix panel scroll
+ move to bottom when minimized
2023-08-18 23:45:55 +02:00
Felix Roos 3afe540111 add hint for zen mode 2023-08-18 23:32:30 +02:00
Felix Roos 35687b1ee0 fix reference style for right panel 2023-08-18 23:32:22 +02:00
Felix Roos 195e6b61f9 fix settings style for right panel 2023-08-18 23:23:12 +02:00
Felix Roos dabc0331cb fix panel scroll 2023-08-18 23:19:19 +02:00
Felix Roos 824abb0406 Merge pull request #666 from daslyfe/osc_fixes
fix osc bundle timestamp glitches caused by drifting clock
2023-08-18 15:59:20 +02:00
Felix Roos f6789aaace togglable panel position 2023-08-18 15:58:51 +02:00
Jade Rowland 10bce85561 fix osc bundle timestamp glitches caused by drifting clock 2023-08-17 18:01:41 -04:00
Felix Roos cd0f1b6c5c bump: superdough 0.9.4 2023-08-17 11:42:24 +02:00
Felix Roos 834bb7d777 Merge pull request #664 from tidalcycles/superdough
superdough: encapsulates web audio output
2023-08-17 11:36:07 +02:00
Felix Roos 5c1d7baf72 superdough readme doc 2023-08-17 11:23:17 +02:00
Felix Roos 8bdbc9ec4c superdough: add logger 2023-08-17 10:08:55 +02:00
Felix Roos 0c6a0720af bump superdough to 0.9.3 2023-08-11 12:35:18 +02:00
Felix Roos c6095fd92a add disableWorklets option to initAudio 2023-08-11 12:35:00 +02:00
Felix Roos 7d0fb0de83 Revert "rename: superdough.mjs > webaudio.mjs"
This reverts commit 75281afe05.
2023-08-11 12:22:03 +02:00
Felix Roos 75281afe05 rename: superdough.mjs > webaudio.mjs
(to keep history?)
2023-08-11 12:20:36 +02:00
Felix Roos 738e714f45 use superdough in webaudio package 2023-08-11 12:13:24 +02:00
Felix Roos 898bfaeecf bump to 0.9.2 2023-08-11 11:13:31 +02:00
Felix Roos 7f44d9cdd2 add link to codesandbox 2023-08-11 11:09:33 +02:00
Felix Roos 8d9be21c8a better code example 2023-08-11 10:56:57 +02:00
Felix Roos 82cb4c8c11 fix: still had strudel core imports 2023-08-11 10:51:59 +02:00
Felix Roos 2778e4d697 fix: build entry file 2023-08-11 10:43:59 +02:00
Felix Roos 30fe2dd502 add superdough package 2023-08-11 10:42:59 +02:00
Felix Roos 2483bf812f Merge pull request #660 from tidalcycles/fix-multiple-triggers
fix: always run previous trigger
2023-08-11 00:06:21 +02:00
Felix Roos 7a999de602 fix: always run previous trigger
+ make dominantTrigger more dominant
2023-08-11 00:04:09 +02:00
Felix Roos 1929c27225 Merge pull request #659 from tidalcycles/fix-console-welcome
fix: welcome message for latestCode
2023-08-10 23:54:58 +02:00
Felix Roos 0cf5c9c7d6 fix: welcome message for latestCode 2023-08-10 23:51:04 +02:00
Felix Roos c20b691683 Merge pull request #657 from daslyfe/main
[Bug Fix] Midi: Don't treat note 0 as false
2023-08-07 07:45:26 +02:00
Jade Rowland 5c2101de1a [Bug Fix] Midi: Don't treat note 0 as false 2023-07-29 13:34:02 -04:00
Felix Roos 2ec2e81511 Merge pull request #656 from daslyfe/main
[Bug Fix] Account for numeral notation when converting to midi
2023-07-29 09:06:17 +02:00
Jade Rowland ac3d8b832c [Bug Fix] Account for numeral notation when converting to midi 2023-07-29 01:29:05 -04:00
Felix Roos ad174957e8 Merge pull request #653 from tidalcycles/ireal-voicings
ireal voicings
2023-07-23 22:18:48 +02:00
Felix Roos 9b3fa83516 add common chord symbol aliases 2023-07-23 22:05:42 +02:00
Felix Roos 52b8d9d361 add octaves control for arpeggios 2023-07-23 21:18:34 +02:00
Felix Roos d231ded7a2 fix: ^7 voicings 2023-07-23 21:18:18 +02:00
Felix Roos 5f57951e3d fix: add empty string as major alias 2023-07-23 14:10:38 +02:00
Felix Roos b1a4788442 a few more voicings 2023-07-23 14:06:25 +02:00
Felix Roos 6377378a3d sort by topnote and then by bottom note
(easier to track diffs)
2023-07-23 13:31:47 +02:00
Felix Roos 45b91cdea5 fix: major chords 2023-07-23 09:57:34 +02:00
Felix Roos d5647c6760 sort voicings by top note 2023-07-23 09:51:21 +02:00
Felix Roos f419559bde Merge remote-tracking branch 'origin/main' into ireal-voicings 2023-07-22 16:22:49 +02:00
Felix Roos 4cfaa39419 fix: flip subtraction 2023-07-22 13:54:43 +02:00
Felix Roos d6b1762adf Merge pull request #652 from tidalcycles/understand-pitch
Understand pitch
2023-07-22 09:30:21 +02:00
Felix Roos 45c3293385 format 2023-07-22 09:28:51 +02:00
Felix Roos 92d574605e small fixes 2023-07-22 09:26:34 +02:00
Felix Roos cec7b90631 finish pitch page 2023-07-22 09:19:13 +02:00
Felix Roos aa3cb35f09 better exponent display 2023-07-21 18:42:47 +02:00
Felix Roos 7373138e3d improve pitch page 2023-07-21 17:46:45 +02:00
Felix Roos 2d8176f213 begin pitch page 2023-07-21 14:40:47 +02:00
Felix Roos 8589d72aee fix: console error 2023-07-21 14:40:40 +02:00
Felix Roos 626b50087a Merge branch 'main' into docs 2023-07-21 10:02:02 +02:00
Felix Roos 1e62554dfb add links 2023-07-20 23:43:53 +02:00
Felix Roos bcf800482b fix: chord typo + export only voicings 2023-07-20 23:43:08 +02:00
Felix Roos 08f94d2122 add ireal voicings 2023-07-20 22:53:02 +02:00
Felix Roos 559cfd11db Merge pull request #651 from tidalcycles/update-vitest
update vitest
2023-07-17 23:42:59 +02:00
Felix Roos ce820c2314 update vitest 2023-07-17 23:37:51 +02:00
Felix Roos 8583ed0e5c Merge pull request #647 from tidalcycles/tonleiter
stateless voicings + tonleiter lib
2023-07-17 23:34:33 +02:00
Felix Roos f2c16a06a1 fix: note2oct 2023-07-17 23:10:13 +02:00
Felix Roos 584d469a73 Merge pull request #650 from bwagner/tonleiter
FIXES: TODO in rotateChroma
2023-07-17 17:57:16 +02:00
Bernhard Wagner 4298e582fb FIXES: note2pc TODOs 2023-07-14 14:38:26 +02:00
Bernhard Wagner a3fdfbbc16 FIXES: weird gain difference 2023-07-14 14:04:35 +02:00
Bernhard Wagner b8929a0d18 FIXES: prettier formatting 2023-07-14 13:56:36 +02:00
Bernhard Wagner 0fa5832b63 FIXES: TODO in rotateChroma 2023-07-14 13:45:27 +02:00
Felix Roos bbb29ebd7c fix: duck mode with n
+ detect chord obj in rootNotes
2023-07-13 12:52:57 +02:00
Felix Roos 0fd9ac6da3 feat: support strings in voicing function 2023-07-13 12:13:00 +02:00
Felix Roos e53098b179 fix: tests 2023-07-13 11:58:24 +02:00
Felix Roos 78dcab2778 fix: control interference 2023-07-13 11:24:57 +02:00
Felix Roos e05f74cef1 docs + rename under -> duck, quak quak 2023-07-13 10:49:08 +02:00
Felix Roos 3d8724c90d snapshot 2023-07-13 01:11:31 +02:00
Felix Roos ffeda19ce5 snapshots + fix default anchor for lefthand 2023-07-13 01:09:53 +02:00
Felix Roos 70fe8f14e3 refactor: voicings -> voicing in tunes 2023-07-13 01:09:29 +02:00
Felix Roos a639d9f49f fix: remove log 2023-07-13 00:25:24 +02:00
Felix Roos e5251422fe revert changing existing addVoicings
+ add registerVoicings for the new logic
2023-07-13 00:20:28 +02:00
Felix Roos d8f80c058d can now set mode and anchor on voicing dictionary 2023-07-12 23:47:13 +02:00
Felix Roos 4cf3655c08 voicing modes: below, under, above 2023-07-12 23:32:27 +02:00
Felix Roos 58fc786462 noteToMidi: adjustable default octave
+ pull out static objects
2023-07-12 23:32:02 +02:00
Felix Roos 81b142704c change call signature of renderVoicing
+ pull logic in from voicings function
2023-07-12 22:55:34 +02:00
Felix Roos b0cbeda796 fix: support existing voicing dicts in new logic
+ rename voiceBelow to renderVoicing
2023-07-12 22:38:44 +02:00
Felix Roos 0b3a8a5f65 - make dictionary a control
- standalone voicing function
- simplify voicing control names
2023-07-11 22:49:34 +02:00
Felix Roos d7d8d2a3e6 Merge pull request #645 from tidalcycles/slice
slice: list mode
2023-07-10 19:07:45 +02:00
Felix Roos d5c0309885 feat: support n for voicing scales
+ simplify voicing logic
2023-07-09 22:11:40 +02:00
Felix Roos 36fa9d81fb fix: pianoroll isActive still used whole.end 2023-07-09 18:51:26 +02:00
Felix Roos 35f5a5d11a feat: add voiceOffset (2) 2023-07-09 16:53:41 +02:00
Felix Roos d1fe50e271 feat: add voiceOffset 2023-07-09 16:53:29 +02:00
Felix Roos 2d08b31f9a fix: tests 2023-07-08 16:14:25 +02:00
Felix Roos 27643f4a48 doc: slice 2023-07-08 16:12:21 +02:00
Felix Roos 110e179e9e wip: new voicing function 2023-07-08 12:41:17 +02:00
Felix Roos fe6d99d4f1 slice array notation 2023-07-07 21:48:19 +02:00
Felix Roos 06fbcbe24b docs: add slice effect 2023-07-07 21:48:03 +02:00
Felix Roos f40b57ee48 hotfix: remove log 2023-07-06 09:59:20 +02:00
Felix Roos 9438c69bed comment 2023-07-06 09:58:57 +02:00
Felix Roos 70d6f3000a move tonleiter 2023-07-06 09:58:48 +02:00
Felix Roos d47fd0cf18 Merge pull request #639 from tidalcycles/delete-old-packages
Delete old packages
2023-07-04 23:50:30 +02:00
Felix Roos 40117fbe54 package-lock 2023-07-04 23:47:37 +02:00
Felix Roos 7466d8a1de delete webdirt 2023-07-04 23:44:36 +02:00
Felix Roos ce09443e48 delete tone 2023-07-04 23:43:49 +02:00
Felix Roos 1718dbe94e delete eval package 2023-07-04 23:41:29 +02:00
Felix Roos 69894db206 Merge pull request #634 from tidalcycles/highlight-ids
Adaptive Highlighting
2023-07-04 23:38:41 +02:00
Felix Roos 6e4c873248 remove tests from soon removed package 2023-07-04 22:17:44 +02:00
Felix Roos 64693ffd26 fix: remove test flag 2023-07-04 22:16:02 +02:00
Felix Roos 66f8ca72c1 refactor: remove old location methods
docs: update repl chapter to reflect new transpiler behavior
2023-07-04 22:13:40 +02:00
Felix Roos c82f7bd8fe Merge pull request #636 from mindofmatthew/highlight-ids
More work on highlight IDs
2023-07-04 21:55:57 +02:00
Felix Roos 34176ab5f8 refactor:
- dedupe flash / highlighting logic
- codemirror logic now lives only in codemirror package
- remove old highlighting logic
- use codemirror package in react package
- cleanup CodeMirror6.jsx
- pull setMiniLocations into useHighlighting
- migrate MiniRepl, nano-repl + Repl to new highlighting
2023-07-04 21:49:39 +02:00
Felix Roos 7f12ce9b45 fix: format 2023-07-04 18:47:44 +02:00
Felix Roos 9e1b9f7f5c Merge remote-tracking branch 'upstream/main' into highlight-ids 2023-07-04 18:46:05 +02:00
Felix Roos 23e949de1c Merge pull request #637 from tidalcycles/snapshots-sort-haps-by-part
snapshot tests: sort haps by part
2023-07-04 18:44:47 +02:00
Felix Roos 00bd60a855 snapshot tests: sort haps by part 2023-07-04 18:43:26 +02:00
Felix Roos dacd9afac0 comment out opacity logic for now 2023-07-04 18:34:22 +02:00
Felix Roos 3fc5bb31d0 fix: do not recreate haps mapping on update 2023-07-04 18:22:22 +02:00
Felix Roos 78770888a5 remove mock positions 2023-07-04 18:16:58 +02:00
Matthew Kaney f5b092acf2 Split up highlighting logic and add progress flash 2023-07-03 17:06:39 -04:00
Matthew Kaney ba9562f000 Remove highlights when code stops 2023-07-03 10:52:06 -04:00
Matthew Kaney aded178ab7 Pass mininotation locations into highlight state 2023-07-03 03:55:49 -04:00
Felix Roos 0b5d905120 fix: adaptive highlighting
- transpiler now uses m function with globalOffset
- patternifyAST now accepts global offset
- patternifyAST now calls .withLoc with global leaf location
- .withLoc replaces .withLocation + .withMiniLocation
- simple locs (offsets) are now used everywhere
- some tests fail, seems some haps have reordered...
- wip: Repl still uses hardcoded updateMiniLocations
- todo: find way to call updateMiniLocations dynamically
2023-07-03 05:15:32 +02:00
Felix Roos 08abec8fd5 fix: init phase 2023-07-02 18:37:56 +02:00
Felix Roos 63c23736ad wip: adaptive highlighting 2023-07-02 18:33:44 +02:00
Felix Roos f7bd373ce6 update package-lock 2023-07-02 14:17:15 +02:00
Felix Roos 8e717d2ea1 - transpiler now always returns an object
- emit transpiler metadata from evaluate / afterEval
- currently logging miniLocations from Repl.jsx
2023-07-02 14:15:54 +02:00
Felix Roos 5f271ed127 add emitMiniLocations flag to transpiler 2023-07-02 13:42:41 +02:00
Felix Roos d686b65dbe add 2 more tests 2023-07-01 23:55:41 +02:00
Felix Roos c7e882e001 mini: leaf location retrieval
- add onEnter callback for patternifyAST
- add leaf node location helpers
- add tests
2023-07-01 23:52:31 +02:00
Felix Roos f07859996e Merge pull request #631 from tidalcycles/fix-canvas-resize
fix: update canvas size on window resize
2023-06-30 22:47:39 +02:00
Felix Roos fbc73bc78a Merge pull request #630 from tidalcycles/out-of-range-error
fix: out of range error
2023-06-30 22:45:41 +02:00
Felix Roos bd8ad1ed1b fix: out of range error due to rerender 2023-06-30 22:40:02 +02:00
Felix Roos dc2ff83fa5 do not flash when empty 2023-06-30 22:38:14 +02:00
Felix Roos 13545d147b optimize rendering 2023-06-30 22:37:55 +02:00
Felix Roos 1b85aa713b fix: update canvas size on window resize 2023-06-30 16:03:56 +02:00
Felix Roos 90a58858ca Publish
- @strudel/codemirror@0.8.4
 - @strudel.cycles/core@0.8.2
 - @strudel.cycles/csound@0.8.0
 - @strudel.cycles/eval@0.8.0
 - @strudel.cycles/midi@0.8.0
 - @strudel.cycles/mini@0.8.2
 - @strudel.cycles/osc@0.8.0
 - @strudel.cycles/react@0.8.0
 - @strudel.cycles/serial@0.8.0
 - @strudel.cycles/soundfonts@0.8.2
 - @strudel.cycles/tonal@0.8.2
 - @strudel.cycles/tone@0.8.0
 - @strudel.cycles/transpiler@0.8.2
 - @strudel/web@0.8.3
 - @strudel.cycles/webaudio@0.8.2
 - @strudel.cycles/webdirt@0.8.0
 - @strudel.cycles/xen@0.8.0
2023-06-30 08:17:19 +02:00
Felix Roos 8149b85a55 Merge pull request #627 from tidalcycles/fix-clock-drift
fix: midi clock drift
2023-06-30 08:14:40 +02:00
Felix Roos e79b6c514a Merge pull request #621 from tidalcycles/tauri-fs
desktop: play samples from disk
2023-06-29 22:02:27 +02:00
Felix Roos aaa894d74b Merge pull request #620 from tidalcycles/loopAt
cps dependent functions
2023-06-29 21:59:42 +02:00
Felix Roos b55721f16f fix: midi clock drift 2023-06-29 21:58:15 +02:00
Felix Roos b39d948d71 fix: build 2023-06-29 12:32:59 +02:00
Felix Roos 0fee279e3d rename fay to fit 2023-06-29 12:05:31 +02:00
Felix Roos c7d8a9cb82 fix: playFile import 2023-06-29 12:05:09 +02:00
Felix Roos b50075f102 feat: can now load disk samples via samples
+ encapsulate file logic from FilesTab
2023-06-29 10:47:20 +02:00
Felix Roos fb3ca9f960 feat: register fn for sample prefix handling 2023-06-29 10:46:32 +02:00
Felix Roos 058b971870 can now play samples from file system
- ui still wip
2023-06-29 02:02:00 +02:00
Felix Roos 610ea414c1 expose tauri fs api 2023-06-29 02:01:22 +02:00
Felix Roos cc6997baec - encapsulate sample map handling
- add resolveUrl param
2023-06-29 02:00:59 +02:00
Felix Roos d126c28b85 fix: loopAt with cps 1 for fallback 2023-06-28 22:25:49 +02:00
Felix Roos c1649b9dc0 - fix: loopAt for non default cps value
- add fay to repitch samples to fit hap duration
- add setcpm function
2023-06-28 22:13:05 +02:00
Felix Roos b0a8d12112 Merge pull request #520 from paikwiki/patch-1
Fix typo on packages.mdx
2023-06-26 22:45:04 +02:00
Felix Roos b5160d38cb another dummy commit 2023-06-26 22:43:14 +02:00
Felix Roos 360e9b5936 dummy commit 2023-06-26 22:41:47 +02:00
Felix Roos 85a6cd828e Merge pull request #606 from tidalcycles/setting-patterning
patterning ui settings
2023-06-26 22:35:02 +02:00
Felix Roos e698f19a64 Merge pull request #614 from tidalcycles/spirals
add spiral viz
2023-06-26 22:32:46 +02:00
Felix Roos 83c1b580dd Merge pull request #613 from vasilymilovidov/tauri
tauri test
2023-06-26 22:17:46 +02:00
vvm 9273e1f8ed fix tauri icons 2023-06-26 13:13:22 +04:00
vvm 5287825279 tauri workflow fixes 2023-06-26 11:27:21 +04:00
vvm 5567959afd add tauri github workflow 2023-06-26 01:58:16 +04:00
Felix Roos eaab36b051 add spiral viz 2023-06-25 14:23:18 +02:00
vvm 9a4f3af580 add README 2023-06-24 02:34:02 +04:00
vvm b83664dfce add build optimizations 2023-06-24 02:32:57 +04:00
vvm 349f7ae965 init 2023-06-23 21:14:55 +04:00
vvm 6f41716ab5 init 2023-06-23 21:06:10 +04:00
Felix Roos abeb62dea8 Merge pull request #612 from tidalcycles/fix-links
fix: doc links
2023-06-23 09:59:43 +02:00
Felix Roos 7cfed80443 Merge pull request #598 from tidalcycles/clip-support-floats
clip now works like legato in tidal
2023-06-23 09:55:18 +02:00
Felix Roos 36aa7d9b25 Merge pull request #607 from tidalcycles/doc-fixes
tonal fixes
2023-06-18 10:36:17 +02:00
Felix Roos 2a449ad3c0 rename tonal api -> tonal functions 2023-06-18 10:32:51 +02:00
Felix Roos a71c5957cc fix: better fix 2023-06-16 14:23:39 +02:00
Felix Roos eef6c7d55e quick fix: typo 2023-06-16 14:20:49 +02:00
Felix Roos 3872322da1 allow patterning theme + font settings 2023-06-16 13:45:13 +02:00
Felix Roos f1e850a62a fix: doc links 2023-06-16 09:20:29 +02:00
Felix Roos 8d9278419d fix: flatten scale lists 2023-06-15 20:19:31 +02:00
Felix Roos 0862827a51 Merge pull request #581 from roipoussiere/line_wrap
editor: enable line wrapping
2023-06-15 12:51:40 +02:00
Roipoussiere f37a220a05 add checkbox in settings to enable line wrapping 2023-06-15 11:52:29 +02:00
Felix Roos aa51c2b6dd Merge remote-tracking branch 'upstream/main' into line_wrap 2023-06-15 10:59:52 +02:00
Felix Roos 92da70a7ae Merge remote-tracking branch 'origin/main' into clip-support-floats 2023-06-15 10:46:10 +02:00
Felix Roos a55862fc25 Merge pull request #602 from tidalcycles/ratio-function
add ratio function
2023-06-15 10:22:46 +02:00
Felix Roos 09b14a5702 add ratio function 2023-06-14 23:19:04 +02:00
Felix Roos 260b831815 refactor: legato -> clip 2023-06-14 00:22:55 +02:00
Felix Roos 665cf6ac65 Merge pull request #588 from roipoussiere/enable_autocomplete
enable auto-completion
2023-06-12 23:24:28 +02:00
Felix Roos 5e90021698 update snapshot 2023-06-12 23:12:40 +02:00
Felix Roos 16aeb7e12c docs: update clip entry 2023-06-12 23:11:07 +02:00
Felix Roos b4cbec173b only accept undefined for clip 2023-06-12 22:58:25 +02:00
Felix Roos 8c9e06c329 - clip now works like legato in tidal
- supports floats
- hap.duration now respects clip value
- hap.endClipped is now end*clip
- visualizations show clipped length
- clip(0) will now be silence
2023-06-12 22:39:32 +02:00
Felix Roos e4a35fdd72 Merge pull request #597 from tidalcycles/cursor-fix
improve cursor
2023-06-11 22:04:17 +02:00
Felix Roos 370d82e66c + disable cursor blink
+ make cursor thicker
2023-06-11 20:21:11 +02:00
Felix Roos 876e851517 Merge pull request #570 from dariacotocu/branch-daria
Solmization added
2023-06-11 20:00:45 +02:00
Felix Roos 190fe37a9f move solmization logic from midi2note to sol2note 2023-06-11 19:55:24 +02:00
Felix Roos 623661f8d2 Merge remote-tracking branch 'upstream/main' into branch-daria 2023-06-11 19:54:55 +02:00
Felix Roos f31bc3f8c5 Merge pull request #591 from tidalcycles/fix-fast0
fix: division by zero
2023-06-11 19:45:00 +02:00
Felix Roos 12fc48544c Merge pull request #596 from tidalcycles/fix-option-dot
Fix option dot
2023-06-11 19:44:40 +02:00
Felix Roos 3b4e44a9fd fix: main repl option-dot on mac 2023-06-11 13:55:11 +02:00
Felix Roos 7850fb727e fix: allow f for flat notes like tidal 2023-06-11 13:23:45 +02:00
Felix Roos 9b3122837e fix: division by zero 2023-06-11 13:15:52 +02:00
Felix Roos d4c59cc73b fix:typo + remove notes 2023-06-11 11:21:19 +02:00
Felix Roos fe36bf54b2 Merge commit '4d8308a763a632cc4dd93ed2a2ab129eb957af72' 2023-06-09 21:39:14 +02:00
Felix Roos 4d8308a763 fix: link goes to wrong language 2023-06-09 21:33:32 +02:00
Felix Roos 11f26c1e2c Merge pull request #587 from tidalcycles/workshop-new
New Workshop
2023-06-09 21:02:11 +02:00
Felix Roos e10104d401 bring de improvements to en workshop 2023-06-09 20:59:40 +02:00
Roipoussiere 5e09c3c318 add autocompletion extension from useMemo 2023-06-09 18:28:16 +02:00
Felix Roos 576b88221d Merge remote-tracking branch 'origin/main' into workshop-new 2023-06-09 18:00:16 +02:00
Felix Roos 7dc2a0091b more detail 2023-06-09 17:59:08 +02:00
Felix Roos 89a4a85589 more friendly heading 2023-06-09 17:58:05 +02:00
Felix Roos a3baf07a5c Merge pull request #580 from roipoussiere/music_metadata
Music metadata
2023-06-09 17:31:58 +02:00
Roipoussiere 8e6909478c autocompletion: lint 2023-06-09 16:42:31 +02:00
Roipoussiere 2d2d0cabb3 Add option in settings to enable autocompletion 2023-06-09 16:38:48 +02:00
Roipoussiere 6f04a1b8fa autocomplete: add vanilla js completion 2023-06-09 14:52:07 +02:00
Roipoussiere a9ad3a56ad autocomplete: avoid tabs overlapping 2023-06-09 14:30:20 +02:00
Roipoussiere 3069b14e9c enable autocompletion 2023-06-09 13:41:40 +02:00
Felix Roos ae868c6372 hide gutter in mini repl 2023-06-09 00:46:04 +02:00
Felix Roos c287ff554c optimize sounds chapter after teach test 2023-06-09 00:26:40 +02:00
Felix Roos 3ccbd35fad add label support to pianoroll 2023-06-09 00:25:54 +02:00
Felix Roos 51cc69208f fix: repl link to docs was old 2023-06-09 00:25:14 +02:00
Felix Roos 1fec2f7a46 fix: build 2023-06-08 21:31:32 +02:00
Felix Roos ffffdd2d7d fix: format 2023-06-08 20:55:26 +02:00
Felix Roos f8c3e3d5aa add missing sounds by hand 2023-06-08 20:43:45 +02:00
Felix Roos eccce6fa84 remove dirt samples
+ load a small selection for workshop instead
2023-06-08 17:48:54 +02:00
Felix Roos 44be6cee2c Merge pull request #585 from srenatus/typo
learn/tonal: fix typo in "scaleTran[s]pose"
2023-06-08 09:33:34 +02:00
Stephan Renatus 799afd511a learn/tonal: fix typo in "scaleTran[s]pose" 2023-06-08 08:17:38 +02:00
Felix Roos 37c4541468 translate link 2023-06-07 21:50:26 +02:00
Felix Roos 119a66acd0 - translate getting-started
- fix de links
- link english docs / german workshop
2023-06-07 21:49:57 +02:00
Felix Roos 105193a452 replace tutorial with workshop
+ move parts of tutorial to more
+ move more before Pattern Functions
2023-06-07 21:22:01 +02:00
Felix Roos 02d793e9e0 Merge remote-tracking branch 'origin/main' into workshop-new 2023-06-07 20:19:34 +02:00
Felix Roos 90647a8ba8 Merge pull request #582 from roipoussiere/line_numbers
repl: add option to display line numbers
2023-06-07 20:19:11 +02:00
Felix Roos 06950a6bd8 translate rest of workshop to german 2023-06-07 16:50:54 +02:00
Roipoussiere 0c2147d9c5 repl: add option to display line numbers 2023-06-07 15:34:57 +02:00
Roipoussiere 51e8174689 metadata: add quotes syntax for titles 2023-06-07 12:26:23 +02:00
Roipoussiere 185318a70c metadata: fix typos 2023-06-07 11:02:38 +02:00
Felix Roos a88759ba94 translate notes chapter 2023-06-06 22:43:51 +02:00
Roipoussiere 96ac054392 metadata: improve doc 2023-06-06 21:34:53 +02:00
Roipoussiere bd7f4dd73f metadata: add tests 2023-06-06 21:34:53 +02:00
Roipoussiere 2e22152089 metadata: allow several values and one-liners 2023-06-06 21:34:53 +02:00
Roipoussiere 98a89aea11 lint: metadata parsing 2023-06-06 21:34:53 +02:00
Roipoussiere 372e33c066 doc: add section about metadata 2023-06-06 21:34:53 +02:00
Roipoussiere 52641db590 load titles from metadata in examples page 2023-06-06 21:34:53 +02:00
Roipoussiere f9ad5f0d01 add tunes metadata 2023-06-06 21:34:53 +02:00
Roipoussiere 3db1c7a521 swatch: look for song titles in code metadata 2023-06-06 21:34:53 +02:00
Roipoussiere b6d1f96930 editor: enable line wrapping 2023-06-06 13:29:55 +02:00
Felix Roos 61a9b01062 started german translation of workshop 2023-06-05 23:48:04 +02:00
Felix Roos f0faf5b42e Merge remote-tracking branch 'origin/main' into workshop-new 2023-06-05 22:48:50 +02:00
Felix Roos 11733d5e05 Merge pull request #563 from tidalcycles/vanilla-highlighting
Vanilla JS Refactoring
2023-06-05 21:35:49 +02:00
Felix Roos c4a38d9008 + pattern effects chapter
+ recap page
+ only load mini repls when visible
2023-05-30 06:16:02 +02:00
Felix Roos 0edd7e14b7 add compound adsr + ds controls 2023-05-29 12:39:36 +02:00
Felix Roos 8b7bb7b6ae add adsr section to effects chapter 2023-05-29 12:39:17 +02:00
Felix Roos 39d5955e58 add function recap to sounds chapter 2023-05-29 12:39:05 +02:00
Felix Roos 536327f403 effects chapter mostly finished 2023-05-29 02:18:36 +02:00
Felix Roos 8679dc63be pianoroll: also reflect gain in transparency 2023-05-29 02:18:27 +02:00
Felix Roos 1e9979ae18 start fleshing out effects chapter 2023-05-28 12:42:24 +02:00
Felix Roos d2dffe3186 MiniRepl: consume font settings 2023-05-28 12:42:15 +02:00
Felix Roos 9971867e2f clamp function 2023-05-28 12:41:53 +02:00
Felix Roos 8c93e578a0 clamp delayfeedback 2023-05-28 12:41:46 +02:00
Felix Roos 1ba5d2e1ca finish notes chapter 2023-05-27 21:52:44 +02:00
Felix Roos ed792fc0d4 continue notes chapter 2023-05-27 16:29:05 +02:00
Felix Roos 4e575c44b3 begin first notes page 2023-05-27 13:31:18 +02:00
Felix Roos fc06181217 - add claviature flag to minirepl
- bring back option+dot on macos
- consume more editor settings in minirepl
2023-05-27 13:30:57 +02:00
Felix Roos 0d6fcf78d8 hide mini repl headers + improve workshop 2023-05-26 16:05:53 +02:00
Felix Roos 82225f0b81 started workshop pages 2023-05-26 14:12:53 +02:00
Felix Roos 2177e13b49 Merge branch 'vanilla-highlighting' into workshop 2023-05-25 21:38:34 +02:00
Daria Cotocu b63f4eb503 Update solmization.test.js 2023-05-24 18:58:11 +01:00
Daria Cotocu cd7bc09f9c Update solmization.test.js 2023-05-24 18:43:24 +01:00
Daria Cotocu ccf775e976 Format code 2023-05-24 17:57:05 +01:00
Daria Cotocu 811346e83d Solmization added 2023-05-24 17:24:34 +01:00
Felix Roos d3240a75d0 fix: dupe style.css to Repl.css
for some reason they were missing after update
+ darken line backgrounds
+ change preview port to keep workbox out of dev server
2023-05-12 11:56:50 +02:00
Felix Roos d93e396ce5 fix: nano-repl 2023-05-12 10:21:05 +02:00
Felix Roos d74cc234c4 fix: thinner outline for highlights with .color 2023-05-12 10:06:55 +02:00
Felix Roos 0fda83d8de bump soundfonts 2023-05-12 10:00:55 +02:00
Felix Roos a1ec8545fb format 2023-05-10 13:03:06 +02:00
Felix Roos 087f574355 update web readme + add more html examples 2023-05-10 12:49:45 +02:00
Felix Roos 1de784b817 add headless-serverless-buildless example 2023-05-10 12:20:27 +02:00
Felix Roos db79c05d9a bump web pkg dependencies 2023-05-10 12:11:51 +02:00
Felix Roos 1fdb1f0da6 bump webaudio package 2023-05-10 12:09:20 +02:00
Felix Roos e2bfaa6233 remove soundfonts from web package for now 2023-05-10 12:06:16 +02:00
Felix Roos 6089849d35 rename init to initStrudel + add to window 2023-05-10 11:53:26 +02:00
Felix Roos 6e02bf59e9 Revert "refactor: remove old draw logic"
This reverts commit 95719654f3.
2023-05-10 11:44:55 +02:00
Felix Roos 12228c56d9 improve api for web package 2023-05-07 22:36:26 +02:00
Felix Roos 3b631cb6af add @strudel/web umbrella package + example 2023-05-07 11:56:35 +02:00
Felix Roos b27a58df7e bump more packages to 0.8.0 2023-05-05 23:58:47 +02:00
Felix Roos 308f139193 bump codemirror again to get new core version 2023-05-05 23:52:55 +02:00
Felix Roos 692fc4e11d revert rename core package 2023-05-05 23:52:04 +02:00
Felix Roos b52f65c4d7 rename core package + bump to 0.8.0 2023-05-05 23:50:06 +02:00
Felix Roos 299275e368 lockfile 2023-05-05 23:47:15 +02:00
Felix Roos bb71c519db bump codemirror package 2023-05-05 23:46:42 +02:00
Felix Roos 9ec7109dc8 + simplify vanilla setup drastically
+ move repl + drawer boilerplate inside StrudelMirror
2023-05-05 23:43:07 +02:00
Felix Roos 5b67fccb1b vanilla-repl-cm6: draw first frame 2023-05-05 16:17:22 +02:00
Felix Roos 9807c4b7a1 move Framer + Drawer to core
... from vite-vanilla-repl-cm6
2023-05-05 16:06:11 +02:00
Felix Roos 9039600f6d bump codemirror to 0.8.1 2023-05-05 15:31:19 +02:00
Felix Roos b5b0156e08 fix: codemirror reeval 2023-05-05 15:31:04 +02:00
Felix Roos 352e647d37 add build script for codemirror package 2023-05-05 15:15:45 +02:00
Felix Roos e6b66f2525 lockfile 2023-05-05 15:14:46 +02:00
Felix Roos f5075906e2 add codemirror package
+ use it in vite-vanilla-repl-cm6
2023-05-05 15:13:17 +02:00
Felix Roos a6f57bced8 prettier 2023-05-05 12:00:13 +02:00
Felix Roos d35bf9591c vanilla-repl-cm6: better theme 2023-05-05 11:54:09 +02:00
Felix Roos d96f3f9dd4 vanilla-repl-cm6: style + codesplitting 2023-05-05 11:09:44 +02:00
Felix Roos 1f4c2f8c5a draw scheduler in vanilla js 2023-05-05 10:52:21 +02:00
Felix Roos 95719654f3 refactor: remove old draw logic
+ pianoroll now uses .onPaint
2023-05-05 09:52:07 +02:00
Felix Roos 4b921c47f5 vanilla-repl-cm6: add flash effect 2023-05-05 09:11:40 +02:00
Felix Roos f069f53fae vanilla-repl-cm6: add keybindings 2023-05-05 09:06:27 +02:00
Felix Roos 1494cc38fc add vite-vanilla-repl-cm6 2023-05-05 08:55:43 +02:00
Felix Roos a035412956 Merge pull request #559 from tidalcycles/patchday
Patchday
2023-05-05 08:34:36 +02:00
Felix Roos 08662ea831 Merge pull request #557 from tidalcycles/some-voicings
add basic triads and guidetone voicings
2023-05-02 21:48:32 +02:00
Felix Roos d97103268b add basic triads and guidetone voicings 2023-05-02 21:40:20 +02:00
Felix Roos a959e2e34d update remaining packages 2023-04-30 08:30:29 +02:00
Felix Roos 602bfbdd79 update a bunch of minor versions 2023-04-30 08:20:51 +02:00
Felix Roos 0785bdd37a update lerna 2023-04-30 08:04:56 +02:00
Felix Roos 8b62b6e1b1 update codemirror + tailwindcss 2023-04-30 08:02:06 +02:00
Felix Roos d1c8ebd15b remove c8 2023-04-30 07:55:23 +02:00
Felix Roos 13088e4001 - remove gh-pages
- update peggy
- rebuild parser
2023-04-30 07:48:08 +02:00
Felix Roos 5c6fa1c82b update vite to v4 2023-04-29 08:10:12 +02:00
Felix Roos fd81867e7b fix: controls import broke on ssr 2023-04-29 07:45:21 +02:00
Felix Roos 78293e64dd lockfile 2023-04-29 07:45:09 +02:00
Felix Roos 930fc10aec bump some minor versions 2023-04-29 07:45:04 +02:00
Felix Roos 0b2a4fda44 update supabase-js 2023-04-29 07:24:03 +02:00
Felix Roos efdce6d4cd update astro 2023-04-29 06:55:34 +02:00
Felix Roos c0b0ea4493 remove preact 2023-04-29 06:25:19 +02:00
Felix Roos 0fcec0a0ca upgrade jsdoc 2023-04-28 13:05:50 +02:00
Felix Roos c0701d8a35 Merge remote-tracking branch 'origin/main' into patchday 2023-04-28 12:50:20 +02:00
Felix Roos ae51092044 Merge pull request #556 from tidalcycles/fix-prebake
fix: make soundfonts import dynamic
2023-04-28 12:46:56 +02:00
Felix Roos b7fcccb8db fix: make soundfonts import dynamic
- it broke the whole docs page
2023-04-28 12:42:07 +02:00
Felix Roos b21a52f5f3 update @vite-pwa/astro 2023-04-28 12:23:10 +02:00
Felix Roos 2bdcfe13e6 update @vitest/ui 2023-04-28 12:20:35 +02:00
Felix Roos 12a97ec429 update nanostores 2023-04-28 12:18:45 +02:00
Felix Roos 99298ce9f2 update vite-plugin-pwa 2023-04-28 12:14:07 +02:00
Felix Roos a0ed25d373 use vitests@^0.28.0 in packages 2023-04-28 12:12:05 +02:00
Felix Roos dc6c29c668 update vitest to ^0.28.0 2023-04-28 12:08:48 +02:00
Felix Roos 3a85f28b5c install with pnpm 8.3.1 2023-04-28 10:33:47 +02:00
Felix Roos 95c44b1ce2 Merge pull request #553 from tidalcycles/highlighting-colors
fix: colorable highlighting
2023-04-22 15:43:22 +02:00
Felix Roos b88e0ecb78 fix: colorable highlighting 2023-04-22 15:25:00 +02:00
paikwiki 74ad521fc9 Fix typo on packages.mdx
I found a broken link.

Because it omits protocol, page move to wrong page(https://strudel.tidalcycles.org/github.com/tidalcycles/strudel)

I add protocol(https://) on that link.
2023-03-14 13:01:40 +09:00
311 changed files with 30300 additions and 19130 deletions
+2 -1
View File
@@ -17,4 +17,5 @@ vite.config.js
**/*.ts
**/*.json
**/dev-dist
**/dist
**/dist
/src-tauri/target/**/*
+62
View File
@@ -0,0 +1,62 @@
name: Tauri Builder
on:
push:
tags:
- "v*"
workflow_dispatch:
jobs:
release:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, ubuntu-latest, windows-latest]
include:
- os: ubuntu-latest
rust_target: x86_64-unknown-linux-gnu
- os: macos-latest
rust_target: x86_64-apple-darwin
- os: macos-latest
rust_target: aarch64-apple-darwin
- os: windows-latest
rust_target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8.6.2
- name: Node.js setup
uses: actions/setup-node@v3
with:
node-version: latest
# node-version-file: '.nvmrc'
- name: Install Rust (Stable)
run:
curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf libasound2-dev
- name: Install app dependencies from lockfile and build web
run: pnpm install
- name: Build the app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# tauri-action replaces \_\_VERSION\_\_ with the app version
tagName: ${{ github.ref_name }}
releaseName: "Strudel v__VERSION__"
releaseBody: |
See the assets to download this version and install.
releaseDraft: true
prerelease: false
+5 -1
View File
@@ -39,4 +39,8 @@ server/samples/old
repl/stats.html
coverage
public/icons/apple-splash-*
dev-dist
dev-dist
Dirt-Samples
tidal-drum-machines
webaudiofontdata
src-tauri/target
+1 -1
View File
@@ -31,7 +31,7 @@ Use one of the Communication Channels listed above.
## Improve the Docs
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/learn/getting-started),
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/workshop/getting-started/),
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
## Propose a Feature
+2 -2
View File
@@ -24,8 +24,8 @@ There are multiple npm packages you can use to use strudel, or only parts of it,
- [`core`](./packages/core/): tidal pattern engine
- [`mini`](./packages/mini): mini notation parser + core binding
- [`eval`](./packages/eval): user code evaluator. syntax sugar + highlighting
- [`tone`](./packages/tone): bindings for Tone.js instruments and effects
- [`transpiler`](./packages/transpiler): user code transpiler
- [`webaudio`](./packages/webaudio): webaudio output
- [`osc`](./packages/osc): bindings to communicate via OSC
- [`midi`](./packages/midi): webmidi bindings
- [`serial`](./packages/serial): webserial bindings
-22
View File
@@ -1,22 +0,0 @@
# Usage
```sh
cd iclc2023/slides
pnpm i
pnpm dev
```
- ctrl+click or ctrl+(left|right) to switch slides
## Samples
To make sure all the samples are available, clone:
```sh
cd public/samples
git clone https://github.com/ritchse/tidal-drum-machines.git
git clone https://github.com/tidalcycles/Dirt-Samples.git
git clone https://github.com/felixroos/webaudiofontdata.git
```
or use symlinks..
-15
View File
@@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Slides</title>
</head>
<body style="background-color: black">
<div id="root">
<span style="color: white">loading... </span>
</div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
-40
View File
@@ -1,40 +0,0 @@
{
"name": "slides",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@mdx-js/rollup": "^2.3.0",
"@nanostores/persistent": "^0.7.0",
"@nanostores/react": "^0.4.1",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/react": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/csound": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@uiw/codemirror-themes-all": "^4.19.8",
"escodegen": "^2.0.0",
"nanostores": "^0.7.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^3.0.1",
"vite-plugin-top-level-await": "^1.3.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.8",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.7",
"vite": "^4.2.0"
}
}
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

File diff suppressed because it is too large Load Diff
-46
View File
@@ -1,46 +0,0 @@
import { useState, useCallback } from 'react';
import { atomone } from '@uiw/codemirror-themes-all';
import { CodeMirror, flash, useKeydown } from '@strudel.cycles/react';
import { initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
import { TimeSpan } from '@strudel.cycles/core';
const initAudio = initAudioOnFirstClick();
function EventEditor({ code: initialCode }) {
const [code, setCode] = useState(initialCode);
const [view, setView] = useState();
useKeydown(
useCallback(
async (e) => {
if (view?.hasFocus) {
if (e.ctrlKey || e.altKey) {
if (e.code === 'Enter') {
/* if (getAudioContext().state !== 'running') {
alert('please click play to initialize the audio. you can use shortcuts after that!');
return;
} */
e.preventDefault();
flash(view);
let json = JSON.parse(code);
if (!Array.isArray(json)) {
json = [json];
}
await initAudio;
json.forEach((value) => {
const hap = new Hap(new TimeSpan(0, 1), new TimeSpan(0, 1), value);
webaudioOutput(hap, 0.01, 1, 1);
});
} else if (e.key === '.') {
stop();
e.preventDefault();
}
}
}
},
[code, view],
),
);
return <CodeMirror value={code} onChange={setCode} theme={atomone} onViewChanged={(v) => setView(v)} fontSize={32} />;
}
export default EventEditor;
-39
View File
@@ -1,39 +0,0 @@
import EventEditor from './EventEditor.jsx';
import Stepper from './Stepper.jsx';
const snippets = [
[`{ "s": "cp", "crush": 4 }`, 'Samples'],
[`{ "s": "sawtooth", "note": "e3" }`, 'Oscillators'],
[
`[
{ "s": "gm_epiano2", "note": "c4" },
{ "s": "gm_epiano2", "note": "eb4" },
{ "s": "gm_epiano2", "note": "g4" }
]`,
'Soundfonts',
],
[
`{
"s": "toys",
"cutoff": 2000,
"delay": 0.5,
"crush": 4
}`,
'Effects',
],
];
function EventsDemo() {
return (
<Stepper
steps={snippets.map(([code, label]) => (
<div className="py-4 space-y-4">
<h3>{label}</h3>
<EventEditor code={code} />
</div>
))}
/>
);
}
export default EventsDemo;
-21
View File
@@ -1,21 +0,0 @@
//import {Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
//import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
//import SyntaxHighlighter from 'react-syntax-highlighter';
import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs';
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import js from 'react-syntax-highlighter/dist/esm/languages/hljs/javascript';
import hs from 'react-syntax-highlighter/dist/esm/languages/hljs/haskell';
SyntaxHighlighter.registerLanguage('javascript', js);
SyntaxHighlighter.registerLanguage('haskell', hs);
function Highlight({ code, language = 'javascript' }) {
return (
<div className="text-[32px] leading-10 rounded-xl overflow-hidden">
<SyntaxHighlighter language={language} style={atomOneDark} customStyle={{ padding: '8px 10px' }}>
{code}
</SyntaxHighlighter>
</div>
);
}
export default Highlight;
-38
View File
@@ -1,38 +0,0 @@
import { SlideRepl } from './SlideRepl.jsx';
import Stepper from './Stepper.jsx';
const snippets = [
[`sound("bd [hh sd]")`, `sound(seq("bd", ["hh", "sd"]))`, 'Nested Sequences'], //
[`sound("<bd sd>")`, `sound(cat("bd", "sd"))`, 'Cyclewise Sequences'], //
[`sound("bd,hh")`, `sound(stack("bd", "hh"))`, 'Stack'], //
[`sound("bd@3 sd@1")`, `sound(timeCat([3, "bd"], [1, "sd"]))`, 'Weighted Sequence'], //
[`sound("{lt ht mt, bd sd}")`, `sound(polymeter(["lt","ht","mt"], ["bd", "sd"]))`, 'Polymeter'], //
[`sound("{bd hh sd ht}%2")`, `sound(polymeterSteps(2, ["bd", "hh", "sd", "ht"]))`, 'Steps per Cycle'],
];
function MiniComparison() {
return (
<>
<h1>User Code: Mini Notation -&gt; JavaScript</h1>
<Stepper
steps={snippets.map(([a, b, label]) => (
<div className="py-4">
<h3 className="pb-4">{label}</h3>
<div className="space-y-2">
<div className="flex space-x-2">
<img src="./img/tidalcycles.svg" className={`h-10 mt-2`} />
<SlideRepl tune={a} hideHeader />
</div>
<div className="flex space-x-2">
<img src="./img/js.jpg" className={`h-10 mt-2`} />
<SlideRepl tune={b} hideHeader />
</div>
</div>
</div>
))}
/>
</>
);
}
export default MiniComparison;
-50
View File
@@ -1,50 +0,0 @@
import { SlideRepl } from './SlideRepl.jsx';
import Highlight from './Highlight.jsx';
import Stepper from './Stepper.jsx';
const snippets = [
[
<>
{' '}
<SlideRepl tune={`s("bd [hh sd]").crush(4).log()`} hideHeader />
</>,
'Logging Events',
],
[
<>
<Highlight
code={`let pattern = s("bd [hh sd]").crush(4);
let events = pattern.queryArc(0, 1);
console.log(events.map(e => e.show()))`}
language="javascript"
/>
<Highlight
language="json"
code={`[
"0/1 -> 1/2 | s:bd crush:4",
"1/2 -> 3/4 | s:hh crush:4",
"3/4 -> 1/1 | s:sd crush:4"
]`}
/>
</>,
'Querying Events Manually',
],
];
function QueryDemo() {
return (
<>
<img src="./img/queryflow.png" className="w-[800px]" />
<Stepper
steps={snippets.map(([snippet, label]) => (
<div className="py-4 space-y-4">
<h3>{label}</h3>
{snippet}
</div>
))}
/>
</>
);
}
export default QueryDemo;
-49
View File
@@ -1,49 +0,0 @@
import { evalScope, controls } from '@strudel.cycles/core';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import { prebake } from './prebake';
import { atomone } from '@uiw/codemirror-themes-all';
import blackscreen from '@strudel.cycles/react/src/themes/blackscreen';
if (typeof window !== 'undefined') {
await evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/csound'),
);
}
if (typeof window !== 'undefined') {
initAudioOnFirstClick();
prebake();
}
export function SlideRepl({
tune,
drawTime,
punchcard,
canvasHeight = 100,
hideHeader = false,
fontSize = 32,
disabled = false,
}) {
return (
<div className="not-prose rounded-xl overflow-hidden">
<_MiniRepl
hideHeader={hideHeader}
tune={tune}
hideOutsideView={true}
drawTime={drawTime}
punchcard={punchcard}
canvasHeight={canvasHeight}
fontSize={fontSize}
theme={disabled ? blackscreen : atomone}
// theme={themes[theme]}
/>
</div>
);
}
-125
View File
@@ -1,125 +0,0 @@
import { persistentAtom } from '@nanostores/persistent';
import { useStore } from '@nanostores/react'; // or '@nanostores/preact'
import useEvent from './useEvent';
function cx(...classes) {
return classes.filter(Boolean).join(' ');
}
// https://vitejs.dev/guide/features.html#glob-import
const slideImports = import.meta.glob('./slides/*.mdx');
//const slideImports = import.meta.glob('./slides/*.mdx', { eager: true });
console.log('load slides...', slideImports);
const loadedMDXFiles = await Promise.all(
Object.entries(slideImports).map(async ([path, load]) => {
//Object.entries(slideImports).map(async ([path, m]) => {
const segments = path.split('/');
const filename = segments[segments.length - 1].slice(0, -4); // expects .mdx at the end
return [filename, (await load()).default];
//return [filename, m.default];
}),
);
console.log('load done!');
const order = [
'cover',
'whatIsStrudel',
'history',
'hs2js',
'usage',
'swatch',
'repl',
//'repl-features',
'viz',
'replflow',
// User Code
'transpilation',
'mininotation',
// 'mini-comparison', // remove?
// Patterns
'patterns-events', // remove?
// scheduler?
// Output
'events-audio', // remove?
'outputs',
// 'csound', // remove?
//
// pattern alignment?
'bad',
'good',
'future',
'end',
];
// examples swatch
// draw examples!!
const slideEntries = order.map((name) => loadedMDXFiles.find(([file]) => file === name));
// current slide index is persisted, so it's safe to refresh the browser
export const slideIndex = persistentAtom('slideIndex', '0');
export const prev = () => slideIndex.set((parseInt(slideIndex.get()) - 1 + slideEntries.length) % slideEntries.length);
export const next = () => slideIndex.set((parseInt(slideIndex.get()) + 1) % slideEntries.length);
function Slides() {
const activeIndex = parseInt(useStore(slideIndex));
/*useEvent('click', (e) => {
if (!e.ctrlKey) {
return;
}
const leftHalf = e.clientX / document.body.clientWidth < 0.5;
if (leftHalf) {
prev();
} else {
next();
}
});*/
/*useEvent('keydown', (e) => {
if (e.key === 'Home') {
prev();
} else if (e.key === 'End') {
next();
}
});*/
return (
<div className="w-screen h-screen overflow-hidden relative">
{slideEntries.map(([path, Slide], i) => (
<div
key={i}
className={cx(
`bg-slate-900 w-full h-full flex justify-center absolute top-0 transition-all ease-in-out duration-500 overflow-auto`,
i < activeIndex && '-translate-x-full',
i > activeIndex && 'translate-x-full',
)}
>
<div className="prose prose-invert w-[1400px] p-12 max-w-full" style={{ fontSize: '2em' }}>
{Math.abs(i - activeIndex) <= 1 && <Slide />}
</div>
</div>
))}
<div className="text-white text-2xl text-bold cursor-pointer select-none">
<div className="fixed left-0 p-4 h-full items-start flex hover:bg-[#00000050]" onClick={() => prev()}>
&lt;
</div>
<div className="fixed right-0 p-4 h-full items-start flex hover:bg-[#00000050]" onClick={() => next()}>
&gt;
</div>
</div>
<div className="absolute top-0 w-full">
<div className="h-[5px] bg-slate-500 w-full z-100">
<div
style={{ width: ((activeIndex / (slideEntries.length - 1)) * 100).toFixed(0) + '%' }}
className="h-full bg-yellow-500"
/>
</div>
</div>
</div>
);
}
export default Slides;
-20
View File
@@ -1,20 +0,0 @@
import { useState } from 'react';
function Stepper({ steps }) {
const [step, setStep] = useState(1);
return (
<div className="not-prose">
{steps.slice(0, step).map((snippet, i) => {
const isActive = i === step - 1;
return (
<div key={i} className={`border-l-4 pl-8 border-gray-500 ${isActive ? 'border-yellow-500' : ''}`}>
{snippet}
</div>
);
})}
{step < steps.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
</div>
);
}
export default Stepper;
-73
View File
@@ -1,73 +0,0 @@
import { SlideRepl } from './SlideRepl.jsx';
import Highlight from './Highlight.jsx';
import Stepper from './Stepper.jsx';
const snippets = [
[`sound "bd ~ [sd cp]"`, [`sound("bd ~ [sd cp]")`], 'Mini Notation'], //
[`sound "bd ~ [sd cp]" # speed "1 2"`, [`sound("bd ~ [sd cp]").speed("1 2")`], 'Composing Patterns'],
[
`fast 2 $ sound "bd ~ [sd cp]"`,
[`fast(2, sound("bd ~ [sd cp]"))`, `sound("bd ~ [sd cp]").fast(2)`],
'Patterns Transformations',
],
[
`speed "1 2 3" + "4 5" # s "bd"`,
['speed("1 2 3".add("4 5")).s("bd")', '"1 2 3".add("4 5").speed().s("bd")'],
'Pattern Arithmetic',
],
[
`jux rev
$ every 3 (fast 2)
$ sound "bd sd"`,
[
`sound("bd sd")
.every(3, fast("2"))
.jux(rev)`,
],
'Higher order transformations (with partial application)',
],
[
`jux rev
$ every "<3 5>" (fast "1 2")
$ sound "bd sd cp mt"`,
[
`sound("bd sd cp mt")
.every("<3 5>", fast("1 2"))
.jux(rev)`,
],
'Patterns all the way down',
],
];
function SyntaxComparison() {
return (
<>
<h1>Haskell -&gt; JavaScript</h1>
<Stepper
steps={snippets
.map(([hs, js, label]) => (
<div className="py-4 space-y-4">
<h3>{label}</h3>
<div className="space-y-2">
<div className="flex space-x-4">
<img src="./img/haskell.png" className="h-10 mt-2" />
<Highlight language="haskell" code={hs} />
</div>
<div>
{js.map((c, j) => (
<div className="flex space-x-4" key={j}>
<img src="./img/js.jpg" className={`h-10 mt-2`} />
<SlideRepl tune={c} hideHeader />
</div>
))}
</div>
</div>
</div>
))
.concat([<>Fluent Interface: Just chain all the things!</>])}
/>
</>
);
}
export default SyntaxComparison;
-84
View File
@@ -1,84 +0,0 @@
import { useState, useCallback } from 'react';
import { atomone } from '@uiw/codemirror-themes-all';
import { CodeMirror, flash, useKeydown } from '@strudel.cycles/react';
import { transpiler } from '@strudel.cycles/transpiler';
import Highlight from './Highlight';
import Stepper from './Stepper';
import escodegen from 'escodegen';
function getTranspiled(code) {
return transpiler(code, {
addReturn: false,
codegenOptions: {
format: escodegen.FORMAT_MINIFY,
},
});
}
function TranspilationEditor({ code: initialCode }) {
const [code, setCode] = useState(initialCode);
const [transpiled, setTranspiled] = useState(getTranspiled(initialCode));
const [view, setView] = useState();
useKeydown(
useCallback(
async (e) => {
if (view?.hasFocus) {
if (e.ctrlKey || e.altKey) {
if (e.code === 'Enter') {
/* if (getAudioContext().state !== 'running') {
alert('please click play to initialize the audio. you can use shortcuts after that!');
return;
} */
e.preventDefault();
flash(view);
const t = getTranspiled(code);
setTranspiled(t);
console.log('transpiled', t);
} else if (e.key === '.') {
stop();
e.preventDefault();
}
}
}
},
[code, view],
),
);
return (
<div className="space-y-2 py-2">
<CodeMirror
value={code}
onChange={(v) => {
setCode(v);
}}
theme={atomone}
onViewChanged={(v) => setView(v)}
fontSize={32}
/>
<span> transpiles to</span>
<Highlight language="javascript" code={transpiled} />
</div>
);
}
function TranspilationDemo() {
return (
<div className="not-prose space-y-8">
<Stepper
steps={[
<>
<img src="./img/transpileflow.png" className="h-[500px]" />
<ul className="list-decimal pl-8">
<li>Parse JS Code with acorn to get AST</li>
<li>Modify AST with estree-walker</li>
<li>Convert modified AST back to JS with escodegen</li>
</ul>
</>,
<TranspilationEditor code={`"bd [hh sd]"`} />,
]}
/>
</div>
);
}
export default TranspilationDemo;
-21
View File
@@ -1,21 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.cm-activeLine {
background-color: transparent !important;
}
.cm-theme {
background-color: var(--background);
border: 1px solid var(--lineHighlight);
padding: 2px;
}
.cm-gutters {
display: none !important;
}
img {
@apply rounded-xl overflow-hidden;
}
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import Slides from './Slides';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Slides />
</React.StrictMode>,
);
-22
View File
@@ -1,22 +0,0 @@
import { registerSynthSounds, samples } from '@strudel.cycles/webaudio';
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
export async function prebake() {
// https://archive.org/details/SalamanderGrandPianoV3
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
await Promise.all([
registerSynthSounds(),
registerSoundfonts(`./samples/webaudiofontdata/sound/`),
/* samples(`./piano.json`, `./piano/`, { prebake: true }),
samples(`./vcsl.json`, 'github:sgossner/VCSL/master/', { prebake: true }),
*/
//samples(`./tidal-drum-machines.json`, 'github:ritchse/tidal-drum-machines/main/machines/', {
samples(`./tidal-drum-machines.json`, './samples/tidal-drum-machines/machines/', {
prebake: true,
tag: 'drum-machines',
}) /*
samples(`./EmuSP12.json`, `./EmuSP12/`, { prebake: true, tag: 'drum-machines' }), */,
// samples('github:tidalcycles/Dirt-Samples/master'),
samples('./Dirt-Samples.json', './samples/Dirt-Samples/'),
]);
}
-6
View File
@@ -1,6 +0,0 @@
# What's bad
- Dynamic Typing in JS
- Fractions and Hot Paths
- Verbose function composition
- Method-chaining tradeoffs
-7
View File
@@ -1,7 +0,0 @@
<div className="flex flex-col justify-between h-full overflow-hidden max-h-screen space-y-8">
<h2 className="m-0">Strudel: live coding patterns on the Web</h2>
<div className="justify-center flex overflow-hidden not-prose">
<img src="./img/strudel_icon.png" className="h-full" />
</div>
<span>Felix Roos, Alex McLean</span>
</div>
-65
View File
@@ -1,65 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
# CSound Example
## Loading ORC instruments
<SlideRepl
tune={`// livecode.orc by Steven Yi
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
stack(
note("<C^7 A7 Dm7 Fm7>/2".voicings('lefthand'))
.cutoff(sine.range(500,2000).round().slow(16))
.euclidLegato(3,8).csound('FM1'),
note("<C2 A1 D2 F2>/2").ply(8).csound('Bass')
.gain("1 4 1 4"),
note("0 7 [4 3] 2".fast(2/3)
.off(".25 .125", add("<2 4 -3 -1>"))
.slow(2).scale('A4 minor'))
.legato(.25).csound('SynHarp'),
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
)`}
hideHeader
/>
## Synthesis
<SlideRepl
tune={`await loadCsound\`
instr CoolSynth
iduration = p3
ifreq = p4
igain = p5
ioct = octcps(ifreq)
kpwm = oscili(.05, 8)
asig = vco2(igain, ifreq, 4, .5 + kpwm)
asig += vco2(igain, ifreq * 2)
idepth = 2
acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope
asig = zdf_2pole(asig, cpsoct(ioct + acut + 2), 0.5)
iattack = .01
isustain = .5
idecay = .1
irelease = .1
asig *= linsegr:a(0, iattack, 1, idecay, isustain, iduration, isustain, irelease, 0)
out(asig, asig)
endin\`
"<0 2 [4 6](3,4,2) 3\*2>"
.off(1/4, add(2))
.off(1/2, add(6))
.scale('D minor')
.note()
//.pianoroll()
.csound('CoolSynth')`}
hideHeader
/>
-1
View File
@@ -1 +0,0 @@
# Strudel Docs
-4
View File
@@ -1,4 +0,0 @@
# Thanks
- ICLC organizers
- Tidal Community
@@ -1,7 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
import Highlight from '../Highlight.jsx';
import EventsDemo from '../EventsDemo.jsx';
# Webaudio Output: Values -> Sound
<EventsDemo />
-15
View File
@@ -1,15 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
# Pattern Example
<SlideRepl
tune={`"<0 2 [4 6](3,4,1) 3>"
.off(1/4, add(2))
.off(1/2, add(6))
.scale('D minor')
.legato(.25)
.note().s("sawtooth square")
.delay(.8).delaytime(.125)`}
hideHeader
punchcard
/>
-10
View File
@@ -1,10 +0,0 @@
# Future Outlook
- (slow) community growth
- block based evaluation
- live collaboration
- more audio backends
- interface experiments, e.g.:
- alternative syntax, visual notations
- time scrubbing
- ui controls
-9
View File
@@ -1,9 +0,0 @@
# What's good
- Zero-install, multi-platform
- works (surprisingly) well on mobile devices
- visual feedback good for teaching + learning
- loads of potential contributors
- instant pattern sharing
- two-way flow of features between strudel and tidal
- full mini-notation in hydra and gibber
-3
View File
@@ -1,3 +0,0 @@
# The journey
![ports](./img/ports.png)
-3
View File
@@ -1,3 +0,0 @@
import SyntaxComparison from '../SyntaxComparison.jsx';
<SyntaxComparison />
@@ -1,5 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
import Highlight from '../Highlight.jsx';
import MiniComparison from '../MiniComparison.jsx';
<MiniComparison />
@@ -1,29 +0,0 @@
import Highlight from '../Highlight';
import Stepper from '../Stepper';
# User Code: Mini Notation
<img src="./img/pegflow.png" />
<Stepper
steps={[
<ul className="list-decimal pl-8">
<li>Parse Mini Notation to get AST</li>
<li>walk AST and construct function calls</li>
</ul>,
<div className="space-y-2 py-2">
<Highlight code={`mini('bd [hh sd]')`} />
<span>⬇️ resulting calls</span>
<Highlight
code={`seq(
reify('bd').withLocation([1,1,1], [1,4,4]),
seq(
reify('hh').withLocation([1,5,5], [1,8,8]),
reify('sd').withLocation([1,8,8], [1,10,10]),
)
)`}
/>
<span>➡️ location can be used for highlighting</span>
</div>,
]}
/>
-7
View File
@@ -1,7 +0,0 @@
# What can I pattern?
- Default: Web Audio API
- OSC via osc-js, e.g. SuperDirt
- Csound via the Csound WebAssembly build
- MIDI via WebMIDI
- Serial via WebSerial, e.g. for robot dancing
@@ -1,5 +0,0 @@
import QueryDemo from '../QueryDemo.jsx';
# Querying: From Patterns to Events
<QueryDemo />
@@ -1,12 +0,0 @@
# REPL: Features
- Visual Feedback
- Example shuffler (Hydra-style)
- Tutorial + reference
- Shareable links
- Sound Browser
- Offline Support (+ installable PWA)
- Outputs: Web Audio, MIDI, OSC, CSound, Serial, ..
- Themes
![illegible](./img/illegible.png)
-7
View File
@@ -1,7 +0,0 @@
# REPL - live coding editor
[strudel.tidalcycles.org](https://strudel.tidalcycles.org/)
{/* <iframe className="w-full h-[600px]" src="https://strudel.tidalcycles.org/#c2FtcGxlcyh7CiAgYmQ6IFsnYmQvQlQwQUFEQS53YXYnLCdiZC9CVDBBQUQwLndhdicsJ2JkL0JUMEEwREEud2F2JywnYmQvQlQwQTBEMy53YXYnLCdiZC9CVDBBMEQwLndhdicsJ2JkL0JUMEEwQTcud2F2J10sCiAgc2Q6IFsnc2Qvcnl0bS0wMS1jbGFzc2ljLndhdicsJ3NkL3J5dG0tMDAtaGFyZC53YXYnXSwKICBoaDogWydoaDI3LzAwMF9oaDI3Y2xvc2VkaGgud2F2JywnaGgvMDAwX2hoM2Nsb3NlZGhoLndhdiddLAp9LCAnZ2l0aHViOnRpZGFsY3ljbGVzL0RpcnQtU2FtcGxlcy9tYXN0ZXIvJyk7CnN0YWNrKAogIHMoImJkLFt%2BIDxzZCEzIHNkKDMsNCwyKT5dLGhoKjQiKSAvLyBkcnVtcwogIC5zcGVlZChwZXJsaW4ucmFuZ2UoLjcsMSkpIC8vIHJhbmRvbSBzYW1wbGUgc3BlZWQgdmFyaWF0aW9uCiAgLy8uaHVzaCgpCiAgLCI8YTEgYjEqMiBhMSgzLDgpIGUyPiIgLy8gYmFzc2xpbmUKICAub2ZmKDEvOCx4PT54LmFkZCgxMikuZGVncmFkZUJ5KC41KSkgLy8gcmFuZG9tIG9jdGF2ZSBqdW1wcwogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLnN1cGVyaW1wb3NlKGFkZCguMDUpKSAvLyBhZGQgc2Vjb25kLCBzbGlnaHRseSBkZXR1bmVkIHZvaWNlCiAgLm4oKSAvLyB3cmFwIGluICJuIgogIC5kZWNheSguMTUpLnN1c3RhaW4oMCkgLy8gbWFrZSBlYWNoIG5vdGUgb2YgZXF1YWwgbGVuZ3RoCiAgLnMoJ3Nhd3Rvb3RoJykgLy8gd2F2ZWZvcm0KICAuZ2FpbiguNCkgLy8gdHVybiBkb3duCiAgLmN1dG9mZihzaW5lLnNsb3coNykucmFuZ2UoMzAwLDUwMDApKSAvLyBhdXRvbWF0ZSBjdXRvZmYKICAvLy5odXNoKCkKICAsIjxBbTchMyA8RW03IEU3YjEzIEVtNyBFYm03YjU%2BPiIudm9pY2luZ3MoJ2xlZnRoYW5kJykgLy8gY2hvcmRzCiAgLnN1cGVyaW1wb3NlKHg9PnguYWRkKC4wNCkpIC8vIGFkZCBzZWNvbmQsIHNsaWdodGx5IGRldHVuZWQgdm9pY2UKICAuYWRkKHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tIHBpdGNoIHZhcmlhdGlvbgogIC5uKCkgLy8gd3JhcCBpbiAibiIKICAucygnc2F3dG9vdGgnKSAvLyB3YXZlZm9ybQogIC5nYWluKC4xNikgLy8gdHVybiBkb3duCiAgLmN1dG9mZig1MDApIC8vIGZpeGVkIGN1dG9mZgogIC5hdHRhY2soMSkgLy8gc2xvd2x5IGZhZGUgaW4KICAvLy5odXNoKCkKICAsImE0IGM1IDxlNiBhNj4iLnN0cnVjdCgieCg1LDgpIikKICAuc3VwZXJpbXBvc2UoeD0%2BeC5hZGQoLjA0KSkgLy8gYWRkIHNlY29uZCwgc2xpZ2h0bHkgZGV0dW5lZCB2b2ljZQogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLm4oKSAvLyB3cmFwIGluICJuIgogIC5kZWNheSguMSkuc3VzdGFpbigwKSAvLyBtYWtlIG5vdGVzIHNob3J0CiAgLnMoJ3RyaWFuZ2xlJykgLy8gd2F2ZWZvcm0KICAuZGVncmFkZUJ5KHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tbHkgY29udHJvbGxlZCByYW5kb20gcmVtb3ZhbCA6KQogIC5lY2hvV2l0aCg0LC4xMjUsKHgsbik9PnguZ2FpbiguMTUqMS8obisxKSkpIC8vIGVjaG8gbm90ZXMKICAvLy5odXNoKCkKKQogIC5zbG93KDEuNCk%3D" /> */}
![repl](./img/repl.png)
-5
View File
@@ -1,5 +0,0 @@
# REPL: System Overview
<center>
<img src="./img/strudelflow.png" className="w-[900px]" />
</center>
@@ -1,3 +0,0 @@
# Strudel Docs
![docs](./img/docs.png)
-5
View File
@@ -1,5 +0,0 @@
# Pattern Swatches
fork strudel - publish your own patterns with a fixed version
![swatch](./img/swatch.png)
@@ -1,5 +0,0 @@
import TranspilationDemo from '../TranspilationDemo';
# User Code: Transpilation
<TranspilationDemo />
-14
View File
@@ -1,14 +0,0 @@
import Highlight from '../Highlight.jsx';
# Ways to use Strudel
- "Strudel REPL" at [strudel.tidalcycles.org](https://strudel.tidalcycles.org)
- strudel on [flok.cc](https://flok.cc/) for collaboration
- `@strudel.cycles/*` npm packages for your own projects
- Embeddable [Web Component](https://github.com/tidalcycles/strudel/tree/main/packages/embed#strudelcyclesembed)
- Browser Plugin for [Mastodon](https://github.com/mortuosplango/topfenstrudel) + Discourse Plugin:
<div className="flex">
<img src="./img/topfenstrudel.png" />
<img src="./img/discourse.png" />
</div>
-20
View File
@@ -1,20 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
# REPL: Visual Feedback
Highlighting + Piano Roll
<SlideRepl
hideHeader
punchcard
canvasHeight={300}
fontSize={24}
tune={`// Steve Reich - Piano Phase (1967)
note("E4 F#4 B4 C#5 D5 F#4 E4 C#5 B4 F#4 D5 C#5")
.layer(
x=>x.color('#00ff0050').pan(0).fast(1), // left speaker
x=>x.color('#ff00ff50').pan(1).fast(1.01) // right speaker
)
.slow(2).room(.5)
`}
/>
@@ -1,28 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
# What is Strudel
- TidalCycles faithfully ported to JavaScript
- zero-install live coding environment for the browser
- free & open source, modular, hackable, sharable, embeddable:
<SlideRepl
hideHeader
fontSize={24}
tune={`// froos - freakuency - 2023/03/29
stack(
stack(
s("bd, ~ <rim cp>,~ hh*2").off(1/8, x=>x.speed(2).gain(.5)).fast(2),
s("oh*6").end(.01).speed(.5).room(.5).fast("1 2")
).bank('RolandTR909'),
//
note("[c a f e](<3 5>,8,<0 1>)".add("0,.1").sub(24)).s('sawtooth').lpf(500),
//
"[~ <Cm7 Fm7 Bb7 Db7>]*2".voicings('lefthand').note().legato(.2)
.echoWith(6, .1, (p,n)=>p.add(note(n*.6)))
.lpf(sine.range(300,1000).slow(8)).lpq(6)
.add(note(perlin.range(0,.8))).s('gm_epiano2')
.mask("<0 1>")
).restart("<1@3 1*[4 [8 16]]>")
.slow(2)`}
/>
-12
View File
@@ -1,12 +0,0 @@
import { useEffect } from 'react';
function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
export default useEvent;
-26
View File
@@ -1,26 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx,mdx}',
'../../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}',
],
theme: {
extend: {
colors: {
// codemirror-theme settings
background: 'var(--background)',
lineBackground: 'var(--lineBackground)',
foreground: 'var(--foreground)',
caret: 'var(--caret)',
selection: 'var(--selection)',
selectionMatch: 'var(--selectionMatch)',
gutterBackground: 'var(--gutterBackground)',
gutterForeground: 'var(--gutterForeground)',
gutterBorder: 'var(--gutterBorder)',
lineHighlight: 'var(--lineHighlight)',
},
},
},
plugins: [require('@tailwindcss/typography')],
};
-28
View File
@@ -1,28 +0,0 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import mdx from '@mdx-js/rollup';
import gfm from 'remark-gfm';
import topLevelAwait from 'vite-plugin-top-level-await';
const options = {
// See https://mdxjs.com/advanced/plugins
remarkPlugins: [gfm],
rehypePlugins: [],
};
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
mdx(options),
topLevelAwait({
// The export name of top-level await promise for each chunk module
promiseExportName: '__tla',
// The function to generate import names of top-level await promise in each chunk module
promiseImportName: (i) => `__tla_${i}`,
}),
],
build: {
target: 'esnext', //browsers can handle the latest ES features
},
});
+1 -3
View File
@@ -2,7 +2,7 @@
export * from './packages/core/index.mjs';
export * from './packages/csound/index.mjs';
export * from './packages/embed/index.mjs';
export * from './packages/eval/index.mjs';
export * from './packages/desktopbridge/index.mjs';
export * from './packages/midi/index.mjs';
export * from './packages/mini/index.mjs';
export * from './packages/osc/index.mjs';
@@ -10,8 +10,6 @@ export * from './packages/react/index.mjs';
export * from './packages/serial/index.mjs';
export * from './packages/soundfonts/index.mjs';
export * from './packages/tonal/index.mjs';
export * from './packages/tone/index.mjs';
export * from './packages/transpiler/index.mjs';
export * from './packages/webaudio/index.mjs';
export * from './packages/webdirt/index.mjs';
export * from './packages/xen/index.mjs';
+11 -13
View File
@@ -18,7 +18,6 @@
"build": "npm run prebuild && cd website && npm run build",
"preview": "cd website && npm run preview",
"osc": "cd packages/osc && npm run server",
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
"lint": "eslint . --ext mjs,js --quiet",
@@ -53,22 +52,21 @@
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*",
"acorn": "^8.8.1",
"dependency-tree": "^9.0.0",
"vitest": "^0.25.7"
"dependency-tree": "^9.0.0"
},
"devDependencies": {
"@vitest/ui": "^0.25.7",
"c8": "^7.12.0",
"canvas": "^2.11.0",
"eslint": "^8.28.0",
"@tauri-apps/cli": "^1.4.0",
"@vitest/ui": "^0.28.0",
"canvas": "^2.11.2",
"eslint": "^8.39.0",
"eslint-plugin-import": "^2.27.5",
"events": "^3.3.0",
"gh-pages": "^4.0.0",
"jsdoc": "^3.6.10",
"jsdoc": "^4.0.2",
"jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^7.1.1",
"lerna": "^6.5.1",
"prettier": "^2.8.1",
"rollup-plugin-visualizer": "^5.8.1"
"jsdoc-to-markdown": "^8.0.0",
"lerna": "^6.6.1",
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.33.0"
}
}
+3
View File
@@ -0,0 +1,3 @@
# @strudel/codemirror
This package contains helpers and extensions to use codemirror6. See [vite-vanilla-repl-cm6](../core/examples/vite-vanilla-repl-cm6/main.js) as an example of using it.
+113
View File
@@ -0,0 +1,113 @@
import { defaultKeymap } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { EditorState } from '@codemirror/state';
import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view';
import { Drawer, repl } from '@strudel.cycles/core';
import { flashField, flash } from './flash.mjs';
import { highlightExtension, highlightMiniLocations } from './highlight.mjs';
import { oneDark } from './themes/one-dark';
// https://codemirror.net/docs/guide/
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) {
let state = EditorState.create({
doc: initialCode,
extensions: [
theme,
javascript(),
lineNumbers(),
highlightExtension,
highlightActiveLineGutter(),
syntaxHighlighting(defaultHighlightStyle),
keymap.of(defaultKeymap),
flashField,
EditorView.updateListener.of((v) => onChange(v)),
keymap.of([
{
key: 'Ctrl-Enter',
run: () => onEvaluate(),
},
{
key: 'Ctrl-.',
run: () => onStop(),
},
]),
],
});
return new EditorView({
state,
parent: root,
});
}
export class StrudelMirror {
constructor(options) {
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, ...replOptions } = options;
this.code = initialCode;
this.drawer = new Drawer((haps, time) => {
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
this.highlight(currentFrame, time);
onDraw?.(haps, time, currentFrame);
}, drawTime);
const prebaked = prebake();
prebaked.then(async () => {
if (!onDraw) {
return;
}
const { scheduler, evaluate } = await this.repl;
// draw first frame instantly
prebaked.then(async () => {
await evaluate(this.code, false);
this.drawer.invalidate(scheduler);
onDraw?.(this.drawer.visibleHaps, 0, []);
});
});
this.repl = repl({
...replOptions,
onToggle: async (started) => {
replOptions?.onToggle?.(started);
const { scheduler } = await this.repl;
if (started) {
this.drawer.start(scheduler);
} else {
this.drawer.stop();
}
},
beforeEval: async () => {
await prebaked;
},
afterEval: (options) => {
replOptions?.afterEval?.(options);
this.drawer.invalidate();
},
});
this.editor = initEditor({
root,
initialCode,
onChange: (v) => {
this.code = v.state.doc.toString();
},
onEvaluate: () => this.evaluate(),
onStop: () => this.stop(),
});
}
async evaluate() {
const { evaluate } = await this.repl;
this.flash();
await evaluate(this.code);
}
async stop() {
const { scheduler } = await this.repl;
scheduler.stop();
}
flash(ms) {
flash(this.editor, ms);
}
highlight(haps, time) {
highlightMiniLocations(this.editor.view, time, haps);
}
}
+35
View File
@@ -0,0 +1,35 @@
import { StateEffect, StateField } from '@codemirror/state';
import { Decoration, EditorView } from '@codemirror/view';
export const setFlash = StateEffect.define();
export const flashField = StateField.define({
create() {
return Decoration.none;
},
update(flash, tr) {
try {
for (let e of tr.effects) {
if (e.is(setFlash)) {
if (e.value && tr.newDoc.length > 0) {
const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } });
flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
} else {
flash = Decoration.set([]);
}
}
}
return flash;
} catch (err) {
console.warn('flash error', err);
return flash;
}
},
provide: (f) => EditorView.decorations.from(f),
});
export const flash = (view, ms = 200) => {
view.dispatch({ effects: setFlash.of(true) });
setTimeout(() => {
view.dispatch({ effects: setFlash.of(false) });
}, ms);
};
+126
View File
@@ -0,0 +1,126 @@
import { RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';
import { Decoration, EditorView } from '@codemirror/view';
export const setMiniLocations = StateEffect.define();
export const showMiniLocations = StateEffect.define();
export const updateMiniLocations = (view, locations) => {
view.dispatch({ effects: setMiniLocations.of(locations) });
};
export const highlightMiniLocations = (view, atTime, haps) => {
view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) });
};
const miniLocations = StateField.define({
create() {
return Decoration.none;
},
update(locations, tr) {
if (tr.docChanged) {
locations = locations.map(tr.changes);
}
for (let e of tr.effects) {
if (e.is(setMiniLocations)) {
// this is called on eval, with the mini locations obtained from the transpiler
// codemirror will automatically remap the marks when the document is edited
// create a mark for each mini location, adding the range to the spec to find it later
const marks = e.value
.filter(([from]) => from < tr.newDoc.length)
.map(([from, to]) => [from, Math.min(to, tr.newDoc.length)])
.map(
(range) =>
Decoration.mark({
id: range.join(':'),
// this green is only to verify that the decoration moves when the document is edited
// it will be removed later, so the mark is not visible by default
attributes: { style: `background-color: #00CA2880` },
}).range(...range), // -> Decoration
);
locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet<Decoration>
}
}
return locations;
},
});
const visibleMiniLocations = StateField.define({
create() {
return { atTime: 0, haps: new Map() };
},
update(visible, tr) {
for (let e of tr.effects) {
if (e.is(showMiniLocations)) {
// this is called every frame to show the locations that are currently active
// we can NOT create new marks because the context.locations haven't changed since eval time
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range
const haps = new Map();
for (let hap of e.value.haps) {
for (let { start, end } of hap.context.locations) {
let id = `${start}:${end}`;
if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) {
haps.set(id, hap);
}
}
}
visible = { atTime: e.value.atTime, haps };
}
}
return visible;
},
});
// // Derive the set of decorations from the miniLocations and visibleLocations
const miniLocationHighlights = EditorView.decorations.compute([miniLocations, visibleMiniLocations], (state) => {
const iterator = state.field(miniLocations).iter();
const { haps } = state.field(visibleMiniLocations);
const builder = new RangeSetBuilder();
while (iterator.value) {
const {
from,
to,
value: {
spec: { id },
},
} = iterator;
if (haps.has(id)) {
const hap = haps.get(id);
const color = hap.context.color ?? 'var(--foreground)';
// Get explicit channels for color values
/*
const swatch = document.createElement('div');
swatch.style.color = color;
document.body.appendChild(swatch);
let channels = getComputedStyle(swatch)
.color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/)
.slice(1)
.map((c) => parseFloat(c || 1));
document.body.removeChild(swatch);
// Get percentage of event
const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration;
channels[3] *= percent;
*/
builder.add(
from,
to,
Decoration.mark({
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
attributes: { style: `outline: solid 2px ${color}` },
}),
);
}
iterator.next();
}
return builder.finish();
});
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
+3
View File
@@ -0,0 +1,3 @@
export * from './codemirror.mjs';
export * from './highlight.mjs';
export * from './flash.mjs';
@@ -1,9 +1,8 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.7.1",
"description": "WebDirt integration for Strudel",
"name": "@strudel/codemirror",
"version": "0.9.0",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
@@ -12,6 +11,7 @@
"build": "vite build",
"prepublishOnly": "npm run build"
},
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
@@ -24,18 +24,24 @@
"algorave"
],
"author": "Felix Roos <flix91@gmail.com>",
"contributors": [
"Alex McLean <alex@slab.org>"
],
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"WebDirt": "github:dktr0/WebDirt"
"@codemirror/commands": "^6.2.4",
"@codemirror/lang-javascript": "^6.1.7",
"@codemirror/language": "^6.6.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.10.0",
"@lezer/highlight": "^1.1.4",
"@strudel.cycles/core": "workspace:*"
},
"devDependencies": {
"vite": "^3.2.2",
"vitest": "^0.25.7"
"vite": "^4.3.3"
}
}
+139
View File
@@ -0,0 +1,139 @@
import { EditorView } from '@codemirror/view';
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { tags as t } from '@lezer/highlight';
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
const chalky = '#e5c07b',
coral = '#e06c75',
cyan = '#56b6c2',
invalid = '#ffffff',
ivory = '#abb2bf',
stone = '#7d8799', // Brightened compared to original to increase contrast
malibu = '#61afef',
sage = '#98c379',
whiskey = '#d19a66',
violet = '#c678dd',
darkBackground = '#21252b',
highlightBackground = '#2c313a',
background = '#282c34',
tooltipBackground = '#353a42',
selection = '#3E4451',
cursor = '#528bff';
/// The colors used in the theme, as CSS color strings.
export const color = {
chalky,
coral,
cyan,
invalid,
ivory,
stone,
malibu,
sage,
whiskey,
violet,
darkBackground,
highlightBackground,
background,
tooltipBackground,
selection,
cursor,
};
/// The editor theme styles for One Dark.
export const oneDarkTheme = EditorView.theme(
{
'&': {
color: ivory,
backgroundColor: background,
},
'.cm-content': {
caretColor: cursor,
},
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
{ backgroundColor: selection },
'.cm-panels': { backgroundColor: darkBackground, color: ivory },
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
'.cm-searchMatch': {
backgroundColor: '#72a1ff59',
outline: '1px solid #457dff',
},
'.cm-searchMatch.cm-searchMatch-selected': {
backgroundColor: '#6199ff2f',
},
'.cm-activeLine': { backgroundColor: '#6699ff0b' },
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
backgroundColor: '#bad0f847',
},
'.cm-gutters': {
backgroundColor: background,
color: stone,
border: 'none',
},
'.cm-activeLineGutter': {
backgroundColor: highlightBackground,
},
'.cm-foldPlaceholder': {
backgroundColor: 'transparent',
border: 'none',
color: '#ddd',
},
'.cm-tooltip': {
border: 'none',
backgroundColor: tooltipBackground,
},
'.cm-tooltip .cm-tooltip-arrow:before': {
borderTopColor: 'transparent',
borderBottomColor: 'transparent',
},
'.cm-tooltip .cm-tooltip-arrow:after': {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground,
},
'.cm-tooltip-autocomplete': {
'& > ul > li[aria-selected]': {
backgroundColor: highlightBackground,
color: ivory,
},
},
},
{ dark: true },
);
/// The highlighting style for code in the One Dark theme.
export const oneDarkHighlightStyle = HighlightStyle.define([
{ tag: t.keyword, color: violet },
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
{ tag: [t.definition(t.name), t.separator], color: ivory },
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
{ tag: [t.meta, t.comment], color: stone },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.link, color: stone, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: coral },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
{ tag: t.invalid, color: invalid },
]);
/// Extension to enable the One Dark theme (both the editor theme and
/// the highlight style).
export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
+2 -1
View File
@@ -1,4 +1,5 @@
import { controls, Pattern, getDrawContext, silence, register, pure } from './index.mjs';
import { Pattern, getDrawContext, silence, register, pure } from './index.mjs';
import controls from './controls.mjs'; // do not import from index.mjs as it breaks for some reason..
const { createParams } = controls;
let clearColor = '#22222210';
+481 -20
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, sequence } from './pattern.mjs';
import { Pattern, register, sequence } from './pattern.mjs';
import { zipWith } from './util.mjs';
const controls = {};
@@ -109,6 +109,97 @@ const generic_params = [
*/
['attack', 'att'],
/**
* Sets the Frequency Modulation Harmonicity Ratio.
* Controls the timbre of the sound.
* Whole numbers and simple ratios sound more natural,
* while decimal numbers and complex ratios sound metallic.
*
* @name fmh
* @param {number | Pattern} harmonicity
* @example
* note("c e g b")
* .fm(4)
* .fmh("<1 2 1.5 1.61>")
* .scope()
*
*/
[['fmh', 'fmi'], 'fmh'],
/**
* Sets the Frequency Modulation of the synth.
* Controls the modulation index, which defines the brightness of the sound.
*
* @name fm
* @param {number | Pattern} brightness modulation index
* @synonyms fmi
* @example
* note("c e g b")
* .fm("<0 1 2 8 32>")
* .scope()
*
*/
[['fmi', 'fmh'], 'fm'],
// fm envelope
/**
* Ramp type of fm envelope. Exp might be a bit broken..
*
* @name fmenv
* @param {number | Pattern} type lin | exp
* @example
* note("c e g b")
* .fm(4)
* .fmdecay(.2)
* .fmsustain(0)
* .fmenv("<exp lin>")
* .scope()
*
*/
['fmenv'],
/**
* Attack time for the FM envelope: time it takes to reach maximum modulation
*
* @name fmattack
* @param {number | Pattern} time attack time
* @example
* note("c e g b")
* .fm(4)
* .fmattack("<0 .05 .1 .2>")
* .scope()
*
*/
['fmattack'],
/**
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
*
* @name fmdecay
* @param {number | Pattern} time decay time
* @example
* note("c e g b")
* .fm(4)
* .fmdecay("<.01 .05 .1 .2>")
* .fmsustain(.4)
* .scope()
*
*/
['fmdecay'],
/**
* Sustain level for the FM envelope: how much modulation is applied after the decay phase
*
* @name fmsustain
* @param {number | Pattern} level sustain level
* @example
* note("c e g b")
* .fm(4)
* .fmdecay(.1)
* .fmsustain("<1 .75 .5 0>")
* .scope()
*
*/
['fmsustain'],
// these are not really useful... skipping for now
['fmrelease'],
['fmvelocity'],
/**
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
*
@@ -120,6 +211,9 @@ const generic_params = [
*/
['bank'],
['analyze'], // analyser node send amount 0 - 1 (used by scope)
['fft'], // fftSize of analyser
/**
* Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level.
* Note that the decay is only audible if the sustain value is lower than 1.
@@ -205,29 +299,43 @@ const generic_params = [
*/
['end'],
/**
* Loops the sample (from `begin` to `end`) the specified number of times.
* Loops the sample.
* Note that the tempo of the loop is not synced with the cycle tempo.
* To change the loop region, use loopBegin / loopEnd.
*
* @name loop
* @param {number | Pattern} times How often the sample is looped
* @param {number | Pattern} on If 1, the sample is looped
* @example
* s("bd").loop("<1 2 3 4>").osc()
* s("casio").loop(1)
*
*/
['loop'],
// TODO: currently duplicated with "native" legato
// TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419
/**
* a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
* Begin to loop at a specific point in the sample (inbetween `begin` and `end`).
* Note that the loop point must be inbetween `begin` and `end`, and before `loopEnd`!
* Note: Samples starting with wt_ will automatically loop! (wt = wavetable)
*
* @name legato
* @param {number | Pattern} duration between 0 and 1, where 1 is the length of the whole hap time
* @name loopBegin
* @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
* @synonyms loopb
* @example
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
*
* s("space").loop(1)
* .loopBegin("<0 .125 .25>").scope()
*/
// ['legato'],
// ['clhatdecay'],
['loopBegin', 'loopb'],
/**
*
* End the looping section at a specific point in the sample (inbetween `begin` and `end`).
* Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`!
*
* @name loopEnd
* @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
* @synonyms loope
* @example
* s("space").loop(1)
* .loopEnd("<1 .75 .5 .25>").scope()
*/
['loopEnd', 'loope'],
/**
* bit crusher effect.
*
@@ -237,6 +345,20 @@ const generic_params = [
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
*
*/
// TODO: currently duplicated with "native" legato
// TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419
/**
* a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
*
* @name legato
* @param {number | Pattern} duration between 0 and 1, where 1 is the length of the whole hap time
* @noAutocomplete
* @example
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
*
*/
// ['legato'],
// ['clhatdecay'],
['crush'],
/**
* fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
@@ -248,7 +370,6 @@ const generic_params = [
*
*/
['coarse'],
/**
* choose the channel the pattern is sent to in superdirt
*
@@ -282,6 +403,227 @@ const generic_params = [
*
*/
[['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'],
/**
* Sets the lowpass filter envelope modulation depth.
* @name lpenv
* @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_
* @synonyms lpe
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .lpf(500)
* .lpa(.5)
* .lpenv("<4 2 1 0 -1 -2 -4>/4")
*/
['lpenv', 'lpe'],
/**
* Sets the highpass filter envelope modulation depth.
* @name hpenv
* @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_
* @synonyms hpe
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .hpf(500)
* .hpa(.5)
* .hpenv("<4 2 1 0 -1 -2 -4>/4")
*/
['hpenv', 'hpe'],
/**
* Sets the bandpass filter envelope modulation depth.
* @name bpenv
* @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_
* @synonyms bpe
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .bpf(500)
* .bpa(.5)
* .bpenv("<4 2 1 0 -1 -2 -4>/4")
*/
['bpenv', 'bpe'],
/**
* Sets the attack duration for the lowpass filter envelope.
* @name lpattack
* @param {number | Pattern} attack time of the filter envelope
* @synonyms lpa
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .lpf(500)
* .lpa("<.5 .25 .1 .01>/4")
* .lpenv(4)
*/
['lpattack', 'lpa'],
/**
* Sets the attack duration for the highpass filter envelope.
* @name hpattack
* @param {number | Pattern} attack time of the highpass filter envelope
* @synonyms hpa
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .hpf(500)
* .hpa("<.5 .25 .1 .01>/4")
* .hpenv(4)
*/
['hpattack', 'hpa'],
/**
* Sets the attack duration for the bandpass filter envelope.
* @name bpattack
* @param {number | Pattern} attack time of the bandpass filter envelope
* @synonyms bpa
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .bpf(500)
* .bpa("<.5 .25 .1 .01>/4")
* .bpenv(4)
*/
['bpattack', 'bpa'],
/**
* Sets the decay duration for the lowpass filter envelope.
* @name lpdecay
* @param {number | Pattern} decay time of the filter envelope
* @synonyms lpd
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .lpf(500)
* .lpd("<.5 .25 .1 0>/4")
* .lps(0.2)
* .lpenv(4)
*/
['lpdecay', 'lpd'],
/**
* Sets the decay duration for the highpass filter envelope.
* @name hpdecay
* @param {number | Pattern} decay time of the highpass filter envelope
* @synonyms hpd
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .hpf(500)
* .hpd("<.5 .25 .1 0>/4")
* .hps(0.2)
* .hpenv(4)
*/
['hpdecay', 'hpd'],
/**
* Sets the decay duration for the bandpass filter envelope.
* @name bpdecay
* @param {number | Pattern} decay time of the bandpass filter envelope
* @synonyms bpd
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .bpf(500)
* .bpd("<.5 .25 .1 0>/4")
* .bps(0.2)
* .bpenv(4)
*/
['bpdecay', 'bpd'],
/**
* Sets the sustain amplitude for the lowpass filter envelope.
* @name lpsustain
* @param {number | Pattern} sustain amplitude of the lowpass filter envelope
* @synonyms lps
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .lpf(500)
* .lpd(.5)
* .lps("<0 .25 .5 1>/4")
* .lpenv(4)
*/
['lpsustain', 'lps'],
/**
* Sets the sustain amplitude for the highpass filter envelope.
* @name hpsustain
* @param {number | Pattern} sustain amplitude of the highpass filter envelope
* @synonyms hps
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .hpf(500)
* .hpd(.5)
* .hps("<0 .25 .5 1>/4")
* .hpenv(4)
*/
['hpsustain', 'hps'],
/**
* Sets the sustain amplitude for the bandpass filter envelope.
* @name bpsustain
* @param {number | Pattern} sustain amplitude of the bandpass filter envelope
* @synonyms bps
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .bpf(500)
* .bpd(.5)
* .bps("<0 .25 .5 1>/4")
* .bpenv(4)
*/
['bpsustain', 'bps'],
/**
* Sets the release time for the lowpass filter envelope.
* @name lprelease
* @param {number | Pattern} release time of the filter envelope
* @synonyms lpr
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .clip(.5)
* .lpf(500)
* .lpenv(4)
* .lpr("<.5 .25 .1 0>/4")
* .release(.5)
*/
['lprelease', 'lpr'],
/**
* Sets the release time for the highpass filter envelope.
* @name hprelease
* @param {number | Pattern} release time of the highpass filter envelope
* @synonyms hpr
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .clip(.5)
* .hpf(500)
* .hpenv(4)
* .hpr("<.5 .25 .1 0>/4")
* .release(.5)
*/
['hprelease', 'hpr'],
/**
* Sets the release time for the bandpass filter envelope.
* @name bprelease
* @param {number | Pattern} release time of the bandpass filter envelope
* @synonyms bpr
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .clip(.5)
* .bpf(500)
* .bpenv(4)
* .bpr("<.5 .25 .1 0>/4")
* .release(.5)
*/
['bprelease', 'bpr'],
/**
* Sets the filter type. The 24db filter is more aggressive. More types might be added in the future.
* @name ftype
* @param {number | Pattern} type 12db (default) or 24db
* @example
* note("<c2 e2 f2 g2>")
* .sound('sawtooth')
* .lpf(500)
* .bpenv(4)
* .ftype("<12db 24db>")
*/
['ftype'],
['fanchor'],
/**
* Applies the cutoff frequency of the **h**igh-**p**ass **f**ilter.
*
@@ -298,6 +640,36 @@ const generic_params = [
*/
// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496
// ['hpf'],
/**
* Applies a vibrato to the frequency of the oscillator.
*
* @name vib
* @synonyms vibrato, v
* @param {number | Pattern} frequency of the vibrato in hertz
* @example
* note("a")
* .vib("<.5 1 2 4 8 16>")
* @example
* // change the modulation depth with ":"
* note("a")
* .vib("<.5 1 2 4 8 16>:12")
*/
[['vib', 'vibmod'], 'vibrato', 'v'],
/**
* Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set
*
* @name vibmod
* @synonyms vmod
* @param {number | Pattern} depth of vibrato (in semitones)
* @example
* note("a").vib(4)
* .vibmod("<.25 .5 1 2 12>")
* @example
* // change the vibrato frequency with ":"
* note("a")
* .vibmod("<.25 .5 1 2 12>:8")
*/
[['vibmod', 'vib'], 'vmod'],
[['hcutoff', 'hresonance'], 'hpf', 'hp'],
/**
* Controls the **h**igh-**p**ass **q**-value.
@@ -476,6 +848,9 @@ const generic_params = [
*
*/
['lsize'],
// label for pianoroll
['activeLabel'],
[['label', 'activeLabel']],
// ['lfo'],
// ['lfocutoffint'],
// ['lfodelay'],
@@ -508,7 +883,7 @@ const generic_params = [
* @superDirtOnly
*/
['octave'],
['offset'], // TODO: what is this? not found in tidal doc
// ['ophatdecay'],
// TODO: example
/**
@@ -572,6 +947,15 @@ const generic_params = [
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
// ['velocity'],
['voice'], // TODO: synth param
// voicings // https://github.com/tidalcycles/strudel/issues/506
['chord'], // chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings
['dictionary', 'dict'], // which dictionary to use for the voicings
['anchor'], // the top note to align the voicing to, defaults to c5
['offset'], // how the voicing is offset from the anchored position
['octaves'], // how many octaves are voicing steps spread apart, defaults to 1
[['mode', 'anchor']], // below = anchor note will be removed from the voicing, useful for melody harmonization
/**
* Sets the level of reverb.
*
@@ -586,6 +970,57 @@ const generic_params = [
*
*/
[['room', 'size']],
/**
* Reverb lowpass starting frequency (in hertz).
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
*
* @name roomlp
* @synonyms rlp
* @param {number} frequency between 0 and 20000hz
* @example
* s("bd sd").room(0.5).rlp(10000)
* @example
* s("bd sd").room(0.5).rlp(5000)
*/
['roomlp', 'rlp'],
/**
* Reverb lowpass frequency at -60dB (in hertz).
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
*
* @name roomdim
* @synonyms rdim
* @param {number} frequency between 0 and 20000hz
* @example
* s("bd sd").room(0.5).rlp(10000).rdim(8000)
* @example
* s("bd sd").room(0.5).rlp(5000).rdim(400)
*
*/
['roomdim', 'rdim'],
/**
* Reverb fade time (in seconds).
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
*
* @name roomfade
* @synonyms rfade
* @param {number} seconds for the reverb to fade
* @example
* s("bd sd").room(0.5).rlp(10000).rfade(0.5)
* @example
* s("bd sd").room(0.5).rlp(5000).rfade(4)
*
*/
['roomfade', 'rfade'],
/**
* Sets the sample to use as an impulse response for the reverb.
*
* @name iresponse
* @param {string | Pattern} sample sample to set as the impulse response
* @example
* s("bd sd").room(.8).ir("<shaker_large:0 shaker_large:2>")
*
*/
[['ir', 'i'], 'iresponse'],
/**
* Sets the room size of the reverb, see {@link room}.
*
@@ -749,18 +1184,33 @@ const generic_params = [
['val'],
['cps'],
/**
* If set to 1, samples will be cut to the duration of their event.
* In tidal, this would be done with legato, which [is about to land in strudel too](https://github.com/tidalcycles/strudel/issues/111)
* Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration.
* In tidal, this would be done with legato, [which has a complicated history in strudel](https://github.com/tidalcycles/strudel/issues/111).
* For now, if you're coming from tidal, just think clip = legato.
*
* @name clip
* @param {number | Pattern} active 1 or 0
* @param {number | Pattern} factor >= 0
* @example
* note("c a f e ~").s("piano").clip(1)
* note("c a f e").s("piano").clip("<.5 1 2>")
*
*/
['clip'],
];
// ZZFX
['zrand'],
['curve'],
['slide'], // superdirt duplicate
['deltaSlide'],
['pitchJump'],
['pitchJumpTime'],
['lfo', 'repeatTime'],
['noise'],
['zmod'],
['zcrush'], // like crush but scaled differently
['zdelay'],
['tremolo'],
['zzfx'],
];
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
controls.createParam = function (names) {
@@ -810,4 +1260,15 @@ generic_params.forEach(([names, ...aliases]) => {
controls.createParams = (...names) =>
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
controls.adsr = register('adsr', (adsr, pat) => {
adsr = !Array.isArray(adsr) ? [adsr] : adsr;
const [attack, decay, sustain, release] = adsr;
return pat.set({ attack, decay, sustain, release });
});
controls.ds = register('ds', (ds, pat) => {
ds = !Array.isArray(ds) ? [ds] : ds;
const [decay, sustain] = ds;
return pat.set({ decay, sustain });
});
export default controls;
+112 -6
View File
@@ -1,6 +1,6 @@
/*
draw.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tone/draw.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/draw.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -9,17 +9,29 @@ import { Pattern, getTime, State, TimeSpan } from './index.mjs';
export const getDrawContext = (id = 'test-canvas') => {
let canvas = document.querySelector('#' + id);
if (!canvas) {
const scale = 2; // 2 = crisp on retina screens
canvas = document.createElement('canvas');
canvas.id = id;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0;z-index:5';
canvas.width = window.innerWidth * scale;
canvas.height = window.innerHeight * scale;
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0';
document.body.prepend(canvas);
let timeout;
window.addEventListener('resize', () => {
timeout && clearTimeout(timeout);
timeout = setTimeout(() => {
canvas.width = window.innerWidth * scale;
canvas.height = window.innerHeight * scale;
}, 200);
});
}
return canvas.getContext('2d');
};
Pattern.prototype.draw = function (callback, { from, to, onQuery }) {
Pattern.prototype.draw = function (callback, { from, to, onQuery } = {}) {
if (typeof window === 'undefined') {
return this;
}
if (window.strudelAnimation) {
cancelAnimationFrame(window.strudelAnimation);
}
@@ -51,7 +63,7 @@ Pattern.prototype.draw = function (callback, { from, to, onQuery }) {
export const cleanupDraw = (clearScreen = true) => {
const ctx = getDrawContext();
clearScreen && ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.width);
if (window.strudelAnimation) {
cancelAnimationFrame(window.strudelAnimation);
}
@@ -65,3 +77,97 @@ Pattern.prototype.onPaint = function (onPaint) {
this.context = { onPaint };
return this;
};
// const round = (x) => Math.round(x * 1000) / 1000;
// encapsulates starting and stopping animation frames
export class Framer {
constructor(onFrame, onError) {
this.onFrame = onFrame;
this.onError = onError;
}
start() {
const self = this;
let frame = requestAnimationFrame(function updateHighlights(time) {
try {
self.onFrame(time);
} catch (err) {
self.onError(err);
}
frame = requestAnimationFrame(updateHighlights);
});
self.cancel = () => {
cancelAnimationFrame(frame);
};
}
stop() {
if (this.cancel) {
this.cancel();
}
}
}
// syncs animation frames to a cyclist scheduler
// see vite-vanilla-repl-cm6 for an example
export class Drawer {
constructor(onDraw, drawTime) {
let [lookbehind, lookahead] = drawTime; // e.g. [-2, 2]
lookbehind = Math.abs(lookbehind);
this.visibleHaps = [];
this.lastFrame = null;
this.drawTime = drawTime;
this.framer = new Framer(
() => {
if (!this.scheduler) {
console.warn('Drawer: no scheduler');
return;
}
// calculate current frame time (think right side of screen for pianoroll)
const phase = this.scheduler.now() + lookahead;
// first frame just captures the phase
if (this.lastFrame === null) {
this.lastFrame = phase;
return;
}
// query haps from last frame till now. take last 100ms max
const haps = this.scheduler.pattern.queryArc(Math.max(this.lastFrame, phase - 1 / 10), phase);
this.lastFrame = phase;
this.visibleHaps = (this.visibleHaps || [])
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
.filter((h) => h.whole.end >= phase - lookbehind - lookahead)
// add new haps with onset (think right edge bars scrolling in)
.concat(haps.filter((h) => h.hasOnset()));
const time = phase - lookahead;
onDraw(this.visibleHaps, time, this);
},
(err) => {
console.warn('draw error', err);
},
);
}
invalidate(scheduler = this.scheduler) {
if (!scheduler) {
return;
}
this.scheduler = scheduler;
const t = scheduler.now();
let [_, lookahead] = this.drawTime;
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
// remove all future haps
this.visibleHaps = this.visibleHaps.filter((h) => h.whole.begin < t);
// query future haps
const futureHaps = scheduler.pattern.queryArc(begin, end); // +0.1 = workaround for weird holes in query..
// append future haps
this.visibleHaps = this.visibleHaps.concat(futureHaps);
}
start(scheduler) {
this.scheduler = scheduler;
this.invalidate();
this.framer.start();
}
stop() {
if (this.framer) {
this.framer.stop();
}
}
}
+7 -3
View File
@@ -1,6 +1,6 @@
/*
evaluate.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/eval/evaluate.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/evaluate.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/>.
*/
@@ -37,8 +37,12 @@ function safeEval(str, options = {}) {
}
export const evaluate = async (code, transpiler) => {
let meta = {};
if (transpiler) {
code = transpiler(code); // transform syntactically correct js code to semantically usable code
// transform syntactically correct js code to semantically usable code
const transpiled = transpiler(code);
code = transpiled.output;
meta = transpiled;
}
// if no transpiler is given, we expect a single instruction (!wrapExpression)
const options = { wrapExpression: !!transpiler };
@@ -48,5 +52,5 @@ export const evaluate = async (code, transpiler) => {
const message = `got "${typeof evaluated}" instead of pattern`;
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
}
return { mode: 'javascript', pattern: evaluated };
return { mode: 'javascript', pattern: evaluated, meta };
};
@@ -22,5 +22,3 @@ dist-ssr
*.njsproj
*.sln
*.sw?
public/samples
@@ -0,0 +1,8 @@
# vite-vanilla-repl-cm6
This folder demonstrates how to set up a strudel repl using vite and vanilla JS + codemirror. Run it using:
```sh
npm i
npm run dev
```
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite Vanilla Strudel REPL</title>
</head>
<body>
<main>
<nav>
<button id="play">eval</button>
<button id="stop">stop</button>
</nav>
<div class="container">
<div id="editor"></div>
<div id="output"></div>
</div>
<canvas id="roll"></canvas>
</main>
<script type="module" src="./main.js"></script>
</body>
</html>
@@ -0,0 +1,39 @@
import { StrudelMirror } from '@strudel/codemirror';
import { funk42 } from './tunes';
import { drawPianoroll, evalScope, controls } from '@strudel.cycles/core';
import './style.css';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel.cycles/webaudio';
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
// init canvas
const canvas = document.getElementById('roll');
canvas.width = canvas.width * 2;
canvas.height = canvas.height * 2;
const drawContext = canvas.getContext('2d');
const drawTime = [-2, 2]; // time window of drawn haps
const editor = new StrudelMirror({
defaultOutput: webaudioOutput,
getTime: () => getAudioContext().currentTime,
transpiler,
root: document.getElementById('editor'),
initialCode: funk42,
drawTime,
onDraw: (haps, time) => drawPianoroll({ haps, time, ctx: drawContext, drawTime, fold: 0 }),
prebake: async () => {
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
const loadModules = evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/webaudio'),
);
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
},
});
document.getElementById('play').addEventListener('click', () => editor.evaluate());
document.getElementById('stop').addEventListener('click', () => editor.stop());
@@ -0,0 +1,23 @@
{
"name": "vite-vanilla-repl-cm6",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^4.3.2"
},
"dependencies": {
"@strudel/codemirror": "workspace:*",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
}
}
@@ -0,0 +1,31 @@
body,
html {
margin: 0;
height: 100%;
background: #282c34;
}
main {
height: 100%;
display: flex;
flex-direction: column;
}
.container {
flex-grow: 1;
max-height: 100%;
position: relative;
overflow: auto;
}
#editor {
overflow: auto;
}
.cm-editor {
height: 100%;
}
#roll {
height: 300px;
}
@@ -0,0 +1,112 @@
export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
await samples('github:felixroos/samples/main')
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
"<[0,<6 7 9>,13,<17 20 22 26>]!2>/2"
// make it 22 edo
.fmap(v => Math.pow(2,v/22))
// mess with the base frequency
.mul("<300 [300@3 200]>/8").freq()
.layer(
// chords
x=>x.div(freq(2)).s("flute").euclidLegato("<3 2>",8)
.shape(.4).lpf(sine.range(800,4000).slow(8)),
// adlibs
x=>x.arp("{0 3 2 [1 3]}%1.5")
.s('xylo').mul(freq(2))
.delay(.5).delayfeedback(.4).juxBy(.5, rev)
.hpf(sine.range(200,3000).slow(8)),
// bass
x=>x.arp("[0 [2 1?]](5,8)").s('sawtooth').div(freq(4))
.lpf(sine.range(400,2000).slow(8)).lpq(8).shape(.4)
.off(1/8, x=>x.mul(freq(2)).degradeBy(.5)).gain(.3)
).clip(1).release(.2)
.stack(
// drums
s("bd sd:<2 1>, [~ hh]*2, [~ rim]").bank('RolandTR909')
.off(1/8, x=>x.speed(2).gain(.4)).sometimes(ply(2)).gain(.8)
.mask("<0@4 1@12>/4")
.reset("<x@15 [x(3,8) x*[4 8]]>")
// wait for it...
).fast(2/3)
//.crush(6) // remove "//" if you dare`;
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
await samples('github:felixroos/samples/main')
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
addVoicings('hip', {
m11: ['2M 3m 4P 7m'],
'^7#11': ['3M 4A 5P 7M'],
}, ['C4', 'C6'])
stack(
stack(
"<Bbm11 A^7#11>/2".voicings('hip').note()
.s("gm_epiano1:2")
.arp("[<[0 1 2 3] [3 2 1 0]> ~@5]/2")
.release(2).late(.25).lpf(2000),
"<Bb1 A1>/2".note().s('gm_acoustic_bass'),
n("<0 2 3>(3,8)".off(1/8, add(4)))
.scale("<Bb4:minor A4:lydian>/2")
.s('gm_electric_guitar_jazz')
.decay(sine.range(.05, .2).slow(32)).sustain(0)
.delay(.5).lpf(sine.range(100,5000).slow(64))
.gain(.7).room(.5).pan(sine.range(0,1).slow(11))
).add(perlin.range(0,.25).note()),
stack(
s("bd:1(3,8) rim").bank('RolandTR707').slow(2).room("<0 <.1 .6>>")
.when("<0@7 1>",x=>x.echoWith(3, .0625, (x,i) => x.speed(1+i*.24))),
s("rim*4").end(.05).bank('RolandTR808').speed(.8).room(.2)
)
)
.late("[0 .05]*2").late(12)
`;
export const funk42 = `// froos - how to funk in 42 lines of code
// adapted from "how to funk in two minutes" by marc rebillet https://www.youtube.com/watch?v=3vBwRfQbXkg
// thanks to peach for the transcription: https://www.youtube.com/watch?v=8eiPXvIgda4
await samples('github:felixroos/samples/main')
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
setcps(.5)
let drums = stack(
s("bd*2, ~ sd").bank('RolandTR707').room("0 .1"),
s("hh*4").begin(.2).release(.02).end(.25).release(.02)
.gain(.3).bank('RolandTR707').late(.02).room(.5),
//s("shaker_small").struct("[x x*2]*2").speed(".8,.9").release(.02)
).fast(2)
let wurli = note(\`<
[[a2,g3,[b3 c4],e4] ~ [g3,c4,e4](3,8)@4 ~@2]!3
[[e2,e3,a3,b3,e4]@3 [e2,e3,ab3,b3,e4]@5]>\`)
.s("gm_epiano1:5").decay(.2).sustain("<[1 0@7]!3 1>")
.gain("<[.8@2 .4@14]!3 .7>").room(.3)
let organ = note("<[~@3 [a3,d4,f#4]@2 [[a3,c4,e4]@2 ~] ~@2]!3 ~>".add(12))
.s("gm_percussive_organ:2").gain(.6).lpf(1800).pan(.2).room(.3);
let clav = note(\`<
[~@3 a2 [g3,[b3 c4],e4]@2 ~ a2 [g3,b3,e4] ~@2 [g3,c4,e4] ~@4]!3
[~@3 e3 [[a3 b3],c3,e3]@2 ~ e2 [e3,a3]@3 [b3,e3] ~@2 [b3,e3]@2]>\`)
.s("gm_clavinet:1").decay("<.25!3 [.25 .4]>").sustain(0)
.gain(.7).pan(.8).room(.2);
let bass = note(\`<
[a1 [~ [g2 a2]] [g1 g#1] [a1 [g2 a2]]]
[a1 [~ [g2 a2]] [e3 d3] [c3 [g3 a3]]]
[a1 [~ [g2 a2]] [g1 g#1] [a1 [g2 a2]]]
[e2@6 e1@5 e1 [[d2 e3] g1]@4]
>\`).s("gm_electric_bass_pick:1").release(.1)
stack(
drums
,wurli
,organ
,clav
,bass
)`;
@@ -6,5 +6,3 @@ This folder demonstrates how to set up a strudel repl using vite and vanilla JS.
npm i
npm run dev
```
or view it [live on githack](https://rawcdn.githack.com/tidalcycles/strudel/5fb36acb046ead7cd6ad3cd10f532e7f585f536a/packages/core/examples/vite-vanilla-repl/dist/index.html)
@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"devDependencies": {
"vite": "^3.2.0"
"vite": "^4.3.3"
},
"dependencies": {
"@strudel.cycles/core": "workspace:*",
+5 -1
View File
@@ -32,7 +32,11 @@ export class Hap {
}
get duration() {
return this.whole.end.sub(this.whole.begin);
return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1);
}
get endClipped() {
return this.whole.begin.add(this.duration);
}
wholeOrPart() {
+2
View File
@@ -18,11 +18,13 @@ export * from './util.mjs';
export * from './speak.mjs';
export * from './evaluate.mjs';
export * from './repl.mjs';
export * from './cyclist.mjs';
export * from './logger.mjs';
export * from './time.mjs';
export * from './draw.mjs';
export * from './animate.mjs';
export * from './pianoroll.mjs';
export * from './spiral.mjs';
export * from './ui.mjs';
export { default as drawLine } from './drawLine.mjs';
export { default as gist } from './gist.js';
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.7.2",
"version": "0.9.0",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
@@ -35,7 +35,7 @@
},
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
"devDependencies": {
"vite": "^3.2.2",
"vitest": "^0.25.7"
"vite": "^4.3.3",
"vitest": "^0.33.0"
}
}
+76 -49
View File
@@ -472,15 +472,15 @@ export class Pattern {
/**
* Returns a new pattern with the given location information added to the
* context of every hap.
* @param {Number} start
* @param {Number} end
* @param {Number} start start offset
* @param {Number} end end offset
* @returns Pattern
* @noAutocomplete
*/
withLocation(start, end) {
withLoc(start, end) {
const location = {
start: { line: start[0], column: start[1], offset: start[2] },
end: { line: end[0], column: end[1], offset: end[2] },
start,
end,
};
return this.withContext((context) => {
const locations = (context.locations || []).concat([location]);
@@ -488,32 +488,6 @@ export class Pattern {
});
}
withMiniLocation(start, end) {
const offset = {
start: { line: start[0], column: start[1], offset: start[2] },
end: { line: end[0], column: end[1], offset: end[2] },
};
return this.withContext((context) => {
let locations = context.locations || [];
locations = locations.map(({ start, end }) => {
const colOffset = start.line === 1 ? offset.start.column : 0;
return {
start: {
...start,
line: start.line - 1 + (offset.start.line - 1) + 1,
column: start.column - 1 + colOffset,
},
end: {
...end,
line: end.line - 1 + (offset.start.line - 1) + 1,
column: end.column - 1 + colOffset,
},
};
});
return { ...context, locations };
});
}
/**
* Returns a new Pattern, which only returns haps that meet the given test.
* @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
@@ -809,13 +783,14 @@ export class Pattern {
hap.setContext({
...hap.context,
onTrigger: (...args) => {
if (!dominant && hap.context.onTrigger) {
hap.context.onTrigger(...args);
}
// run previously set trigger, if it exists
hap.context.onTrigger?.(...args);
onTrigger(...args);
},
// we need this to know later if the default trigger should still fire
dominantTrigger: dominant,
// if dominantTrigger is set to true, the default output (webaudio) will be disabled
// when using multiple triggers, you cannot flip this flag to false again!
// example: x.csound('CooLSynth').log() as well as x.log().csound('CooLSynth') should work the same
dominantTrigger: hap.context.dominantTrigger || dominant,
}),
);
}
@@ -1582,6 +1557,24 @@ export const range2 = register('range2', function (min, max, pat) {
return pat.fromBipolar()._range(min, max);
});
/**
* Allows dividing numbers via list notation using ":".
* Returns a new pattern with just numbers.
* @name ratio
* @memberof Pattern
* @returns Pattern
* @example
* ratio("1, 5:4, 3:2").mul(110).freq().s("piano").slow(2)
*/
export const ratio = register('ratio', (pat) =>
pat.fmap((v) => {
if (!Array.isArray(v)) {
return v;
}
return v.slice(1).reduce((acc, n) => acc / n, v[0]);
}),
);
//////////////////////////////////////////////////////////////////////
// Structural and temporal transformations
@@ -1677,6 +1670,9 @@ export const ply = register('ply', function (factor, pat) {
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
*/
export const { fast, density } = register(['fast', 'density'], function (factor, pat) {
if (factor === 0) {
return silence;
}
factor = Fraction(factor);
const fastQuery = pat.withQueryTime((t) => t.mul(factor));
return fastQuery.withHapTime((t) => t.div(factor));
@@ -1703,6 +1699,9 @@ export const hurry = register('hurry', function (r, pat) {
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
*/
export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) {
if (factor === 0) {
return silence;
}
return pat._fast(Fraction(1).div(factor));
});
@@ -2029,7 +2028,7 @@ export const jux = register('jux', function (func, pat) {
* @example
* "<0 [2 4]>"
* .echoWith(4, 1/8, (p,n) => p.add(n*2))
* .scale('C minor').note().legato(.2)
* .scale('C minor').note().clip(.2)
*/
export const { echoWith, echowith, stutWith, stutwith } = register(
['echoWith', 'echowith', 'stutWith', 'stutwith'],
@@ -2179,6 +2178,7 @@ export const velocity = register('velocity', function (velocity, pat) {
/**
*
* Multiplies the hap duration with the given factor.
* With samples, `clip` might be a better function to use ([more info](https://github.com/tidalcycles/strudel/pull/598))
* @name legato
* @memberof Pattern
* @example
@@ -2241,17 +2241,21 @@ const _loopAt = function (factor, pat, cps = 1) {
.slow(factor);
};
/*
/**
* Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
* Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points.
* @name slice
* @memberof Pattern
* @returns Pattern
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks125/2").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")
*/
const slice = register(
export const slice = register(
'slice',
function (npat, ipat, opat) {
return npat.innerBind((n) =>
@@ -2259,9 +2263,9 @@ const slice = register(
opat.outerBind((o) => {
// If it's not an object, assume it's a string and make it a 's' control parameter
o = o instanceof Object ? o : { s: o };
// Remember we must stay pure and avoid editing the object directly
const toAdd = { begin: i / n, end: (i + 1) / n, _slices: n };
return pure({ ...toAdd, ...o });
const begin = Array.isArray(n) ? n[i] : i / n;
const end = Array.isArray(n) ? n[i + 1] : (i + 1) / n;
return pure({ begin, end, _slices: n, ...o });
}),
),
);
@@ -2269,17 +2273,17 @@ const slice = register(
false, // turns off auto-patternification
);
/*
/**
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
* @name splice
* @memberof Pattern
* @returns Pattern
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65)
* s("breaks165")
* .splice(8, "0 1 [2 3 0]@2 3 0@2 7")
* .hurry(0.65)
*/
const splice = register(
export const splice = register(
'splice',
function (npat, ipat, opat) {
const sliced = slice(npat, ipat, opat);
@@ -2296,10 +2300,33 @@ const splice = register(
false, // turns off auto-patternification
);
const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
// this function will be redefined in repl.mjs to use the correct cps value.
// It is still here to work in cases where repl.mjs is not used
export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
return _loopAt(factor, pat, 1);
});
// the fit function will be redefined in repl.mjs to use the correct cps value.
// It is still here to work in cases where repl.mjs is not used
/**
* Makes the sample fit its event duration. Good for rhythmical loops like drum breaks.
* Similar to loopAt.
* @name fit
* @example
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
* s("rhodes/4").fit()
*/
export const fit = register('fit', (pat) =>
pat.withHap((hap) =>
hap.withValue((v) => ({
...v,
speed: 1 / hap.whole.duration,
unit: 'c',
})),
),
);
/**
* Makes the sample fit the given number of cycles and cps value, by
* changing the speed. Please note that at some point cps will be
@@ -2313,6 +2340,6 @@ const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat)
* s("rhodes").loopAtCps(4,1.5).cps(1.5)
*/
// TODO - global cps clock
const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
return _loopAt(factor, pat, cps);
});
+101 -154
View File
@@ -1,6 +1,6 @@
/*
pianoroll.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tone/pianoroll.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/pianoroll.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -29,129 +29,26 @@ const getValue = (e) => {
return value;
};
Pattern.prototype.pianoroll = function ({
cycles = 4,
playhead = 0.5,
overscan = 1,
flipTime = 0,
flipValues = 0,
hideNegative = false,
// inactive = '#C9E597',
// inactive = '#FFCA28',
inactive = '#7491D2',
active = '#FFCA28',
// background = '#2A3236',
background = 'transparent',
smear = 0,
playheadColor = 'white',
minMidi = 10,
maxMidi = 90,
autorange = 0,
timeframe: timeframeProp,
fold = 0,
vertical = 0,
} = {}) {
const ctx = getDrawContext();
const w = ctx.canvas.width;
const h = ctx.canvas.height;
Pattern.prototype.pianoroll = function (options = {}) {
let { cycles = 4, playhead = 0.5, overscan = 1, hideNegative = false } = options;
let from = -cycles * playhead;
let to = cycles * (1 - playhead);
if (timeframeProp) {
console.warn('timeframe is deprecated! use from/to instead');
from = 0;
to = timeframeProp;
}
const timeAxis = vertical ? h : w;
const valueAxis = vertical ? w : h;
let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time
const timeExtent = to - from; // number of seconds that fit inside the canvas frame
const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0]; // pixel range for values
let valueExtent = maxMidi - minMidi + 1; // number of "slots" for values, overwritten if autorange true
let barThickness = valueAxis / valueExtent; // pixels per value, overwritten if autorange true
let foldValues = [];
flipTime && timeRange.reverse();
flipValues && valueRange.reverse();
this.draw(
(ctx, events, t) => {
ctx.fillStyle = background;
ctx.globalAlpha = 1; // reset!
if (!smear) {
ctx.clearRect(0, 0, w, h);
ctx.fillRect(0, 0, w, h);
}
(ctx, haps, t) => {
const inFrame = (event) =>
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.whole.end >= t + from;
events.filter(inFrame).forEach((event) => {
const isActive = event.whole.begin <= t && event.whole.end > t;
ctx.fillStyle = event.context?.color || inactive;
ctx.strokeStyle = event.context?.color || active;
ctx.globalAlpha = event.context.velocity ?? 1;
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
const value = getValue(event);
const valuePx = scale(
fold ? foldValues.indexOf(value) / foldValues.length : (Number(value) - minMidi) / valueExtent,
...valueRange,
);
let margin = 0;
const offset = scale(t / timeExtent, ...timeRange);
let coords;
if (vertical) {
coords = [
valuePx + 1 - (flipValues ? barThickness : 0), // x
timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y
barThickness - 2, // width
durationPx - 2, // height
];
} else {
coords = [
timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x
valuePx + 1 - (flipValues ? 0 : barThickness), // y
durationPx - 2, // widith
barThickness - 2, // height
];
}
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.endClipped >= t + from;
pianoroll({
...options,
time: t,
ctx,
haps: haps.filter(inFrame),
});
ctx.globalAlpha = 1; // reset!
const playheadPosition = scale(-from / timeExtent, ...timeRange);
// draw playhead
ctx.strokeStyle = playheadColor;
ctx.beginPath();
if (vertical) {
ctx.moveTo(0, playheadPosition);
ctx.lineTo(valueAxis, playheadPosition);
} else {
ctx.moveTo(playheadPosition, 0);
ctx.lineTo(playheadPosition, valueAxis);
}
ctx.stroke();
},
{
from: from - overscan,
to: to + overscan,
onQuery: (events) => {
const { min, max, values } = events.reduce(
({ min, max, values }, e) => {
const v = getValue(e);
return {
min: v < min ? v : min,
max: v > max ? v : max,
values: values.includes(v) ? values : [...values, v],
};
},
{ min: Infinity, max: -Infinity, values: [] },
);
if (autorange) {
minMidi = min;
maxMidi = max;
valueExtent = maxMidi - minMidi + 1;
}
foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
},
},
);
return this;
@@ -181,6 +78,14 @@ export function pianoroll({
timeframe: timeframeProp,
fold = 0,
vertical = 0,
labels = false,
fill = 1,
fillActive = false,
strokeActive = true,
stroke,
hideInactive = 0,
colorizeInactive = 1,
fontFamily,
ctx,
} = {}) {
const w = ctx.canvas.width;
@@ -224,50 +129,75 @@ export function pianoroll({
// foldValues = values.sort((a, b) => a - b);
foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
ctx.fillStyle = background;
ctx.globalAlpha = 1; // reset!
if (!smear) {
ctx.clearRect(0, 0, w, h);
ctx.fillRect(0, 0, w, h);
}
/* const inFrame = (event) =>
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= time + to && event.whole.end >= time + from; */
haps
// .filter(inFrame)
.forEach((event) => {
const isActive = event.whole.begin <= time && event.whole.end > time;
const color = event.value?.color || event.context?.color;
ctx.fillStyle = color || inactive;
ctx.strokeStyle = color || active;
ctx.globalAlpha = event.context.velocity ?? 1;
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
const value = getValue(event);
const valuePx = scale(
fold ? foldValues.indexOf(value) / foldValues.length : (Number(value) - minMidi) / valueExtent,
...valueRange,
);
let margin = 0;
const offset = scale(time / timeExtent, ...timeRange);
let coords;
if (vertical) {
coords = [
valuePx + 1 - (flipValues ? barThickness : 0), // x
timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y
barThickness - 2, // width
durationPx - 2, // height
];
} else {
coords = [
timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x
valuePx + 1 - (flipValues ? 0 : barThickness), // y
durationPx - 2, // widith
barThickness - 2, // height
];
}
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
});
haps.forEach((event) => {
const isActive = event.whole.begin <= time && event.endClipped > time;
let strokeCurrent = stroke ?? (strokeActive && isActive);
let fillCurrent = (!isActive && fill) || (isActive && fillActive);
if (hideInactive && !isActive) {
return;
}
let color = event.value?.color || event.context?.color;
active = color || active;
inactive = colorizeInactive ? color || inactive : inactive;
color = isActive ? active : inactive;
ctx.fillStyle = fillCurrent ? color : 'transparent';
ctx.strokeStyle = color;
ctx.globalAlpha = event.context.velocity ?? event.value?.gain ?? 1;
const timeProgress = (event.whole.begin - (flipTime ? to : from)) / timeExtent;
const timePx = scale(timeProgress, ...timeRange);
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
const value = getValue(event);
const valueProgress = fold
? foldValues.indexOf(value) / foldValues.length
: (Number(value) - minMidi) / valueExtent;
const valuePx = scale(valueProgress, ...valueRange);
let margin = 0;
const offset = scale(time / timeExtent, ...timeRange);
let coords;
if (vertical) {
coords = [
valuePx + 1 - (flipValues ? barThickness : 0), // x
timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y
barThickness - 2, // width
durationPx - 2, // height
];
} else {
coords = [
timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x
valuePx + 1 - (flipValues ? 0 : barThickness), // y
durationPx - 2, // widith
barThickness - 2, // height
];
}
/* const xFactor = Math.sin(performance.now() / 500) + 1;
coords[0] *= xFactor; */
if (strokeCurrent) {
ctx.strokeRect(...coords);
}
if (fillCurrent) {
ctx.fillRect(...coords);
}
//ctx.ellipse(...ellipseFromRect(...coords))
if (labels) {
const defaultLabel = event.value.note ?? event.value.s + (event.value.n ? `:${event.value.n}` : '');
const { label: inactiveLabel, activeLabel } = event.value;
const customLabel = isActive ? activeLabel || inactiveLabel : inactiveLabel;
const label = customLabel ?? defaultLabel;
let measure = vertical ? durationPx : barThickness * 0.75;
ctx.font = `${measure}px ${fontFamily || 'monospace'}`;
// font color
ctx.fillStyle = /* isActive && */ !fillCurrent ? color : 'black';
ctx.textBaseline = 'top';
ctx.fillText(label, ...coords);
}
});
ctx.globalAlpha = 1; // reset!
const playheadPosition = scale(-from / timeExtent, ...timeRange);
// draw playhead
@@ -284,7 +214,7 @@ export function pianoroll({
return this;
}
function getOptions(drawTime, options = {}) {
export function getDrawOptions(drawTime, options = {}) {
let [lookbehind, lookahead] = drawTime;
lookbehind = Math.abs(lookbehind);
const cycles = lookahead + lookbehind;
@@ -293,5 +223,22 @@ function getOptions(drawTime, options = {}) {
}
Pattern.prototype.punchcard = function (options) {
return this.onPaint((ctx, time, haps, drawTime) => pianoroll({ ctx, time, haps, ...getOptions(drawTime, options) }));
return this.onPaint((ctx, time, haps, drawTime, paintOptions = {}) =>
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) }),
);
};
Pattern.prototype.wordfall = function (options) {
return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options });
};
/* Pattern.prototype.pianoroll = function (options) {
return this.onPaint((ctx, time, haps, drawTime) =>
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { fold: 0, ...options }) }),
);
}; */
export function drawPianoroll(options) {
const { drawTime, ...rest } = options;
pianoroll({ ...getDrawOptions(drawTime), ...rest });
}
+47 -25
View File
@@ -3,6 +3,7 @@ import { evaluate as _evaluate } from './evaluate.mjs';
import { logger } from './logger.mjs';
import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs';
import { register } from './pattern.mjs';
export function repl({
interval,
@@ -16,26 +17,17 @@ export function repl({
onToggle,
editPattern,
}) {
const id = s4();
const scheduler = new Cyclist({
interval,
onTrigger: async (hap, deadline, duration, cps) => {
try {
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
await defaultOutput(hap, deadline, duration, cps);
}
if (hap.context.onTrigger) {
// call signature of output / onTrigger is different...
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
}
} catch (err) {
logger(`[cyclist] error: ${err.message}`, 'error');
}
},
onTrigger: getTrigger({ defaultOutput, getTime }),
onError: onSchedulerError,
getTime,
onToggle,
});
const setPattern = (pattern, autostart = true) => {
pattern = editPattern?.(pattern) || pattern;
scheduler.setPattern(pattern, autostart);
};
setTime(() => scheduler.now()); // TODO: refactor?
const evaluate = async (code, autostart = true) => {
if (!code) {
@@ -43,12 +35,10 @@ export function repl({
}
try {
await beforeEval?.({ code });
let { pattern } = await _evaluate(code, transpiler);
let { pattern, meta } = await _evaluate(code, transpiler);
logger(`[eval] code updated`);
pattern = editPattern?.(pattern, id) || pattern;
scheduler.setPattern(pattern, autostart);
afterEval?.({ code, pattern });
setPattern(pattern, autostart);
afterEval?.({ code, pattern, meta });
return pattern;
} catch (err) {
// console.warn(`[repl] eval error: ${err.message}`);
@@ -60,15 +50,47 @@ export function repl({
const start = () => scheduler.start();
const pause = () => scheduler.pause();
const setCps = (cps) => scheduler.setCps(cps);
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
// the following functions use the cps value, which is why they are defined here..
const loopAt = register('loopAt', (cycles, pat) => {
return pat.loopAtCps(cycles, scheduler.cps);
});
const fit = register('fit', (pat) =>
pat.withHap((hap) =>
hap.withValue((v) => ({
...v,
speed: scheduler.cps / hap.whole.duration, // overwrite speed completely?
unit: 'c',
})),
),
);
evalScope({
loopAt,
fit,
setCps,
setcps: setCps,
setCpm,
setcpm: setCpm,
});
return { scheduler, evaluate, start, stop, pause, setCps, id };
return { scheduler, evaluate, start, stop, pause, setCps, setPattern };
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
export const getTrigger =
({ getTime, defaultOutput }) =>
async (hap, deadline, duration, cps) => {
try {
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
await defaultOutput(hap, deadline, duration, cps);
}
if (hap.context.onTrigger) {
// call signature of output / onTrigger is different...
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
}
} catch (err) {
logger(`[cyclist] error: ${err.message}`, 'error');
}
};
+1 -1
View File
@@ -27,7 +27,7 @@ export const isaw2 = isaw.toBipolar();
*
* @return {Pattern}
* @example
* "c3 [eb3,g3] g2 [g3,bb3]".legato(saw.slow(4)).note()
* "c3 [eb3,g3] g2 [g3,bb3]".note().clip(saw.slow(4))
* @example
* saw.range(0,8).segment(8).scale('C major').slow(4).note()
*
+118
View File
@@ -0,0 +1,118 @@
import { Pattern } from './index.mjs';
// polar coords -> xy
function fromPolar(angle, radius, cx, cy) {
const radians = ((angle - 90) * Math.PI) / 180;
return [cx + Math.cos(radians) * radius, cy + Math.sin(radians) * radius];
}
const xyOnSpiral = (angle, margin, cx, cy, rotate = 0) => fromPolar((angle + rotate) * 360, margin * angle, cx, cy); // TODO: logSpiral
// draw spiral / segment of spiral
function spiralSegment(options) {
let {
ctx,
from = 0,
to = 3,
margin = 50,
cx = 100,
cy = 100,
rotate = 0,
thickness = margin / 2,
color = '#0000ff30',
cap = 'round',
stretch = 1,
fromOpacity = 1,
toOpacity = 1,
} = options;
from *= stretch;
to *= stretch;
rotate *= stretch;
ctx.lineWidth = thickness;
ctx.lineCap = cap;
ctx.strokeStyle = color;
ctx.globalAlpha = fromOpacity;
ctx.beginPath();
let [sx, sy] = xyOnSpiral(from, margin, cx, cy, rotate);
ctx.moveTo(sx, sy);
const increment = 1 / 60;
let angle = from;
while (angle <= to) {
const [x, y] = xyOnSpiral(angle, margin, cx, cy, rotate);
//ctx.lineWidth = angle*thickness;
ctx.globalAlpha = ((angle - from) / (to - from)) * toOpacity;
ctx.lineTo(x, y);
angle += increment;
}
ctx.stroke();
}
Pattern.prototype.spiral = function (options = {}) {
const {
stretch = 1,
size = 80,
thickness = size / 2,
cap = 'butt', // round butt squar,
inset = 3, // start angl,
playheadColor = '#ffffff90',
playheadLength = 0.02,
playheadThickness = thickness,
padding = 0,
steady = 1,
inactiveColor = '#ffffff20',
colorizeInactive = 0,
fade = true,
// logSpiral = true,
} = options;
function spiral({ ctx, time, haps, drawTime }) {
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
ctx.clearRect(0, 0, w * 2, h * 2);
const [cx, cy] = [w / 2, h / 2];
const settings = {
margin: size / stretch,
cx,
cy,
stretch,
cap,
thickness,
};
const playhead = {
...settings,
thickness: playheadThickness,
from: inset - playheadLength,
to: inset,
color: playheadColor,
};
const [min] = drawTime;
const rotate = steady * time;
haps.forEach((hap) => {
const isActive = hap.whole.begin <= time && hap.endClipped > time;
const from = hap.whole.begin - time + inset;
const to = hap.endClipped - time + inset - padding;
const { color } = hap.context;
const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
spiralSegment({
ctx,
...settings,
from,
to,
rotate,
color: colorizeInactive || isActive ? color : inactiveColor,
fromOpacity: opacity,
toOpacity: opacity,
});
});
spiralSegment({
ctx,
...playhead,
rotate,
});
}
return this.onPaint((ctx, time, haps, drawTime) => spiral({ ctx, time, haps, drawTime }));
};
+40
View File
@@ -0,0 +1,40 @@
/*test for issue 302 support alternative solmization types */
import { sol2note } from '../util.mjs';
import { test } from 'vitest';
import assert from 'assert';
test('solmization - letters', () => {
const result = sol2note(60, 'letters');
const expected = 'C4';
assert.equal(result, expected);
});
test('solmization - solfeggio', () => {
const result = sol2note(60, 'solfeggio');
const expected = 'Do4';
assert.equal(result, expected);
});
test('solmization - indian', () => {
const result = sol2note(60, 'indian');
const expected = 'Sa4';
assert.equal(result, expected);
});
test('solmization - german', () => {
const result = sol2note(60, 'german');
const expected = 'C4';
assert.equal(result, expected);
});
test('solmization - byzantine', () => {
const result = sol2note(60, 'byzantine');
const expected = 'Ni4';
assert.equal(result, expected);
});
test('solmization - japanese', () => {
const result = sol2note(60, 'japanese');
const expected = 'I4';
assert.equal(result, expected);
});
+1 -1
View File
@@ -1,6 +1,6 @@
/*
ui.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tone/ui.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/ui.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/>.
*/
+70 -8
View File
@@ -6,26 +6,29 @@ This program is free software: you can redistribute it and/or modify it under th
// returns true if the given string is a note
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bs]*[0-9]?$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
export const tokenizeNote = (note) => {
if (typeof note !== 'string') {
return [];
}
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bs]*)([0-9])?$/)?.slice(1) || [];
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9]*)$/)?.slice(1) || [];
if (!pc) {
return [];
}
return [pc, acc, oct ? Number(oct) : undefined];
};
const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
const accs = { '#': 1, b: -1, s: 1, f: -1 };
// turns the given note into its midi number representation
export const noteToMidi = (note) => {
const [pc, acc, oct = 3] = tokenizeNote(note);
export const noteToMidi = (note, defaultOctave = 3) => {
const [pc, acc, oct = defaultOctave] = tokenizeNote(note);
if (!pc) {
throw new Error('not a note: "' + note + '"');
}
const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc.toLowerCase()];
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1, s: 1 }[char], 0) || 0;
const chroma = chromas[pc.toLowerCase()];
const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0;
return (Number(oct) + 1) * 12 + chroma + offset;
};
export const midiToFreq = (n) => {
@@ -67,13 +70,14 @@ export const getFreq = (noteOrMidi) => {
return midiToFreq(noteToMidi(noteOrMidi));
};
const pcs = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
/**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
* @deprecated only used in workshop (first-notes)
* @noAutocomplete
*/
export const midi2note = (n) => {
const oct = Math.floor(n / 12) - 1;
const pc = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'][n % 12];
const pc = pcs[n % 12];
return pc + oct;
};
@@ -212,3 +216,61 @@ export const splitAt = function (index, value) {
};
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
/* solmization, not used yet */
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
const indian = [
'Sa',
'Re',
'Ga',
'Ma',
'Pa',
'Dha',
'Ni',
]; /*indian musical notes, seems like they do not use flats or sharps*/
const german = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Hb', 'H']; /*german & dutch musical notes*/
const byzantine = [
'Ni',
'Pab',
'Pa',
'Voub',
'Vou',
'Ga',
'Dib',
'Di',
'Keb',
'Ke',
'Zob',
'Zo',
]; /*byzantine musical notes*/
const japanese = [
'I',
'Ro',
'Ha',
'Ni',
'Ho',
'He',
'To',
]; /*traditional japanese musical notes, seems like they do not use falts or sharps*/
const english = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
export const sol2note = (n, notation = 'letters') => {
const pc =
notation === 'solfeggio'
? solfeggio /*check if its is any of the following*/
: notation === 'indian'
? indian
: notation === 'german'
? german
: notation === 'byzantine'
? byzantine
: notation === 'japanese'
? japanese
: english; /*if not use standard version*/
const note = pc[n % 12]; /*calculating the midi value to the note*/
const oct = Math.floor(n / 12) - 1;
return note + oct;
};
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/csound",
"version": "0.7.1",
"version": "0.9.0",
"description": "csound bindings for strudel",
"main": "index.mjs",
"publishConfig": {
@@ -32,11 +32,11 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@csound/browser": "6.18.5",
"@csound/browser": "6.18.7",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^3.2.2"
"vite": "^4.3.3"
}
}
+3
View File
@@ -0,0 +1,3 @@
# @strudel/desktopbridge
This package contains utilities used to communicate with the Tauri backend

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