What's wrong with this One Pole HPF?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

I copied the OnePole design from Ear Level which works great as an LPF. He also gives suggestions for how to turn it into a HPF but it doesn't work.

You can get an HPF for example by subtracting the LPF signal from the original signal:

Code: Select all

testLPF.setFc(40.0 / sampleRate);
output = output - testLPF.processSample(output);
This could easily be put into a new function like "processHPF". But I want to know why the code for setting it as an HPF doesn't work.

Here's his code exactly copied over to Juce format:

Code: Select all

#ifndef OnePole_h
#define OnePole_h

#include <JuceHeader.h>

class OnePole {
public:
	OnePole() { a0 = 1.0; b1 = 0.0; z1 = 0.0; };
	OnePole(double Fc) { z1 = 0.0; setFc(Fc); };
	//~OnePole();
	void setFc(double Fc);
	void setFc_HPF(double Fc);
	void setInitialValue(double initial_z1);
	double process(double in);

protected:
	double a0, b1, z1;
};

inline void OnePole::setInitialValue(double initial_z1) {
	z1 = initial_z1;
}
inline void OnePole::setFc(double Fc) {
	b1 = exp(-2.0 * MathConstants<float>::pi * Fc);
	a0 = 1.0 - b1;
}

inline void OnePole::setFc_HPF(double Fc) {
	b1 = -exp(-2.0 * MathConstants<float>::pi * (0.5 - Fc));
	a0 = 1.0 + b1;
}

inline double OnePole::process(double in) {
	return z1 = in * a0 + z1 * b1;
}

#endif
The only thing I added was "setFc_HPF" according to his instructions here:

https://www.earlevel.com/main/2012/12/1 ... le-filter/
It’s trivial to add one-pole highpass response, if you’d like—just negate the lowpass calculation; this inverts the spectrum, so you’ll need to flip the frequency range by subtracting Fc from 0.5:

b1 = -exp(-2.0 * M_PI * (0.5 - Fc));
a0 = 1.0 + b1;
That's exactly what I did for setting the coefficients for a high pass in "setFc_HPF" but it doesn't actually work.

Any idea why or what the error is? It's just frustrating to not have it working.

Thanks.

Post

Hi mikejm,

I don't have time to look at what you did in detail, but I do wonder if you saw Nigel's later comment in which he responded to a post also complaining about weirdness for the highpass filter on the same web page as you quoted:

"Nigel Redmon says:
February 4, 2019 at 6:58 pm

"Note my comment in the article, 'a one-pole makes a poor highpass filter for cases in which we might be likely to use it.' Put a different way, they work well for lowpass filters that have a corner frequency closer to 0 Hz, and highpass filters that have a corner near half the sample rate. But we usually have different expectations for lowpass and highpass—if you set a one-pole lowpass to 16 kHz, you don’t expect much effect on the audio. But if you set a highpass to 50 Hz, you wonder why it isn’t having much effect on the audio.

"For a useful low frequency highpass, you really need a zero at DC to actively pull the frequency response down there (a one-pole/one-zero filter, or a biquad for second-order). As I noted with the DC blocker example, you can also subtract a lowpass filter from the input signal to get highpass. That’s not something you’d normally do with higher order filters, due to the phase response of the filter relative to the input (unless that’s the response you want). But it’s close enough for rock ‘n’ roll with a one-pole."

I myself don't recall having used the formulation he provided there, but have implemented filters that do what he suggests near the end of this quote, namely to "subtract a lowpass filter from the input signal to get highpass."

Regards,
Dave Clark

Post

