ChowPhaser icon indicating copy to clipboard operation
ChowPhaser copied to clipboard

Sound worsening with each sample rate increment.

Open RafaGago opened this issue 2 years ago • 12 comments

I've developed MixMaxtrix, a kind-of mixer and multifx. It is up for KVR Developer Contest 2021. One the FX I bundle is a functionally equivalent port of Chow Phaser (credited and with the original name, of course). https://www.kvraudio.com/product/mixmaxtrix-by-artv

Now when adding a brute force oversampler to some of the FX I noticed that the sound worsens on ChowPhaser instead of improving. It changes a lot in a non-subtle way.

I tried with your original ChowPhaser v1.1.1 (on Reaper Linux if that matters) by changing sample rate from 44100 to 176400. It happens too, so it seems that it is not an issue of my port.

Playing a drum loop with hihats. ChowPhaserMono settings that make it obvious: Depth: 0.86 Freq: 8.76 Skew: -1 Feedback: 0.79 Modulation: 0.54 Stages: 33.48 Drive: 4 Trash: 4.34 Dirt: 4.58

With these settings the difference between 44100 and 176000 is substatial (4x). It worsens more at 16x.

Unfortunately I can almost nothing about DSP and my math is extremely rusty. I haven't made the effort to understand the code in full but I wasn't able to find a simple fix.

RafaGago avatar Jul 13 '21 13:07 RafaGago

Playing a bit more, it seems like this might be a problem feedback section, specially with the "trash" stage.

RafaGago avatar Jul 13 '21 13:07 RafaGago

Thanks for reporting this! On one level, it does make sense since the nonlinear behaviour controlled by the "thrash" parameter is somewhat dependent on the sample rate. I'll investigate a bit more and see if I can figure out a way to correct for it so that at least it doesn't sound worse as the sample rate increases.

Thanks, Jatin

jatinchowdhury18 avatar Jul 14 '21 01:07 jatinchowdhury18

I'm playing around with adding a sample rate "correction" factor for the thrash parameter. You can see the change here. To my ears it sounds a bit better at higher sample rates with no change at lower sample rates, but I'd love a second opinion.

jatinchowdhury18 avatar Jul 15 '21 05:07 jatinchowdhury18

I tested on my port and it didn't make a lot of difference at 16x. Notice that the Drive/d1 parameter also shows the side effect to a certain extent. D3 doesn't.

I tried compiling the commit you passed, but it is requiring jack to be installed and I had to leave. I'll try to check later.

RafaGago avatar Jul 15 '21 13:07 RafaGago

It reproduces in your code. I played the same drum loop and these settings:

Depth: 0.74 Freq: 8.71 Skew:-1.06 Feedback: 0.79 Mod: 0.54 Stages: 33.48 Drive: 0.1 Trash: 5 Dirt: 0.1

At 44100 I adjust for a 0dB peak. At 176k the level peaks at +5dB. Although the settings are nonsense, at 44100 it sounds like a phaser, at 176k it sounds like stuttering.

RafaGago avatar Jul 15 '21 18:07 RafaGago

Ah okay, thanks for sharing your settings again. I think I'm seeing what the problem is now. Basically, the configuration of the nonlinearities is causing a DC build-up somewhere in the feedback filter structure, that only seems to triggered at higher sample rates The way the DC build-up interacts with the nonlinearities is what's causing the "stuttering" type of sound. With that in mind, I've got a few things to try:

  • Remove the nonlinearities, and make sure the DC build-up isn't being caused by something "upstream" from the feedback filter.
  • Try tweaking nonlinearities to remove any DC build-up that they are causing directly. I have a decent idea of how this might work, but it might be tricky to do it in a way that keeps the filter sounding the same as it does now at the same settings.
  • Try adding a DC blocker in the filter's feedback path. I don't love this option since it would add a little bit of group delay to the feedback path, but if it's only enabled at higher sampling rates, it might not make a difference.

Anyway, I'll keep investigating this weekend, and let you know what I find!

Thanks, Jatin

