Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix gain compensation for the Moog filter #11177

Merged

Conversation

robbert-vdh
Copy link
Contributor

@robbert-vdh robbert-vdh commented Jan 2, 2023

This would previously add a bunch of gain when the low-pass filter is engaged. These new coefficients have been determined by filtering 1/sqrt(2) RMS pink noise using all resonance settings and then fitting a curve on the ratio between the input's RMS values and the output's. Here is the Jupyter notebook used to compute these coefficients:
mixxx-moog-filter-gain-compensation.tar.gz

This would previously add a bunch of gain when the low-pass filter is
engaged. These coefficients have been determined by filtering 1/sqrt(2)
RMS pink noise using all resonance settings and then fitting a curve on
the ratio between the input's RMS values and the output's.
For deriving the post gain compensation for the Moog filter.
@daschuer
Copy link
Member

daschuer commented Jan 3, 2023

What did you intend to fix exactly?

I can confirm a gain issue and I that this PR improves the situation for some settings but fails with others. Here is a table that shows the different curves. The original curve depends on the cut off frequency:
grafik
Is it intended to remove this dependency? For my feeling this is required. Did you test some cut off frequencies of the LPF?
Can we apply your changes on top of the gain of the reference implementation?

In addition there seems to be an other issue with the HPF. It has resonance even after setting the resonance to zero. We also have a gain raise just because of enabling the filter at neutral position. This should not happen to allow to use the filter as quick effect. HPF behavior is not changed by this PR-

How does the gain compare to the normal filter?

* (2 - (1.0f - resonance / 4) * (1.0f - resonance / 4));
// See https://github.com/mixxxdj/mixxx/pull/11177 for the Jupyter
// notebook used to derive this
m_postGainNew = 1.0001784074555027f + (resonance * 0.9331585678097162f);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With float math
becomes:
1.00017797946929931640625

0.9331585678097162
becomes
0.933158576488494873046875

So lets use only significant digits, not not lead readers wrong:

Suggested change
m_postGainNew = 1.0001784074555027f + (resonance * 0.9331585678097162f);
m_postGainNew = 1.0001784f + (resonance * 0.93315857f);

I have used https://www.h-schmidt.net/FloatConverter/IEEE754.html to check it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know. I kept the extra precision since the compiler truncates this anyways, and if for whatever reason you ever want to switch over to doubles you don't need to recompute the value. The definition of kPi in the same source file also has excess decimals for a 32-bit floating point number.

@robbert-vdh
Copy link
Contributor Author

robbert-vdh commented Jan 3, 2023

Is it intended to remove this dependency?

Yes, that was intentional. I have no idea why the original implementation increased the gain for low center frequencies, but that's not typical. I checked at least ten other Moog-style ladder filter implementations and none increased the gain for lower cutoff frequencies. Increasing the gain when turning the low-pass filter down makes the filter sound very boomy/muddy when turned down far enough that just the lowest sub bass comes through.

And you're right, the gain is also off for the high-pass filter. I also fitted a curve for that based on the response at 1 Hz compared to DC filtered pink noise. Since the filter is resonant you'll still see the gain increase on the master VU meter, but perceptually it sounds like there's no gain difference. This is the updated notebook: <see the OP for the latest version>

See below. This is the notebook updated again:
<see the OP for the latest version>

@daschuer
Copy link
Member

daschuer commented Jan 3, 2023

I have no idea why the original implementation increased the gain for low center frequencies, but that's not typical.

The original implemention adds a gain of 7 in the unity case with the resonance cranked up to 4. This is lowered to 3.5 when using a 500 Hz cut if frequency. Your implementation uses even more grain ~5 in that case. I think your intention is the opposite at low cut off frequencies.
Maybe I miss something?

Unfortunately I cannot yet use the Jupyter notebooks. Can you show your results as a screenshot?

For me it is a big issue that our Moog ladder is not neutral at unity. When comparing other implementations, did you find one that does not suffer that issue and also has not the gain issue?
Maybe we can add that in addition?

I wonder if the filter knob range is correct?
Maybe we need to increase the maximum cut of frequency.

How do you like the gain of the standard filter? Can we use this as reference? Maybe we can use a sweep over the whole frequency band to compare both?

@robbert-vdh
Copy link
Contributor Author

robbert-vdh commented Jan 3, 2023

The reason why the resonance based gain compensation is needed for these ladder filters is because increasing the resonance also causes the passband's gain to decrease. I made this plot from the Moog filter used in Mixxx, with the gain compensation always set to 1:
image

Adding the gain compensation I computed in this PR you get this:
image

