How to get a smooth peak/envelope follower?

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

Trying to find an envelope follower that's smooth and can be applied to chunks of 1024 samples.
Have tried the algorithm here: https://github.com/mbrucher/AudioTK/blo ... er.cpp#L65, which seems to be a fast peak follower, but which distorts the output (the envelope I'm getting is not with smooth changes, and it may be too fast).

What I need is an envelope that's fast enough to do its work in chunks of 1024 samples, but does not distort.

Or is it something e.g. thresholding that would get rid of the distorting? So that envelope wouldn't be reacting to the full bandwidth? Or do I need interpolation?

My application is similar to a peak limiter or a fast compressor.

Post

Typically an envelope detector would work like an iir filter-- The envelope detector runs across consecutive audio buffers from beginning of playback until stop.

Whatever is the per-sample behavior of the envelope detector, it does that for every sample in the current buffer, and then the envelope detector saves its current state. Then on the next buffer to process, the envelope detector reloads its saved state and continues on thru the next buffer as if all the buffers are one contiguous audio stream.

So the buffer size doesn't matter. If programmed to properly remember its state between buffers, the envelope will give the same output regardless how big or small are the buffers.

For instance that 1024 sample size, at 44.1 K samplerate, is about 23 ms. Unless the envelope detector can save its work and continue across buffers, it plainly can't work with attack or decay longer than 23 ms, and it would only be good for the full 23 ms if a transient accidentally happens at a buffer boundary. If a drum hit or guitar note happens to start near the end of a buffer, then there ain't time for more than a brief blip of envelope, unless the detector can continue on in the next buffer, treating all of the buffers as a long continuous stream.

Post

Fluky wrote:Trying to find an envelope follower that's smooth and can be applied to chunks of 1024 samples.
Have tried the algorithm here: https://github.com/mbrucher/AudioTK/blo ... er.cpp#L65, which seems to be a fast peak follower, but which distorts the output (the envelope I'm getting is not with smooth changes, and it may be too fast).

What I need is an envelope that's fast enough to do its work in chunks of 1024 samples, but does not distort.

Or is it something e.g. thresholding that would get rid of the distorting? So that envelope wouldn't be reacting to the full bandwidth? Or do I need interpolation?

My application is similar to a peak limiter or a fast compressor.
What do you mean by distort? If both times are identical, this is a linear filter, so it doesn't distort anything :?:
Can you show us an image of the issue? I have trouble following the problem (because it doesn't distort anything, it works perfectly in several compressors and expanders I made).

Post

Miles1981 wrote: What do you mean by distort? If both times are identical, this is a linear filter, so it doesn't distort anything :?:
Can you show us an image of the issue? I have trouble following the problem (because it doesn't distort anything, it works perfectly in several compressors and expanders I made).
I'm testing out the envelope by applying it to varying buffer sizes of audio and then applying the envelope straight to the output to hear its shape. The transitions it makes e.g. with 1024 buffer size are not smooth, but "jump" too much, which creates distortion. If I increase to 8192 it distorts less, the envelope sounds smoother, but not smooth enough.

So what I was wondering is, what I need to do to make it not distort, but so that the transitions between values of the envelope are smooth enough. Add longer buffer size? Or a ratio parameter or a threshold parameter?

Post

Strange, I never had any issue like this. I usually use a memory factor based on the exponential of the number of samples (https://github.com/mbrucher/ATK-plugins ... r.cpp#L157).
If you could provide the example you are using or a plot of the envelope? Otherwise, the only thing I can say is that you forgot something somewhere.

Post

Miles1981 wrote:Strange, I never had any issue like this. I usually use a memory factor based on the exponential of the number of samples (https://github.com/mbrucher/ATK-plugins ... r.cpp#L157).
If you could provide the example you are using or a plot of the envelope? Otherwise, the only thing I can say is that you forgot something somewhere.
Of course it will distort if the envelope has to move too fast between e.g. the full range of amplitude values as the curve it draws is more close to a square wave. Doing a test with an array of 1.0s and buffer size 128, I'm seeing step sizes as large as 0.2 (20% of the whole amplitude scale), if the attack or release is short enough, which is a very large step to be taken between two samples.

The value range for the ATK AttackReleaseFilter seemed to be 0.0-1.0, so when scaling the audio, which is (when the absolute value is taken) in the range of 0.0-1.0, those large changes can occur.

The timing does affect it, of course. Might try that "memory factor".

Post

The memory factor (for attack or release) is the amount of the past signal you retain. If you use an attack of 0, release of 0, then it's a signal follower. It won't distort anything, it will only follow the original signal. And with values higher (strictly inferior to 1), it will retain that fraction of the former signal and add (1-memory) of the new signal.
Of course you will get steps of 20% if you have a short attack and introduce a step to the filter!

Post

Actually there might be something wrong how I took that code since putting same attack and release does process the audio (or is this not what you meant by linearity?). I needed to translate it to use finite length std::vector and was wondering why the index i-1 started at -1. Figured that I'll shift that by one and initialize the output std::vector with a 0.0.

Post

ATK knows how to handle bounds. The attack/release filter has an input delay and an output delay of 1, meaning that the temporary buffers inside will be a copy of your input + the last additional element. Same for the output before it's handled by OutPointerFilter.
If you process 2 different vectors by the same filter without calling full_setup(), you will get an artefact at the transition, as it needs the last output value when it starts processing the next vector, which is the last one of the last buffer.

Post Reply

Return to “DSP and Plugin Development”