What is wrong with this code?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

This one states benefits of feedback design are only relevant in analog, also without stating why (or what those benefits might be)

http://c4dm.eecs.qmul.ac.uk/audioengine ... ession.pdf

Basically a 'tutorial' composed of a list of recommended articles to be read :)

edit: There is an answer here : https://www.researchgate.net/publicatio ... d_Analysis
There are two possible topologies for the side-chain, afeedback or feedforward topology. In the feedback topology the input to the side-chain is taken after the gain has been applied. This was traditionally used in early compressors and had the benefit that the side-chain could rectify possible inaccuracies of the gain stage.
~stratum~

Post

Have you ever tried debugging it with comments and a calculator?
It's pretty obvious why it won't work.

In my comments, some numbers use . as decimal and some use , but don't let that worry you. Dots come from manual typing, commas from copy/pasting from/to Spotlight.

Code: Select all

// Assuming input samples *in1 == 0.001 and *in2 == 0.02

// Repeatedly calling function might return different sample rate mid-calculation
const double samplerate = GetSampleRate();

double gain = 1.0;						// initial vca gain
double seekgain = 1.0;					// guessing envelope gain modifier?

const double c = 8.65617025;			// divisor for dB to gain factor conversion

double t = 0.0;							// no clue

const double b = -exp(-62.83185307 / samplerate );
										// whut?! always similar! -exp(-62,83185307/44100) ==> -0,9985762559

double a = 1.0 + b;						// 1.0 + -0,9985762559 ==> 0,0014237441

const double threshDB = mThr; 			// threshold dB ==> -12 (assuming for calculations)
const double threshLin = threshDB/c;	// non dB gain ==> -1,3862943604
const double thresh = exp(threshLin);	// gain factor exp(-12/8,65617025) ==> 0,2500000002

const double ratio = 1.0/20.0;			// compression ratio 1/20  (assuming for calculations) ==> 0,05

// simplify calculations:
// 	10 * x / 1000 == x / 100
//	100 * x / 1000 == x / 10
// 	threshDB / x / c  == threshDB / c / x == threshLin / x
double attack  = exp( threshLin / (samplerate/100.0));	// exp( -12 / 441  / 8,65617025) ==> 0,9968614111
double release = exp( threshLin / (samplerate/10.0));	// exp( -12 / 4410 / 8,65617025) ==> 0,9996856969

double rms = std::max( fabs(*in1) , fabs(*in2) );		// stores larger of both fabs-ed input samples; max ( 0.001, 0.02 ) ==> 0.02

// a * rms = 0,0014237441 * 0,02 ==> 0,000028474882
// b * t   = -0,9985762559 * 0,0 ==> 0,0
// t = 0,000028474882 - 0,0 ==> 0,000028474882
// sqrt(t) ==> 0,005336186091
rms = std::max( sqrt( (t = (a*rms) - (b*t) ) ) , rms);	// stores larger value: max( 0,005336186091, 0.02 ) ==> 0.02

if (rms > thresh)						// rms == 0.02, thresh == 0,2500000002
{
	// Don't know if this ever becomes TRUE

	// rms ==> 0.02
	// ratio ==> 0.05
	// threshDB ==> -12.0
	
	// log(rms)*c = log(0,02) * 8,65617025 ==> -14,7065736072
	
	// exp((-12,0 + ( (log(0,02) * 8,65617025 - (-12,0)) * 0,05) ) / 8,65617025) / 0,02
	seekgain = exp((threshDB + (log(rms)*c-threshDB)*ratio) /c) / rms;	// seekgain ==> 12,3060973967
}
// since seekgain already reset to 1.0 initially, no ELSE block necessary

// gain ==> 1.0
// seekgain ==> 12,3060973967 
if (gain > seekgain)					// 1.0 > 12,3060973967 ==> false (never becomes TRUE)
{
	// gain ==> 1.0
	// seekgain ==> 12,3060973967
	// attack ==> 0,9968614111
	gain = std::max( gain*attack , seekgain );	// max( 1.0 * 0,9968614111, 12,3060973967 ) ==> 12,3060973967
}
else									// 1.0 <= 12,3060973967 ==> true
{
	// gain ==> 1.0
	// seekgain ==> 12,3060973967
	// release ==> 0,9996856969
	gain = std::min( gain/release , seekgain );	// min( 1.0 / 0,9996856969, 12,3060973967 ) ==> 1,0003144019
}

