Oscillator antialiasing (BLEP, polyBLEP, oversampling ,...)

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Hi,

I am currently working on my first proper antialiased oscillator and have some questions on this topic. I've went through many KVR posts regarding polyBLEP, BLEP and minBLEP and also read some articles about DPW and so on so I hope I did my homework before asking you questions. :)

Currently I have an oscillator with two point polyBLEP working. The results are fine for some frequencys but with this approach, I hit a brick wall really fast.

(1)
So the first general question would be what antialiasing method or oscillator algorithm you would recommend for saw waves. BLEP, polyBLEP, with or without oversampling, what factor and so on.

(2)
My current implementation integrates a square signal to create a triangle wave. When the square signals is antialiased, is it safe to say that the triangle wave won't get additional aliasing issues by integrating the square signals?

(3)
When I want to oversample the oscillator, what kind of filter should I use at nyquist? is a one pole sufficient?

(4)
About BLEP, I know that Increasing the number of points to correct will yield in better results, however, a fixed number will bring problems when the BLEPs overlap. The solution would be the same as with wavetable oscillators, using multiple BLEP tables for different frequencies, right? The question I have is which number of points per side I should use (1, 2, 4, 8, 16,...?) and if the table size needs to be adapted as well? (Or keep the table size constant at 4096 samples / cycle?) Is there a rule of thumb?

Thats it for now, I hope you can help me out a bit. :)

Post

WRodewald wrote:what kind of filter should I use at nyquist? is a one pole sufficient?
No, its not. As soon as the downsampling occurs, everything that remains above the nyquist will be mirrored into the passband. Ideally, there should be nothing above nyquist before downsampling, hence steep filters.

Post

(1) linear-phase BLEPs are probably the best and most efficient approach in most cases where simple mip-mapped wavetables is not what you want; oversampling isn't really required, but if you are going to oversample after the oscillator anyway (eg. for filter) then it's usually faster to just run the BLEP oscillator at the higher rate directly (and just adjust the BLEPs to filter at a lower frequency)

(2) integrating squares work.. you can also synthesize BLEP triangles directly, which works better if you want analog-like results with modulation (since the integration isn't all that stable with modulation, typically)

(3) one-pole doesn't do the trick, you need a "brickwall" filter .. generally poly-phase FIR (google) is probably the ideal choice for quality, for 2x case you could also use IIR half-band (which .. are a bit tricky, but quite cost efficient at low orders; if you need more quality they do get a bit costly though). With BLEP oscillator it's usually cheaper to just generate at the desired target frequency .. but you still need the filter for downsampling.

(4) see my PolyBLEP sample code on this forum somewhere... if you structure your oscillator in a similar way, it should be really straight-forward to use a longer buffer where you can mix longer BLEPs (on the spot) whenever you encounter a discontinuity event.. then it doesn't really matter if you have multiple BLEPs at the same sample, or whatever, you just keep mixing them into the buffer; the problem with most BLEP code I've seen is that people try to play back the BLEPs at the same time with the naive signal.. don't do it, mix the whole BLEP at once into a buffer and keep stepping the naive waveform (adding it into the buffer too) and what you then end up after it's all done is the correct signal, no tracking of BLEPs, they are just "stamps" you add to the buffer :)

Post

Hi,

thanks for the response. :)

About the triangle thing: I know that simply using BLEPs won't work since there is no disconuity in the trivial wave but only in its derivation, right? so what I have to do is not use the sinc(x) function to generate a step but the derivation which would be sin(x)/x². Is this the correct approach?

@mystran, you mean this post right? http://www.kvraudio.com/forum/viewtopic.php?t=398553
I will have a look. I think I will post my code here once the oscillator is working with good antialiasing.

Thanks!

Post

WRodewald wrote: @mystran, you mean this post right? http://www.kvraudio.com/forum/viewtopic.php?t=398553
I will have a look. I think I will post my code here once the oscillator is working with good antialiasing.
Yeah that's the one. Obeserve that it handles the complete BLEP independent of actually stepping the wave, which means that you can handle longer BLEPs as long as you have longer buffer to put them in (that example code just has a single sample "buffer" but that's just 'cos adding buffering code would have made the code about twice as long, and then adding some linear-phase BLEP design to make it work out of the box would have doubled the code size again).

edit: as far as the triangles, for steps in waveform, you use Si(x), the integral of sin(x)/x and for step in the 1st derivative you use the integral of Si(x) and so on for higher derivatives. I just use FFT to generate the relevant filters, time domain integration is kinda too inaccurate for this (you'll go mad trying to get the scaling right), but you could design from the analytic function if you want (though you'll need a numeric method for actually evaluating them).

Post

FWIW, I have recently posted a 2-sample "polyBLAMP" here:
http://www.kvraudio.com/forum/viewtopic ... 8#p6107378

Post

Hi,

thanks for the triangle. :)

I've worked on my blep a bit and somehow lost track writing a tool which lets me create all kinds of BLEP tables. However, even using 8 points per side tables with sinc() and hamming window with 8192 samples, I create more aliasing then another 4 point per Side 4096 table I have from another source.

I had some strange issues with the integration of the sinc() function, is there a good trick? I used this algorithm for now:

Code: Select all

//sinc functiopn
    for (int i = 0; i < mBufferSize; i++) 
    {
        double x = mPI * pointNum * 2 * (((float)i / (mBufferSize - 1)) - 0.5);

        double val;
        if (x != 0)
        {
            val = window(float(i) / (mBufferSize - 1)) * sin(x) / (x); // hamming window 
        }
        else
        {
            val = window(float(i) / (mBufferSize - 1));
        }
        mSINCBuffer[i] = val;
    }

    // integrate

    double factor = ((double)(pointNum)* 4.0) / (mBufferSize - 1);
    mBLEPBuffer[0] = mSINCBuffer[0];

    for (int i = 1; i < mBufferSize; i++) {
        mBLEPBuffer[i] = mSINCBuffer[i] + mBLEPBuffer[i - 1];
    }

    for (int i = 0; i < mBufferSize; i++) {
        mBLEPBuffer[i] *= factor;
    }
    
    // residual
    for (int i = mBufferSize * 0.5; i < mBufferSize; i++) {
       mBLEPBuffer[i] -= 2;
    }
