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/>