*out1 = *in1 * gain; // Ch1 sample = 0,001 * 1,0003144019 ==> 0,001000314402
*out2 = *in2 * gain; // Ch2 sample = 0,02  * 1,0003144019 ==> 0,02000628804
Your RMS is not an RMS calculation, it's just a L+R link. Whichever sample is louder will trigger the compressor and let it multiply both channels with the same gain. RMS means that you should store a window, that's the history of samples for a certain time frame, usually a few milliseconds, and then you sum them all up and divide them so you get an average sample value over that time frame/window.
Confucamus.

Post

About the feedback design and good sounding simple analog compressors: This looks like a polite way of saying if your envelope detector does not output true rms value then you should better be estimating the envelope from the output instead of input as this kind of feedback would tend to correct errors in the same way it does in analog compressors, which also do not have envelope detectors that output true rms value.
~stratum~

Post

stratum wrote:About the feedback design and good sounding simple analog compressors: This looks like a polite way of saying if your envelope detector does not output true rms value then you should better be estimating the envelope from the output instead of input as this kind of feedback would tend to correct errors in the same way it does in analog compressors, which also do not have envelope detectors that output true rms value.
But does that really matter? Is it supposed to be a measurement device or a creative sound device?

If a compressor uses feedback topology, it's usually better at reducing dynamic range, whereas a feedforward is better at increasing dynamic range. Just my 2c but I find this happens a lot.

Post

My first experiemce building simple compressors were optical compressors. Audio is made to light up some kind of light source, which is coupled to cds opto resistor which has high resistance in the dark and then resistance is progressively diminished as illumination is increased.

I read that some early devices used optical sensitive vacuum tubes but I never played with that or even saw one in the flesh. I saw circuits of such in old "circuit collection" design books so presumably some were actually made, dunno. Maybe in the era of early days of "talkies" the early films with sound?

Those cds cells, maybe some selected devices somewhere had linear resistance change according to light input, but in general it was not possible to use a cds optoisolator in a feedforward compressor. The resistance change simply wasn't linear enough. I suppose a person could glom together a complicated circuit to "linearize" a cds optoisolator, but would probably be a fairly elaborate compensation circuit with some trimmer resistors on the circuit board. Needing to trim each board to match the unique opto installed in each board.

So feedback design could get "fairly consistent" compressor behavior even with large part-to-part variations in optoisolators, none of them having linear light->resistance response.

It was similarly difficult to make a linear CV->Gain response using FET gain elements, so the easiest way to make a practical FET compressor was also feedback compressor. Feedback was also preferable for diode compressors.

The "linear enough" CV->Gain analog gain cells included transconductance-based VCA (OTA, 2 or 4 quadrant analog multiplier, etc), high-freq PWM switch in series with a resistor, and digital controlled analog multiplier. Maybe others I'm forgetting. Those were linear enough to possibly work in either feedforward or feedback topology.

I'm usually biased to prefer feedforward but am not an expert, just personal pref.

MyStran's ideas about log envelopes is interesting. Will think about it some. I guess one would do ln() or sqrt() the individual samples before envelope smoothing?

A couple years ago playing with an RMS compressor, where I was smoothing the squared samples, noticed that filtering the squared samples made attack faster and release slower than filtering the simple rectified samples.

For instance if attack and release first order time constants are equal, then filtering rectified samples, if it takes 10 ms to attack from silence to 0.5 of max attained envelope, and it also takes 10 ms to release to 0.5 of max attained envelope when releasing to silence again--

If the same filter is applied to the squared samples, and then we take sqrt of the smoothed envelope (to get RMS), the attack reaches the half-amplitude point faster and release reaches the half-amplitude point slower.

It was pointed out that one can do gain calculations without bothering with the sqrt, but it would still result in RMS attack "faster than an idiot like me would have expected" and RMS release slower than naively expected.

I added fudge factors in the attack/release time constant calc to make it behave "as I'd expect". Not rocket science.