Notice how I derived the gain compensation formula for the low-pass filter by setting the cutoff frequency to 5 Hz below Nyquist, yet the passband gain is still unity at a cutoff frequency of 1 kHz (and this also goes for any other frequency, 1 kHz just made the plot nicer to look at). That's because the passband gain for these filters doesn't depend on the cutoff frequency. Like I said yesterday, just staring at spectrum analyzers and sending impulses through a dozen commercial Moog filter emulations I don't see any of them doing cutoff frequency based gain compensation. I also asked in a DSP community of others compensate ladder filter gain based on the cutoff frequency and I didn't get any responses.

I wonder if the filter knob range is correct?
Maybe we need to increase the maximum cut of frequency.

You can't. The maximum cutoff frequency is the Nyquist frequency, or half the sample rate: https://en.wikipedia.org/wiki/Nyquist_frequency It is not possible to represent frequencies in discrete sampled signals above the Nyquist frequency. That's when you get aliasing, because those frequencies will appear the same as another frequency sampled at the same sample rate.

How do you like the gain of the standard filter?

Not a huge fan of the standard filter's sound (that's why I use the Moog filter, it has more character and it doesn't suffer from the zipper noises the regular filter does), but just from a quick A/B test it seems to have the exact same gaining as the Moog filter with the changes from this PR. So that's good.

@robbert-vdh
Copy link
Contributor Author

For completeness' sake, this is what the curve looks like now at the same cutoff frequency:
image

The more interesting comparison is the frequency response with the cutoff frequency set to just below Nyquist (so, when the filter gets engaged). This is the new compensated magnitude response:
image

And this was the old response curve:

image

@daschuer
Copy link
Member

daschuer commented Jan 3, 2023

Ah, I see. Thank you for all the details. I have measured some points but there is still a gain change in the pass band with some settings, tested with 44.1 kHz

LPF = 0.236 Res 3.977 HPF = 0.0003 Gain -1,83 dB
LPF = 0.236 Res 1 HPF = 0.0003 Gain -1,19 dB
LPF = 0.236 Res 0 HPF = 0.0003 Gain 0 dB

LPF = 0.025 Res 3.23 HPF = 0.0003 Gain −0.707 dB
LPF = 0.025 Res 1 HPF = 0.0003 Gain -0.45 dB
LPF = 0.025 Res 0 HPF = 0.0003 Gain 0 dB

So it looks like the cut off frequency dependency cannot be removed. I wonder why this was wrong in the first place.
Did we fail to adjust the original values when porting the effect?

The values are the faction for the sampling rate, hard to interpret. Since we have recently introduced the value display in the GUI this is exposed to the user.

The HPF is also introducing a Gain

As already mentioned this PR Improves the situation. Not sure how much love you like to put into it to iron out the remaining gain shift.

