1--- 2title: REPL 3layout: ../../layouts/MainLayout.astro 4--- 5 6import { MiniRepl } from '../../docs/MiniRepl'; 7 8# REPL 9 10{/* The [REPL](https://strudel.cc/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library. */} 11 12While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the Strudel REPL[^1], which is a browser-based live coding environment. This live code editor is dedicated to manipulating Strudel patterns while they play. The REPL features built-in visual feedback, highlighting which elements in the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is designed to support both learning and live use of Strudel. 13 14[^1]: REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive programming interface from computing heritage, usually for a commandline interface but also applied to live coding editors. 15 16Besides a UI for playback control and meta information, the main part of the REPL interface is the code editor powered by CodeMirror. In it, the user can edit and evaluate pattern code live, using one of the available synthesis outputs to create music and/or sound art. The control flow of the REPL follows 3 basic steps: 17 181. The user writes and updates code. Each update transpiles and evaluates it to create a `Pattern` instance 192. While the REPL is running, the `Scheduler` queries the active `Pattern` by a regular interval, generating `Events` (also known as `Haps` in Strudel) for the next time span. 203. For each scheduling tick, all generated `Events` are triggered by calling their `onTrigger` method, which is set by the output. 21 22<img src="https://codeberg.org/uzu/strudel/raw/branch/talk/talk/public/strudelflow.png" width="600" /> 23 24## User Code 25 26To create a `Pattern` from the user code, two steps are needed: 27 281. Transpile the JS input code to make it functional 292. Evaluate the transpiled code 30 31### Transpilation & Evaluation 32 33In the JavaScript world, using transpilation is a common practise to be able to use language features that are not supported by the base language. Tools like `babel` will transpile code that contains unsupported language features into a version of the code without those features. 34 35In the same tradition, Strudel can add a transpilation step to simplify the user code in the context of live coding. For example, the Strudel REPL lets the user create mini-notation patterns using just double quoted strings, while single quoted strings remain what they are: 36 37```strudel 38note("c3 [e3 g3]*2") 39``` 40 41is transpiled to: 42 43```strudel 44note(m('c3 [e3 g3]*2', 5)) 45``` 46 47Here, the string is wrapped in `m`, which will create a pattern from a mini-notation string. As the second parameter, it gets passed source code location of the string, which enables highlighting active events later. 48 49After the transpilation, the code is ready to be evaluated into a `Pattern`. 50 51Behind the scenes, the user code string is parsed with `acorn`, turning it into an Abstract Syntax Tree (AST). The AST allows changing the structure of the code before generating the transpiled version using `escodegen`. 52 53### Mini-notation 54 55While the transpilation allows JavaScript to express Patterns in a less verbose way, it is still preferable to use the mini-notation as a more compact way to express rhythm. Strudel aims to provide the same mini-notation features and syntax as used in Tidal. 56 57The mini-notation parser is implemented using `peggy`, which allows generating performant parsers for Domain Specific Languages (DSLs) using a concise grammar notation. The generated parser turns the mini-notation string into an AST which is used to call the respective Strudel functions with the given structure. For example, `"c3 [e3 g3]*2"` will result in the following calls: 58 59```strudel 60seq( 61 reify('c3').withLoc(6, 9), 62 seq(reify('e3').withLoc(10, 12), reify('g3',).withLoc(13, 15)) 63) 64``` 65 66### Highlighting Locations 67 68As seen in the examples above, both the mini-notation parser adds the source code locations using `withLoc`. 69This location is calculated inside the `m` function, as the sum of 2 locations: 70 711. the location where the mini notation string begins, as obtained from the JS parser 722. the location of the substring inside the mini notation, as obtained from the mini notation parser 73 74The sum of both is passed to `withLoc` to tell each element its location, which can be later used for highlighting when it's active. 75 76### Mini Notation 77 78Another important part of the user code is the mini notation, which allows to express rhythms in a short manner. 79 80- the mini notation is [implemented as a PEG grammar](https://codeberg.org/uzu/strudel/src/branch/talk/packages/mini/krill.pegjs), living in the [mini package](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini) 81- it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn 82- the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/) 83- the generated parser takes a mini notation string and outputs an AST 84- the AST can then be used to construct a pattern using the regular Strudel API 85 86Here's an example AST for `c3 [e3 g3]` 87 88```json 89{ 90 "type_": "pattern", 91 "arguments_": { "alignment": "h" }, 92 "source_": [ 93 { 94 "type_": "element", "source_": "c3", 95 "location_": { "start": { "offset": 1, "line": 1, "column": 2 }, "end": { "offset": 4, "line": 1, "column": 5 } } 96 }, 97 { 98 "type_": "element", 99 "location_": { "start": { "offset": 4, "line": 1, "column": 5 }, "end": { "offset": 11, "line": 1, "column": 12 } } 100 "source_": { 101 "type_": "pattern", "arguments_": { "alignment": "h" }, 102 "source_": [ 103 { 104 "type_": "element", "source_": "e3", 105 "location_": { "start": { "offset": 5, "line": 1, "column": 6 }, "end": { "offset": 8, "line": 1, "column": 9 } } 106 }, 107 { 108 "type_": "element", "source_": "g3", 109 "location_": { "start": { "offset": 8, "line": 1, "column": 9 }, "end": { "offset": 10, "line": 1, "column": 11 } } 110 } 111 ] 112 }, 113 } 114 ] 115} 116``` 117 118which translates to `seq(c3, seq(e3, g3))` 119 120## Vim Keybindings 121 122See the separate page on Vim shortcuts for a quick reference: [/technical-manual/vim](/technical-manual/vim) 123 124## Scheduling Events 125 126After an instance of `Pattern` is obtained from the user code, 127it is used by the scheduler to get queried for events. Once started, the scheduler runs at a fixed interval to query the active pattern for events within the current interval's time span. A simplified implementation looks like this: 128 129```js 130let pattern = seq('c3', ['e3', 'g3']); // pattern from user 131let interval = 0.5; // query interval in seconds 132let time = 0; // beginning of current time span 133let minLatency = 0.1; // min time before a hap should trigger 134setInterval(() => { 135 const haps = pattern.queryArc(time, time + interval); 136 time += interval; // increment time 137 haps.forEach((hap) => { 138 const deadline = hap.whole.begin - time + minLatency; 139 onTrigger(hap, deadline, duration); 140 }); 141}, interval * 1000); // query each "interval" seconds 142``` 143 144Note that the above code is simplified for illustrative purposes. The actual implementation has to work around imprecise callbacks of `setInterval`. More about the implementation details can be read in [this blog post](https://loophole-letters.vercel.app/web-audio-scheduling). 145 146The fact that `Pattern.queryArc` is a pure function that maps a time span to a set of events allows us to choose any interval we like without changing the resulting output. It also means that when the pattern is changed from outside, the next scheduling callback will work with the new pattern, keeping its clock running. 147 148The latency between the time the pattern is evaluated and the change is heard is between `minLatency` and `interval + minLatency`, in our example between 100ms and 600ms. In Strudel, the current query interval is 50ms with a minLatency of 100ms, meaning the latency is between 50ms and 150ms. 149 150## Output 151 152The last step is to trigger each event in the chosen output. 153This is where the given time and value of each event is used to generate audio or any other form of time based output. The default output of the Strudel REPL is the WebAudio output. To understand what an output does, we first have to understand what control parameters are. 154 155### Control Parameters 156 157To be able to manipulate multiple aspects of sound in parallel, so called control parameters are used to shape the value of each event. Example: 158 159```js 160note('c3 e3') 161 .cutoff(1000) 162 .s('sawtooth') 163 .queryArc(0, 1) 164 .map((hap) => hap.value); 165/* [ 166 { note: 'c3', cutoff: 1000, s: 'sawtooth' } 167 { note: 'e3', cutoff: 1000, s: 'sawtooth' } 168] */ 169``` 170 171Here, the control parameter functions `note`, `cutoff` and `s` are used, where each controls a different property in the value object. Each control parameter function accepts a primitive value, a list of values to be sequenced into a `Pattern`, or a `Pattern`. In the example, `note` gets a `Pattern` from a mini-notation expression (double quoted), while `cutoff` and `s` are given a `Number` and a (single quoted) `String` respectively. 172 173Strudel comes with a large default set of control parameter functions that are based on the ones used by Tidal and SuperDirt, focusing on music and audio terminology. It is however possible to create custom control parameters for any purpose: 174 175```js 176const { x, y } = createParams('x', 'y'); 177x(sine.range(0, 200)).y(cosine.range(0, 200)); 178``` 179 180This example creates the custom control parameters `x` and `y` which are then used to form a pattern that descibes the coordinates of a circle. 181 182### Outputs 183 184Now that we know how the value of an event is manipulated using control parameters, we can look at how outputs can use that value to generate anything. The scheduler above was calling the `onTrigger` function which is used to implement the output. A very simple version of the web audio output could look like this: 185 186```js 187function onTrigger(hap, deadline, duration) { 188 const { note } = hap.value; 189 const time = getAudioContext().currentTime + deadline; 190 const o = getAudioContext().createOscillator(); 191 o.frequency.value = getFreq(note); 192 o.start(time); 193 o.stop(time + event.duration); 194 o.connect(getAudioContext().destination); 195} 196``` 197 198The above example will create an `OscillatorNode` for each event, where the frequency is controlled by the `note` param. In essence, this is how the WebAudio API output of Strudel works, only with many more parameters to control synths, samples and effects. 199 200I want to help, how do I contribute to the [Docs](/technical-manual/docs)?