For one pole HP = Input - LP to work the LP needs the correct phase and magnitude response. This would be the case if the filter was derived from the Bilinear Transform. However I think the code here is Matched Z Transform (didn't check thoroughly just saw the exp() in the coefficient calculation), so the resultant HP might be a little off

Post

Dave caught the key points. Maybe I shouldn't have muddied the water with the "one-pole highpass", which is rarely what you want to do for audio. For audio, most often, you want the Fc set low (DC blocker, rumble blocker, plosive blocker), and you really need a zero at 0 Hz to pull the curve down for everything below Fc in that case, or its next to useless. It's more useful (or less useless) at higher frequencies, but there are few audio needs for that. setFc_HPF is to set the coeffients for the marginally-useful one-pole highpass, mainly an academic point.

But for the "subtract the one-pole lowpass" trick, the coefficients are the same as the lowpass. In other words, if you want a highpass at 30 Hz, make a lowpass at 30 Hz (using setFc, not setFc_HPF) and take the difference with the signal.
My audio DSP blog: earlevel.com

Post

mikejm wrote: Sun May 10, 2020 3:57 am I copied the OnePole design [...] but it doesn't work.

[...] I want to know why the code for setting it as an HPF doesn't work.

[...] That's exactly what I did [...] but it doesn't actually work.

Any idea why or what the error is? It's just frustrating to not have it working.
For troubleshooting you don't really give much clues or whatever to work with. How do you mean "doesn't work"? What behaviour is different than what you expected? How do you know that, how have you measured that?

As per the thread title:
What's wrong with this One Pole HPF?
Well, you tell us what you mean with "doesn't work". Not enough details...
We are the KVR collective. Resistance is futile. You will be assimilated. Image
My MusicCalc is served over https!!

Post

earlevel wrote: Sun May 10, 2020 5:44 pm Dave caught the key points. Maybe I shouldn't have muddied the water with the "one-pole highpass", which is rarely what you want to do for audio. For audio, most often, you want the Fc set low (DC blocker, rumble blocker, plosive blocker), and you really need a zero at 0 Hz to pull the curve down for everything below Fc in that case, or its next to useless. It's more useful (or less useless) at higher frequencies, but there are few audio needs for that. setFc_HPF is to set the coeffients for the marginally-useful one-pole highpass, mainly an academic point.

But for the "subtract the one-pole lowpass" trick, the coefficients are the same as the lowpass. In other words, if you want a highpass at 30 Hz, make a lowpass at 30 Hz (using setFc, not setFc_HPF) and take the difference with the signal.
Thanks for your tutorials and thanks to Dave, yourself, and Matt for clarifying as well. Yeah, in 99% of cases I'm using something simple like this to "HPF" it's just to get rid of some DC.

So I redid my class as this:

Code: Select all

#include <JuceHeader.h>

class OnePole {
public:
	OnePole() { a0 = 1.0; b1 = 0.0; z1 = 0.0; };
	OnePole(double Fc) { z1 = 0.0; setFc(Fc); };
	void setFc(double Fc);
	void setInitialValue(double initial_z1);
	double process(double in);
	double process_HPF(double in);

protected:
	double a0, b1, z1;
};

inline void OnePole::setInitialValue(double initial_z1) {
	z1 = initial_z1;
}
inline void OnePole::setFc(double Fc) {
	b1 = exp(-2.0 * MathConstants<float>::pi * Fc);
	a0 = 1.0 - b1;
}

inline double OnePole::process(double in) {
	return z1 = in * a0 + z1 * b1;
}

inline double OnePole::process_HPF(double in) { 
	z1 = in * a0 + z1 * b1;
	return in - z1;
}
#endif
The new "process_HPF" just does the LPF and subtraction built in and this seems to be performing correctly for the intended purpose.

Technically I get there's a bit of phase delay in the LPF and I think that's what Matt is saying makes it slightly incorrect, but I don't know if there's any practical issue from this in this application or how people better approach this issue of needing to get rid of DC offset.

Is there a more correct or phase/group delay free way of doing DC blocking?

I just looked and found this cool article:

https://www.iro.umontreal.ca/~mignotte/ ... rithms.pdf

It summarizes a linear phase moving average method for DC blocking and some others that maybe are more ideal for me long term. I'll have to work on trying to make one when I have time.

Does anyone have any code for something like this they can share? Or does anyone know if there are any good ones publicly available eg. on Github?

Post

mikejm wrote: Sun May 10, 2020 6:37 pmTechnically I get there's a bit of phase delay in the LPF and I think that's what Matt is saying makes it slightly incorrect, but I don't know if there's any practical issue from this in this application
What I'm saying is that the phase response is frequency dependant. For HP = Input - LP to work correctly you need the correct phase and magnitude response at each frequency. The issue in this implementation is that you won't end up with a zero at DC as you would with other designs. Additional the MZT LP filter are gain compensated for unity at DC, so I'm guessing this design has variable gain at nyquist

Post

matt42 wrote: Sun May 10, 2020 6:52 pmFor HP = Input - LP to work correctly you need the correct phase and magnitude response at each frequency. The issue in this implementation is that you won't end up with a zero at DC as you would with other designs.
Don't forget this is a first-order filter—this doesn't have the same degree of concern you'd have with higher orders. Any phase issues are where you largely don't care, and the passband remains flat. Using this as a DC blocker, while it may not be the best you could code for that purpose, at the same time is relatively immune to the numerical sensitivities of, for instance, a one-pole one-zero filter where you put the zero a DC and try to get a pole awfully close to it (and hence the unit circle).

As far as not having a zero at DC, recognize that the underlying lowpass has unity gain at DC, as you said, there is no "phase" at DC, so how is there not a zero at DC (within numerical accuracy of the system, as usual) when you subtract the DC value from itself?

Just trying to address the issues, I don't care who uses it or not. But I can tell you that it's used in many professional music and audio products, has been for decades.
My audio DSP blog: earlevel.com

Post

Yes, you are right about zero at DC. I'm not sure what I was thinking there actually. I'm sure you are right that it's perfectly fine for rock and roll. I just don't see how it's a problem to get a more accurate response with another method, but as you say for most purposes it is most likely fine. Does it need a nyquist correction though? Im not trying to have a go by the way - just discussing its properties and, really I have no idea where my idea about DC gain came from

Post

matt42 wrote: Sun May 10, 2020 8:20 pm Yes, you are right about zero at DC. I'm not sure what I was thinking there actually. I'm sure you are right that it's perfectly fine for rock and roll. I just don't see how it's a problem to get a more accurate response with another method, but as you say for most purposes it is most likely fine. Does it need a nyquist correction though? Im not trying to have a go by the way - just discussing its properties and, really I have no idea where my idea about DC gain came from
At Nyquist...good question, a long time since I've looked at this closely, with numerical considerations, so just thining it through...For use as a low-end/DC blocker, the lowpass will have a very small (not not zero, since we don't have one) at Nyquist. Phase shouldn't be an issue, but even it were, the maximum possible error would be very small, limited by the numerical precision. So I don't see how it could be an issue.

