Wavetable Synthesis producing artifacts at High Frequencies, How to fix this? - Aliasing

A frequency sweep of different waveshapes would be interesting to hear for sure. And aliasing is a lot easier to hear with frequency sweeps, but if you are not using band-limited tables and not doing any oversampling, I’m guessing you get aliasing in the audible range and it’s probably more impactful than the phase reset to 0…

I could really do with a decent pitch detector. You know of any free or inexpensive plug-ins that can do accurate pitch detection?

Not with proper anti-aliasing. When you have a band limited signal, then it doesn’t matter if you sample it with an offset every now and then. The result will be correct both in frequency and timbre and without aliasing.

If you want that, you will have to do anti-aliasing and wrap.

I‘d recommend using python, octave, or Matlab to analyze the different methods and even create the signals there. Easier, more accurate, and controllable :slight_smile:

Frequency for regular wrap: 43.6537
Frequency for hard wrap: 43.6364
As both signals are sinusoids, I located the first and last positive zero crossing, divided the time difference of both by the number of positive zero crossings minus 1, and inverted it -> frequency

The difference is tiny, not perceivable, however I didn’t expect big differences for low frequencies, there also anti-aliasing wouldn’t be a problem.

Thanks a lot for your input. What tool did you use to get those exact frequencies?

Well I’ve done some further testing, using higher frequencies, and I now realize that the frequency difference you were referring to, gets greater the higher the frequency is. Also it seems a simple delta adjustment, like a multiplier to compensate, does not have any effect at all.

So the technique of a “Hard Wrap” or rather “Hard Start”, instead of regular wrap, sounds great to my ears up to about C7, where after that point a frequency deviation (error) gets noticeable. So for a bass synth this technique should be quite usable, however for a full range, unless I find a way to compensate, as others have posted you have to do band limited tables and/or use an anti-aliasing filter.

I used this little matlab script:

[x, fs] = audioread ('Sine 43.6535Hz - Regular Wrap.wav');
x = x(:, 1);
crossings = diff (x > 0) == 1;
T = (find (crossings, 1, 'last') - find (crossings, 1, 'first')) / (sum (crossings) - 1) / fs;
f = 1 / T

Be aware that this will only work with sinusoid signals

Thanks for that. BTW do you or anyone else reading, know of public bandlimited code or a website with same? I need something that can make filtered table variations on any kind of imaginable waveform, as my synth focuses on waveforms other than the standard saw, square, and triangle. So I need some code I can run after, I have used my synth’s Waveform Creator to make a crazy waveform, but before I then start playing notes.

image

Edit: Oh I see you were asking for ready-to-use code, thought just algrithms. For the later you should find enough information here in this thread :slight_smile: I suppose the FFT approach should be the quickest and easiest way to create the lookup-tables / bandlimited versions of your waveform

Lots of good stuff (including code) concerning wavetables here:

https://www.earlevel.com/main/category/digital-audio/oscillators/wavetable-oscillators/

Yes thanks a lot, I’ve read his fine articles, and have had a look at his code below for supposedly creating a bandlimited version of a table, however having not much experience with filters I am a bit confused on how to use it with my 4096 float size array (table). The below function from Earlevel’s site, takes three arguments. First should be my table size (4096), second my array, but what is the third? Also is the filtering automatic dependent on the table size? I mean it’s supposedly cutting off more and more frequencies the smaller the table size gets - Very confused.

// fft
//
// I grabbed (and slightly modified) this Rabiner & Gold translation...
//
// (could modify for real data, could use a template version, blah blah--just keeping it short)
//
void fft(int N, myFloat *ar, myFloat *ai)
/*
 in-place complex fft
 
 After Cooley, Lewis, and Welch; from Rabiner & Gold (1975)
 
 program adapted from FORTRAN 
 by K. Steiglitz  (ken@princeton.edu)
 Computer Science Dept. 
 Princeton University 08544          */
{    
    int i, j, k, L;            /* indexes */
    int M, TEMP, LE, LE1, ip;  /* M = log N */
    int NV2, NM1;
    myFloat t;               /* temp */
    myFloat Ur, Ui, Wr, Wi, Tr, Ti;
    myFloat Ur_old;
    
    // if ((N > 1) && !(N & (N - 1)))   // make sure we have a power of 2
    
    NV2 = N >> 1;
    NM1 = N - 1;
    TEMP = N; /* get M = log N */
    M = 0;
    while (TEMP >>= 1) ++M;
    
    /* shuffle */
    j = 1;
    for (i = 1; i <= NM1; i++) {
        if(i<j) {             /* swap a[i] and a[j] */
            t = ar[j-1];     
            ar[j-1] = ar[i-1];
            ar[i-1] = t;
            t = ai[j-1];
            ai[j-1] = ai[i-1];
            ai[i-1] = t;
        }
        
        k = NV2;             /* bit-reversed counter */
        while(k < j) {
            j -= k;
            k /= 2;
        }
        
        j += k;
    }
    
    LE = 1.;
    for (L = 1; L <= M; L++) {            // stage L
        LE1 = LE;                         // (LE1 = LE/2) 
        LE *= 2;                          // (LE = 2^L)
        Ur = 1.0;
        Ui = 0.; 
        Wr = cos(M_PI/(float)LE1);
        Wi = -sin(M_PI/(float)LE1); // Cooley, Lewis, and Welch have "+" here
        for (j = 1; j <= LE1; j++) {
            for (i = j; i <= N; i += LE) { // butterfly
                ip = i+LE1;
                Tr = ar[ip-1] * Ur - ai[ip-1] * Ui;
                Ti = ar[ip-1] * Ui + ai[ip-1] * Ur;
                ar[ip-1] = ar[i-1] - Tr;
                ai[ip-1] = ai[i-1] - Ti;
                ar[i-1]  = ar[i-1] + Tr;
                ai[i-1]  = ai[i-1] + Ti;
            }
            Ur_old = Ur;
            Ur = Ur_old * Wr - Ui * Wi;
            Ui = Ur_old * Wi + Ui * Wr;
        }
    }
}

