Oversampling for an EQ

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

At 44.1k or 48k samplerate I would probably be inclined to try oversampling at least 4X if trying to avoid high frequency BLT "frequency warping" filter response oddities. And I would rather put up with high frequency response oddities than unexpected phase response oddities, so would probably try oversampling rather than screwing up the phase response just to make non-oversampled high-frequency amplitude response look "better".

However I'm an ignorant geezer and in addition it has been more than a decade since I last made an equalizer effect. The following may be hopelessly full of errors, read at your own risk. :) Just some silly novelty ideas or "something you might play with" to see how bad it performs.

Included is some jsfx example code snippets. I have tested the JOS allpass delay parts, but at the moment I don't have a good "EQ suitable" filter object in jsfx so in the following code example in commented-out lines are example use of a fictional peaking filter object to make a crude 2 band EQ just to give the basic idea. If you drop in a working peaking filter object then maybe it would actually work. Or maybe there are logic flaws.

Long ago I used the JOS allpass interpolator as part of an oversampling toolkit but hadn't used it for years. In retrospect I had decided the allpass interpolation was maybe a "fatally flawed" cheap trick.

Then writing a hobby peaklimiter, I tested 4X JOS AP "intersample peak detection" compared to 4X sinc FIR interpolation oversampling. In context of my little peaklimiter, surprisingly the allpass interpolator picked out intersample peak levels very close to the same as sinc oversampling. As measured on music mix test audio, only a fraction of a dB difference, I decided to use the allpass interpolation rather than the FIR oversampling because they measured ISPs "about the same" so far as I could determine on practical mixes, and the allpass interpolation was so much simpler than FIR oversampling.

So I got interested in it again and MAYBE it wouldn't necessarily be so bad a way to cheap oversample for an equalizer or maybe some other thangs.

https://ccrma.stanford.edu/~jos/pasp/Fi ... ation.html I have trouble understanding a lot of JOS articles. In my ignorance the difference equation he writes seems to describe an allpass topology slightly different from the schematic block diagram in the article. Most likely both versions are completely equivalent but I'm too ignorant to judge. So having to choose and not knowing any better, in the code below I copied the flowchart in the article's picture rather than the text difference equation in the article. It seems to work as advertised in my testing.

It borrows some code from the peaklimiter. Only oversamples 4X for lower samplerates, etc. At higher samplerates you don't really need to oversample so much.

https://www.dsprelated.com/freebooks/pa ... ation.html At the top of this article is described linear interpolation which tends to FIR lowpass filter similar to the "repeat-sample" naive oversampling of an IIR filter just by feeding it the same sample 2 or more times in a row.

As shown in both articles, above about 10 kHz at 44.1 k samplerate, the allpass interpolator begins to degrade toward "repeat sample" behavior but not so bad except in the top audio octave. I'm guessing maybe this could maybe add a lowpass characteristic way up high if we use the allpass interpolator for oversampling, just like linear interpolation or repeat-sample oversampling, but I don't think the lowpass would be as extreme. Also, maybe some tracks can contain a lot of energy above 10 kHz, but I think the majority of practical music tracks do not have very much percent of RMS power above 10 kHz.

I haven't tested it, but for instance if this kind of allpass oversampling, its worst effect was a slight high-frequency loss, then maybe it could be compensated with a little bit of high-frequency pre-emphasis or post-emphasis adjustment.

OK here is another thing maybe I'm thinking about it wrong in the pseudo-code below-- The fictional EQ prototype is the kind that takes a center freq in Hz and a samplerate. The actual filter-setting math figures it as the center frequency as a ratio against the sample rate, so initting the filter with a higher samplerate has the effect of tuning the filter lower as a percentage of the samplerate.

When you "temp oversample" a signal it is like transposing the signal down. If the input signal is 1000 Hz and you double the number of samples, then the oversampled "slowed down" frequency is 500 Hz relative to the original samplerate. So if the filter has been initted with 2X the samplerate then it is tuned down to match that same slowed-down pitch (if the oversampled audio was played-back at the original samplerate).

Therefore at the last step if you drop half the samples, it gets "speeded back up" to the original 1000 Hz at the original samplerate again.

The following code, in the case of 4X oversample-- At the beginning of the @sample block, the EQ filters were last hit with the previous spl0 and spl1 from the last time the @sample block got called. So on this current entry, we need three new samples in-between the last spl0/spl1 and the current spl0/spl1.