It's always a lot easier to analyze these things in fixed point, and most concern with DC blockers came from the old days of TDM plugins and embedded DSP. Even with the extended 24-bit math capabilities of the (TDM) 56k families, it was dam easy to make an unstable blocker with a direct-form pole/zero, and of course higher orders just got much worse, and higher sample rates. There are other filter forms that have better precision around zero—but usually take more cycles and would often be excluded for that reason. Anyway, this was an example of a cheap filter that does the job, a sidebar of the greater utility of a one-pole lowpass that's great for many uses.
My audio DSP blog: earlevel.com

Post

BLT high-pass has a single zero at DC and a single pole at cutoff. Whether you use BLT or Matched Z formulas to position the pole only changes the cutoff frequency slightly. In fact, when we are discussing DC blockers the frequency is usually so low (compared to the sample rate) that the difference between the cutoff of the two is essentially zero.

If the DC blocker is actually supposed to block DC, then I would actually suggest an implementation that first uses a naive pole-only LP with unity DC gain, then takes the difference between the current and previous output sample. This way even if the low-pass doesn't match the input DC exactly (eg. due to losing some bits to rounding), your output will still be exactly zero when the low-pass part outputs the same value for two consequtive sample (which it should usually do with DC input, even if it might not output the exactly correct value). Anything else (at least in floating point) and you're probably going to end up with a bit of DC passing through.

