[DSP module discussion] New Oversampling class

dsp_module

#21

You’re right, I do get an assertion, turns out I was “debugging” a release build rather than a debug build. Still, if I just comment out that assertion, then everything works fine with the IIR, and fine up to 64x on the FIR, is there any particular reason I shouldn’t be doing this?

Also, it seems your oversampling class might not be suitable for ISP detection, or it least it seems to generate different peak magnitudes than the intersample peaks detected with K-Meter.

I am already using a custom version of your class, but I’m not sure how (if it’s even possible) to embed detection while also accounting for the new peaks introduced during the multiple stages of downsampling, I’m not sure it’s possible to detect them without upsampling again after the downsampling.


#22

Well, the thing is I didn’t really tried to optimize further the way the filter coefficients are generated when the oversampling factor is high. And the most important reason I didn’t want people to use the oversampling that way is because for me, more than 16x oversampling is always overkill for solving any specific aliasing issue. Oversampling is not just about filtering like hell aliasing, it’s also something that multiplies by the oversampling factor the CPU load of the processing embedded inside !

So if you still have too much aliasing in any specific context even with 16 times, maybe what you really need to do is to code a custom class heriting from Oversample so you can customize the filter design functions in the constructor and make them more powerful.

About the ISP detection, your concern is more than relevant, and after some additonal thought I think I can say that embedding it inside processing oversampling might not be the best thing to do in general, since multi-stage oversampling is the thing widely used for oversampling 4 times and more for obvious reasons.

The ideal situation there would have been to have only one stage oversampling algorithms, and a detection made after the downsampling filtering before the downsampling itself, to take into account the implication of the filtering on the ISP.

But in general, I think most of audio developers have multi-stages oversampling for obvious reasons, so the only pratical and general solution is additional oversampling after to do the detection. That’s what happens for example if your meter is in a dedicated plug-in. Then, the question is how to perform that oversampling without getting something not relevant because of the filtering… I guess I could find some bibliography on this specific issue, right now unfortunately I don’t have the answer…


#23

I actually agree that >16x oversampling is mostly snake oil and a huge cpu hog, but some consumers (mastering engineers) insist on it so I feel like I should at least give it as an option. Still for what it’s worth, your oversampling class even at very high factors is more cpu efficient than other solutions I’ve tried.

What I’ve done for now is use a second instance of your class with 4x oversampling using FIR. At normal levels it seems to catch ISPs fine, and when pushed hard it only seems to get it “wrong” (relative to K-meter) by about 0.1db over or under.


#24

I have just read this article : https://techblog.izotope.com/2015/08/24/true-peak-detection/

Apparently, in the BS.1770 specification, the coefficients of a filter for 4 times upsampling are provided, but the resulting ISP detection isn’t that good. I really wonder how better detectors work, and I’ll probably have a look soon :wink:

