repl.mdx200 lines · 10.6 KB · raw
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)?