Basic Envelope Follower + Meter (Need some advise)


#1

Hi
I’m trying to design a simple rectangle meter . The result is not satisfactory since its jumpy and not accurate. I will lay out what i have done and maybe experts could give me some advise on this.
in my processor (inside process block):

float a = pow( 0.01, 1.0 / ( Attack_ms * getSampleRate() * 0.001 ) );
float r = pow( 0.01, 1.0 / ( Release_ms * getSampleRate() * 0.001 ) );
float envelope = 0;
for(int i=0; i<buffer.getNumSamples(); i++)
{
// Envelope Follower —
floate envIn = fabsf (channelData[i]);
if (envelope < envIn)
envelope += a * (envIn - envelope);
else if (envelope >= envIn)
envelope -= r * (envelope - envIn);
ChannelData[channel] = envelope;
}

And in processorEditor timerCallback something like this :

//Setting the height
LevelMeter.SetHeight(processor.ChannelData[i]);

And drawing and repainting in LevelMeter Component:

void LevelMeter::paint (Graphics& g)
{g.fillRect (0.0,0.0,getWidth(),(HeightVal*getHeight());}
void LevelMeter::SetHeight(float Height)
{HeightVal = Height; repaint();}

a) UI: Is this the right approach to drawing the meter?
b) DSP: Why I’m getting incorrect values from envelope follower? It moves up and down (Seams like low frequency) even when the input has a static level. It doesn’t react any good to fast hits either.
Thanks in advance.


#2

Search the codebase for SimpleDeviceManagerInputLevelMeter, and have a look at how that’s done.


#3

Hello ! I have a few remarks about your code :

  • Your enveloper follower is wrong because it should add and not substract the term for the release to your envelope state variable.
  • Moreover, such an envelope follower would make some sense only if the release constant time is a lot higher than the attack constant time.

If you do so, you will have something that looks like a meter. But coding a proper meter isn’t an easy task…

  • The envelope follower is used in general for the UI instead of in the DSP classes. Indeed, if you do a peak-style meter, you want the maximum peak value to be displayed, not to be affected by an envelope follower…
  • It is better to use a HighResTimer in this situation than a simple Timer, so your repainting is put in another thread than the other UI tasks
  • In general, a buffer is used for the calculation of the value to display which has a fixed size. With your implementation, the behaviour of the meter changes depending on the audio buffer size, which is not right…
  • Finally, the communication between the DSP and the UI is better handled by lock-free structures, so it is thread-safe without any potential deadlock or any wrong concurrency coding issues. The reason why we care about that is because your algorithm might return wrong values in “stress situations”, when the CPU is already being used a lot by other plug-ins. A way to do so in your implementation would be for example to make your ChannelData variable an Atomic instead of a simple float. See also that thread for more information : Concurrency, meters, JUCE classes

#4

Thanks jules, i found that code, it was helpful.

The envelope follower in my code were wrong, i changed it to something like this:

normIn = fabsf (channelData[i]);
if(normIn >= envout) c = attack;
else c = release;
envout = c * (envout - normIn) + normIn;
MyChannelData[channelID] = envout;

Which is not bad. but i think it needs a peak detector somehow to make it smoother.
Maybe something like this to find the max in a number of samples…

for(int i=0; i<buffer.getNumSamples(); i++)
{
if(i%1024==0){pkout=0;}
pkin = std::abs (channelData[i]);
pkout=jmax(pkout, pkin);

  • envelope code (with pkout);
    }
    I don’t know…
    But i don’t think we need a hiResTimer for this, UI repainting seems fine.