if (MODE == MoogMode::HighPassOversampling || MODE == MoogMode::HighPass) {
m_postGainNew = 1;
m_postGainNew = 0.8478175496499384f + (0.09022626934231257f * resonance);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange, these values makes the gain dependency worse.
Why do we see different results?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reran the numbers for a more aggressive cutoff frequency (0.001 Hz instead of 1 Hz) and that that got me the following polynomial:

image

Which is the 1.0 value that the filter used before this PR. Going back to this (or the above polynomial) does sound correct to me. When did you notice a problem with this? Note that I'm talking about the perceived loudness. You will obviously see your VU meter increase a bit when you start increasing the high-pass filter's frequency. This is to be expected because of the resonant bump around the cutoff frequency. At low cutoff values the low frequencies are amplified while the other frequencies in the filter's pass band stay at equal levels. So a 1.0 post gain compensation value for the HPF is correct here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have recorded a sweep sound generated by Audacity. One time without the filter and one time with the filter active. Than I have compared the peak samples in the pass-band. I will repeat this tonight.

Copy link
Contributor Author

@robbert-vdh robbert-vdh Jan 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try playing regular music through the filter. I optimized these curves so that when the filter gets engaged from its neutral position (the highest cutoff frequency for the low-pass filter, and the lowest cutoff frequency for the high-pass filter) there is no perceived gain different for most music.

@robbert-vdh
Copy link
Contributor Author

Like I said, there is not supposed to be any post gain changes based on the cutoff frequency. At least not if this is supposed to be a Moog-style ladder filter. You can compensate for lots of things, but then it's not longer a ladder filter. Just like you usually don't compensate for the change in transition frequency when changing the resonance. If you hadn't already noticed, check the magnitude response screenshots I posted again. The effective center frequency is pushed upwards as the resonance parameter is increased. That's one of the defining characteristics of these filters. The reason why you might want gain compensation for these filters at all (many implementations don't even do that at all) is that increasing the resonance substantially decreases the gain of the pass band. Sometimes this idea is also referred to as bass compensation, since these filters were often used to design bass sounds and in those situations the pass band would mostly consists of bass frequencies.

The gain compensation polynomials from this PR were derived to minimize the difference in perceived gain when the low-pass and high-pass filters are engaged from their neutral position. So, if you take the included Moog Filter effect chain, when turning the super knob clockwards or counter-clockwards from its neutral 12 o'clock position. Is this not the case for you? With these changes doing so sounds pretty smooth for me.

robbert-vdh added a commit to robbert-vdh/mixxx that referenced this pull request Jan 3, 2023
This was changed in response to
mixxxdj#11177 (comment), but
the 1.0 behavior is correct. The pass band gain does not change with
resonance.
This was changed in response to
mixxxdj#11177 (comment), but
the 1.0 behavior is correct. The pass band gain does not change with
resonance.
@daschuer
Copy link
Member

daschuer commented Jan 4, 2023

I just had a close look at the two graphs of:
#11177 (comment)
I can see at 1 KHz cut off frequency I see a gain drop of ~0.8 dB with 3.5 resonance.

This is is in the magnitude of my audacity measurements of
10.4 kHz + 3,97 resonance = -1,83 dB
1.1 kHz + 3,23 resonance = -0.707 dB

The normal filter has always 0 dB

Like I said, there is not supposed to be any post gain changes based on the cutoff frequency. At least not if this is supposed to be a Moog-style ladder filter. You can compensate for lots of things, but then it's not longer a ladder filter. Just like you usually don't compensate for the change in transition frequency when changing the resonance.

So the remaining Gain drop is desired? My impression was that the original author wants to compensate that as well and we have only messed up the coefficients when porting the code.
I have found here a implementation that confirms that:
https://github.com/esluyter/houvilainenfilter/blob/2037d8503ea42ecddc15a656cbadd3594e1f33e9/plugins/HouvilainenFilter/FilterLp24db.h#L145

@daschuer
Copy link
Member

daschuer commented Jan 4, 2023

I have found the original paper:
https://cpb-us-w2.wpmucdn.com/sites.gatech.edu/dist/e/466/files/2016/11/P_061.pdf
It is talking of "tuning and resonance compensation"

But anyway, since It finally needs to sound good and not look good in graph, I am open to any type of compensation.
We just need to place a comment in the source that justifies our decision.

@robbert-vdh
Copy link
Contributor Author

I can see at 1 KHz cut off frequency I see a gain drop of ~0.8 dB with 3.5 resonance.

The actual value there is -1.1738444262801244 dB of gain in the passband. But, this is a) normal for these kinds of filters, b) probably desirable since the resonant bump adds a lot of gain to the sound as a whole and c) noone would use this setting unless they were trying to deafen their crowd.

I don't see having a slight gain drop in the passband as you almost close the filter is a bad thing. It makes the filter sound more gentle as you bring the cutoff frequency down until you finally close the filter completely. But in this case the gain difference is so minimal I'd be surprised if anyone not staring at a spectrum analyzer would point it out. Try playing a small bit of music on a 1-4 beat loop (so you can clearly hear what's going on) and play with the filters. The only thing that stands out to me is that in the 0-60 Hz range the high-pass resonance may be a bit too much when the resonant peak lines up just right with sub bass from the song. If we want would be possible to avoid that by reducing the resonance parameter when the filter is just barely engaged. I know some other people designing DJM-style filters who did that, but I don't know if it's really needed in practical use here.

robbert-vdh added a commit to robbert-vdh/mixxx that referenced this pull request Jan 4, 2023
This was changed in response to
mixxxdj#11177 (comment), but
the 1.0 behavior is correct. The pass band gain does not change with
resonance.
@daschuer
Copy link
Member

daschuer commented Jan 4, 2023

OK, than please add a comment to the source that we have intentionally removed the tuning compensation, that is suggested by Antti and was is part of the original implementation and add the reasons for our decision.

@ronso0
Copy link
Member

ronso0 commented Jan 5, 2023

The only thing that stands out to me is that in the 0-60 Hz range the high-pass resonance may be a bit too much when the resonant peak lines up just right with sub bass from the song. If we want would be possible to avoid that by reducing the resonance parameter when the filter is just barely engaged.

I'd consider that a big improvement, because that resonance is the only reason I try to skip the sub bass range when doing a high-pass sweep. (talking about the current implementation, not this branch which I didn't test yet)