This seems to be the FFT with pointers to real part and to imaginary part of the data. So for the third argument I suppose you will have to provide a pointer to a zeroed array. You can also use JUCEs FFT for that

Thanks again for chiming in, much appreciated as after six months of thinking I was doing ok, the realization that I failed to test better early on for high frequencies with my “Hard Start/Reset” method is a serious blow.

Again I am confused as to what this “FFT” function filters? It is “auto” filtering dependent on table size or what is going on? As I understand from the originating earlevel.com website, tables of decreasing sizes are made, each filtered more and more I guess or? Note I have also put a comment the Earlevel site about this, and hope it will be approved and answered one of these days.

a) in music, anything is allowed, if it sounds good, it is good! So if you’ve liked your synth so far, no reason to not like it any more.
b) serious blow? I am amazed that apparently there’s no aliasing with that simple method. Don’t give yourself a hard time, you’ve simply reached the next level :slight_smile:

Yes, each table is filtered more and more, you can see the results here: A wavetable oscillator—Part 3 | EarLevel Engineering

In the last table, only a single sine is playing, as all the harmonics above that one would be above Nyquist → aliasing.

This is how in a first step, your bandlimited signals should look like:

I used a sawtooth signal, calculated the Fourier transform, set the bins above my target Nyquist to zero, and calculated the inverse of it. Once you have that, you can even do a downsampling as well (not setting to 0 but simply getting rid of it → FFT-size gets down

1 Like

Agree with a), there are lot of soft synths out there that don’t take this so seriously, just connect an oscilloscope to Alchemy for example. It’s all about the sound experience you are going for, for virtual analog you will not get away without carefully decimating harmonics across the full key range, for range-limited sounds rules can be more relaxed. A rewarding exercise is to run a quick alias foldback tabulation in Excel, you’ll quickly see up to what note you have more degrees of freedom and when it starts to impact your project. Couple comments on using FFT, this is not a silver bullet, you need to develop a bit of an understanding what FFT does. Post-Transformation waveforms are not necessarily the same, and chances are that a large variety of forms may not result in an equally large number of significantly distinctive post-FFT. In addition some forms are not easily transformable, sine pow 4 for example. It might be helpful to create a small standalone routine that writes out the FFT and iFFT results for plotting, helps me to decide on waveform ‘survivorship’. Hope any of this makes sense …

1 Like

That example has all of the aliasing distortion as indicated in your first FFT, not the nice clean one in the second picture.

OMG Thank you very much for pointing that out. I just rechecked that I recorded with the right “Hard Reset” method, and did not mistakenly use a wrong file. This is freaking Audacity that aliases the sound when it saves it to 16-bit wav format! After I recorded in Audacity it’s alias free. I then exported at wav signed 16-bit PCM, closed Audacity and reopen the file and suddenly it is aliased like I was using wrap - wow!

Please check the same post (file) again, as I re-recorded, exported as 32-bit and now it should sound like I hear it in my synth, although at that frequency the frequency error starts to become noticeable as instead of 3520Hz it is more like 3400Hz.

Ok, i’m now seeing the waveform you referred to. From a quick look it appears that you are quantising the frequency in order to avoid problems (whether intentionally or not). The 3,520Hz waveform you have generated is actually showing in the frequency domain as 3,428.57Hz, which is actually 1/14th of 48,000 which i’m assuming is your sampling rate?

Now if this frequency difference isn’t important to you, then this will work. I’d be interested in hearing what a frequency sweep, say 20Hz to 20,000Hz over 10 seconds sounds like with this approach. It’s all to do with whether the steps in the sweep are annoying or not, as in, whether the pitch quantisation is annoying for the frequency range you want to use the algorithm with.

BTW, if it’s nearly but not good enough, you could probably oversample and decimate to improve the frequency resolution and that might be a good trade-off.

Personally, I just build bandlimited wavetables!

1 Like

Thanks for that input, phun intended :slight_smile: Reason I am still spending time on this, instead of using bandlimited tables, is a mainly that my synth is focusing on changing sound (waveforms) in real-time, using a plethora of methods ranging from 17 types of duty cycle manipulation (think pulse width, but with any waveform), about 36 ways of modulation (AM, RM, PD, PM, FM with many variations of each), waveshaping (not just on itself, but waveshaping against any one of seven other tone generators, so actually further modulation types about 40), plus about 27 types of wavefolding. All of this works great and I have a lot of fun just tweaking my countless knob to create cool sounds, however I am afraid that even if the originating waveforms would be bandlimited, all my processing would nullify that and still produce aliasing. Also I don’t think an analog synth, and I may have to pull out my Roland JX-3P to test, is making a sawtooth wave more and more into a sine wave with increasing frequency. Finally and that is not really a problem, is that I am not just using the “normal” analog type waveforms, but instead I have a special waveform creator, where I can literally create endless waveforms, well tens, if not hundreds of thousands at least. And yes sure I could then bandlimit the created waveform, after creation, but before using to play notes.

Anways if you have the time, I would really appreciate if you will elaborate what exactly you mean by oversample and decimate to improve frequency resolution. Right now as I explained earlier, I am only using one table of 4096 size floats, one table per tone generator, of which I have 8.

Anyways I am going to make a short video of my crazy synth, and include here as a link, to show what I am doing, and why bandlimiting may not work for it.

1 Like