lfo.mdx301 lines · 7.7 KB · raw
1---
2title: Low-frequency oscillators (LFO)
3layout: ../../layouts/MainLayout.astro
4---
5
6import { MiniRepl } from '../../docs/MiniRepl';
7import { JsDoc } from '../../docs/JsDoc';
8
9# Low frequency oscillators (LFO)
10
11A low frequency oscillator (or short LFO) is a common way on synthesizers to continuously modulate various signals.
12
13This documentation is an interactive version of glossing's tutorial https://www.youtube.com/watch?v=11frBA9L638
14
15## Signals vs LFOs
16
17In Strudel, there are two ways to modulate:
18
191. [signals](/learn/signals/) for pattern-level modulation
202. `lfo` (this page) for audio-level modulation
21
22## Applying an LFO
23
24Here, 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.
25
26<MiniRepl
27  client:idle
28  tune={`
29s("saw").lfo() 
30  .lpf(800)
31  ._spectrum({height: 300, width: 800})`}
32/>
33
34By default, the LFO will modulate the control parameter which is right before `.lfo()`:
35
36<MiniRepl
37  client:idle
38  tune={`
39s("saw")
40  .lpf(800).lfo() 
41  ._spectrum({height: 300, width: 800})`}
42/>
43
44Here, the LFO will modulate the low pass filter `.lpf`.
45
46## Moving away from the default
47
48The 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 `}`.
49All the parameters (except `id`) are written as `key: value` inside and separated by `,`.
50
51The reference refers to them as `config.key`, i.e. for the following one as `config.control` but you use them like below.
52
53## Control
54
55`control` determines which parameter will be modulated. This allows you to place your `lfo` at different places,
56not necessarily immediately after the controlled parameter.
57
58Here, we place `lfo` after the `s`, but it modulates the low pass filter.
59
60<MiniRepl
61  client:idle
62  tune={`
63s("saw")
64  .lfo({control:'lpf'})
65  .lpf(800)
66  ._spectrum({height: 300, width: 800})`}
67/>
68You can even influence parameters which are always present, even if you haven't explicitly written them, like `gain`.
69
70<MiniRepl
71  client:idle
72  tune={`
73s("saw")
74  .lfo({control:'gain'})
75  .lpf(800)
76  ._spectrum({height: 300, width: 800})`}
77/>
78
79`control` has an alias `c`.
80
81## Rate
82
83`rate` determines how often the `lfo` oscillates per second:
84
85<MiniRepl
86  client:idle
87  tune={`
88 s("saw")
89  .lfo({c:'gain', rate:"<2 4>"})
90  .lpf(800)
91  ._spectrum({height: 300, width: 800})`}
92/>
93The alias of `rate` is `r`.{' '}
94
95## Sync
96
97Instead of controlling the frequency with `rate` by setting a frequency in Hz, you can use `sync`
98to snychronize your lfo with your other patterns.
99
100`sync` expresses this frequency as "times per cycle"
101
102Try removing the `sync: "<2 4 8 0.5 >"` from the pattern and notice that there is something not in sync.
103
104<MiniRepl
105  client:idle
106  tune={`
107$: s("bd*4").bank("TR909").postgain(0.5)
108$: s("saw")
109  .lpf(800)
110  .lfo({sync: "<2 4 8 0.5 >"})
111  ._spectrum({height: 300, width: 800})`}
112/>
113
114You can put patterns into the parameters of `lfo` if you want them to change over time, as seen above.
115
116## Relative Depth
117
118`.depth` is relative depth, a value of 1 (the default) means that
119the value goes above and below by half of the value which is being modulated
120
121E.g. for depth `1`, it modulates the frequency to oscillate between 32 = (64/2) and 96 (= 64 + 64/2).
122
123<MiniRepl
124  client:idle
125  tune={`
126s("saw").freq(64)
127  .lfo({r: 2, depth: "<1 2 3>"})
128  .lpf(800)
129  ._spectrum({height: 300, width: 800})`}
130/>
131The `freq` is not needed here, as this is the default frequency, it's just for instructive purposes.
132
133The aliases of `depth` are `dr` and `dep`.
134
135## Absolute Depth
136
137`depthabs` controls the absolute modulation depth. For example you can modulate
138the low pass filter by exactly 250 Hz up and below:
139
140<MiniRepl
141  client:idle
142  tune={`
143s("saw")
144  .lpf(800)
145  .lfo({r: 2, depthabs: "250"})`}
146/>
147
148The alias of `depthabs` is `da`.
149
150## DC offset
151
152If 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`.
153The default value is -0.5, which is the middle point between:
154"dcoffset = 0: All modulations increase the control parameter (or keep it constant)"
155"dcoffset = -1: All modulations decrease the control parameter (or keep it constant)"
156
157Other values will work as well.
158
159<MiniRepl
160  client:idle
161  tune={`
162s("saw")
163  .lpf(600)
164  .lfo({r: 2, da: "200", dcoffset: "<-0.5 -1 -0.5 0>"})`}
165/>
166The marvellous alias of `dcoffset` is `dc`.
167
168## Shape
169
170You can change the shape of the modulation with `shape`. The default is `triangle`, but other shapes are available too:
171
172<MiniRepl
173  client:idle
174  tune={`
175s("saw")
176  .lpf(800)
177  .lfo({r: 2, shape: "<triangle sine ramp saw square>"}) `}
178/>
179You can add `._spectrum()` to see the shape of the modulations in the spectrum.
180
181You can also get these shapes by using numbers:
182
183| Shape    | number |
184| -------- | ------ |
185| triangle | 0      |
186| sine     | 1      |
187| ramp     | 2      |
188| saw      | 3      |
189| square   | 4      |
190
191this way, you can use a function like `irand` to generate numbers:
192
193<MiniRepl
194  client:idle
195  tune={`
196s("saw")
197  .lpf(800)
198  .lfo({r: 2, shape: irand(4)}) `}
199/>
200
201The alias of `shape` is `sh`.
202
203## Skewing some shapes
204
205You can influence some of the shapes (`triangle` and `square`) in more detail.
206
207The default skew is 0.5, it does different things for these two.
208
209For `triangle`, it skews the top of the triangle to the left or right,
210where 0 makes it look like `saw` and 1 makes it look like `ramp`.
211
212For `square`, the skew influences the pulse width (see reference for `pulse` and `.pw`):
213
214<MiniRepl
215  client:idle
216  tune={`
217s("saw")
218  .lpf(800)
219  .lfo({r: 2, sh: "<triangle square>/5", 
220    skew: "<0 0.25 0.5 0.75 1>"}) `}
221/>
222The alias of `skew` is `sk`.{' '}
223
224## Curve
225
226You can change the curves of your lfo and can make it more intense.
227The default value is 1. Writing numbers greater than one can make it more intense,
228numbers between 0 and 1 will make it less intense.
229This will raise the lfo to the power of curve, so larger numbers (such as 10) can have very unexpected results.
230
231<MiniRepl
232  client:idle
233  tune={`
234s("saw")
235  .lpf(800)
236  .lfo({r: 2, sh: "<triangle>", curve: "<1.3 1 0.75 1>"}) `}
237/>
238## Referencing your lfos with id
239
240All the lfos are numbered from the first starting with 0, the second having an `id` of 1 and so on.
241
242You can refer to this in a later call if you want to modify a specific `lfo`.
243
244The `id` is outside of the config json (which is different from the other parameters of `lfo()`
245
246Try out how the sound changes when you replace the 0 with a 1.
247
248<MiniRepl
249  client:idle
250  tune={`
251s("saw").lfo().lpf(800)
252.lfo({s: "<4 8 0.5>"})
253.sometimes(x => x.lfo({dr: "4"},0))`}
254/>
255
256You can also name your lfos and refer to them by name, using the `id` parameter
257
258<MiniRepl
259  client:idle
260  tune={`
261s("saw").lfo({}, "lfo_freq_saw").lpf(800)
262.lfo({s: "<4 8 0.5>"}, "lfo_lpf")
263.sometimes(x => x.lfo({dr: "4"},"<lfo_lpf lfo_freq_saw>"))`}
264/>
265
266## FX index
267
268If you are using `FX()` to reorder your effects, you dont need to write your lfos inside the `FX`,
269but instead can refer to them by their FX index (starting with 0)
270
271<MiniRepl
272  client:idle
273  tune={`
274s("saw").FX(
275  distort(3),
276  gain(0.3), // this has the fx index 1
277  lpf(400)
278).lfo({s: 16, dr:2, c:"gain", fxi: 1})`}
279/>
280
281## Modulating other LFOs with Sub-control
282
283LFOs can modulate other lfos and will modulate their frequency (given by `r` or `s`):
284
285<MiniRepl
286  client:idle
287  tune={`
288s("saw").lpf(400).gain(0.8)
289  .lfo({s: 16, dr:2, c:"gain"})
290  .lfo({s: 0.3, dc:-1, dr: 0.8})`}
291/>
292To modulate other parameters of the first lfo (like `skew`, `depth` and so on), we can specify this with `subControl`or
293its alias `sc`
294
295<MiniRepl
296  client:idle
297  tune={`
298s("saw").lpf(400).gain(0.8)
299  .lfo({s: 4, dr:2, c:"gain"})
300.lfo({s: 0.3, sc: "skew"})`}
301/>