So K-meter (this one ? http://www.meterplugs.com/kmeter ) is considered as a reference in this area ?


#25

I’m not sure if K-meter is considered a reference, it just happens to be what I’ve been using for a long time.

Let us know if you find if you find a better solution! :slight_smile:


#26

There resulting intersample peaks will depend on which algorithm for oversampling is used. That’s why there is a specification.


#27

The question is whether the most important thing is to have an algorithm following a specification for true peak detection, or an algorithm efficient for detecting these peaks… That’s the question the izotope blog article is asking, and I really don’t know this area enough to have an opinion there !


#28

I’m assuming that the JUCE resampling option here is the older ResamplingAudioSource class: http://src.infinitewave.ca/ ? How do you think the new Oversampling class with the high quality option would compare in that list? (perhaps you could get it updated on that site :wink: )


#29

Note that the comparison is for 96000Hz to 44100Hz while the Oversampling’s underlying down/up-sampling are for ratios 2, 4, 8, 16…


#30

I have questions regarding quality and differences between FIR-Equiripple and Polyphase-IIR - ran this very simple test of oversampling an input of zeros with a very simple process that just adds a sine wave at the oversampled frequency’s nyquist, and looks at the signal level of the last block.
Basically an ideal downsampling should make this high-freq sine disappear.

Here’s the test:

dsp::Oversampling<double> oversampling (1, 4, dsp::Oversampling<double>::filterHalfBandFIREquiripple);
AudioBuffer<double> buffer (1, 256);
dsp::AudioBlock<double> block (buffer);
oversampling.initProcessing (buffer.getNumSamples());
for (int block_i = 0; block_i < 1000; ++block_i)
{
    block.clear();
    dsp::AudioBlock<double> highSampleRateBlock = oversampling.processSamplesUp (block);
    double* samples = highSampleRateBlock.getChannelPointer (0);
    for (int i = 0; i < highSampleRateBlock.getNumSamples(); ++i)
        samples[i] += (i % 2 == 0) ? 1.0 : -1.0;
    oversampling.processSamplesDown (block);
}
for (int i = 0; i < 10; ++i)
    cout << block.getSample (0, i) << " ";
cout << endl;
cout << Decibels::gainToDecibels (fabs (buffer.getRMSLevel (0, 0, buffer.getNumSamples())), -10000.0) << endl;

Using the filterHalfBandPolyphaseIIR option, the signal level / difference from the ideal (which is a zero signal) was under -300 dB at all ratios, which seems very reasonable.
But when using filterHalfBandFIREquiripple the results were far from as good: -50 dB for the x16 ratio, -64 dB for the x4 ratio.

Am I doing something wrong in my test? Is polyphase-iir expected to have much better qualitity than fir-equiripple?


#31

Using the filterHalfBandPolyphaseIIR option, the signal level / difference from the ideal (which is a zero signal) was under -300 dB at all ratios, which seems very reasonable.

It seems that this is actually the symptom of a bug in Oversampling2TimesPolyphaseIIR. It was fixed on the develop branch (https://github.com/WeAreROLI/JUCE/commit/1ff97d3688dbdb67a61c7de1cafc58f3f48a24af), but not on master nor in the JUCE 5.2.0 release.


#32
  • The Polyphase-IIR option was the one behaving fine, so that’s probably not it.
  • Just pulled latest develop and I get the exact same results.

#33

Yair - that’s the absolute worst case scenario you’re testing isn’t it? I wouldn’t draw too many conclusions from it - maybe try setting up a test bed where you sweep a few harmonics through it instead.

I’ve spent a lot of time experimenting with half band filters and generating higher order harmonics and have learnt that selection of filter characteristics really is an exercise in finding the best compromise for your particular application. And that brings me to some feedback for Ivan…

Ivan, I’ve been taking a look at this class over the last couple of days and comparing it to my own wrapper for Laurent de Soras’ HIIR. I must say your solution for handling multiple stages is quite elegant! I have however got a few constructive comments:

  • As noted above, I think it’s important for users to be able to design their own filters
    • Your code comments say to inherit from Oversampling to do this
    • However this is not practical unless you change most of the private members to protected
  • I think your choice of transition bandwidths could be improved a little
    • First stage of upsampling and last stage of downsampling could be tighter (I use 0.01 for high quality)
    • Other stages could be a lot looser (0.255 should be fine for all of them)
  • I find it a bit confusing where you refer to the factor when I think you’re referring to the order (i.e. 16X is a factor while 4 is the order)
  • I think there are important use cases where factors much higher than 16X are useful (e.g. guitar cabinet modelling)
  • Performance notes
    • I adapted your code to match the filter design I use in my own library and found that on some systems it was 45% slower than the SSE optimised HIIR routines by Laurent de Soras. On other systems the performance was approximately the same.
    • If I unroll the for loops for the direct and delayed paths then performance more or less matches (at block sizes of 256)
      • This of course makes the code quite verbose if you want to cater for say 4…13 coefficients (but I’d be happy to provide this if you like!)
    • For mastering quality filters it might be worth considering using SIMD instructions to process the channels in parallel
      • I haven’t thought this through, but other than exposing those private members, I don’t think you’d need to make any other changes to your interface
  • I haven’t yet validated the outputs of your filters against my own library, but I’ll get to it eventually :slight_smile:

#34

Hello @yairadix !

I think your test is wrong, since your assumption of the oversampling filtering which should act on the half sampling frequency is wrong.

A better test would be to use a sine with a frequency around 10 kHz, then applying some heavy waveshaping to it, and oversampling that waveshaping. With a spectrum analyzer, you might see how much the aliased components are attenuated with the different options. Even better would be to use a sine sweep and run the tests like in the SRC website.

@Andrew_J : thanks for the extensive and constructive feedback ! I have to confess I have not spent that much time exploring the best options for the different stages filtering options, because I have not been able to get enough information on this prior to the development of the DSP module. Which was the reason I thought it might a good thing to let users create their derived class from Oversampling to set the filters depending on their needs. And of course some of the members should be protected instead of private to allow this !

The other reason I didn’t spend much time on this is because I wanted to have my PlotComponent class fully developed before working again on this, so users (and me) might be able to see the influence of the filters parameters thanks to some curves in a demo app, to be able to choose the best options. Anyway, I might change at some point the default values for something more relevant, I’ll do it when I have some time (and I’ll update the comments to prevent any confusion between “order” and “factor” or “number of stages”).

Regarding performance, I have not done yet a lot of benchmarking there, I’ll have a look in the code at the same time to see if I can improve it there as well. SIMD is something I usually don’t do when it’s not an absolute necessity and that I have a lot of parallel processing. But since Laurent de Soras did it that means there is something relevant to do on the SIMD side in my class as well.

(Going back to finishing the PlotComponent class, + the delay line class in my own personal module, + debugging some stuff in the Convolution class + working on the next DSP module discussions topics for the JUCE forum + finishing work for upcoming deadlines… :joy: :rofl: )


#35

By the way, I have created this topic on KVR : http://www.kvraudio.com/forum/viewtopic.php?f=33&t=497984

Don’t hesitate to participate, I’ll include all the new intel from there in the next iterations of the Oversampling class :wink: