PolyBLEP oscillators

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Tale wrote:
Tale wrote:But I haven't tried DPW or DPW2X myself (perhaps I should).
Alright... I have now compared polyBLEP and DPW myself, and here are the results (of a 220 Hz saw, graphs from REAPER's JS: Analysis/gfxanalyzer):

Image
Naive

Image
DPW (order 2)

Image
DPW2X (order 2, 2x oversampled)

Image
PolyBLEP

Image
PolyBLEP 2x oversampled
Interisting results - thanks for doing that! Even though I like DPW(low-order+oversampling) - because I find it very intuitive - i.e. easy to transform into a myriad of other shapes. I going to have to take a look at polyBlep sometime :cry: :lol: :cry: :lol:

Post

I have just updated the graphs (slightly smaller i.e. more KVR friendly, reduced gain so fundamental is at 2/pi). And here is another of the (order 5?) DPW I found in this old thread:

Image
DPW (order 5?)

Post

not sure if it's related, but i was also trying to see if i can "invent" something myself
http://www.kvraudio.com/forum/viewtopic.php?p=4374424 (i called it band limited difference curve)
i was too optimistic ;]
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

Here is yet another graph, this time of the brute-force Fourier series band-limited saw. After all this is what we are aiming for, right?

Image
Fourier series

Notice how all the band-limited saw approximations I posted in this thread roll off slightly too much. In this regard DPW without oversampling seems to give the best results. Fortunately the extra roll off starts at a relatively high frequency (depending on the sample rate, I guess), so it shouldn't be that audible.

Post

I tried this too at some point around 2009. I liked how PWM sounded. FM was out of my reach.

I remember that it was easy to do variable cycle triangle waves (aka triangles with different angle at the positive and negative cycle), even modulating them, kind of triangle like PWM. Obviously one has to take care that the "fastest" half-cycle stays inside Nyquist limits either by oversampling or by scaling depending on note pitch.

Unfortunately I got lost on the details doing a very efficient SSE implementation instead of prototyping more, and then I started a full time job which didn't leave me room to keep going with this.

Post

Tale wrote:Here is yet another graph, this time of the brute-force Fourier series band-limited saw. After all this is what we are aiming for, right?
Depends...while I think such an alias-free, neutral sawtooth like this is useful, you could also aim for something else, e.g. mimic a specific analog synth.
Tale wrote:Notice how all the band-limited saw approximations I posted in this thread roll off slightly too much. In this regard DPW without oversampling seems to give the best results. Fortunately the extra roll off starts at a relatively high frequency (depending on the sample rate, I guess), so it shouldn't be that audible.
If you don't need sync than DPW is very good and robust, yes. The higher-order version is ihmo not worth bothering with, however. IIRC you can't even change the pitch dynamically without a lot of hassle- otherwise it could be interesting for other purposes, like alias-reduced FM or something.

Richard
Synapse Audio Software - www.synapse-audio.com

Post

A big thumbs up for PolyBLEP from my side because that's what I'm using and I like how well it performs in suppressing aliasing while being efficient and relatively easy to implement. And yes, it does slightly attenuate frequencies near Nyquist, but this really becomes a non-issue as soon as you combine it with oversampling. Also, I kind of feel like this extra attenuation removes some of the "fizziness" often associated with VA oscillators. I got the impression that our ears generally don't like this sharp roll-off at Nyquist you get with near-ideal sinc pulses.

For those who are curious, [1] does a thorough performance study of various low-order polynomials for approximating the BLIT.

[1] Nam et al., "Efficient Antialiasing Oscillator Algorithms Using Low-Order Fractional Delay Filters", 2010

Post

I have just made graphs for two higher order polyBLEPs (as found here):

Image
Higher order polyBLEP

Image
Alternate higher order polyBLEP

I have also added all graphs to my earlier post.

Post

Since it was asked how to deal with overlapping BLEPs (whether poly or proper): render into buffer and simply add the complete BLEP into the buffer at the time it's encountered.

So essentially each sample you check if there's an "event" such as PW-crossing or reset or another segment of a piece-wise wave-shape or whatever else you want to handle. Then you handle that event by inserting the relevant BLEP into the buffer and then check if there's another event. Then when there are no more events during the active sample, you add the naive sample into the buffer (with offset if your BLEPs have latency), advance the sample position and repeat for the next sample.

