Низкочастотные осцилляторы (LFO) 🌀 Bulka

Низкочастотные осцилляторы (LFO)

LFO (низкочастотный осциллятор) — распространённый на синтезаторах способ непрерывно модулировать различные сигналы.

Эта документация — интерактивная версия туториала от glossing https://www.youtube.com/watch?v=11frBA9L638

Сигналы против LFO

В Strudel/Bulka есть два способа модуляции:

  1. сигналы для модуляции на уровне паттерна
  2. lfo (эта страница) для модуляции на уровне звука

Применение LFO

Здесь LFO будет менять частоту saw. Поставьте комментарий вот так //.lfo(), чтобы увидеть и услышать, как меняется звук, а затем снова уберите комментарий.


s("saw").lfo() 
.lpf(800)
._spectrum({height: 300, width: 800})

По умолчанию LFO модулирует тот управляющий параметр, который стоит прямо перед .lfo():


s("saw")
.lpf(800).lfo() 
._spectrum({height: 300, width: 800})

Здесь LFO будет модулировать фильтр низких частот .lpf.

Отходим от значений по умолчанию

В следующих разделах объясняется, как передавать параметры в .lfo. Как и у ._spectrum выше, почти вся конфигурация lfo находится внутри json-объекта, начинающегося с { и заканчивающегося на }. Все параметры (кроме id) записываются внутри как key: value и разделяются ,.

В справочнике они обозначаются как config.key, то есть для следующего примера как config.control, но использовать их нужно так, как показано ниже.

Control

control определяет, какой параметр будет модулироваться. Это позволяет размещать lfo в разных местах, необязательно сразу после управляемого параметра.

Здесь мы ставим lfo после s, но он модулирует фильтр низких частот.


s("saw")
.lfo({control:'lpf'})
.lpf(800)
._spectrum({height: 300, width: 800})

Можно даже влиять на параметры, которые присутствуют всегда, даже если вы не написали их явно, например gain.


s("saw")
.lfo({control:'gain'})
.lpf(800)
._spectrum({height: 300, width: 800})

У control есть псевдоним c.

Rate

rate определяет, как часто lfo осциллирует в секунду:


s("saw")
.lfo({c:'gain', rate:"<2 4>"})
.lpf(800)
._spectrum({height: 300, width: 800})

Псевдоним rater.

Sync

Вместо того чтобы управлять частотой через rate, задавая её в герцах, вы можете использовать sync, чтобы синхронизировать свой lfo с остальными паттернами.

sync выражает эту частоту как «число раз за цикл»

Попробуйте убрать sync: "<2 4 8 0.5 >" из паттерна и заметьте, что что-то оказывается не в такт.


$: s("bd*4").bank("TR909").postgain(0.5)
$: s("saw")
.lpf(800)
.lfo({sync: "<2 4 8 0.5 >"})
._spectrum({height: 300, width: 800})

В параметры lfo можно подставлять паттерны, если хотите, чтобы они менялись со временем, как показано выше.

Относительная глубина

.depth — это относительная глубина, значение 1 (по умолчанию) означает, что значение уходит вверх и вниз на половину модулируемой величины.

Например, при глубине 1 частота модулируется так, что осциллирует между 32 = (64/2) и 96 (= 64 + 64/2).


s("saw").freq(64)
.lfo({r: 2, depth: "<1 2 3>"})
.lpf(800)
._spectrum({height: 300, width: 800})

freq здесь не обязателен, так как это частота по умолчанию, он приведён лишь в наглядных целях.

Псевдонимы depthdr и dep.

Абсолютная глубина

depthabs задаёт абсолютную глубину модуляции. Например, вы можете модулировать фильтр низких частот ровно на 250 Гц вверх и вниз:


s("saw")
.lpf(800)
.lfo({r: 2, depthabs: "250"})

Псевдоним depthabsda.

Смещение постоянной составляющей (DC offset)

Если вы не хотите уходить вверх и вниз на одинаковую величину при модуляции, вы можете сместить центр модуляции с помощью dcoffset. Значение по умолчанию — -0.5, это середина между: “dcoffset = 0: все модуляции увеличивают управляющий параметр (или оставляют его неизменным)” “dcoffset = -1: все модуляции уменьшают управляющий параметр (или оставляют его неизменным)”

Другие значения тоже работают.


s("saw")
.lpf(600)
.lfo({r: 2, da: "200", dcoffset: "<-0.5 -1 -0.5 0>"})

Восхитительный псевдоним dcoffsetdc.

Форма

Форму модуляции можно менять с помощью shape. По умолчанию это triangle, но доступны и другие формы:


s("saw")
.lpf(800)
.lfo({r: 2, shape: "<triangle sine ramp saw square>"}) 

Вы можете добавить ._spectrum(), чтобы увидеть форму модуляций в спектре.

Эти же формы можно получить с помощью чисел:

Формачисло
triangle0
sine1
ramp2
saw3
square4

таким образом, вы можете использовать функцию вроде irand для генерации чисел:


s("saw")
.lpf(800)
.lfo({r: 2, shape: irand(4)}) 

Псевдоним shapesh.

Перекос некоторых форм

На некоторые формы (triangle и square) можно влиять более тонко.

Перекос по умолчанию — 0.5, и для этих двух форм он делает разные вещи.

Для triangle он смещает вершину треугольника влево или вправо, где 0 делает его похожим на saw, а 1 — на ramp.

Для square перекос влияет на ширину импульса (см. справочник по pulse и .pw):


s("saw")
.lpf(800)
.lfo({r: 2, sh: "<triangle square>/5", 
  skew: "<0 0.25 0.5 0.75 1>"}) 

Псевдоним skewsk.

Кривая

Вы можете менять кривые своего lfo и делать его более интенсивным. Значение по умолчанию — 1. Числа больше единицы могут сделать его более интенсивным, числа между 0 и 1 — менее интенсивным. При этом lfo возводится в степень curve, так что большие числа (например, 10) могут давать совершенно неожиданные результаты.


s("saw")
.lpf(800)
.lfo({r: 2, sh: "<triangle>", curve: "<1.3 1 0.75 1>"}) 

Ссылки на ваши lfo через id

Все lfo нумеруются: первый начинается с 0, второй имеет id 1 и так далее.

Вы можете сослаться на этот номер в последующем вызове, если хотите изменить конкретный lfo.

id находится вне json конфигурации (в отличие от остальных параметров lfo()).

Попробуйте, как меняется звук, когда вы заменяете 0 на 1.


s("saw").lfo().lpf(800)
.lfo({s: "<4 8 0.5>"})
.sometimes(x => x.lfo({dr: "4"},0))

Вы также можете давать своим lfo имена и ссылаться на них по имени, используя параметр id


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

Если вы используете FX() для переупорядочивания своих эффектов, вам не нужно писать свои lfo внутри FX — вместо этого можно ссылаться на них по их индексу FX (начиная с 0)


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})

Модуляция других LFO через Sub-control

LFO могут модулировать другие lfo и будут модулировать их частоту (заданную через r или s):


s("saw").lpf(400).gain(0.8)
.lfo({s: 16, dr:2, c:"gain"})
.lfo({s: 0.3, dc:-1, dr: 0.8})

Чтобы модулировать другие параметры первого lfo (например, skew, depth и так далее), мы можем указать это с помощью subControl или его псевдонима sc


s("saw").lpf(400).gain(0.8)
.lfo({s: 4, dr:2, c:"gain"})
.lfo({s: 0.3, sc: "skew"})