What's wrong with my envelope follower function?

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

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
Hi guys, I'm pretty new at this.
I'm working on a waveform generator that displays the wave visually.
Github: https://github.com/djleprechaunz/OscProject (https://github.com/djleprechaunz/OscProject)

Currently I'm wanting to implement and attack/release compressor, so it looks like I need to create a Envelope follower first.

So I figure it's a basic RMS of the (bufferSize) samples before it:

Here's my function:

Code: Select all (#)

	public double getValue(double t)
	{
		buffer.add(t); 
		
		Iterator<Double> itr = buffer.iterator();
		double sum = 0;
		while (itr.hasNext())
		{
			double value = itr.next(); 
			sum += Math.pow(value, 2.0); 
		}

		sum = sum/buffer.size(); 
		sum = Math.sqrt(sum); 

		return sum; 
		
	}
	
here's what it displays
Image

increasing the buffer size doesn't help.

Have I totally misunderstood how I implement an envelope follower, or am I on the right track?

Post

That's average power. So peaks will reach above that average power. This is normal. Detecting peaks is a different algo.

Post

I take it your buffer size is the same as the window size that you are using to extract the amplitude data from the signal? In that case, just keep track of the (absolute) maximum amplitude sample in each window to generate the envelope based on peaks.

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
That's average power. So peaks will reach above that average power. This is normal. Detecting peaks is a different algo.
Thanks.
I take it your buffer size is the same as the window size that you are using to extract the amplitude data from the signal? In that case, just keep track of the (absolute) maximum amplitude sample in each window to generate the envelope based on peaks.

No, the window just shows that much of the wave form. It's 1000 samples wide, and represents one second. The frequency ranges from 1 to 100hz.

The buffersize is arbitarily defined at the moment, I'm trying from 20 and 200 hundred samples.

I did maximum algo as you suggested:

Code: Select all (#)

	public double getValue(double t)
	{
		buffer.add(Math.abs(t)); 
		
		Iterator<Double> itr = buffer.iterator();
		
		double max = 0; 
		while (itr.hasNext())
		{
			double value = itr.next(); 
		
			if (value> max) max = value; 
		
		}
		return max; 
		
	}
	
Results: (with buffer of 20 samples).


Image

Image


this is actually pretty good.
Now I need to smooth those lines out - is this what they mean by filtering? What do I do? (research time).

But also, you notice that the jaggedness is worse with lower frequencies. What's the way to deal with this - do you just have to make your buffer suit your lowest frequencies?

Post

looks like a rectifier perhaps.
Maybe u could modify amplitude,
so we can see how the first example tracks?

Post

This is one with an amplitude envelope applied, if that's useful.

Image

reduced amplitude

Image

Post

it looks pretty good ty!
I can make some graphics from Flowstone,
if you would like to compare.
I actually used a Schmidt(shmitt?) trigger in one application for an envelope follower.

edit- oh yeah, I used a standard one in an auto filter

Post

If you want to smooth the lines out, you can do a couple things. The simplest option would be to add an else statement to your code that decreases MAX by subtracting a very small number when the current input is less than MAX. This will decay the envelope at a linear rate. You could also multiply MAX by a number close to 1, or do some exponential function.

If you want to define more of a filter that has specific controls for the attack and decay time of the envelope, take a look at this thread (http://music.columbia.edu/pipermail/mus ... 66267.html). I was able to use this in my plugin to create custom VU classes with specific attack and decay times. I am sure I am oversimplifying things, but a VU is basically an envelop follower (albeit with a 300ms attack and decay time).

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
^Ok, here's what we've got.

Using a buffer size of 200, with no smoothing function like you suggest:

Image
Image

Using a buffer size of 20, with no smooth function:

Image
Image

So the problem is that increasing the buffer size helps handle the lower frequencies, but then screws up the decay envelope.

So by adding the smoothing function you suggest, we can force the envelope to decay.

My smoothing function:

Code: Select all (#)

						if (value> max) max = value; 
			else max = Math.pow((max*SMOOTHING_MULTIPLIER),  SMOOTHING_EXPONENT); 
You need to use the SMOOTHING_SMOOTHING multiplier, as if the max value is close to 1, then the exponent won't have much effect.

With buffer size of 200, Exponent = 1.01 and multiplier = 0.99:

Image
Image

This this solves the problem that the large buffer size has with not following the decaying envelope. But we still have the problem with lower frequencies.

Image
Also - it's less effective for short envelopes.

I'm thinking in terms of the trade off between 'being able to follow a short envelope' and 'being smooth for low frequncies' this approach can't solve both.

Post

Unless you delay the signal to calculate feedforward and feedback values, it is always a compromise. You should use the method that best suits the purpose of the algorithm. A little ripple might be acceptable for a gain envelope, but might make a filter sound terrible. A very simple follower is to rectify the input signal and integrate, but this has attack lag and a good amount of ripple.

Post

camsr wrote:Unless you delay the signal to calculate feedforward and feedback values, it is always a compromise.
Yes, however even without delaying anything there are some tricks to mitigate the compromise. Some of the simple ones I'm going to share with you all.

The code examples will be in JesuSonic. It is either available as standalone or as VST plug-in. However the code is very simple to read and thus can easily be transfered into any other language.

The most simple envelope follower is a simple RC filter working on a rectified signal such as:

Code: Select all

/* Copyright (c) 2012, ro.cking
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 * THIS SOFTWARE.
 */

desc: RC (Envelope Follower)

@init
/* rc smoothing filter */
C = exp(-1/(25/1000*srate));

@sample
/* input is absolute value of left channel; so this is mono only! */
in = abs(spl0);

/* smooth via a rc low pass */
out = in>out ? in : in + C * (out-in);

/* output the envelope on both channels */
spl0 = spl1 = out;
This as already seen in this thread has the problem of ripple vs response.

A very simple trick is to use 3 (or more) peak followers in parallel that are reset in a round robin fashion as presented by Harry Bissell, Royal Oak also available as pdf.

Code: Select all

/* Copyright (c) 2012, ro.cking
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 * THIS SOFTWARE.
 */
/*
 * Based on http://www.edn.com/design/analog/4344656/
 *                 Envelope-follower-combines-fast-response-low-ripple
 */
desc: Triple Peak Hold (Envelope Follower)

@init
/* total peak hold chunk length; is good down to 50Hz !! */
t = srate/50;
/* total peak hold chunk length; is good down to 20Hz !! */
//t = srate/20;


/* peak hold 1 */
p1 = 0;
t1 = 0 * t/3;

/* peak hold 2 */
p2 = 0;
t2 = 1 * t/3;

/* peak hold 3 */
p3 = 0;
t3 = 2 * t/3;

/* rc smoothing filter; tune this to the total peak hold chunk length */
C = exp(-1/(20/1000*srate));

@sample
/* input is absolute value of left channel; so this is mono only! */
in = abs(spl0);

/* get new maximas */
p1 = max(p1, in);
p2 = max(p2, in);
p3 = max(p3, in);

/* get maximum of all peak holds */
p = max(max(p1,p2),p3);

/* increment peak hold times and conditionally reset the peak holds */
(t1+=1) >= t ? (p1 = t1 = 0);
(t2+=1) >= t ? (p2 = t2 = 0);
(t3+=1) >= t ? (p3 = t3 = 0);

/* smooth the peak hold output via a rc low pass */
out = p>out ? p : p + C * (out-p);

/* output the envelope on both channels */
spl0 = spl1 = out;
This also suffers ripple vs response. However with the peak hold time and also the number of peak holds combined with the RC smoothing filter it is very configurable and if you are willing to pay the processing cycles for additional peak holds can cover a vast range of frequencies with good ripple and response.

Then a more musical approach is to use the actual pitch of the signal to determine whether the envelope should decay fast or slow. This is mostly used in compressors for automatic release functions, however it will also work wonderful on the sine waves posted in this thread. A quick test revealed it will be smooth down to 1Hz! But still fast at 1000Hz. However it will not be fast at 1Hz, but in musical terms this is wanted behavior. But without further ado here it is.

Code: Select all

/* Copyright (c) 2012, ro.cking
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 * THIS SOFTWARE.
 */

desc: Pitch Aware (Envelope Follower)

@init
/* rc hp to remove dc */
Chp = exp(-2*$pi*20/srate);

/* filtering to smooth cylce length changes */
Cl = exp(-1/(5/1000*srate));

/* initial pitch */
pitch = 1000;

@sample
/* input is left channel; so this is mono only! */
in = spl0;

/* calculate lp coef; lp at the base freq so upper hmx dont interfere */
Clp = exp(-2*$pi*pitch/srate);

/* do lp 234523w3t5 to remove hmx freqs or formants */
in  = ylp = in + Clp*(ylp-in);

/* do hp to remove dc */
in -= yhp = in + Chp*(yhp-in);

/* count length between to zero crossings */
old_s = s;
s = sign(in);
old_s < s ? (
	l = cnt;
	cnt = 0;
);
/* filter this length to get a more steady read out */
len = l + Cl*(len-l);

/* calc pitch, cap this >20Hz so the lp ref 234523w3t5 isn't filtering too low */
pitch = max(srate/len,20);
cnt+=1;

/* input for envelope is absolute value of left channel; this is mono! */
in = abs(spl0);

/* calculate decay according to cycle length; after 20 cycles it will drop
 * to 36%, this should be smooth enough but yet fast enough
 */
rel = exp(-1/(len*20));

/* rc smoothing */
out = in > out ? in : in + rel*(out-in);

/* assign envelope to outputs */
spl0 = spl1 = out;
If this is for a compressor a simple RC envelope follower will most likely be enough. Believe me I've been there and done that even developed "better" approaches than the above presented and acoustically the only provide a minimal improvement over the RC approach at a heavy cost.

As you are trying to develop a compressor you might find the following free source code useful:

Code: Select all

/* Copyright (c) 2012
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 * THIS SOFTWARE.
 */
desc:FreeComp

slider1:0<-60,6,1>Threshold (dB)
slider2:1<1,100,1>Ratio (n:1)
slider3:20<0,250,1>Attack (ms)
slider4:200<25,2500,1>Release (ms)
slider5:0<0,30,1>Output (dB)

@init
/* set values for sample n=-1, that is what the state was/is before we got input 
 * this is more formal than anything, but if we do not set these we might have fade-ins
 * or other weird effects when starting processing, e.g. the gain reduction is at max
 * because we forgot to reset the 'final_gain' value ... see @sample section for what
 * it does, same goes for the 'env' (envelope)
 */
final_gain = 1;
env = 0;

@slider
/********************
 *  to 1) ENVELOPE
 *******************/
/* RC filter coefficient for envelope decay, we set this to 1/4 of the release
 * value to get a good smooth envelope, but still allow for _very_ fast release
 * timings
 */
env_rel = exp(-1/ (0.25*slider4*srate) );


/**************************
 * to 2) TRANFER FUNCTION
 ***************************/
/* the threshold's amplitude value */
thresh = 10^(slider1/20); /* convert dB from slider1 to amplitude */

/* pre-calculate values as described in @sample comments*/
transA = (1/slider2)-1;
transB = 10^(slider5/20) * pow(thresh,1-(1/slider2));

/* output_gain's amplitude value */
output_gain = 10^(slider5/20);


/*************************
 * to 3) GAIN FOLLOWER
 *********************/
/* calculate the RC coefficients for the attack and release filter parts */
att_coef = exp(-1 / (slider3/1000*srate) );
rel_coef = exp(-1 / (slider4/1000*srate) );

@sample

/***************************************************
 * 1) ENVELOPE
 * First step for an compressor is to get the level, that is
 * the envelope of the signal that will then later be used
 * to determine whether and also how much to compress etc..
 * Note: there are a lot of different ways to get this level:
 *       1) envelope follower (a.k.a. detector) (there are also different kinds of that)
 *       2) RMS averaging
 *       3) and many more
 * ... we will stick with simple here
 ************************************/
det = max( abs(spl0),abs(spl1) ); /* the detector input for the
                                   * envelope follower */

det += 10e-30; /* add tiny DC offset (-600dB) to prevent denormals
                * Note: JesuSonic doesn't need this because it prevents denormals
                * internally itself, but when you are going to port this code
                * you will need it
                */

env = det >= env ? det : /* ramp up instantly on attack so we
                          * have a peak compressor */
      det+env_rel*(env-det); /* otherwise we use a RC filter to
                              * produce a smooth decay
                              * We need 'env_rel' for this which we calculated in @slider
                              * Note: a simple decay like 'env=env*env_rel'
                              * would be "wrong" here (or a bad approach)
                              * while this is arguable
                              * I tell you why I think it's bad:
                              * imagine 'env' only being a _tad_ BIGGER
                              * than 'det' but 'env*env_rel' being SMALLER
                              * than 'det' what you would get would be a constantly
                              * readjusting envelope follower, which
                              * our instant ramp up (to make the follower
                              * peak detect) would make even worse
                              * ... anyway so far the envelope follower
                              */

/**************************************
 * 2) TRANSFER FUNCTION
 * The transfer function is the "command center" of a compressor
 * it basically decides on how much gain reduction should be applied
 * for a given input (level).
 * As always there are many different ways to do it, but in general
 * _any_ waveshaping function can be used here, but again we gonna
 * do it simple and use the typical equation:
 * Out[dB] = Thresh[dB]+(In[dB]-Thresh[dB])*Ratio[1/(n:1)], if In[dB] > Thresh[dB]
 *           In[dB]                                   , otherwise
 *                                                              (1)
 * Note: that these figures are all in dB and when we use the
 * sample values (which are given in amplitude) like this:
 * 'out = in>thresh ? thresh+(in-thresh)*ratio:in;'
 * (using the variables we declared in @slider)
 * we would get "wrong" results. We would get a curve that is
 * only linear in the amplitude space but in dB the curve would be inverse
 * non-linear, that is the gain reduction would _decrease_ at higher input
 * levels .... however many call this "vintage" or something, whatever
 * it could be used as a quick trade of for a CPU saving ratio implementation.
 * Anyway here we gonna use the above "correct" equation. So we need to 
 * convert the values into dB and then apply equation.
 * But first we need to change the equation to give us the 'gain'
 * we will need and we do this simply by subtracting In[dB] from
 * the left side of (1) which gives us:
 * Gain[dB] = -(Ratio[1/(n:1)]-1)*(Thresh[dB]-In[dB]), if In[dB] > Thresh[dB]
 *            0                                  , otherwise
 *                                                             (2)
 * Now we can already add our final output gain to (2) (to save
 * the extra scaling at the output later on) by simply adding it to
 * (2), and we have:
 * Gain[dB] = Output_Gain[dB]-(Ratio[1/(n:1)]-1)*(Thresh[dB]-In[dB]), if In[dB] > Thresh[dB]
 *            Output_Gain[dB]                                   , otherwise
 *                                                              (3)
 * Next we need to convert (3) to work with values in amplitude instead
 * of dB, so we need to convert the entire equation to give:
 * Gain[Amp] = 10^((20*log10(Output_Gain[Amp])-(Ratio[1/(n:1)]-1)*(20*log10(Thresh[dB])-20*log10(In[dB])))/20), if In[dB] > Thresh[dB]
 *             10^((20*log10(Output_Gain[Amp])                                                            , otherwise
 * which we can transform into:
 * Gain[Amp] = In[Amp]^(Ratio[1/(n:1)]-1)*Output_Gain[Amp]*Thresh[Amp]^(1-Ratio[n:1]), In[Amp] > Thresh[Amp]
 *             Output_Gain[Amp]                                                  , otherwise
 *                                                                              (4)
 * As we can see (4) now consists of "only" one raise to the Ratio'th power and a
 * multiplication because everything else can be precomputed, yay!
 * So we can pre-compute the two values:
 * 
 * TransA = (Ratio[n:1]-1)               (5)
 *
 * and
 *
 * TransB = (Output_Gain[Amp]*Thresh[Amp]^(1-Ratio[n:1])          (6)
 *
 * Combining (4), (5) and (6) we get
 * Gain[Amp] = In[Amp]^TransA * TransB  , In[Amp] > Thresh[Amp]
 *             Output_Gain[Amp]         , else                    (7)
 * which is the final transfer function that we will implement.
 * (Note: there might be much easier and faster ways to calculate a transfer curve)
 **************************************/

gain = env > thresh ? pow(env,transA)*transB: /* Transfer function as developed
                                        * thresh, transA and B pre-calced in @slider
                                        */
                        output_gain; /* output_gain also calculated in @slider */

/***********************************************
 * 3) GAIN FOLLOWER (the attack and release)
 * Now the final step applying the attack and the release.
 * There are also various ways to apply attack and release. Sometimes they
 * are already calculated in the envelope, so the envelope will ramp up and
 * decay according to the attack and release times, however, I don't consider
 * this a good approach because then the attack and release also affect the
 * non-linear transfer curve which they in my opinion shouldn't, but whatever
 * sounds good IS good, but since mostly _everything_ sounds good I actually
 * go by measurements most of the time and found out that by getting a good
 * and smooth envelope you get a smoother overall operation in the compressor,
 * read less distortion, which is better, but a lot of people argued this in the
 * past so I won't go into detail but rather will give you another reason for
 * _not_ botch the release and attack into the envelope is that when keeping
 * the gain follower separate you can exchange the "modules" as you like and
 * can work on improving each part individually.
 * Anyway there are two main types of attack and release curves:
 * Linear (where the gain in/decreases linearly overtime)
 * Exponential (where the gain in/decreases exponentially overtime)
 *
 * We will use an exponential curve, because it is smoother, easy to implement
 * and is in a sense level independent, because unlike a linear curve that
 * changes x dB over time, an exponential curve actually changes to x % of the initial
 * value over time.
 * A simple exponential curve can be done again with a simple RC filter, like the
 * envelope, however we use two cases one that deals with lowering (decaying)
 * the gain, that is the _attack_ (important here on a compressor the decay of
 * gain, that is the reduction of gain is the attack and _not_ the release (a.k.a.
 * decay)) and the increase of gain again, which is the release.
 * Equation would be something like: (using the real variables we will use in the code)
 * final_gain = gain+att_coef*(final_gain-gain) , if gain < final_gain
 *              gain+rel_coef*(final_gain-gain) , else
 ************************************************/

final_gain = gain < final_gain ? gain+att_coef*(final_gain-gain):
                                 gain+rel_coef*(final_gain-gain);

/***********************
 * 4) "VCA"
 * Luckily we are in the digital domain so applying gain is as simple as multiplication, literally.
 *******************/
spl0 *= final_gain;
spl1 *= final_gain;





/*********************************
 * ADDITIONAL NOTES
 *
 * The algorithm as implemented here is a particularly bad music processor.
 * ---------------------------------------------------------
 * A linear attack and release curve (linear in in/decrease of the _level's amplitude_) can be implemented with:
 * @slider
 * [...]
 * lin_att = 1/(slider3/1000*srate);
 * lin_rel = 1/(slider4/1000*srate);
 * [...]
 * @sample
 * [...] // replace old 3) GAIN FOLLOWER section with
 * final_gain = gain<final_gain ? max(gain,final_gain-lin_att):
 *                                min(gain,final_gain+lin_rel);
 * [...]
 * 
 * ----------------------------------------------------------
 * A linear attack and release curve (linear in in/decrease of _level in dB_) can be implemented with:
 * [...]
 * @sample
 * [...] // replace old 3) GAIN FOLLOWER section with
 * final_gain = gain<final_gain ? max(gain,final_gain*lin_att):
 *                                min(gain,final_gain/lin_rel);
 * [...]
 * 
 * ----------------------------------------------------------
 * Ratios < 1 will instantly turn this compressor in an expander ;) (just remember
 * that in that case the attack and release are swapped).
 * ----------------------------------------------------------
 * A Gate implementation can be achieved simply by changing the transfer function
 * to have gain of 0 (amplitude) when below the threshold and 1 (~0dB) when above
 * it. Just like with the expander, attack and release need to be swapped. A hold
 * is mostly always wanted when implementing a gate as well.
 * ----------------------------------------------------------
 * Implementing a little hold either in the gain follower or the envelope follower can
 * decrease distortion (due to (unnecessary much) amplitude modulation) at lower frequencies
 * but also making the (release) response of compressor slower. The current implementation
 * however should already be suitable for most applications. In fact the envelope's decay
 * (in combination with the gain followers release) is in some way already acting as a
 * hold.
 * Note: some people disagree with this and think that reducing the amplitude modulation
 * to the necessary minimum will inevitable make the compressors response way too slow
 * to be usable. Anyway judge for yourself.
 * -----------------------------------------------------------
 * TESTING the most underrated thing in programming.
 * While I was doing my research I've seen a few compressors that had "flaws" in
 * there implementation that would have been instantly discovered by the following
 * simple tests (I wrote "flaws" because maybe it was/is intended behavior):
 * Transfer function: disable the gain follower section and apply the raw gain from the
 * transfer function to the signal, then plot the curve and make sure it is what you
 * intended; a useful tool for this is Christian Budde's VST Analyser
 * (http://www.savioursofsoul.de/Christian/?page_id=106). This is especially advised
 * when you try implementing a soft knee curve.
 * Attack and Release curve: Use a white noise (or a sine wave) signal that
 * starts with a level below the threshold than instantly has its level increased to
 * go over the threshold and after a while instantly falling back down to its initial
 * level. Now by feeding this test signal through your compressor you can see (by looking
 * at the output waveform) what your attack and release are really doing.
 *****************************/
Anyway good luck.

EDIT: Sorry all for the rather longish post.
Last edited by ro.cking on Fri Dec 21, 2012 11:04 am, edited 1 time in total.

Post

Interesting post, but this goes too far:

"* The algorithm as implemented here was developed to be simple, but at the same time
* delivering highest quality suitable for all applications."

The reason relates to the fact that the proposed detector produces a substantially aliased control signal (i.e. totally inaccurate). It "looks" right at first, but it is particularly weak from the audio processing quality perspective. The log conversion, thresholding, soft-knee trickery, VCA and attack/release filters are all non-linear processes. They needs lots of care to work as expected, not just in the frequency domain, but in the time domain as well!

Such kind of compressor implementations fail to accurately track the actual waveform. The Nyquist theorem mentions an output filter! The data fed into the compressor input does not accurately represent the waveform, and it's pretty difficult to "control" the level of the material without knowing the true waveform.

Both issues go hand in hand and are definitely responsible for the horrible reputation digital dynamics processors have. This model works nice on paper because it ignores elementary facts such as aliasing and the nyquist theorem. It's a particularly bad music processor. I think you should correct the comment!
Fabien from Tokyo Dawn Records

Check out my audio processors over at the Tokyo Dawn Labs!

Post

ro.cking wrote:A very simple trick is to use 3 (or more) peak followers in parallel that are reset in a round robin fashion as presented by Harry Bissell, Royal Oak also available as pdf.

Code: Select all

[...]
@init
/* total peak hold chunk length; is good down to 50Hz !! */
t = srate/50;
/* total peak hold chunk length; is good down to 20Hz !! */
//t = srate/20;


/* peak hold 1 */
p1 = 0;
t1 = 0 * t/3;

/* peak hold 2 */
p2 = 0;
t2 = 1 * t/3;

/* peak hold 3 */
p3 = 0;
t2 = 2 * t/3;
Shouldn't that be t3 in the last quoted code line?
"Until you spread your wings, you'll have no idea how far you can walk." Image

Post Reply

Return to “DSP and Plugin Development”