sdatkinson / AudioDSPTools

A library of basic audio DSP tools
MIT License
35 stars 10 forks source link

Adding a low-pass filter before downsample reduces aliasing quite dramatically when using non-linear processing at higher rate (96k or 192k) #12

Open honkkis opened 9 months ago

honkkis commented 9 months ago

This pull request fixes the aliasing by adding a simple low pass filter in the resample container just before downsampling.

This commit implements https://github.com/sdatkinson/AudioDSPTools/issues/10

The original resampling code did not have a lowpass filter before downsampling. This creates a lot of alias because the NAM block creates a lot of harmonic distortion, thus content above nyquist limit of the target signal.

For example, see here from up to down. 1) reaper 4x effects oversampling 2) original NAM 0.7.8 oversampling 3) This PULL request: input signal 48k -> upsample 192k -> NAM 192k -> *lowpass 0.924k** -> downsample 48k.

Screenshot 2024-02-04 at 12 37 25

Could you include this in the main version of the plugin, it would greatly improve the alias behaviour when using NAM captures of higher sampling rates and input / output at lower sampling rates.

Let me know if further work / study is needed.

sdatkinson commented 9 months ago

I'm sorry, but this is not the right way to go about doing this.

The purpose of the Lanczos-filtering resampler is to low-pass when resampling. Have a look at Lanczos resampling to learn more.

The problems with your solution are:

  1. It only works when the sample rate of the container is higher than the surrounding context. If the external context is running at 96k and the encapsulated context is at 48k, then this doesn't work.
  2. using a biquad low-pass filter has phasing effects that leak into the high frequencies. (sinc-type resampling filters have "pre-ringing", so they're not perfect either. All of these sorts of solutions have compromises...)

If you want to improve aliasing around the resampler, I'd suggest getting more familiar with the Lanczos algo that's happening. It's possible there's a bug in it (or how it's getting used here)--in which case I'd be happy for a fix. But if you really wanted a first-order LPF for anti-aliasing, then I'd recommend (1) implementing an extension to the Lanczos resampler to apply the LPF in addition then (2) refactoring the resampling container here to use different resampler modules (i.e. your Lanczos+LPF).

Related question: how are you generating your spectrogram plots? Knowing how to make those would help me dig into this myself as well 🙂

honkkis commented 9 months ago

Thank you for your comments @sdatkinson! It would be great if someone in addition to me could look at this! Currently this LP filter is the only way I can get rid of the huge amount of aliasing when using 96kHz NAM captures.

To listen and visualize the aliasing it, I just render a sine sweep through NAM in Reaper. I visualize it by turning on the Reaper’s waveform+spectrogram view. In my tests the spectrogram visuals match very well with listening tests when one listens to a sweep at higher frequencies e.g., ~6-12kHz.

Here is one Lanczos resampler where they explicitly state “​​If used for downsampling, make sure to apply an appropriate anti-aliasing lowpass filter first.”. https://docs.obspy.org/master/packages/autogen/obspy.signal.interpolation.lanczos_interpolation.html

Do we have an example of good-quality Lanczos (or any other real-time method) audio downsampling implementation that works well without a low-pass filter in case where there is a lot of energy above Nyquist of the target samplerate? I had always assumed most real-time downsampling methods need a LP.

Based on Oli's comments, I first tried changing this line: https://github.com/sdatkinson/NeuralAmpModelerPlugin/blob/51e151f1bf908d5709e082feacf603c607f241b4/NeuralAmpModeler/NeuralAmpModeler.h#L192, but there was no change in the aliasing - could it be that the value is not passed down to the actual resampler? Or as you said, maybe the resampling algorithm has a bug, who could debug it?

Answers:

  1. EDIT: I moved the code to a subclassed resampler and made it conditional so that it is only run in the case of downsampling.
  2. The current filter is second order 12db/octave, but I am planning to experiment with steeper filters, but for my tests this is already quite good for my purposes (I cannot hear the aliasing with a sine sweep).

EDIT: Regarding your suggestion on refactoring the LP filter into a derived Lanczos resampler, it is now done.

honkkis commented 9 months ago

@sdatkinson I moved the LPF code away from the container as suggested. I hope this was the way you envisioned! Let me know if further changes/analysis is needed.

honkkis commented 9 months ago

It seems that changing the 2nd order filter to 4rd order filter (24dB/oct) before the downsampilng step reduces the aliasing even further and now it seems to be very close to Reaper plugin resampling quality. From top:

TOP: Reaper 4x effects oversampling from 48k to 192k MIDDLE (this PULL REQ) 12dB/oct BOTTOM (branch WIP_steeper ) 24 db/oct

Screenshot 2024-02-06 at 20 24 26

Here's the original 0.7.8 downsampling:

Screenshot 2024-02-08 at 20 08 45

The 4rd order filter code is in this branch: https://github.com/sdatkinson/AudioDSPTools/compare/main...honkkis:AudioDSPTools:WIP_steeper