Post

mystran wrote: Sun May 10, 2020 9:10 pmIf the DC blocker is actually supposed to block DC, then I would actually suggest an implementation that first uses a naive pole-only LP with unity DC gain, then takes the difference between the current and previous output sample. This way even if the low-pass doesn't match the input DC exactly (eg. due to losing some bits to rounding), your output will still be exactly zero when the low-pass part outputs the same value for two consequtive sample (which it should usually do with DC input, even if it might not output the exactly correct value). Anything else (at least in floating point) and you're probably going to end up with a bit of DC passing through.
True, but in practice it's extra work (not much), to save from error that's far below the minimum error of hardware—your DAC is going to have more DC error, as will everything else in the chain.
My audio DSP blog: earlevel.com

Post

earlevel wrote: Sun May 10, 2020 9:30 pm
mystran wrote: Sun May 10, 2020 9:10 pmIf the DC blocker is actually supposed to block DC, then I would actually suggest an implementation that first uses a naive pole-only LP with unity DC gain, then takes the difference between the current and previous output sample. This way even if the low-pass doesn't match the input DC exactly (eg. due to losing some bits to rounding), your output will still be exactly zero when the low-pass part outputs the same value for two consequtive sample (which it should usually do with DC input, even if it might not output the exactly correct value). Anything else (at least in floating point) and you're probably going to end up with a bit of DC passing through.
True, but in practice it's extra work (not much), to save from error that's far below the minimum error of hardware—your DAC is going to have more DC error, as will everything else in the chain.
If you are DC blocking just for the DAC, then sure. If you are DC blocking, because you don't want your downstream processing to worry about DC offsets, then you might want to do it properly. For feedback processing especially, surprisingly small amounts of DC offsets can cause all kinds of annoying artifacts, even if said process was leaky enough to ultimately remain stable.

Post

mystran wrote: Sun May 10, 2020 10:52 pmIf you are DC blocking just for the DAC, then sure. If you are DC blocking, because you don't want your downstream processing to worry about DC offsets, then you might want to do it properly. For feedback processing especially, surprisingly small amounts of DC offsets can cause all kinds of annoying artifacts, even if said process was leaky enough to ultimately remain stable.
Sorry, because I’m fine with your sentiment and addition to the thread and was going to leave it alone, but I have to say I think you’re overstating this. First, you imply doing it as I said isn't "proper"—good enough for a simple use like output to a DAC, but not adequate in general. It's simply the opposite—it's good enough for all but possibly atypical edge cases.

Second, when you say that with your method you get perfect DC cancellation as soon as the lowpass outputs two of the same samples in a row, this has less significance in real audio use than it might seem to someone on first read. The impulse response of the lowpass filter is necessarily long, so you will rarely encounter it in typical audio use. (And in cases of limit cycles, you’ll never encounter it.)

No intention to argue, I think these points are worth consideration to someone reading this thread. Also, I think any more exhaustive discussion of DC blockers is worth its own thread—this thread was about confusion over a "side-comment" feature in my blog post, which happens to be a usable DC blocker. But it was never presented as the end-all solution to DC blocking, so it seems quite a tangent for me to go on about it.
My audio DSP blog: earlevel.com

Post

I just brought it up, because I've actually had it happen a few times that I put a simple HP filter as a DC blocker somewhere without thinking about it too much and a few days later I'm trying to figure out why my DC levels downstream are going all bonkers (eg. think a distortion with lots of gain, or some feedback system with a very long decay time).

As it turns out, there are tons of ways to write a high-pass filter, but very few of them will give you an exact zero output when you feed them some constant DC level. As you said it often doesn't matter, but then when it matters, it's better to know how to fix it.

Post Reply

Return to “DSP and Plugin Development”