Cameron1470 / audio-programming-project

First shot at getting wavetable synthesis from a file working
0 stars 0 forks source link

Antialiasing #9

Open Cameron1470 opened 3 years ago

Cameron1470 commented 3 years ago

https://github.com/Cameron1470/audio-programming-project/blob/368b153ecf9ccfaaf5b4c556e901f9cf7c978115/Source/WavetableSynthesiser.cpp#L140-L148

I've started trying to add some antialiasing for my wavetable synthesizer. Any tips for how I should go about doing this? So far I've divided the range of midi notes into 10 portions which each have their own juce::AudioBuffer which can be called in startNote depending on the input note. My thinking was that there would be different levels of band limiting/low pass filtering going on in each of these sections but I'm not sure about this. Should I be looking at using FFTs and truncation?

OR should I just put a low pass filter on the whole thing in the renderNextBlock? Is the splitting up of the scale just an additive synthesis thing if your making a saw wave for example and need varying amounts of harmonic components?

Cameron1470 commented 3 years ago

I've been going off what's in this blog a lot but I'm not sure how relevant it is to what I'm doing https://www.earlevel.com/main/category/digital-audio/oscillators/wavetable-oscillators/

mhamilt commented 3 years ago

You can low pass, but you’ll need to make the roll off pretty steep.

I think a wave table for each octave isn’t a bad approach as well, but you might want to A / B to see if there is any noticeable change in output.

You won’t be able to have a dynamic number of harmonics AND a wave table. Well, you could, but I wouldn’t see the benefit.

If you want to be exceptionally thorough, implement all of the approaches and quantify which is best for your purposes in terms of aliasing, performance and memory usage.

Cameron1470 commented 3 years ago

I think I've come up with a reasonable solution to the aliasing in this commit here https://github.com/Cameron1470/audio-programming-project/commit/6fead66a99aa051940c04a3ebbdd9bf8f431bba7

Managed to do as I suggested and add different levels of low pass filtering to each octave using a lower cutoff as it gets higher and it sounds much better. Also added some normalization to the volumes so there's no dip going between them. Cobbled together this method using what I know and there's probably a more elegant way but it works. Still definitely a little dodgy at the extreme high note notes but overall I'm happy with it and I think its the best I'll get in this timeframe

Cameron1470 commented 3 years ago

So I've now been trying to make multiple "slots" for different wavetables and needing some advice. at the moment my single wavetable is stored in a data structure array with the range of octaves

    struct wavetablesAntialiased {
        int wavetableLength;
        juce::AudioBuffer<float> wavetableAntialiased; 
        juce::IIRFilter wtFilter;   // filter for reducing aliasing of wavetables
    };
    static constexpr int numWavetableOctaves = 10;

    wavetablesAntialiased mWavetables[numWavetableOctaves];

and I now want to have have multiple instances of this data structure (ie an array of data structure arrays). I tried something like this

    // number of wavescanning slots
    int wavescanningSlots = 2;

    // create data structure pointer of above size
    wavetablesAntialiased* wavescanning = new wavetablesAntialiased[wavescanningSlots];

So this initializes the size of the pointer array but how do I initialize the size of the data structure array AND the size of the pointer? I thought it could be something like this..

wavetablesAntialiased* wavescanning = new wavetablesAntialiased[numWavetableOctaves][wavescanningSlots];

but it doesn't seem to like that

mhamilt commented 3 years ago

Cobbled together this method using what I know and there's probably a more elegant way but it works.

I wouldn't knock it, this way you can actually play about with dispersion in harmonics. It also allows you to mess around with the wavetable without interrupting sound.

So this initializes the size of the pointer array but how do I initialize the size of the data structure array AND the size of the pointer? I thought it could be something like this..

wavetablesAntialiased* wavescanning = new wavetablesAntialiased[numWavetableOctaves][wavescanningSlots ];

wavetablesAntialiased isn't the best name in this instance. I'd also question it being a struct aswell. If wtFilter is going to be acting on wavetableAntialiased then it might as well be a class. A structs is sort of a holdover from C and is best used when you have a list of related variables, like a parameter list for an effect.

Also, perhaps call it AntialiasedWaveTable.

I think your wavescanner should be it's own class as well. Am I right in saying you need wavetablesAntialiased[numWavetableOctaves] per wavescanningSlots? e.g.

If so, you need a double pointer, as you will have an array of pointers. You could get round this by hard coding some variables (and you probably should for testing) but ultimately making it dynamic is more fun

int wavescanningSlots   = 2;    
int numWavetableOctaves = 8;

wavetablesAntialiased** wavescanning; // double pointer as you need to make an array of pointers

// make an array of wavetablesAntialiased pointers
wavescanning = new wavetablesAntialiased*[wavescanningSlots]; // makes sense to have the scanning slot first, ultimately doesn't matter

for (int i = 0; i < wavescanningSlots; ++i)
{
    wavescanning[i] = new wavetablesAntialiased[numWavetableOctaves];
}

now you can write something like;

wavescanning[0][0];
Cameron1470 commented 3 years ago

Yeah, I think you're definitely right. I just need to utilize classes more in this and it would solve my problems. For each wave scanning slot having a class that contains all the information you need in it would make this so much easier. Interesting, I didn't know that about the structure thing but it makes sense!

I tried a really crude way this afternoon and have got it working and can now crossfade between two shapes in the plugin window. Look at the code at your own risk, you'll probably find it quite upsetting at how much repeated code there is haha. Takeaway though is that it works as I was hoping it would and I know just need to sort out a more efficient way

Thanks a lot for the help, definitely learning a lot with this project!

mhamilt commented 3 years ago

Starting it janky to prove a concept in principle is fine, but you should be able to generalise and clean it up