Auto-Wah effect


#1

Hi,
I’m new to Juce and trying to create an auto-wah plugin. I can’t get any effect to output audio no matter what I do, there is just some slight noise but no auto-wah. What could be the problem? The filters should be implemented correctly, as the same code creates a wah-effect on matlab.

Here is the processblock:

for (int i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
	buffer.clear(i, 0, buffer.getNumSamples());

// This is the place where you'd normally do the guts of your plugin's
// audio processing...
for (int channel = 0; channel < 2; ++channel)
{
	
	// Output
	float* channelData = buffer.getWritePointer(channel);

	// Input
	const float* x = buffer.getReadPointer(channel);

	// Iterate over the signal
	for (int n = 0; n < buffer.getNumSamples(); n++) {
		
		//auto x = channelData;
		float y = 0.0;


		// Level detector
		if (abs(x[n]) > yLn1) {
			yLn = a_A * yLn1 + (1 - a_A)*abs(x[n]); // Attack
		}
		else {
			yLn = a_R * yLn1 + (1 - a_R)*abs(x[n]); // Release
		}
		
		yLn1 = yLn;
		fc = minf + (maxf - minf)*yLn;

		// Peak filter parameters
		d = -cos(2 * PI*fc / getSampleRate());
		H0 = pow(10, (Gain / 20)) - 1;
		c = (tan(PI*fb / getSampleRate()) - 1) / (tan(PI*fb / getSampleRate() + 1));

		// Peak filter difference equations
		xhn = x[n] - d*(1-c) * xhn1 + c * xhn2;
		y1 = -c * xhn + d * (1 - c) * xhn1 + xhn2;
		y = 0.5 * H0 * (x[n] - y1) + x[n];
		
		xhn2 = xhn1;
		xhn1 = xhn;

		// channelData: Output channel
		channelData[n] = y;

}


#2

I am so tired of seeing people make this mistake, I’ve finally got around to making a post explaining it.

Hopefully this will save us all some time replying to the next hundred posts about the same thing:


#3

Well how could I’ve known? this is the first day using Juce and doing real-time audio processing for me. But thanks for the quick answer!

This’ll annoy you, but what do you mean by keeping a separate state for each channel? Just reset variables? Could you show a simple example?


#4

The simple answer is you have to keep separate variables (which hold the state) for each channel. So, throughout your code, the left and right channels, for example, will have their own distinct variables. That is how you keep the processing separate and avoid artifacts, like clicking and pops, in the output.


#5

Any of the variables that remember something for the next iteration of the loop, like yLn1, xhn1, xhn2.


#6

Thanks! So can I just remove the channel loop and do the same signal processing for both buffer.getWritePointer(0) and buffer.getWritePointer(1)?


#7

Ideally you put all your yLn1 xhn1, xhn2 etc. into a struct or class and have one instance for each channel…
Others call them then Coefficients, so you can look into the juce sources, how they do it for other filters:

You might be able to adapt that pattern to your implementation


#8

Please don’t take this as a criticism of you - as I said in the linked post, there’s really no way a newcomer could reasonably be expected to find the other posts describing the same problem, because there isn’t a keyword or other term you could have searched for!

And TBH I think that’s one of the reasons why it’s such a common problem. There are probably many other common mistakes where a quick Google would immediately give you the solution, so we never hear about them.


#9

I still couldn’t get it right :confused: I did it this time with the IIRFilter functionality and with separate variables for both channels, now there is no noise but also no difference in output audio at all. Why aren’t the audio samples processed? Can you help me out?

    yLn1_left = yLn2_left;
yLn1_right = yLn2_right;

IIRFilter filter_left;
IIRFilter filter_right;
float *left = buffer.getWritePointer(0);
float *right = buffer.getWritePointer(1);
int numsamples = buffer.getNumSamples();


    for (int n = 0; n < buffer.getNumSamples(); n++) {
	
	const float x_left = left[n];
	const float x_right = right[n];
	
	// Level detector
	if (abs(x_left) > yLn1_left) {
		yLn_left = a_A * yLn1_left + (1 - a_A)*abs(x_left); // Attack
	}
	else {
		yLn_left = a_R * yLn1_left + (1 - a_R)*abs(x_left); // Release
	}

	if (abs(x_right) > yLn1_right) {
		yLn_right = a_A * yLn1_right + (1 - a_A)*abs(x_right); // Attack
	}
	else {
		yLn_right = a_R * yLn1_right + (1 - a_R)*abs(x_right); // Release
	}


	// Update filter parameters depending on level
	yLn1_left = yLn_left;
	yLn1_right = yLn_right;

	fc_left = minf + (maxf - minf)*yLn_left;
	fc_right = minf + (maxf - minf)*yLn_right;

	Q_left = fc_left / fb;
	Q_right = fc_right / fb;

	// Peak filter
	IIRCoefficients coefs_left = IIRCoefficients::makePeakFilter(getSampleRate(), fc_left, Q, 0.8);
	IIRCoefficients coefs_right = IIRCoefficients::makePeakFilter(getSampleRate(), fc_right, Q, 0.8);


	filter_left.setCoefficients(coefs_left);
	filter_right.setCoefficients(coefs_right);

	filter_left.processSingleSampleRaw(x_left);
	filter_right.processSingleSampleRaw(x_right);


}


yLn2_left = yLn1_left;
yLn2_right = yLn2_right;


}

#10

they’re declared in the header file


#11

processSingleSampleRaw() returns the processed sample value. So you probably need to change the end of your for-loop to something like:
left[n] = filter_left.processSingleSampleRaw(x_left);
right[n] = filter_right.processSingleSampleRaw(x_right);


#12

Not followed your code completely, but this looks like a typo, second line specifically…


#13

You’re right, thanks. But still no difference, the filter doesn’t do anything at all