band limited oscillator with T3.2

Status
Not open for further replies.

emmanuel63

Well-known member
Hello,

I have made a small synth using the new BAND_LIMITED_SAWTOOTH and BAND_LIMITED_SQUARE oscillator waveforms. It sounds much better than the non-band limited waveforms. However, it seems to use more CPU time, and I had to reduce polyphony from 6 voices to 3. I want to use T3.2 because I have a lot of them (for workshops with kids). I also use the built-in DAC of the T3.2 to keep the project simple and the budget as low as possible. So a T4 is not an option for this project. Overclocking the T3.2 works very well, but it is still not enough.

I would like to know if there are other ways to implement band limited oscillators that would be less CPU intensive. I know I can use wavetables but tis method seems to be quite CPU demanding too. Do you know other ways ?
On the other hand, is there any tricks I could use to save CPU time ?

Emmanuel
 
Its a fundamental trade-off - the bandlimited waveforms use table-driven sinc-interpolation, which is the fastest way
I could find to implement a good quality interpolation (all spurs well down to inaudibility). This means having to do a
lot of operations per sample.

You can use less accurate interpolation (polynomial interpolation) which will have higher spurs but fewer operations
per sample. Here's a relevant paper: http://yehar.com/blog/wp-content/uploads/2009/08/deip.pdf

[ another useful resource: https://www.metafunction.co.uk/post/all-about-digital-oscillators-part-2-blits-bleps
https://forum.pjrc.com/threads/62240-New-Audio-Object-polyBLEP-Oscillator-with-bandlimited-hard-sync?highlight=blep ]
And this thread may be more what you want:
 
Last edited:
Thanks Mark.
I have tried polyBLEPS. It works but still to much CPU load for more than 3 voices.
I am going to look to the other links you mentioned. I am afraid it is going to be hard for me to understand but I will try !
Emmanuel
 
I'm surprised the extra bit of polybleps math on the edge transitions are killing your performance. Maybe it's not implemented quite right? By re-writing polyBleps to eliminate division, or used fixed-point arithmetic, you can make it more efficient. Especially if you just did a copy and paste from the code on the Internet make sure you're not using doubles! At least use floats!. But in order to optimize you need to understand it a bit.

Code:
// Naive version as float
// http://www.martin-finke.de/blog/articles/audio-plugins-018-polyblep-oscillator/
float polyBlep(float t, float dt)
{
    // 0 <= t < 1
    if (t < dt) {
        t /= dt;
        float ret = t+t - t*t - 1.0f;
        return ret;
    }
    // -1 < t < 0
    else if (t > (1.0f - dt)) {
        t = (t - 1.0f) / dt;
        float ret = t*t + t+t + 1.0f;  // added 'f' as per FrankB suggestion. Good eye!
        return ret;
    }
    // 0 otherwise
    else return 0.0f;
}

If we want to optimize this code, there are a couple things we can do. It helps to understand how this code works as the Internet resources don't do a great job. Note to aspiring programmers, document your input/outputs! '

DT Parameter: 'dt' in this function is the proportion of the wave that will be modified, a value between 0 and 1.0. Normally you see this set equivalent to one sample which reduces some aliasing. Traditional implementation tries to only modified 3 samples around a discontinuity. The sample before, the same at, and the sample after the discontinuity.

E.g. at 44100 Hz sampling rate, and an LFO of 5 Hz, you have (1/44100) / (1/5) --> dt = 0.000113379. Meaning one sample is about .01% of the wave period. But 'dt' is best interpreted as a 'rounding' control. As you turn up 'dt', you get more rounding ( a greater precentage of samples) of the waveform discontinuities. At 5%, it's very easy to see in visually in Matlab or on a scope. At 1%, it's barely visible visually, but still provides very good rounding. The more you increase 'dt', the more often you need to perform the extra algo math, so it's also to be used conservatively if you are compute-constrained. For sound frequency synthesis (generating notes), you only need 1 sample, or maybe a few. For LFOs you need more (greater %) to prevent aliasing after waveform modulation.

T PARAMETER: the 't' parameter is a value between 0 and 1.0 representing the current samples position between one discontinuity and the next. I like to call this 'normalized phase' as it's typically (but not always!) the phase in radians divided by two pi. I say 'not always' because you often use polybleps to handle more than one discontinuity per wave period. Meaning polybleps can also be applied to segments of the wave. E.g. for a Sawtooth, polybleps covers 2*pi radians, but for a triangle wave, it could could cover only PI radians, as there is an extra discontinuity at 180 degress (or PI radians) so you need to overlay two polybleps regions, side by side to cover the waveform. A polybleps region describes what to as you leave, and as you approach a discontinuity.

E.g. think of a Sawtooth. It linearly rises (or falls) starting at phase 0 radians ( discontinuity) and 2*pi radians later, it jumps back to the starting value. So at t=0.5, the current sample is halfway through the waveform.

POLYBLEPS SUMMARY: so, the naive polyBleps code checks the current sample's normalized phase position and check to see if it is within 'dt' of the start or 'dt' of the end. If it's near the start/finish, it returns a non-zero modifier. If it's in the middle, it returns zero. So the goal is to overlay this 'polybleps' region over all or part of the waveform depending on the number of discontinuities and their locations. We do this shifting and scaling the numbers we pass into polybleps.

OPTIMIZATION:
- you can eliminate the divisions. Easy optimization: The value for 'dt' can probably be precomputed, which means 1/dt can be precomputed. Instead of dividing t/dt, perform t*(1/dt) where the 1/dt is precomputed. This converts an expensive float division into a float multiplication. This save CPU time if you can precompute all the 'dt's you need, then use those values with multiplies in polyblep().

Advanced optimization: Convert to fixed-point. Polybleps will take the normalized phase 't', and divide it by 'dt'. So we start with a phase normalized to the length of the waveform region analyzed by polybleps (the analysis region). If it's within the modification area, we need to normalize it yet again as the phase must be interpreted as how far along the 'modification' region it is. For notes, again this is supposed to be the sample at and next to the discontinuity. Even when working with LFOs and using higher 'dt' percentages, rather than using floating point to do all the normalization, you can re-write polybleps to work with with fixed-point input integers and division by powers of two. By expressing normalized phases as integer value divided by a power of two, you can convert the division to binary right shift operations.

Explaining how fixed-point arithmetic works is beyond the scope of this blog post, just know that if you understand fixed-point arithmetic well, you can re-write polybleps to use integers and the divisions get replaced by a inexpensive right-shift operation.

USAGE: Polybleps is very easy to mis-use as it's not well documented in the few resources you find on the internet. For starters, it assumes you want to create notes, not LFOs. Second, people state you don't need it for LFOs (you do). Third, it is never explained as a general purpose smoothing tool. Everyone starts with the sawtooth and then uses tricks to create the otherwave forms. The method assumes you are creating notes, so it does things like integrations, etc. Which don't work for all waveforms when creating LFOs waveforms. E.g. when applying polybleps to Triange LFO, you need to to scale the polybleps amplitude by 'dt' before combining with the naive wave.

Kind of a long post, but if you really want cheap waveform bandlimiting, polybleps is probably still the best solution, but you may need to go as far as converting it to fixed-point and use it correctly if you are really that constrained. However, I'd be surprised if converting the float divide to float multiply and only doing it on a few samples is too much for the T3.2 to handle.
 
Last edited:
Code:
float ret = t*t + t+t + 1.0;
I think it is a little typo only.. better use 1.0f to prevent a double promotion.
 
Status
Not open for further replies.
Back
Top