jatinchowdhury18 avatar Jul 17 '21 01:07 jatinchowdhury18

That makes sense.

Maybe I will say something foolish, because all I know is just by tweaking and reading sources of what other people do, KVR, etc. I have no formal education on DSP. Unrelated, but thanks for making your code open source, I'd never have understood ADAA reading directly from the papers.

Coming back to the topic, if the DC blocker phase response has actually a bad effect on the sound (this is a phaser after all, so there is no need for phase preservation), maybe there is a way to put a matched allpass filter correcting/minimizing the phase response of the DC blocker just afterwards, then a fractional delay line to round to a sample boundary.

If this block amounts to 1 sample delay, then the feedback timing would be the same-ish (the correction might not be perfect) at 44100 and 88200 and keep improving from there. The bandwith would still be higher at higher SR, of course.

There is also the possibility that the DC blocker improves the sound at base rate. If the sound changes are very minimal at non-extreme settings maybe it's worth to go for it.

RafaGago avatar Jul 17 '21 11:07 RafaGago

Sorry, I ended up being super busy this weekend, and wasn't able to make much progress. I'll try to dig into it further today and tomorrow.

Coming back to the topic, if the DC blocker phase response has actually a bad effect on the sound (this is a phaser after all, so there is no need for phase preservation), maybe there is a way to put a matched allpass filter correcting/minimizing the phase response of the DC blocker just afterwards, then a fractional delay line to round to a sample boundary.

This is definitely a good idea, but might require further correction elsewhere in the filter. The main reason I'm worried about the DC blocker in the feedback path is that the delay it would add to the signal would actually alter the cutoff frequency of the filter. Adding the matched allpass filter like you mentioned should make the added delay close to an integer number of samples. So for example, if we could make the DC blocker + matched allpass have a total delay of 1 sample, then I could add a sample delay to all the other delayed paths in the filter, and then design the filter coefficients at half the sample rate. That said, I'm hoping there will be a simpler solution than that!

There is also the possibility that the DC blocker improves the sound at base rate. If the sound changes are very minimal at non-extreme settings maybe it's worth to go for it.

Absolutely :)!

jatinchowdhury18 avatar Jul 19 '21 16:07 jatinchowdhury18

Okay, so I've made a few more changes on the nl-samplerate branch. Basically adding the DC blocker (turns out it doesn't have much effect on the phase/frequency response of the overall filter), and tweaking the range of the "Dirt" parameter. It does change the sound of the effect a little bit, but I think I like it a little better. That said, definitely needs some more testing and tweaking.

jatinchowdhury18 avatar Jul 20 '21 19:07 jatinchowdhury18

Cool, I'll try tomorrow

RafaGago avatar Jul 20 '21 20:07 RafaGago

There is an improvement. Now I'd say that it feels like a phaser. With this setting on a drum loop containing hi hats at 176k:

Depth: 0.95 Frequency: 2.6 Skew: 0.6 FeedBack: 0.95 Modulation: 1 Stages: 47 Drive 0.1 Trash: 0.1 Dirt: 0.1

With this "extreme" setting Drive/Trash/Dirt can be tested independently.

Dirt is acceptable. Trash and to a lesser degree Drive still make the treble to sound painful with a lot of transients. Maybe the top octaves can be removed/smeared on the FB loop (?). Maybe the more frequent feedback causes more aliasing and that is what we hear? It would be nice to try a 2/4 pole butterworth LP on the feedback loop cutting around 5kHz?.

RafaGago avatar Jul 21 '21 13:07 RafaGago

Using 3 of the DC blockers named by "mystran" on the thread below and double precision seems to make things much better, even at 16x.

https://www.kvraudio.com/forum/viewtopic.php?f=33&t=545280#top

One after each tanh call.

auto prev_lp_out = states[onepole::z1];
onepole::tick (coeffs, states, in);
return in - prev_lp_out;

The problem it changes the sound a lot on higher sample rates. Enough to be called a new plugin. The "trash" control becomes brutal.

RafaGago avatar Aug 23 '21 19:08 RafaGago