The problem I had was that the curve didn't end at y = 2 (or y = 0 after offseting the second half). The problem is less noticable with windowing and I also correct the curve linearly so that the curve end's with y = 0, but I still found it quite strange,...

Post

WRodewald wrote: I've worked on my blep a bit and somehow lost track writing a tool which lets me create all kinds of BLEP tables. However, even using 8 points per side tables with sinc() and hamming window with 8192 samples, I create more aliasing then another 4 point per Side 4096 table I have from another source.
The amount of attenuation actually depends more on the window used than on the number of points in the filter. What more points gives you is a sharper transition around the cutoff (although windows that give you more attenuation also tend to widen the transition).

See http://en.wikipedia.org/wiki/Window_function for different window functions. For audio purposes, I'd always use something with at least around -100dB attenuation for most of the stop-band. Nuttall or Nuttall-Blackman work well if you want a fixed one, and Keiser let's you adjust a parameter (try alpha = 3 or so).
I had some strange issues with the integration of the sinc() function, is there a good trick? I used this algorithm for now:

[...]
The problem I had was that the curve didn't end at y = 2 (or y = 0 after offseting the second half). The problem is less noticable with windowing and I also correct the curve linearly so that the curve end's with y = 0, but I still found it quite strange,...
This is normal (and just gets worse if you try to integrate twice or more for the high-order BLEPs). The time-domain integration won't really be exact (you could use a better integration method, but still), so it'll accumulate some error. This is kinda what I was referring to when I mentioned earlier that I like to use FFT for these.

Post

Hi,

I will give Nuttall a try, used Hamming so far but the frequency response looks way better for Nuttall.

I worked a bit more on my BLEP generating application, and figured out what my problem was by comparing the BLEP tables with a given one. I assumed that the number of zero crossing of the sinc() function is directly related to the number of points which get adjusted with the BLEP. I now assume that the number of points, the BLEP adjusts per side, is equal to the number of zero crossings of the sinc() times squared, is that correct?

So using a sinc() from -pi to pi (one zero crossing per side) will give me a blep for 1² points on each side summing up to two points. Using a sinc() function from -2pi to 2pi will give me 2² points on each side so 8 in total. The next step should be 18 and then 32 right?

Post

Perfect integration of BLIPs is possible if you only use integers. (call it a summation/accumulation...)

I typically use a set of 64 bandlimited impulses.
Each pulse is offseted by 1/64 of a sample.
8 samples sincs with a Hann window sound good to me.
They are made of integers and they are normalised so that the sum is strictly 512 for every one of them.
I do not use interpolation, and only use integer multiplication.

It is fast and work quite fine.
Hence, it is not a universal solution.
It is good for "steppy" waveforms, not for saws.
See you here and there... Youtube, Google Play, SoundCloud...

Post

Smashed Transistors wrote:Perfect integration of BLIPs is possible if you only use integers. (call it a summation/accumulation...)
Not really. The problem here is not with numerical accuracy as such, but rather with the fact that you really want to integrate a continuous wave, but you are doing it by some sort of approximation (running sum, trapezoidal, etc) on a sampled wave-form, which introduces some error.

edit: actually upon further thought, if you DC normalize the sinc after windowing (required, because windowing usually alters the gain slightly), then you shouldn't really have a scaling issue with the first integral at all. Still, I find that it's kinda tricky to get good quality higher-order BLEPs this way, YMMV.

Post

I accumulate them in real time, just like good old blits...
Using integers, is just a trick to avoid integrator drift for free.

I didn't tried to chain another accumulator... I think i would have to design some feedback control to avoid second order drift.

We are integrating pulses, steps or linear sections... they do not have high order derivatives.
I think that accumulators will do the trick.
See you here and there... Youtube, Google Play, SoundCloud...

Post

@mystran,

you mentioned that you use FFT to generate tables but I don't really know how exactly that would look like.

One idea I had is to take a large buffer containing a step from 1 to -1 or so and convert this to the frequency domain (FFT), manually removing (?) unwanted frequencies and then converting it back to the time domain.
Is this a legit way?

I've got some progress using BLEPs with 8-16 steps per side but comparing to other BLEP tables with the same buffer size and points per size, mine are still quite inaccurate.

Cheers

Post

WRodewald wrote: One idea I had is to take a large buffer containing a step from 1 to -1 or so and convert this to the frequency domain (FFT), manually removing (?) unwanted frequencies and then converting it back to the time domain.
Is this a legit way?
Yeah, ideally you have the initial "large buffer" to be periodic (ie. saw-wave for step, parabola for first derivative, and so on; you'll remove the low frequencies so this works fine), but that's pretty much how I normally do it. Also, you can either take an FFT of a prototype lowpass (eg. windowed sinc) and multiply by that (or 1-sinc directly) in spectral domain (in which case IFFT should be directly ready to use), or you can just zero the bins you want to get rid of and window after IFFT (to smooth it out, like any windowed filter design).

Post

Hi,

I might just add this in case somebody else gets started with oscillators. I've noticed that BLIT oscillators are in many points superior to BLEP oscillators (more or less no aliasing at any frequency) and don't create huge performance issues. So I decided to go with BLIT oscillators after all, using saws to create squares and squares to create triangle.

Post Reply

Return to “DSP and Plugin Development”