Just sayin, I'd need to study awhile on envelope smoothing of log values to get it straight in my mind how it would actually behave. Interesting idea.

Post

But does that really matter? Is it supposed to be a measurement device or a creative sound device?
According to my ears it probably would not. I see discussions in the effects forum, for example, people arguing about whether or not this channel strip sounds better, posting examples - to me they sound 99.9% the same, even including the original dry sample:)
If a compressor uses feedback topology, it's usually better at reducing dynamic range, whereas a feedforward is better at increasing dynamic range. Just my 2c but I find this happens a lot.
Thanks for the tip. As for the way too frequent 2c's, thanks a lot for them, I guess that's expected when you no longer expect that you are likely to have a career about your favorite discussion topic, and quit doing experimentation a long time ago. As for the OP, I guess he is young enough to have hope while not even recognizing 1/20 evaluates to zero and asks why this code isn't working (I didn't even bother reading the rest:) ). Now I have to go back to my not so fun day time job and make a web front end to a server, and the only fun part is, emscripten is pretty cool. it may even run a DAW inside a web browser some day :lol:
~stratum~

Post

JCJR wrote:I'd need to study awhile on envelope smoothing of log values to get it straight in my mind how it would actually behave. Interesting idea.
I can save you some time and tell you it will ride the gain down too hard, but that might be less appearant with a feedback design. Also I would think any enhancement of the envelope filter would be making up for it's deficencies, but that's inevitable even with a linear envelope.

Post

stratum wrote:
If a compressor uses feedback topology, it's usually better at reducing dynamic range, whereas a feedforward is better at increasing dynamic range. Just my 2c but I find this happens a lot.
I was thinking about it some more and I think it's safe to say a basic feedforwards compressor "overcompensates" which is what can increase the dynamic.

Post

Rockatansky wrote:Have you ever tried debugging it with comments and a calculator?
It's pretty obvious why it won't work.

In my comments, some numbers use . as decimal and some use , but don't let that worry you. Dots come from manual typing, commas from copy/pasting from/to Spotlight.

Code: Select all

// Assuming input samples *in1 == 0.001 and *in2 == 0.02

// Repeatedly calling function might return different sample rate mid-calculation
const double samplerate = GetSampleRate();

double gain = 1.0;						// initial vca gain
double seekgain = 1.0;					// guessing envelope gain modifier?

const double c = 8.65617025;			// divisor for dB to gain factor conversion

double t = 0.0;							// no clue

const double b = -exp(-62.83185307 / samplerate );
										// whut?! always similar! -exp(-62,83185307/44100) ==> -0,9985762559

double a = 1.0 + b;						// 1.0 + -0,9985762559 ==> 0,0014237441

const double threshDB = mThr; 			// threshold dB ==> -12 (assuming for calculations)
const double threshLin = threshDB/c;	// non dB gain ==> -1,3862943604
const double thresh = exp(threshLin);	// gain factor exp(-12/8,65617025) ==> 0,2500000002

const double ratio = 1.0/20.0;			// compression ratio 1/20  (assuming for calculations) ==> 0,05

// simplify calculations:
// 	10 * x / 1000 == x / 100
//	100 * x / 1000 == x / 10
// 	threshDB / x / c  == threshDB / c / x == threshLin / x
double attack  = exp( threshLin / (samplerate/100.0));	// exp( -12 / 441  / 8,65617025) ==> 0,9968614111
double release = exp( threshLin / (samplerate/10.0));	// exp( -12 / 4410 / 8,65617025) ==> 0,9996856969

double rms = std::max( fabs(*in1) , fabs(*in2) );		// stores larger of both fabs-ed input samples; max ( 0.001, 0.02 ) ==> 0.02

// a * rms = 0,0014237441 * 0,02 ==> 0,000028474882
// b * t   = -0,9985762559 * 0,0 ==> 0,0
// t = 0,000028474882 - 0,0 ==> 0,000028474882
// sqrt(t) ==> 0,005336186091
rms = std::max( sqrt( (t = (a*rms) - (b*t) ) ) , rms);	// stores larger value: max( 0,005336186091, 0.02 ) ==> 0.02

