Merge pull request 'Signal flow documentation' (#1504) from glossing/strudel:glossing/signal-flow-documentation into main

Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1504
Reviewed-by: Switch Angel AKA Jade Rose <daslyfe@noreply.codeberg.org>
This commit is contained in:
froos 2025-09-07 21:48:18 +02:00
commit f2608e712b
2 changed files with 123 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -11,6 +11,129 @@ import { JsDoc } from '../../docs/JsDoc';
Whether you're using a synth or a sample, you can apply any of the following built-in audio effects.
As you might suspect, the effects can be chained together, and they accept a pattern string as their argument.
# Signal chain
<img src="/img/strudel-signal-flow.png"></img>
The signal chain in Strudel is as follows:
- An sound-generating event is triggered by a pattern
- This has a start time and a duration, which is usually
controlled by the note length and ADSR parameters
- If we exceed the max polyphony, old sounds begin to die off
- Muted sounds (one whose `s` value is `-`, `~`, or `_`) are skipped
- A sound is produced (through, say, a sample or an oscillator)
- This is where detune-based effects (like `detune`, `penv`, etc. occur)
- The following will occur _in order_ and only if they've been called in the pattern. Note that all of these are
single use effects, meaning that multiple occurrences of them in a pattern will simply override the values
(e.g. you can't do `s("bd").lpf(100).distort(2).lpf(800)` to lowpass, distort, and then lowpass
again)
- Phase vocoder (`stretch`)
- Gain is applied (`gain`)
- This is where the main (volume) ADSR happens
- A lowpass filter (`lpf`)
- A highpass filter (`hpf`)
- A bandpass filter (`bandpass`)
- A vowel filter (`vowel`)
- Sample rate reduction (`coarse`)
- Bit crushing (`crush`)
- Waveshape distortion (`shape`)
- Normal distortion (`distort`)
- Tremolo (`tremolo`)
- Compressor (`compressor`)
- Panning (`pan`)
- Phaser (`phaser`)
- Postgain (`post`)
- The sound is then split into multiple destinations
- Dry output (amount controlled by `dry` parameter)
- The sends
- Analyzers
- These are used for tooling like `scope` and `spectrum` and their setup usually happens behind the scenes
- Delay (amount controlled by `delay` parameter)
- Reverb (amount controlled by `room` parameter)
- The dry output, delay, and reverb are joined into what is called the "orbit" of the pattern (see more in the section below)
- The `duck` effect affects the volume of all signals in the orbit
- The orbit is then sent to the mixer
## Orbits
Orbits are the way in which outputs are handled in Strudel. They also prescribe which delay and reverb to associate with the dry signal.
By default, all orbits are mixed down to channels `1` and `2` in stereo, however with the "Multi Channel Orbits" setting
(under Settings at the right) you can use them as individual 2 channel stereo outs (orbit `i` will be mapped to
to channels `2i` and `2i + 1`). You can then use routers like Blackhole 16 to retrieve and record all of the channels in a DAW for later processing.
The default orbit is `1` and it is set with `orbit`. You may send a sound to multiple orbits via mininotation
<MiniRepl client:visible tune={`s("white").orbit("2,3,4").gain(0.2)`} />
but please be careful as this will create three copies of the sound behind the scenes, meaning that if they are mixed
down to a single output, they will triple the volume. We've reduced the gain here to save your ears.
⚠️ There is only one delay and reverb per orbit, so please be aware that if you attempt to change the parameters on two
patterns pointing to the same orbit, it can lead to unpredictable results. Compare, for example, this pretty pluck
with a large reverb:
<MiniRepl
client:visible
tune={`
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
.room(1).roomsize(10)`}
/>
versus the same pluck with a muted kick drum coming in and overwriting the `roomsize` value:
<MiniRepl
client:visible
tune={`
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
.room(1).roomsize(10)
$: s("bd\*4").room(0.01).roomsize(0.01).postgain(0)`}
/>
This is due to them sharing the same orbit: the default of `1`. It can be corrected simply by updating the orbits to be
distinct:
<MiniRepl
client:visible
tune={`
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
.room(1).roomsize(10).orbit(2)
$: s("bd\*4").room(0.01).roomsize(0.01).postgain(0)`}
/>
## Continuous changes
As all of the above is triggered by a _sound occurring_, it is often the case that parameters may not be
modified continuously in time. For example,
<MiniRepl
client:visible
tune={`
s("supersaw").lpf(tri.range(100, 5000).slow(2))`}
/>
Will not produce a continually LFO'd low-pass filter due to the `tri` only being sampled every time the note hits
(in this case the default of once per cycle). You can fake it by introducing more sound-generating events, e.g.:
<MiniRepl
client:visible
tune={`
s("supersaw").seg(16).lpf(tri.range(100, 5000).slow(2))`}
/>
Some parameters _do_ induce continuous variations in time, though:
- The ADSR curve (governed by `attack`, `sustain`, `decay`, `release`)
- The pitch envelope curve (governed by `penv` and its associated ADSR)
- The FM curve (`fmenv`)
- The filter envelopes (`lpenv`, `hpenv`, `bpenv`)
- Tremolo (`tremolo`)
- Phaser (`phaser`)
- Vibrato (`vib`)
- Ducking (`duckorbit`)
# Filters
Filters are an essential building block of [subtractive synthesis](https://en.wikipedia.org/wiki/Subtractive_synthesis).