@robbert-vdh
Copy link
Contributor Author

OK, than please add a comment to the source that we have intentionally removed the tuning compensation, that is suggested by Antti and was is part of the original implementation and add the reasons for our decision.

The current implementation has never had any tuning compensation. That's the effect I described earlier where the effective center frequency is dependent on the resonance parameter, and is never quite at the frequency you set it to. I checked the paper and nowhere does it mention anything about tuning based gain compensation. It briefly mentions only resonance gain compensation.

The only thing that stands out to me is that in the 0-60 Hz range the high-pass resonance may be a bit too much when the resonant peak lines up just right with sub bass from the song. If we want would be possible to avoid that by reducing the resonance parameter when the filter is just barely engaged.

I'd consider that a big improvement, because that resonance is the only reason I try to skip the sub bass range when doing a high-pass sweep. (talking about the current implementation, not this branch which I didn't test yet)

What do you propose? I can add an (on by default?) parameter to the Moog filter that rolls off the resonance during the first 100 Hz (for both low-pass and high-pass filters). Might also be useful to do the same thing above 20 kHz to make that less ear piercing and to make the transition into the low-pass filter even smoother. Do you want me to track that onto this PR or should I do it in another PR?

@ronso0
Copy link
Member

ronso0 commented Jan 6, 2023

I don't "want", I just appreciate you take care of Mixxx' sound quality. And I think it'd be better to fix separate issues in separate PRs : ) small PRs getting merged quickly vs. feature creep

@robbert-vdh
Copy link
Contributor Author

I don't "want",

Oh but I do ;). Would make engaging the high-pass filter just that little bit smoother. But yeah I'll look into this for a next PR after this one. The actual implementation will be super simple so it's mostly fine tuning some behavior until it feels right.

@daschuer
Copy link
Member

daschuer commented Jan 6, 2023

checked the paper and nowhere does it mention anything about tuning based gain compensation.

Read the last sentence in § 6.

To summarize: I am fine with the current state. We just need to document our decisions.

Oh but I do ;)

Thank you. This would finally allow to use a high resonance without blowing the speakers. (In a follow up PR)

@robbert-vdh
Copy link
Contributor Author

Read the last sentence in § 6.

I did, that's what I quoted. Tuning compensation. And gain resonance compensation. We do the latter, but tuning compensation was never part of Mixxx's Moog filter. I could add a comment that the passband gain is slightly dependent on the center frequency (that's also to be expected because of the way the transition band in these filters work, it's always a gradual slope). I'm just curious where the original formula came from.

@daschuer
Copy link
Member

daschuer commented Jan 6, 2023

It reads as a "Tuning and resonance compensation"

This part was the original tuning compensation:

m_postGainNew = (1 + resonance / 4 * (1.1f + cutoff / sampleRate * 3.5f))
* (2 - (1.0f - resonance / 4) * (1.0f - resonance / 4));

I have introduced that here:
8a5cc38
as
m_postGain = (1 + resonance * (1.1f + cutoff / sampleRate * 3.5f))
* (2 - (1.0f - resonance) * (1.0f - resonance));

My guess is that it as working at one pint and it was messed up later.

And it is also part of https://github.com/esluyter/houvilainenfilter as

*input = amf * (resonanceCorrPost + cutoffIn * resonance * 3.5f);

@robbert-vdh
Copy link
Contributor Author

robbert-vdh commented Jan 6, 2023

That's not tuning compensation. That's gain compensation. Tuning compensation would alter the value of cutoff.

And *input = amf * (resonanceCorrPost + cutoffIn * resonance * 3.5f); makes the filter louder at higher cutoff frequencies. Or quieter at lower cutoff frequencies depending on how you do your gain compensation. That's probably not what we want, no? Like I said, none of the transistor ladder filter implementations I've used do this and the couple DSP engineers I asked also don't do this in their implementations.

@daschuer
Copy link
Member

daschuer commented Jan 6, 2023

I read the sentence "Tuning and resonance compensation" as add a gain depending on the tuning and the resonance parameter. This is what we original did and what the other implementation did. You have removed the tuning part, that's my point.
But I guess this discussion is not goal-orientate. We need to decide for a compensation that suits to us and tell future readers why we decided like that. That's my only final request for this PR.

IMHO 0 dB in the pass band for any tuning and resonance is desired just like the normal filter.