So first we feed the current spl0 into the 0.75 sample delay, to get "one quarter sample past the last spl0". Then we feed that 0.75 delayed sample thru the oversample-tuned EQ filters and discard/ignore the output (but the EQ filters remember their states). Then we feed the current spl0 into the 0.5 sample delay and EQ filter it, and then feed the current spl0 into the 0.25 sample delay and EQ filter it.

Then finally we feed the current spl0/spl1 thru the EQ filters and then keep that result as the final "downsampled" EQ output.

Because everything in the process would be linear except some tendency toward phase clumping in the top octave, I wonder if anything would need to be bandlimited during the up or down sampling? Maybe if the input is bandlimited there isn't much way that aliasing would get added in this kind of scheme.

It probably sucks bad for some reason or the other, because hardly anything in the real world is inexpensive and also good, but dunno. Maybe I'll test it sometime.


Code: Select all

@init

  //https://ccrma.stanford.edu/~jos/pasp/First_Order_Allpass_Interpolation.html
  //https://www.dsprelated.com/freebooks/pasp/Delay_Line_Interpolation.html
  function JOS_AP_FracDly_Init(a_FracSampDly)
  (
    this.FracSampDly = max(min(a_FracSampDly, 0.99), 0.01);
    this.k = (1 - this.FracSampDly) / (1 + this.FracSampDly);
    this.p1 = 0.0;
    this.last_p1 = 0.0;
  );

  //only call after filter was created via _Init
  function JOS_AP_FracDly_SetFracSampDly(a_FracSampDly)
  (
    this.FracSampDly = max(min(a_FracSampDly, 0.99), 0.01);
    this.k = (1 - this.FracSampDly) / (1 + this.FracSampDly);
  );

  //only call after filter was created via _Init
  //Returns the delayed sample
  function JOS_AP_FracDly_DoSamp(a_InSamp)
  local (l_result)
  (
    this.p1 = a_InSamp - (this.k * this.last_p1);
    l_result = this.last_p1 + (this.k * this.p1);
    this.last_p1 = this.p1;
    l_result;
  );
  
  //assume that EQ Filter objects have been defined here to your specs
  //no specific EQ filter is included in this simple example
  //maybe something that works like this:
  //  PeakingFilter_Init(a_Filt_Freq_Hz, a_Filt_Bandwidth, a_FiltBoostOrCutDB, a_SamplingRate)
  //  PeakingFilter_Set((a_Filt_Freq_Hz, a_Filt_Bandwidth, a_FiltBoostOrCutDB, a_SamplingRate)
  //  PeakingFilter_DoSamp(a_InSamp)
   
  (srate >= 176400) ?
    g_OvrSmpMul = 1.0
  :
  (
    (srate >= 88200) ?
      g_OvrSmpMul = 2.0
    :
      g_OvrSmpMul = 4.0;
  );  

  //init JOS fractional delay filters
  o_JosApDly_075_L.JOS_AP_FracDly_Init(0.75);
  o_JosApDly_050_L.JOS_AP_FracDly_Init(0.50);
  o_JosApDly_025_L.JOS_AP_FracDly_Init(0.25);
  o_JosApDly_075_R.JOS_AP_FracDly_Init(0.75);
  o_JosApDly_050_R.JOS_AP_FracDly_Init(0.50);
  o_JosApDly_025_R.JOS_AP_FracDly_Init(0.25);
 
  //init fictional crude stereo 2 band EQ
  //o_Bass_L.PeakingFilter_Init(80, 2.0, 3, srate * g_OvrSmpMul);
  //o_Bass_R.PeakingFilter_Init(80, 2.0, 3, srate * g_OvrSmpMul);
  //o_Treble_L.PeakingFilter_Init(5000, 2.0, 3, srate * g_OvrSmpMul);
  //o_Treble_R.PeakingFilter_Init(5000, 2.0, 3, srate * g_OvrSmpMul);

  @sample
  (g_OvrSmpMul >= 4) ? //4X oversample
  (
    s_Tmp_L = o_JosApDly_075_L.JOS_AP_FracDly_DoSamp(spl0);
	//s_Tmp_L = o_Bass_L.PeakingFilter_DoSamp(s_Tmp_L);
	//s_Tmp_L = o_Treble_L.PeakingFilter_DoSamp(s_Tmp_L);

    s_Tmp_L = o_JosApDly_050_L.JOS_AP_FracDly_DoSamp(spl0);
	//s_Tmp_L = o_Bass_L.PeakingFilter_DoSamp(s_Tmp_L);
	//s_Tmp_L = o_Treble_L.PeakingFilter_DoSamp(s_Tmp_L);

    s_Tmp_L = o_JosApDly_025_L.JOS_AP_FracDly_DoSamp(spl0);
	//s_Tmp_L = o_Bass_L.PeakingFilter_DoSamp(s_Tmp_L);
	//s_Tmp_L = o_Treble_L.PeakingFilter_DoSamp(s_Tmp_L);

    s_Tmp_R = o_JosApDly_075_R.JOS_AP_FracDly_DoSamp(spl1);
	//s_Tmp_R = o_Bass_R.PeakingFilter_DoSamp(s_Tmp_R);
	//s_Tmp_R = o_Treble_R.PeakingFilter_DoSamp(s_Tmp_R);

    s_Tmp_R = o_JosApDly_050_R.JOS_AP_FracDly_DoSamp(spl1);
	//s_Tmp_R = o_Bass_R.PeakingFilter_DoSamp(s_Tmp_R);
	//s_Tmp_R = o_Treble_R.PeakingFilter_DoSamp(s_Tmp_R);

    s_Tmp_R = o_JosApDly_025_R.JOS_AP_FracDly_DoSamp(spl1);
	//s_Tmp_R = o_Bass_R.PeakingFilter_DoSamp(s_Tmp_R);
	//s_Tmp_R = o_Treble_R.PeakingFilter_DoSamp(s_Tmp_R);
  )
  :
  (
    (g_OvrSmpMul >= 2) ? //2X oversample
    (
    s_Tmp_L = o_JosApDly_050_L.JOS_AP_FracDly_DoSamp(spl0);
	//s_Tmp_L = o_Bass_L.PeakingFilter_DoSamp(s_Tmp_L);
	//s_Tmp_L = o_Treble_L.PeakingFilter_DoSamp(s_Tmp_L);

    s_Tmp_R = o_JosApDly_050_R.JOS_AP_FracDly_DoSamp(spl1);
	//s_Tmp_R = o_Bass_R.PeakingFilter_DoSamp(s_Tmp_R);
	//s_Tmp_R = o_Treble_R.PeakingFilter_DoSamp(s_Tmp_R);
    ); 
  );

  //keep the last time thru the EQ filters and write back to spl0 and spl1
  //s_Tmp_L = o_Bass_L.PeakingFilter_DoSamp(spl0);
  //spl0 = o_Treble_L.PeakingFilter_DoSamp(s_Tmp_L);

  //s_Tmp_R = o_Bass_R.PeakingFilter_DoSamp(spl1);
  //spl1 = o_Treble_R.PeakingFilter_DoSamp(s_Tmp_R); 

