anthonyalfimov / Stable-Delay

AU, VST3. A WIP delay/chorus/flanger plugin
GNU General Public License v3.0
5 stars 1 forks source link

Gain staging overhaul: level-agnostic Drive #62

Open anthonyalfimov opened 3 years ago

anthonyalfimov commented 3 years ago

Currently, the dry signal in the plugin is affected by the Input and Output gain stages, as well as the output soft clipping (#28).

This is reflected in the plugin UI: the "Mix" control is located on the FX Panel that is positioned between Input Panel and Output Panel. This corresponds to the "Mix" knob blending the output of the Input Gain "module" with the output of the FX "module".


Reasoning

Dry signal affected by Input and Output gain stages

Output gain stage acts as a master output level control. As such, it needs to affect the whole output.

Input gain stage affecting the dry signal ensures that at 50% "Mix" the delayed and the dry signals have the same level (provided "Feedback" is set to a sensible value).

The delay line of the FX "module" contains a soft clipping/folding stage to prevent runaway feedback. The threshold of the clipping stage is fixed. Therefore, the Input gain stage can be used to drive the delayed signal more or less into the clipper. The Output gain stage allows to compensate for that.


Dry signal affected by Output soft clipper [#28]

The wet signal is always clipped to 0 dBFS by the delay line saturation stage. However, the output signal still can clip when the wet signal is blender with the dry one. An extra 0 dBFS soft clipper is added right before the Output gain stage to ensure the plugin never clips unless you specifically boost its output.

The amount of dry signal saturation is indicated with colour of the Input Meters. Just like with the delay line saturation, the output clipping can be "bypassed" by lowering the input gain and boosting the output.


Issues

1. Delay line saturation and output clipping cannot be controlled separately

Both clipping modules in the plugin use the same fixed 0 dBFS threshold. The amount of clipping in both cases is controlled by the same Input Gain parameter. Therefore, two saturation stages cannot be controlled independently.

You can't have a mix of saturated delayed signal with a clean dry signal.


2. Level balance when using runaway feedback (around or over 100%)

The feedback is always clipped at 0 dBFS, regardless of the input signal level. Therefore, the wet signal level can become significantly higher than the dry level.

The difference can be compensated by setting the "Mix" knob lower, but this reduces the usable range and precision of the "Mix" control. And if the input level is sufficiently low, this might even be impossible.

The difference can also be compensated by increasing the Input gain. However, the dry signal will become more saturated due to the output soft clipping. The Output gain must be adjusted accordingly to compensate for the increased Input gain.


EDIT: Runaway feedback usually has pretty small dynamic range (difference between peak and rms level). In this case, clipping of the dry signal can actually help to match the perceived loudness of the feedback.


3. Level drop due to output soft clipping

If the input signal level is close enough to 0 dBFS, the plugin introduces an unavoidable level drop. This happens even when both gain controls are set to unity and the "Mix" control is at 0% (all dry). This level drop is caused by the output soft clipping curve across the whole plugin output.


4. The presence of soft clipper before the Output gain stage is not obvious, and its behaviour can be confusing.

There's nothing in the plugin UI that suggests there's a clipper in front of the Output gain stage.

The Input Meters indicate the amount of dry signal drive both into the Delay line clipper-folder and the Output clipper.

So you might get the impression that any clipping is happening right after the Input gain stage. Practically, the differences between this and reality are: runaway feedback gets clipped (and clipping occurs before the signal written into the delay buffer), and wet signal gets clipped / saturated twice. This is not necessarily important to understand to use the plugin.

However, the fact that the dry signal is getting clipped at all is not clear and is not always desirable. Commonly, delay plugins apply any sort of "drive" only to the wet signal. This is one of my first derived clipping curves and I like how it sounds, so I want it to be easy to use. But this is not practical in a delay plugin, and it should be easy not to use it.

Finally, the interaction of the Output soft clipper with the Output gain stage can be confusing. The output of the plugin will never clip if the Output gain is set to 0 or lower, but it is not communicated in any way. If the user assumes that clipping occurs right after the Input gain stage, this will make sense. But if we clarify that this is Output clipping, then you would expect that the plugin output will never clip, that the clipper follows after the Output gain stage.

Positioning the clipper at the very end of the chain, of course, will make it harder to use in creative purposes, i.e. to deliberately saturate the signal. But is this really needed in a delay plugin?

anthonyalfimov commented 3 years ago

I believe that the reasoning for the Dry signal being affected by Input and Output gain stages still holds well enough. There are better ways to achieve the same result, but they likely require significant changes to the plugin structure. For now, changes like this lie outside of the project scope.

For example, the "Input Gain" control could be abandoned in favour of a "Drive" control. It could directly control the threshold of the delay line clipping / folding stage.

[The problem here is that a "Drive" control like this does not belong on the Input Gain Panel. It should be a part of the FX Panel, since it only affects the feedback loop in the delay line. This breaks the symmetry of Input and Output Gain Panels, unless there's still some reason for the Input Gain control. If not, the whole existence of the Input Gain Panel is in question. And that leads to a major UI redesign.]


The Dry signal being affected by Output soft clipper appears to be the weaker point out of the two. The original plan #28 for the output clipping intended it to be optional. This would at least partly solve issues 1 and 3.

Originally it was also considered to put the output clipping after the Output Gain stage. This makes it easier to avoid output saturation and solves issue 1. On the other hand, it makes it harder to actually use output saturation when you want it. This way, it becomes just a "safety clipper".

anthonyalfimov commented 3 years ago

Summary on the titular question:

anthonyalfimov commented 3 years ago

An option to remedy issue 1 and, to some extent, 2 in the meantime is to lower the fixed clipping threshold in the delay line. This decouples the delay line saturation from the master output clipping to a degree.

anthonyalfimov commented 3 years ago

Replaced the "Input Gain" parameter with a "Drive" parameter that controls the threshold of the delay line saturator module and compensates for approximate equal loudness. This solves issues 1 and 2.


A number of related tasks remains.

Fixes

Usability

Output clipper

OR

Enhancements

Under the hood

anthonyalfimov commented 3 years ago

"Drive" parameter effectively lowers the Delay Saturator threshold: it increases the signal gain before the saturator and lowers it after. To compensate for loudness loss due to soft clipping, when pre-boost is +X dB, the post-cut is -X/2 dB.

When the input signal level is low, increasing "Drive" will result in boosting the wet signal by +X/2 dB until the signal reaches the Saturator threshold.

The Input Panel toggle control could be used to address this issue. The most straightforward solution is to allow for lowering the Saturator threshold without gain compensation.

The following ideas are not exclusive and can be used in a combination.


  1. Use "Boost" toggle to lower the Saturator threshold by a fixed amount (8..12 dB). This allows to get low signals closer to the threshold without audible wet signal boost. However, when the signal is already driven well past the threshold, enabling this option results in wet level drop (and more distortion, of course). This behaviour is counter-intuitive. The effect is not too dramatic, and it favours smaller amounts of "boost". Perhaps, this wet level drop can be sufficiently explained if we visualise the Saturator threshold. Or, there could be a better name than "Boost".

  1. Lower the Saturator threshold to a sensible value (-6..-12 dB) suitable for common track levels. Use a "Pad" toggle to bring the threshold to 0 dBFS. This would also solve the "clipping when "Drive" is set to 0 and "Feedback" is set to over 100%" issue. However, this strongly depends on what is considered a "common track level". The louder we expect an average track to be, the less range this approach will allow. The "Pad" parameter name fits well in this case and should be intuitive. Enabling the "Pad" would bring up the runaway feedback level. But since "Pad" is designed to be used with "hot" signal, this should not be a problem in most cases.

  1. Use a non-linear post-gain dependency on the pre-gain. Instead of fixing it to -X/2 dB, use a more complex dependency on the amount of drive X. Some ideas:

Introduce a minimum amount of cut to help taming runaway feedback clipping. Ease in the amount of gain compensation as the "Drive" value is increased. Reduce the amount of gain compensation as the "Drive" value is increased.


  1. Modify the saturation curve to negate the +X/2 dB boost at low levels. Change the curve so that it settles to a -X/2 dB cut sufficiently below the threshold. This approach should allow to just extend the range of the "Drive" control to accommodate low-level inputs. Alternatively, we could add a "ledge" to the curve, a "secondary threshold" to add some saturation for lower levels.

Screenshot 2021-08-06 at 21 56 42

EDIT: Tested this approach, it works. Feedback compensation works well, it needs to be adjusted to counteract -X dB cut instead of the -X/2 dB one. Gain compensation of +X/2 dB feels excessive, so it could be either reduced, or idea 3 could be employed. The sound of the modified curve is very interesting. It combines light expansion with saturation of just the "expanded" portion of the waveform. But this sound might be too aggressive for the delay line application.


  1. Use a ballistic filter reading to place the "Drive = 0" threshold position. Our current clipper is transparent 10 dB below its threshold. We could dynamically place the threshold 6-10 dB above the current input peak level (at "Drive" set to 0). Then increasing "Drive" would result in consistent amount of saturation regardless of the input level. The possible "starting" threshold position should be clamped to a reasonable range: no more than 0 dBFS, and not too low to avoid bringing up the noise floor too much (-18..-10?). But how would it affect the feedback? We'd need to dynamically compensate the amount of cut we apply to lower the threshold. And the self-oscillation level will always be somewhat slaved to the input level.
anthonyalfimov commented 3 years ago

Option 5, Dynamic Clipping Threshold, is now our main approach to gain staging the Drive section of the plugin.

Dynamic clipper threshold positioning has been implemented. Now the internal parameters have to be tweaked to achieve satisfactory saturation behaviour.

TO DO:


Internal Parameters

Detector Rise Time: 0.2 ms Detector Fall Time: 1200 ms Clipper Curve: beta Post-clipper Cut Factor: x0.65 Threshold Baseline Relative to Detector Level: +8 dB Drive Range: 0..+24 dB Minimum Threshold: -72 dB or no limit [?] Maximum Threshold: -1 dB or +8 dB or +5 dB

Output Gain Range: -10..+10 dB


Dynamic Clipping Threshold

Minimum Threshold Minimum threshold value should be as low as reasonable. Setting it too high can result in boosting the noise floor by up to 12 dB (when Drive is set to 24 dB).

Setting the minimum threshold to -72 dB affects an attack transient after silence almost identically to when it's set to -36 dB. Minimum threshold of -72 dB corresponds to the detector level of -80 dB (given that threshold baseline is detector +8 dB).

If gain-to-decibels conversion is used on the detector output, use the optional minusInfinityDb parameter of the Decibels::decibelsToGain() JUCE function to set the minimum value.

Maximum Threshold Maximum threshold value can be limited to ensure that wet signal can never clip. Note, however, that with dynamic clipping, this limit is not the primary way for containing runaway feedback. Therefore, it is not mandatory. Generally, the dynamic clipping and feedback compensation mechanisms keep the feedback peak level the same as the first delay.

The premise of dynamic clipping is to ensure that the saturation sounds the same regardless of the signal level. When limiting max threshold to -1 dB, this statement only holds true while the input peak level is below -9 dB. We might want to extend this to the whole digital scale up to 0 dBFS by extending the max threshold to +8 dB. After all, the plugin is not a limiter, and it even features clipping indication. If the input is clipping or close to it, it's not unexpected that the plugin output would clip too.

We could remove the limit entirely, but for extra safety I would rather keep it.

When dynamic threshold max limit is exceeded, the feedback becomes under-compensated. It decays until its level falls back into the threshold range, where it becomes properly compensated again. Then it continues to behave as expected from the plugin parameters. This behaviour is perfectly acceptable and doesn't require any correction.

Even if we still want to make sure that the wet signal can never clip, the -1 dB limit is excessive. For the current Drive configuration (-0.65x post cut), a +5 dB limit would be sufficient and completely safe.


Feedback Level Stability

Two goals with regards to the feedback level are

We have two possible approaches to this:

"Dynamic Tape"

Only the input signal level directly controls the clipping threshold. The feedback signal enables "threshold hold" when it exceeds the input level.

The idea is to achieve a behaviour that is most similar to a static threshold. The feedback decay is prevented because the threshold for feedback is quasi-static. The feedback signal can decay to a lower level where the attenuation from the clipping curve is negligible. The runaway feedback amplification is prevented by the quasi-static threshold as well.

Without additional modifications, this approach still allows a wide range of feedback level variation that depends on the "Drive" value.

"Limiter"

The clipper output level is measured and attenuated to not exceed a target. The target is set by measuring the input and feedback signal pre-clipping in some combination. Feedback decay is compensated by mixing in "dry" feedback sample.

This approach exerts much tighter control over feedback level, but is prone to audible artefacts.


We will proceed with the "Dynamic Tape" approach. It avoids compression artefacts and excessive feedback distortion of the "Limiter" approach.

The main problem of the "Dynamic Tape" approach is the range of possible feedback level variation. We will address this via Drive-dependent parameter mapping. E.g. the Feedback max value over 100% should depend on the current Drive value.

anthonyalfimov commented 2 years ago

Threshold fall time determines at which level it is help by feedback

Consider a situation where a short signal is fed into the plugin. The duration of this input signal is lower than the delay time. Hence, the signal and its delayed version do not overlap in time. The delay buffer remains empty until the first delay, and all this time the clipper threshold decays. The threshold will become held only when some signal is read from the delay buffer.

So, the level at which the clipping threshold will be held depends on the threshold fall time and the delay time. With sufficiently short fall time and long delay time, this can eliminate feedback completely.


Should we add some bias towards holding the threshold?

  1. Hold the threshold not only when the feedback sample is louder than the input sample, but when they are equal as well. This would only make a difference when the input goes completely quiet.

  2. Set a minimal level for input signal. When the input level goes below this level, the hold is enabled.

  3. Always fill the delay buffer with low-level noise. Beyond other potential benefits (enabling self-oscillation with no input, dithering for #63), this also acts as a bias when comparing input level and feedback level.

These biases, however, will practically prevent the clipping threshold from decaying after playback start.

Is there an alternative condition to enable hold?

anthonyalfimov commented 2 years ago

As mentioned, adding a bias towards holding the clipper threshold will impede the plugins ability to process dynamic signals. If a loud signal is followed by silence and then a quieter signal, the threshold might not be able to correctly adjust for the quiet part. It will remain held for the duration of silence between signals.


NB!: We don't have to measure the feedback signal level for setting the clipper threshold. The Feedback parameter tells us what the feedback level is going to be in comparison to the input level. When Feedback is greater or equal to 100%, we know that the threshold shouldn't be falling. This can be used as a hold condition.


I see three potential ways to react to this issue:

  1. Make the clipper threshold fall time depend on the Feedback parameter. When Feedback is greater or equal to 100%, the threshold should be held (infinite fall time). In addition, the fall time could also depend on the Delay Time for consistent repeat attenuation.

  2. Make the clipper threshold fall time depend on the Delay Time parameter. This would make the loudness of repeats consistent.

  3. Allow the feedback signal to raise the clipper threshold in a limited way. E.g. use a slow detector on the input signal to determine the maximum threshold that feedback could set. The fall time of this slow detector should depend on the Feedback parameter. Compared to (1), this approach would result in the clipper threshold following the contour of the signal more closely.