---
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 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"})`}
/>
