tidalcycles / strudel

Web-based environment for live coding algorithmic patterns, incorporating a faithful port of TidalCycles to JavaScript
https://strudel.cc/
GNU Affero General Public License v3.0
646 stars 111 forks source link

block based evaluation #34

Open felixroos opened 2 years ago

felixroos commented 2 years ago

Think about if we should implement block based evaluation, like in mainline tidal. Currently, the whole code is reevaluated when hitting ctrl+enter. To make that work, some way of naming patterns needs to be found. One idea is using the rarely used javascript label statements:

d1: s("bd sn")

d1: s("bd ~")

If we run the fist line, and then the second, only the second should play.

To make that work, the runtime needs to store patterns in some map that stores { [name]: pattern }. When a line is evaluated, the pattern map is updated. After each evaluation all patterns in the map are stacked together and queried.

One question is when non pattern code should be evaluated? Example:

const p = "x ~ x*2"

d1: "c3 e3 g3".struct(p)

Here, d1 references p, so we need to have that evaluated too. It would make usage much more complicated if the user is expected to run the first line before the second otherwise it crashes.

felixroos commented 1 year ago

somewhat related: https://extemporelang.github.io/docs/overview/using-extempore/

felixroos commented 1 year ago

before it gets forgotten, from chat:

yaxu: With the usual tidal editor plugin approach, blocks are pasted into a repl. it makes it more of a conversation but it's difficult to keep aware of which code is being run with the strudel approach it's just the final line that brings things together. If there's mininotation in play it's pretty obvious which bits are running but it's a bit awkward having to scroll to this line at the bottom being in charge of everything The feedforward approach is more of a structural approach, where blocks automatically get tagged with numbers and can be switched on and off with a shortcut. You also get a VU meter per block. You can only write patterns though, not e.g. definitions of a new function to use With tidal editor plugins you have to do the work of assigning numbers to patterns and it's really easy to accidentally replace a running pattern that you didn't want to. Unexpected breakdowns can be nice, but.. Another approach would be tiling multiple editors and having one editor per tile. Siren did something like this but mixed with a old school tracker paradigm https://toplap.org/2017/04/12/siren/ It could be interesting to have a semi-structured approach like if you did ctrl-enter on s("bd sd") it would be transformed into s("bd sd").send(3) , where '3' is a unique number. Then you could toggle it with ctrl-3 and it'd toggle between that and s("bd sd").mute(3). this could be called a 'performance mode' the send(3) would do the work of adding the pattern to be scheduled

froos: I thought about evaluating all code by default, but if you have a named block, the evaluated block wins and the rest is ignored. this would make sure variable declarations etc are evaluated

drums: s("bd sd")

bass: note("g1 c2(3,8)")

drums: s("bd sd*2")

if you press play the default behavior could be to take the first block now if you evaluate the last block, the evaluator will search for blocks with the same name and cut them out this is valid js btw

yaxu: one thing is what happens if there is a syntax error somewhere.. in feedforward iirc everything else still gets evaluated and updates. In tidal it doesn't matter as you only eval one block at a time if everything has to be valid for anything to be edited that would be a sticking point when live coding

froos: true.. another idea i had was to use the evaluated block as a starting point and search for variable references within the ast of that block and evaluate everything that matches.. this is the spreadsheet approach but it involves basically writing a subset of a js runtime that means only the block and potential references must be valid or we just throw an error if you haven't evaluated the variable declaration yet.. but a thing i really would like to keep is that you can just press play and the music starts. This is really important for beginners

felixroos commented 1 year ago

this little algorithm could be used for block detection:

https://codesandbox.io/s/block-detection-p8zozv?file=/src/index.js

felixroos commented 1 year ago

more fleshed out, using codemirror + adjusted flash logic + added gutter lines to show individual blocks: https://codesandbox.io/s/block-based-evaluation-codemirror-7fu5vo?file=/src/codemirror.js

felixroos commented 10 months ago

maybe just use:

s("bd").p(1)
s("bd").p(2)

analogous to tidal. there could also be the d functions:

s("bd").d1
s("bd").d2

with global evaluation, patterns with the same number would always win when they are last. example:

note("a").p(1)
note("b").p(1)

here, note("b") would win. BUT: why not use the cursor position at eval time to edit the code accordingly, allowing to reflect the active pattern state in the code. assuming the above code is evaluated with the cursor in the first line, the code could change to:

note("a").p(1)
note("b").q(1) // <-- this line is changed automatically

the .q could mean that that pattern is quiet, still indicating it belongs to group 1. If now the second line is evaluated, the code changes to:

note("a").q(1)
note("b").p(1)

There could be additional shortcuts / ui elements to switch patterns on and off, the crucial part is just that the state that contains the info which patterns are on and off is saved to the code itself.

A piece of code can then also be evaluated globally without a cursor info and it would still reflect the correct playback state.

This approach might still be problematic when the code as a whole contains syntax errors (with collaborative editing the same document in mind) but maybe it's at least a step in the direction of better live codability + compatibility with tidal. It can also be backward compatible, as the old behavior (use last expression as the pattern to use) can kick in when no p functions are used.

felixroos commented 10 months ago

above idea is now implemented in https://github.com/tidalcycles/strudel/pull/805 (without code transforms)