Post

Hi JCJR,

It's not like the amplitude matched filters totally mess up the phase response or something. The error is small and is it's worst at higher frequencies, say above 10khz which I noticed is the point at which you're no longer worried about the quality of your over-sampling method :)

By the way regarding all-pass interpolation. I think you are right that there's an error in the block diagram.

Also, if I have it right, to up sample by x4 then you need 3 instances of the interpolator. One for 0.25, 0.5 and 0.75 each. Then you have to "stitch" the three signals together with the original signal to get a 4x signal. Is that right? If so it's an inherently non-linear process, I think. I'd be wary of such a scheme, but I haven't tested it out.

Post

JCJR

It's curious to mention that "the phase oddity" you'll introduce with such allpass interpolator would be higher than the "continuous prototype - matching design" phase error we're so afraid of ;)
(I'm not counting that this (at first glance) also won't do the trick simply because in that near-FN range we're fighting for it will work exactly like "no-interpolator" resulting in all weird effects (both freq and phase) any naive "oversampling w/o antialiasing" would. I won't get into details as it's not important for this my post - I'll leave the analysis to someone else again :).

In other words, when looking for a cheap oversampling it's important to ensure that the amount of "oddities" you introduce then are not higher than the oddities you're trying to avoid. That's why it also important to understand what kind (and what amount) of "phase error" we get with matching designs. It's not even that we get any "additional" and/or really "weird" phase-shift. With matching designs (its min-phase version) we in fact get a filter with its phase shift "distortion" being lower than the shift of the corresponding analog filter. And the problem is not in the phase shift as such but in contradicting with the main goal of the "matching" thing (and the primary property of a continuous filters we're hunting for): "filter response must remain the same regardless of center/cut frequency (and/or sample rate) we use". With matching designs we never can get this to be perfect for the phase response - but yet again it's important to understand the amount of the error to decide if it's (yet again a "lower phase distortion as higher in frequency range we move" to push an emotional propaganda ;) acceptable or not.

Post

The thing is, typical oversampling methods use linear phase filtering, meaning there is no phase distortion whatsoever. Personally I have higher concerns about untypical phase behavior. The effects of amplitude errors are relatively obvious, while with the phase it's difficult to predict when and how it can become audible (as this strongly depends on the material). YMMV.

I'm not into matching designs, but I'd expect that they'd try to turn a lowpass into a high-shelf (in BLT terms) and a band-shelf into a mixture of a bandshelf and highshelf. If I'm not mistaken, switching a lowpass to a high-shelf has a drastic effect on the phase (no matter how little of a highpass signal is added). Instead of being positive everywhere, the group delay will become negative at high frequencies. Adding high-shelf to the band-shelf is maybe not as drastic, don't know.

Post

Instead of being positive everywhere, the group delay will become negative at high frequencies.

If it would be the only concern then we could simply switch from a min-phase design to a "mid-phase" one (I doubt there's a widely accepted nickname for it - basically the one having 180° instead of 0° at Fn). Though personally I don't think it could be really worth it (always positive shift at cost of higher phase error for lower Fc settings - though on the other hand it's all just about +1 sample group-delay near Fn) unless there's some specific goal. It's interesting and sort-of unexplored topic but I'm afraid it will tend to turn into philosophical "what phase response we think is more natural and why exactly?" So let this be just my minor remark ("we have options").

The thing is, typical oversampling methods use linear phase filtering ...

Yes. If we can throw in a nearly-perfect resampler (like x8+ with a hundred(s)-of-taps antialiasing linear-phase FIR(s)) then it's an absolute winner by all means (except maybe latency). And there's no reason to bother with matching designs in this case.

Post

Max M. wrote:Yes. If we can throw in a nearly-perfect resampler (like x8+ with a hundred(s)-of-taps antialiasing linear-phase FIR(s)) then it's an absolute winner by all means (except maybe latency). And there's no reason to bother with matching designs in this case.
I think "hundreds" is a bit excessive, as you can have pretty decent quality with a lower order of magnitude size.

Post

I think "hundreds" is a bit excessive

True. But the lower we go the more concerns of 'is it descent enough to be a winner?' rise (we don't have any objective criteria to decide if +/-0.25dB error at fs/3 is more tolerable than +/-0.25 samples group-delay drift at the same frequency - all numbers are absolutely artificial and synthetic of course).

Post

Max M. wrote:(we don't have any objective criteria to decide if +/-0.25dB error at fs/3 is more tolerable than +/-0.25 samples group-delay drift at the same frequency - all numbers are absolutely artificial and synthetic of course).
That's my point. And then I'm inclined to be more cautious about phase artifacts (particularly about those which change the sign of the group delay) than amplitude artifacts, since the former are more difficult to assess. Plus, as soon as the signals are mixed, phase artifacts turn into amplitude ones and vice versa, while, given that we are staying strictly in BLT domain, there is only frequency warping, which is pretty predictable.

Post

matt42 wrote:It's not like the amplitude matched filters totally mess up the phase response or something. The error is small and is it's worst at higher frequencies, say above 10khz which I noticed is the point at which you're no longer worried about the quality of your over-sampling method :)
Thanks matt42. I did not intend to argue that above 10 kHz "does not matter." I try to view it in perspective though that is hard to do. We have about 10 octaves from 20 Hz to 20 kHz and in my listening / watching RTA displays, most of the energy in music I listen to, is in the center 8 octaves.

Some stereophiles become obsessed and bother themselves mightily about ultra-low-distortion reproduction above 10 kHz or below 40 Hz. I just sometimes wonder if it can be assigning really high priority to "the lowest priority frequency bands"? Of course I too have probably paid "too much" attention to above-10KHz and below-40Hz fidelity over the years. It is just wondering in an imperfect world that will never be perfected, how much EXTRA attention do the outer bands deserve?

I mean, if there happens to be some phase shift in that exposed finger cymbal hit which happens one time in a pause midway of a 20 minute symphony, but no aliasing or amplitude weirdness, how important would it be in the grand scheme of things? :) I don't know.
matt42 wrote:By the way regarding all-pass interpolation. I think you are right that there's an error in the block diagram.
It may be an error but that page has been on the internet a very long time and maybe would have been corrected were it an error. I wildly guess that JOS considers the two forms equivalent, a fact which should be obvious even to an idiot. Therefore does not explain because he assumes that everyone is as smart as himself. :)

He explains that his difference equation only needs 1 mul and 2 adds. So far as I can see, the flowchart needs 2 muls and 2 adds. OTOH his difference equation uses 2 state variables, x[-1] and y[-1], wheras the flowchart version only needs one delayed state variable.

For my use I don't care about the extra mul and the flowchart version seems to me "more elegant" or "easier to understand". It seemed to work as expected when tested.

I don't remember which version I used long ago when I used the same allpass delays.
matt42 wrote:Also, if I have it right, to up sample by x4 then you need 3 instances of the interpolator. One for 0.25, 0.5 and 0.75 each. Then you have to "stitch" the three signals together with the original signal to get a 4x signal. Is that right? If so it's an inherently non-linear process, I think. I'd be wary of such a scheme, but I haven't tested it out.
Yes that is right. Maybe it is an entirely worthless toy or at least might need a layer of antialias filtering somewhere in the up/downsample process. When I dragged it out for testing as cheap intersample peak estimation oversampling, I tested it some but could have tested more thoroughly.

One test processed sine sweep input files and wrote 4X upsampled audio files, interleaving three allpass delays with the original sample stream. Then examined the result zoomed-in to the sample level in Cooledit Pro. Even high frequency sines did not look "badly mutilated".

By comparison, linear interpolation or 4x repeat-sample would indeed have looked rather badly mutilated viewed at the sample level. My intuition is not very reliable, but that test is the basis for a wild guess that the FIR lowpass characteristic would not be as extreme compared to simple repeat-sample or linear interpolation.

Post

JCJR wrote:By comparison, linear interpolation or 4x repeat-sample would indeed have looked rather badly mutilated viewed at the sample level.
I was thinking that (the 4x repeat-sample) is quite similar to the allpass delay method. The only difference is with the allpass version there's a phase shift on each of the repeated samples. Maybe it just works out that combining the phase shifted signals conspires to cancel out content above nyquist like some kind of polyphase filter. To get a better idea would probably need some spectral analysis

Post

matt42 wrote:
JCJR wrote:By comparison, linear interpolation or 4x repeat-sample would indeed have looked rather badly mutilated viewed at the sample level.
I was thinking that (the 4x repeat-sample) is quite similar to the allpass delay method. The only difference is with the allpass version there's a phase shift on each of the repeated samples. Maybe it just works out that combining the phase shifted signals conspires to cancel out content above nyquist like some kind of polyphase filter. To get a better idea would probably need some spectral analysis
Yes I was expecting it to look uglier than it actually turned out in my crude test. Possibly it would turn out to alias very badly without some additional bandlimiting lowpass filtering, dunno. Long ago when it was used in some variants of cheap resampling I always used antialias filtering in addition to the allpass interpolation.

Post

I've implemented oversampling X 2 now and used the musicdsp.org polyphase code and coefficients, the steep one.

Works great, looks like I'm getting the right frequency response.

However, I'm using a null test to see how close it is to a base line track without oversampling. In other words, to hear if there's any difference using polyphase filter alone.

There is and it's quite audible. I have other plugins that use oversampling X 2 and they null test against a base line perfectly. So close!

Could this be a phase issue or PDC delay issue?

Post

Could this be a phase issue or PDC delay issue?
The musicdsp IIR polyphase filters for oversampling add some latency (between 1 and 6 samples), it needs to be compensated. Moreover, the amount of delay is not integer and not the same on the whole frequency range. But you can decrease a lot the amount of difference if you compensate the latency close enough.

Post

The polyphase filters are minimal phase, you can choose to compensate for perceived delay, if you feel the need. However the phase shift varies across the frequency range and, so won't ever null against the original.

By the way for linear filters distortion should be at very low values, so I'd suggest checking the amount of noise/distortion. If it's suitably low, the frequency response is good and it holds up to listening tests then you should be good.

If you decide to stick with the polyphase filter it's worth optimising it for zero padding as you can half the amount of processing performed.

Post

Ok, I knew that latency would need to be accounted for. No problem.

About zero stuffing...every time I've tried it the output drops in amplitude. Here's what I've got:

Code: Select all

    y0 = half_band.half_band_filter_process(input);
    y1 = half_band.half_band_filter_process(0);

    output = do_filters(y0);
    do_filters(y1);
If I replace the 0 with 'input' then it gets to the right amplitude. There's still an audible difference, it doesn't null. There must be a better way, Ableton's EQ8 and Melda's AutoEqualizer both null when in oversampling mode...

Post Reply

Return to “DSP and Plugin Development”