mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-15 09:38:28 -04:00
302 lines
7.7 KiB
Plaintext
302 lines
7.7 KiB
Plaintext
---
|
|
title: Low-frequency oscillators (LFO)
|
|
layout: ../../layouts/MainLayout.astro
|
|
---
|
|
|
|
import { MiniRepl } from '../../docs/MiniRepl';
|
|
import { JsDoc } from '../../docs/JsDoc';
|
|
|
|
# Low frequency oscillators (LFO)
|
|
|
|
A low frequency oscillator (or short LFO) is a common way on synthesizers to continuously modulate various signals.
|
|
|
|
This documentation is an interactive version of glossing's tutorial https://www.youtube.com/watch?v=11frBA9L638
|
|
|
|
## Signals vs LFOs
|
|
|
|
In Strudel, there are two ways to modulate:
|
|
|
|
1. [signals](/learn/signals/) for pattern-level modulation
|
|
2. `lfo` (this page) for audio-level modulation
|
|
|
|
## Applying an LFO
|
|
|
|
Here, the LFO will change the frequency of the `saw`. Put a comment like this `//.lfo()` to see and hear how it changes, and remove the comment again.
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw").lfo()
|
|
.lpf(800)
|
|
._spectrum({height: 300, width: 800})`}
|
|
/>
|
|
|
|
By default, the LFO will modulate the control parameter which is right before `.lfo()`:
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lpf(800).lfo()
|
|
._spectrum({height: 300, width: 800})`}
|
|
/>
|
|
|
|
Here, the LFO will modulate the low pass filter `.lpf`.
|
|
|
|
## Moving away from the default
|
|
|
|
The following sections explain how to pass parameters to `.lfo`. Similar to `._spectrum` above, almost all the configuration of `lfo` lives inside a json object, starting with `{` and ending with `}`.
|
|
All the parameters (except `id`) are written as `key: value` inside and separated by `,`.
|
|
|
|
The reference refers to them as `config.key`, i.e. for the following one as `config.control` but you use them like below.
|
|
|
|
## Control
|
|
|
|
`control` determines which parameter will be modulated. This allows you to place your `lfo` at different places,
|
|
not necessarily immediately after the controlled parameter.
|
|
|
|
Here, we place `lfo` after the `s`, but it modulates the low pass filter.
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lfo({control:'lpf'})
|
|
.lpf(800)
|
|
._spectrum({height: 300, width: 800})`}
|
|
/>
|
|
You can even influence parameters which are always present, even if you haven't explicitly written them, like `gain`.
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lfo({control:'gain'})
|
|
.lpf(800)
|
|
._spectrum({height: 300, width: 800})`}
|
|
/>
|
|
|
|
`control` has an alias `c`.
|
|
|
|
## Rate
|
|
|
|
`rate` determines how often the `lfo` oscillates per second:
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lfo({c:'gain', rate:"<2 4>"})
|
|
.lpf(800)
|
|
._spectrum({height: 300, width: 800})`}
|
|
/>
|
|
The alias of `rate` is `r`.{' '}
|
|
|
|
## Sync
|
|
|
|
Instead of controlling the frequency with `rate` by setting a frequency in Hz, you can use `sync`
|
|
to snychronize your lfo with your other patterns.
|
|
|
|
`sync` expresses this frequency as "times per cycle"
|
|
|
|
Try removing the `sync: "<2 4 8 0.5 >"` from the pattern and notice that there is something not in sync.
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
$: s("bd*4").bank("TR909").postgain(0.5)
|
|
$: s("saw")
|
|
.lpf(800)
|
|
.lfo({sync: "<2 4 8 0.5 >"})
|
|
._spectrum({height: 300, width: 800})`}
|
|
/>
|
|
|
|
You can put patterns into the parameters of `lfo` if you want them to change over time, as seen above.
|
|
|
|
## Relative Depth
|
|
|
|
`.depth` is relative depth, a value of 1 (the default) means that
|
|
the value goes above and below by half of the value which is being modulated
|
|
|
|
E.g. for depth `1`, it modulates the frequency to oscillate between 32 = (64/2) and 96 (= 64 + 64/2).
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw").freq(64)
|
|
.lfo({r: 2, depth: "<1 2 3>"})
|
|
.lpf(800)
|
|
._spectrum({height: 300, width: 800})`}
|
|
/>
|
|
The `freq` is not needed here, as this is the default frequency, it's just for instructive purposes.
|
|
|
|
The aliases of `depth` are `dr` and `dep`.
|
|
|
|
## Absolute Depth
|
|
|
|
`depthabs` controls the absolute modulation depth. For example you can modulate
|
|
the low pass filter by exactly 250 Hz up and below:
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lpf(800)
|
|
.lfo({r: 2, depthabs: "250"})`}
|
|
/>
|
|
|
|
The alias of `depthabs` is `da`.
|
|
|
|
## DC offset
|
|
|
|
If you don't want to go up or down by the same amount with your modulation, then you can shift the center of the modulation with `dcoffset`.
|
|
The default value is -0.5, which is the middle point between:
|
|
"dcoffset = 0: All modulations increase the control parameter (or keep it constant)"
|
|
"dcoffset = -1: All modulations decrease the control parameter (or keep it constant)"
|
|
|
|
Other values will work as well.
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lpf(600)
|
|
.lfo({r: 2, da: "200", dcoffset: "<-0.5 -1 -0.5 0>"})`}
|
|
/>
|
|
The marvellous alias of `dcoffset` is `dc`.
|
|
|
|
## Shape
|
|
|
|
You can change the shape of the modulation with `shape`. The default is `triangle`, but other shapes are available too:
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lpf(800)
|
|
.lfo({r: 2, shape: "<triangle sine ramp saw square>"}) `}
|
|
/>
|
|
You can add `._spectrum()` to see the shape of the modulations in the spectrum.
|
|
|
|
You can also get these shapes by using numbers:
|
|
|
|
| Shape | number |
|
|
| -------- | ------ |
|
|
| triangle | 0 |
|
|
| sine | 1 |
|
|
| ramp | 2 |
|
|
| saw | 3 |
|
|
| square | 4 |
|
|
|
|
this way, you can use a function like `irand` to generate numbers:
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lpf(800)
|
|
.lfo({r: 2, shape: irand(4)}) `}
|
|
/>
|
|
|
|
The alias of `shape` is `sh`.
|
|
|
|
## Skewing some shapes
|
|
|
|
You can influence some of the shapes (`triangle` and `square`) in more detail.
|
|
|
|
The default skew is 0.5, it does different things for these two.
|
|
|
|
For `triangle`, it skews the top of the triangle to the left or right,
|
|
where 0 makes it look like `saw` and 1 makes it look like `ramp`.
|
|
|
|
For `square`, the skew influences the pulse width (see reference for `pulse` and `.pw`):
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lpf(800)
|
|
.lfo({r: 2, sh: "<triangle square>/5",
|
|
skew: "<0 0.25 0.5 0.75 1>"}) `}
|
|
/>
|
|
The alias of `skew` is `sk`.{' '}
|
|
|
|
## Curve
|
|
|
|
You can change the curves of your lfo and can make it more intense.
|
|
The default value is 1. Writing numbers greater than one can make it more intense,
|
|
numbers between 0 and 1 will make it less intense.
|
|
This will raise the lfo to the power of curve, so larger numbers (such as 10) can have very unexpected results.
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw")
|
|
.lpf(800)
|
|
.lfo({r: 2, sh: "<triangle>", curve: "<1.3 1 0.75 1>"}) `}
|
|
/>
|
|
## Referencing your lfos with id
|
|
|
|
All the lfos are numbered from the first starting with 0, the second having an `id` of 1 and so on.
|
|
|
|
You can refer to this in a later call if you want to modify a specific `lfo`.
|
|
|
|
The `id` is outside of the config json (which is different from the other parameters of `lfo()`
|
|
|
|
Try out how the sound changes when you replace the 0 with a 1.
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw").lfo().lpf(800)
|
|
.lfo({s: "<4 8 0.5>"})
|
|
.sometimes(x => x.lfo({dr: "4"},0))`}
|
|
/>
|
|
|
|
You can also name your lfos and refer to them by name, using the `id` parameter
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw").lfo({}, "lfo_freq_saw").lpf(800)
|
|
.lfo({s: "<4 8 0.5>"}, "lfo_lpf")
|
|
.sometimes(x => x.lfo({dr: "4"},"<lfo_lpf lfo_freq_saw>"))`}
|
|
/>
|
|
|
|
## FX index
|
|
|
|
If you are using `FX()` to reorder your effects, you dont need to write your lfos inside the `FX`,
|
|
but instead can refer to them by their FX index (starting with 0)
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw").FX(
|
|
distort(3),
|
|
gain(0.3), // this has the fx index 1
|
|
lpf(400)
|
|
).lfo({s: 16, dr:2, c:"gain", fxi: 1})`}
|
|
/>
|
|
|
|
## Modulating other LFOs with Sub-control
|
|
|
|
LFOs can modulate other lfos and will modulate their frequency (given by `r` or `s`):
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw").lpf(400).gain(0.8)
|
|
.lfo({s: 16, dr:2, c:"gain"})
|
|
.lfo({s: 0.3, dc:-1, dr: 0.8})`}
|
|
/>
|
|
To modulate other parameters of the first lfo (like `skew`, `depth` and so on), we can specify this with `subControl`or
|
|
its alias `sc`
|
|
|
|
<MiniRepl
|
|
client:idle
|
|
tune={`
|
|
s("saw").lpf(400).gain(0.8)
|
|
.lfo({s: 4, dr:2, c:"gain"})
|
|
.lfo({s: 0.3, sc: "skew"})`}
|
|
/>
|