if (rms > thresh)						// rms == 0.02, thresh == 0,2500000002
{
	// Don't know if this ever becomes TRUE

	// rms ==> 0.02
	// ratio ==> 0.05
	// threshDB ==> -12.0
	
	// log(rms)*c = log(0,02) * 8,65617025 ==> -14,7065736072
	
	// exp((-12,0 + ( (log(0,02) * 8,65617025 - (-12,0)) * 0,05) ) / 8,65617025) / 0,02
	seekgain = exp((threshDB + (log(rms)*c-threshDB)*ratio) /c) / rms;	// seekgain ==> 12,3060973967
}
// since seekgain already reset to 1.0 initially, no ELSE block necessary

// gain ==> 1.0
// seekgain ==> 12,3060973967 
if (gain > seekgain)					// 1.0 > 12,3060973967 ==> false (never becomes TRUE)
{
	// gain ==> 1.0
	// seekgain ==> 12,3060973967
	// attack ==> 0,9968614111
	gain = std::max( gain*attack , seekgain );	// max( 1.0 * 0,9968614111, 12,3060973967 ) ==> 12,3060973967
}
else									// 1.0 <= 12,3060973967 ==> true
{
	// gain ==> 1.0
	// seekgain ==> 12,3060973967
	// release ==> 0,9996856969
	gain = std::min( gain/release , seekgain );	// min( 1.0 / 0,9996856969, 12,3060973967 ) ==> 1,0003144019
}

*out1 = *in1 * gain; // Ch1 sample = 0,001 * 1,0003144019 ==> 0,001000314402
*out2 = *in2 * gain; // Ch2 sample = 0,02  * 1,0003144019 ==> 0,02000628804
Your RMS is not an RMS calculation, it's just a L+R link. Whichever sample is louder will trigger the compressor and let it multiply both channels with the same gain. RMS means that you should store a window, that's the history of samples for a certain time frame, usually a few milliseconds, and then you sum them all up and divide them so you get an average sample value over that time frame/window.
Thanks for detailed info!

With this design i'm getting -0.5 reduction with -30dB threshold.Something wrong but i can't find...
http://analogobsession.com/ VST, AU, AAX for WIN & MAC

Post

tunca wrote:Thanks for detailed info!
With this design i'm getting -0.5 reduction with -30dB threshold.Something wrong but i can't find...
Uhm, the code I posted wasn't a solution. I only tried to show you how to fix those number type errors and basically did some "remote debugging" for you. Call me a sellout if you will, but for a solution you'd have to pay me better. :D
Confucamus.

Post

Rockatansky wrote:
tunca wrote:Thanks for detailed info!
With this design i'm getting -0.5 reduction with -30dB threshold.Something wrong but i can't find...
Uhm, the code I posted wasn't a solution. I only tried to show you how to fix those number type errors and basically did some "remote debugging" for you. Call me a sellout if you will, but for a solution you'd have to pay me better. :D
I didn't try your code.It's same.I know that you showed me how to calculate and debug.Thanks for that.
http://analogobsession.com/ VST, AU, AAX for WIN & MAC

Post

camsr wrote:
JCJR wrote:I'd need to study awhile on envelope smoothing of log values to get it straight in my mind how it would actually behave. Interesting idea.
I can save you some time and tell you it will ride the gain down too hard, but that might be less appearant with a feedback design. Also I would think any enhancement of the envelope filter would be making up for it's deficencies, but that's inevitable even with a linear envelope.
Thanks. Might eventually get back to playing with it. Had done the jsfx lookahead RMS compressor "good enough for what I wanted" then cleaned up enough for open source release. Did the jsfx lookahead oversampled true peak adaptive envelope limiter good enough for what I wanted but didn't yet get around to cleaning it up enough for open source release. Was thinking a low-ripple but fast peak compressor sometime, might play with log envelopes while I'm at it, if ever get around to it.

Though maybe the only one likely confused would be me, a potential source of confusion in feedback vs feedforward is whether talkin about the gain cell's location in the audio path, vs the source of the side-chain audio?

I gather that some modern analog vca's are finally purt well-behaved devices, but it was common in the past for gain cells to have rather narrow undistorted dynamic range, and also not unusual for them to be fairly noisy.

