diff --git a/website/src/pages/learn/lfo.mdx b/website/src/pages/learn/lfo.mdx
new file mode 100644
index 00000000..d4dfd273
--- /dev/null
+++ b/website/src/pages/learn/lfo.mdx
@@ -0,0 +1,294 @@
+---
+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
+
+## 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.
+
+
+
+By default, the LFO will modulate the control parameter which is right before `.lfo()`:
+
+
+
+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.
+
+
+You can even influence parameters which are always present, even if you haven't explicitly written them, like `gain`.
+
+
+
+`control` has an alias `c`.
+
+## Rate
+
+`rate` determines how often the `lfo` oscillates per second:
+
+"})
+ .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.
+
+"})
+ ._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).
+
+"})
+ .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:
+
+
+
+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.
+
+"})`}
+/>
+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:
+
+"}) `}
+/>
+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:
+
+
+
+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`):
+
+/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.
+
+", 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.
+
+"})
+.sometimes(x => x.lfo({dr: "4"},0))`}
+/>
+
+You can also name your lfos and refer to them by name, using the `id` parameter
+
+"}, "lfo_lpf")
+.sometimes(x => x.lfo({dr: "4"},""))`}
+/>
+
+## 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)
+
+
+
+## Modulating other LFOs with Sub-control
+
+LFOs can modulate other lfos and will modulate their frequency (given by `r` or `s`):
+
+
+To modulate other parameters of the first lfo (like `skew`, `depth` and so on), we can specify this with `subControl`or
+its alias `sc`
+
+