I have recorded 3 times a 10 Hz sine waveform to see the current state. It is a LPF sweep with 3.5 1 and 0 resonance.
It start at unity, for some seconds than you can see the gain dip and at the end the resonance.
The resonance 0 value does not suffer the issue.

grafik

However as said, this is not really an issue when paling music, because due to the resonance and the missing highs, you cannot note the gain issue. In current main the gain issue IS notable when the LGF is engaged. This issue is fixed here.

@daschuer
Copy link
Member

daschuer commented Jan 6, 2023

It looks like the gain drops relative fast and than raises gently again. Maybe my original curve was not optimized for the part wher the gain drops?

@robbert-vdh
Copy link
Contributor Author

I posted this yesterday in Zullip but I did some more analysis on the filter. These are the DC gains with the original and this PR's gain compensation formulas:

image
image

The new curve is much better gain between resonance values, but there is indeed a tiny dip in passband gain when the cutoff frequency parameter is set to around the equivalent of 10 kHz. I'll do some experimentation to see if compensating for this dip in DC gain makes the filter sound better. I can imagine it might actually be desirable since higher frequencies are perceived as louder, so when the resonant peak is centered around those frequencies having the overall gain of the rest of the signal reduced a bit might not be a bad thing.

@robbert-vdh
Copy link
Contributor Author

Also keep in mind that analyzing filters this way only works for linear filters, and these filters are not linear. With normal settings and input gains you can consider them to be almost linear which is why these analyses still mostly work, but they don't paint the full picture. And whether or not I see a dip in DC/bass register gain while the cutoff frequency is still well above that seems to depend a lot on the track. Which makes sense to me considering the tanh nonlinearities in the filter. With most inputs I tried I don't actually see a dip like that. And I personally do like how a downwards low-pass filter sweep sounds now, it rolls off in a very smooth way.

Oh and another thing (for another PR) that I haven't mentioned yet but that I'm sure everyone has noticed is that the Moog high-pass filter doesn't close fully when you turn up the high-pass filter. Here's a filter sweep with the low-pass and the high-pass filter for reference:

simplescreenrecorder-2023-01-08_14.33.44-reencode.mp4

@daschuer
Copy link
Member

daschuer commented Jan 8, 2023

Thank you for the analysis. So it looks like my original compensation was a tuning compensation for the pass band, but it fails at high cut off frequencies used when the filter is turned in. When I compare the moog type with the normal filter switching on at 10 KHz LPG, the difference is notable, but not bad.

Regarding this PR the only missing point is a summary of these findings a some words why we pick the current compensation curve.

Proposal:

// The m_postGainNew is calculated to compensate the gain off the pass-band at 
// the maximum cutoff frequency in a way that there is no gain offset when the filter 
// is turned in. There is however a remaining gain drop at cut off with a maximum 
// at 10 kHz cutoff and high resonance which can be neglected, because it is barely notable
// and cause by the desired non linearity nature of the Moog filter.  

@robbert-vdh
Copy link
Contributor Author

robbert-vdh commented Jan 8, 2023

So it looks like my original compensation was a tuning compensation for the pass band

There never was any tuning compensation. Tuning compensation would have changed the cutoff frequency parameter so the effective center frequency would remain constant when changing the resonance.

And yeah that dip only occurs on some material. Of the four tracks I randomly grabbed it only happened with one of them. Notice how the bass keeps peaking at the same level I set the low-pass filter to around 0.20 in the video I posted above. That could be attributed to the filters being nonlinear.

@daschuer
Copy link
Member

daschuer commented Jan 8, 2023

I don't mind how it is called. What do you think about my proposed comment? Does it work for you?

@robbert-vdh
Copy link
Contributor Author

I'll add a comment saying we don't do gain compensation based on the cutoff frequency but like I already showed I'm not actually seeing a meaningful dip in the bass region when staring at a spectrum analyzer during actual usage (so when playing music through it rather than an impulse. which I already explained mostly works but is not technically the correct way to analyze these kinds of filters).

@robbert-vdh
Copy link
Contributor Author

robbert-vdh commented Jan 8, 2023

Done. Also updated the Jupyter notebook in the OP with the latest version containing more all the additional analysis.

robbert-vdh added a commit to robbert-vdh/mixxx that referenced this pull request Jan 8, 2023
This was changed in response to
mixxxdj#11177 (comment), but
the 1.0 behavior is correct. The pass band gain does not change with
resonance.
Copy link
Member

@daschuer daschuer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for all the work you put into this. LGTM
Just waiting for CI to finish before merge.

@daschuer daschuer merged commit 5b65b51 into mixxxdj:main Jan 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants