-
Notifications
You must be signed in to change notification settings - Fork 1
Planning: ADSR LFO controls
When controlling volume over time, or filter shift over time, etc, ADSR envelopes and LFOs can be used.
In the context of volume enveloping, ADSR and LFOs can be applied to individual frequencies, such that the volume of one frequency is described differently than for another frequency. This also applies to panning (and panning within the delay effect).
In the context of a frequency filter, the controls merely describe the shift (any maybe stretch) of the filter envelope. It's not very intuitive to have the filter be shifted differently for each frequency. Then the filter shape actually loses meaning.
We need to determine the appropriate control scheme for each of these scenarios.
A decent control scheme is env(idx, t) = ADSR(idx, t) * (1+LfoDepth*sin(LfoPhase(idx, t))
.
ADSR(t) is already pretty well-defined: basic ADSR envelope with duration stretched by a factor influenced by idx
.
LfoPhase(idx, t)
is currently just some constant multiplied by t, such that all partials have the same LFO frequency. For volume / panning, it would be beneficial to have something like the ADSR stretch
parameter also for LFO. This could be linked to the ADSR stretch knob for UI simplicity, though that doesn't have any performance benefits. It would actually be very useful to have the frequency be controlled by an ADSR envelope.
LfoDepth
could also be more usable if it were controlled via an ADSR. However, it need not start and end at a level of 0. If we define a 3-point envelope, that may give sufficient control: startLevel, attackTime, sustainLevel, releaseLevel, releaseTime (plus a stretch parameter). The envelope becomes a linear interpolation from start to sustain, and then sustain to release when the note is released. This also solves the issue of needing another knob for the total depth. This new envelope type would be better for the LFO frequency control as well.
This could be compatible with the other ADSR by modifying the other implementation to support a release level, and then setting A=0, multiplying everything by startLevel, D=attackTime, S=sustainLevel/startLevel, R=releaseTime, and then releaseLevel=releaseLevel/startLevel.
The simplest control scheme is offset(t) = ADSR(t) + LfoDepth*sin(LfoPhase(t))
Just like with the volume envelope, it would be more useful if LfoDepth were controlled via an ADSR-like envelope. The one described above should work fine, just without the frequency-specific stretch parameters.
Per-partial detuning can be accomplished via detune(t) = depth*rand(idx, seed)*(ADSR(idx, t) + LfoDepth*sin(LfoPhase(idx, t))
. Note that this is separate from pitch-bending.
Depth
and seed
can be constant (or automated externally). LfoDepth
and LfoFrequency
should be ASR-type envelopes just like in the volume envelope. In fact, the depth parameter can be moved right into the ADSR envelope. Then we just have detune(t) = rand(idx, seed)*(ADSR(idx, t) + LfoDepth*sin(LfoPhase(idx, t))
.
It's not obvious to me if the LFO should be multiplied by the random value, or added as above. The former would create more "randomness", whereas the latter would have more predictability, as the entire sound gets detuned together, or as a sweep-type function. Keeping it as addition allows for one to detune the entire sound as one, detune the individual harmonics randomly, OR do some mix of the two, so it seems the best method.
Having ADSR controls for both LFO depth & frequency in the volume envelope means at least 10 parameters, so maybe it should be considered as a different "stage" than the ADSR volume envelope. On the other hand, LfoDepth can be configured to initially just display a constant until clicked to expand, as could the LFO frequency. Then the interface becomes manageable.
For the frequency-based filtering, as well as detune envelope, it only really makes sense if ADSR and LFO are all displayed together. Therefore, for consistency, it seems better to organize them the same way for the volume enveloping.