For instance if we have an OTA based VCA, that is noisier than we would like and has max noise at max gain, and has 1 percent distortion at 100 mv input, only getting worse at louder levels--

If we use this VCA "feedforward" in the audio path, device input goes to VCA input, VCA output goes to device output-- If we want to tolerate up to 10 volts input level at no more than 1 percent distortion, we have to first attenuate input as much as -40 dB before feeding the VCA (bad for noise), pump the attenuated audio thru the "kinda noisy" VCA, and then add EVEN MORE NOISE by makeup gain, possibly as much as +40 dB or more to deliver a good hot line level output. Its hard to add that much gain without adding some noise.

In "feedforward" audio path, if max VCA gain is set to unity, then the VCA is noisiest when input signal is quiet and no compression is happening. When the input signal gets louder and the VCA does some gain reduction, the VCA noise goes down, but loud signal would help mask the noise anyway. We would prefer minimum noise at the lowest level input signals, not the other way around.

An EE I know who keeps up with such things, sent me data sheets of modern VCA devices which claim to be lowest noise at highest gain, but AFAIK it wasn't common in the past.

So given a noisy old low-dynamic-range OTA, if we put the OTA in the negative feedback path of an opamp (in parallel with a unity-gain-setting resistor)-- For the opamp to have unity gain, the OTA in the feedback loop is basically turned off, generating no noise to speak of. As compression is added, the OTA gain is RAISED, which lowers the opamp circuit gain, because the OTA is in the feedback loop. So the highest-noise is when the compressor as a whole is running at lowest gain, where there ought to be lots of signal to mask that OTA noise.

Also, with the OTA in the feedback loop, for instance if the compressor as a whole happens to be in -40 dB of gain reduction with a 100 millivolt opamp output, the input resistor of the opamp is getting hit with 10 volts, but the OTA in the feedback loop is only getting hit with 100 mv. The feedback OTA is "protecting itself" from excessive overload distortion without having to severely pre-attenuate the hot line input signal to the opamp circuit.

I'm just saying, you could use that OTA as a side-chain feedforward compressor either feeding audio forward thru the OTA (turning DOWN the OTA gain to add compression), or a side-chain feedforward compressor with the OTA in the negative feedback loop of an opamp (turning UP the OTA gain to add compression).

And you could use either kind of audio path to make a side-chain feedback compressor.

IOW, side-chain feedback or feedforward one thang, and gain cell feedback or feedforward something else, and the two can be "mixed and matched". At least in analog, to try to make the best of imperfect components. In digital probably only the sidechain routing makes any sense to meddle with, because a simple float multiply is a near-perfect gain cell.

Post

tunca wrote:I didn't try your code.It's same.I know that you showed me how to calculate and debug.Thanks for that.
It seems from your PM that I didn't manage to get it across a such: that "pay me more" thing was meant to be a joke. Tongue in cheek.

Since, you know, your intention certainly won't be to make the project open source in the end so that others can learn from it, but much rather to sell it for money. So if I solved your problems for you here and finished your envelope follower, RMS detection and all that, I'd basically be writing the guts of your commercial product for you. And I'm not doing that, Sir. ;)

To be quite honest, I have no idea why you would make your code so complex. And I have no clue where you get all those exp-things for seekgain calculations from, or why you would use the sample rate in a gain calculation?! (Samplerate is in b, which is added to a, which is later used as a multiplier in your "RMS" calculation.)
Confucamus.

Post

To be quite honest, I have no idea why you would make your code so complex. And I have no clue where you get all those exp-things for seekgain calculations from, or why you would use the sample rate in a gain calculation?! (Samplerate is in b, which is added to a, which is later used as a multiplier in your "RMS" calculation.)
I was wondering why music-dsp site is out of bandwidth. Seems like Tunca has run wget -r on it. :D
(just kidding).

Anyway - I had told him to make a simple compressor in some other thread but had not meant 'get some unreadable piece of short code from somewhere and hack it'. The chunkware source code -apparently he was using that for the related plugin- is about 50 times longer and yet is more readable.
~stratum~

Post Reply

Return to “DSP and Plugin Development”