The fast path still needs one test (for the event expected next) and the trivial generation. The trick is just to go back to the test (with the next expected event) in a while() loop until the test fails (next event isn't during this sample). Since you're not interleaving BLEP rendering with naive rendering (rather handling BLEPs "on the spot") there's no need to keep track of multiple BLEPs at once.

Post

karrikuh wrote:Also, I kind of feel like this extra attenuation removes some of the "fizziness" often associated with VA oscillators. I got the impression that our ears generally don't like this sharp roll-off at Nyquist you get with near-ideal sinc pulses.
If you record an analog oscillator then your AD converter will have to roll-off sharply too (and that sounds just fine ihmo). Perhaps there's another reason why you prefer analog oscillators? The problem with attenuation in the upper spectrum is that you'll almost always lowpass-filter a VA oscillator which yields yet more attenuation, and can make everything sound somewhat dull (unless you use oversampling).

Perceptually speaking, my favorite oscillators are actually precisely those which have a maximally sharp rolloff, ie wavetable- or additive based algorithms. The difference to good VA algorithms is ihmo negligible though.

Richard
Synapse Audio Software - www.synapse-audio.com

Post

I still recommend box-filtered, integral based oscillators. Done right they've got nearly no aliasing while being pretty fast (< 0.5% CPU per OSC on a i7 3.5GHz). It's also easily possible to get aliasing free hard-sync (even on sine waves).

Here's a sound example featuring a hard synced sawtooth sweep at fs=96kHz (having a 6dB LPF at 8.5kHz to make it sound a little softer) (FLAC, 1.4MB).
... when time becomes a loop ...
---
Intel i7 3770k @3.5GHz, 16GB RAM, Windows 7 / Ubuntu 16.04, Cubase Artist, Reaktor 6, Superior Drummer 3, M-Audio Audiophile 2496, Akai MPK-249, Roland TD-11KV+

Post

neotec wrote:It's also easily possible to get aliasing free hard-sync (even on sine waves).
???
Care to explain?

Regards,
{Z}

Post

It's relatively simple in fact: Instead of calculating the real value of the waveform you calculate the integral and divide by your step width.

The first version I wrote was pretty complicated and featured a lot of switches, now I'm using the 'brute force' version:

Code: Select all

newPhase = phase + step;
out = (integral(newPhase) - integral(phase)) / step
phase = newPhase
That's roughly the whole audio generation code. Well, you need the special case, when phase wraps.

Now, the basic integrals:

Code: Select all

static const float PI2 = PI * 2.f;
static const float RPI4 = 1.f / (PI * 4.f)

float pw; // between ]0, 1[
float rpw = 1.f / pw;
float pw1 = 1.f - pw;
float rpw1 = 1.f / rpw;

float sineIntegral(float to)
{
    return (1.f - fastSin2(to + 0.25f) + to * PI2) * RPI4;
}

float squareIntegral(float to)
{
    return min(pw, to);
}

float sawtoothIntegral(float to)
{
    return to * to * 0.5f;
}

float triangleIntegral(float to)
{
    float integ1 = sawtoothIntegral(min(1.f, 2.f * to));
    return 0.5f * (to > 0.5f 
        ? integ1 + 0.5f - sawtoothIntegral(2.f * (1.f - to)) 
        : integ1);
}

float triSawIntegral(float to)
{
    float integ1 = pw 
        * sawtoothIntegral(min(to, pw) * rpw);
    if (to < pw) return integ1;
    float integ2 = pw1 
        * (0.5f - sawtoothIntegral((pw1 - to + pw)
        * rpw1));
    return integ1 + integ2;
}
fastSin2 just returns sin(x * 2 * PI) (Taylor series).

All the integral functions depend on sane input values, i.e. [0.f, 1.f[.

Now I also store the last step width and integral calculated, so my output looks like this:

Code: Select all

out = (integral(newPhase) - integral(phase) + lastIntegral) / (step + lastStep)
For hard sync:
On the master: calculate the moment the phase wraps, like:

Code: Select all

syncValue = (1 - phase) / step
This assumes you're using the code above (newPhase ...)

The slave now exactly knows where it has to wrap (phase + syncValue * step).

So, you just need to calculate the integrals from phase to phase + syncValue*step and from 0 to (1 - syncValue) * step.

Here's a hardsynced sine, using the same settings as in my hardsynced sawtooth example.

Oh, I forgot, the output value is between [0, 1], so you have to do a 2 * out - 1 to get a value between -1 and 1.

The only thing that bothers me a wee bit is that I have one division per sample, but using the previous integral approach, this can't be avoided. You can also just use the current integral to avoid the division, but using two integrals enhances aliasing suppression a lot.

Another benefit, when also using the previous integrals, is to further avoid aliasing when the pulse width changes (speak pw modulation). Otherwise it might be possible to miss a transition and produce sharp edges (thus aliasing) here and then.

Here's another sample: a raw 220Hz sawtooth, rendered at 44.1kHz without any filtering or the like.

CPU consumption depends on the waveform selected, but e.g. a single sawtooth OSC is below 0.2% (real time) on my machine.

Another neat idea: use some other function to which you can calculate the integral :)
... when time becomes a loop ...
---
Intel i7 3770k @3.5GHz, 16GB RAM, Windows 7 / Ubuntu 16.04, Cubase Artist, Reaktor 6, Superior Drummer 3, M-Audio Audiophile 2496, Akai MPK-249, Roland TD-11KV+

Post

It sounds like an oscillator with rising pitch, rather than fixed pitch with rising timbre. So it sounds to me like you're sweeping the master tuning rather than the slave. Typical usage is the other way around.
Chris Jones
www.sonigen.com

Post

sonigen wrote:It sounds like an oscillator with rising pitch, rather than fixed pitch with rising timbre. So it sounds to me like you're sweeping the master tuning rather than the slave. Typical usage is the other way around.
Yep, you're right. Here are two examples with constant master pitch and sweeping slave pitch:

Sawtooth, 96kHz, no filters, flac (1.5MB)
Sine, 96kHz, no filters, flac (1.7MB)

Master pitch stays at 220Hz, slave sweeps from 55Hz to 1760Hz.
... when time becomes a loop ...
---
Intel i7 3770k @3.5GHz, 16GB RAM, Windows 7 / Ubuntu 16.04, Cubase Artist, Reaktor 6, Superior Drummer 3, M-Audio Audiophile 2496, Akai MPK-249, Roland TD-11KV+

Post Reply

Return to “DSP and Plugin Development”