WaveTable / ADSR patches

Hi Owlers !

I am working on wavetable patches and I selected a few to share:
-[single osc + lvl] rebeltech.org/patch-library/patch/WT_Single_Osc
-[double osc + lvl] rebeltech.org/patch-library/patch/WT2NotesChord
-[gate ADSR] rebeltech.org/patch-library/patch/WTOscADSR
-[trig AR + shape] rebeltech.org/patch-library/patch/WTOscAR
-[trig pitch AR] rebeltech.org/patch-library/patch/WTPitchAR

Those first WT (WaveTable) patches use frequency ranges wavetables synthesising to avoid aliasing in high frequencies. 10 WTs cover the audiable frequency range. The lowest frequency WT is square, triangle or sawtooth (depending on your choice) and each WT following loses more and more harmonics to end up with a sine. Each WT shape is close enough to its neighbor so we can’t hear the WT junction.
Additionnaly, I made some ADSR control paired with WT oscillators.
Both WT and ADSR are based on Nigel Redmon 's code from earlevel:
-Wavetable Oscillators | EarLevel Engineering
-Envelope Generators | EarLevel Engineering
He probably explains lot better than I could do if you are interested in.

My patches are still in dev. As you can see I kept a lot of comment lines because I had different ideas.
Few things to correct:
-change waveform easier (at the moment you have to copy/paste/uncoment)
-improve sound and accuracy
-use more owl options (expression parameter, inputs, outputs)
-get them load with chrome :relieved:

But for now I will focus on making some WT morphing combined with those freq WTs. I’d be glad to hear your feedbacks about those patches and how to improve them.

Wa

1 Like

Hi,
I finally made these wavetable patches I worked on a few months and I am going to give some details about how I made them. After using earlevel codes, I decided to apply the same antialiasing method to complex waveforms. In order to generate any kind of waveforms I used this editor called waveedit. I will give instruction to let you know how to use it in a next post.
I decided to go back from 0 and try different way of filtering the unwanted harmonics that cause the aliasing. I used the same structure as earlevel code for wavetable storage. So the idea is to filter from a full harmonic initial single cycle sample and store the filtered sample in the wavetable. Every of those filtered sample correspond to a frequency band, we call them BandLimitedSample (I will reduce as BL sample). Earlevel code 10 bands starting from 20Hz to 20480Hz. So every of them is one octave. In fact, I will use only 7 bands starting from 160Hz to 20480Hz and I’ll also explain it in the next post because it all depends on what kind of samples you are working with.
First I tried using biquads. A biquad is a second order IIR digital filter. Earlevel made a coef calculator so hear you can see what should be the last filter for the 10240Hz-20480Hz band.

The idea in the last band is to filter everything except the fondamental frequency: 187.5Hz (samplerate/samplelength). Everything over 375Hz is 100dB attenuated but the frequency band that needs to be audible is also attenuated by 9dB. A single harmonic is a sine wave and it is pretty much what we got here. But the slope is not square enough. We need to approach a brick-wall filter.
So we will have a lower level and a different sound. Different sound because closer to a sine which is what we want: the higher frequencies we go, the more sine it has to be. The harmonic content between each following BL sample has to be slightly different so the human ear can not hear the trick. What we don’t want is general level attenuation. But time domain filtering always produces attenuation. One solution is to normalize every BL sample so the all have the same RMS power. Then, there are no any more level differences between BL samples but by reamplifying the highest BL samples, some aliasing frequencies also come back. Another solution is to filter at some higher frequencies but obviously aliasing comes back even if you cascade a few biquads. The result is not satisfying enough, especially with some really harmonic rich signals.

So next possibility was FIR filters. To get close to a brickwall, those filters need a really high order, which means a really long sample. For example, for 3000 tabs, filtering will only be effective after the 3000th float sample value. So we need to apply this filter on a 13 * 256bytes loop of the original 256bytes sample. Then we just keep the last 256 post filter float values.
On the following screenshot, figure 2 would be the 2nd BL filter and figure 3 would the 7th (the last).


As you can imagine, figure 2 will work fine but figure 3 filter will give us the same kind of issues we had with biquads. This slope is too far from brickwall. So we will keep way too many harmonics that will go back aliasing, even if we change our cut-off freq. A bigger order could help but 3000 is already quite enormous for the Owl. After normalizing and shifting cut-off frequencies, we have a little less aliasing than biquads but still way too much with harmonics rich waveforms.
That led me to a different way of manipulating harmonics. By going through frequency domain it is way more efficient to just cancel every harmonics we don’t want. We just need to do a fourier transform and inverse fourier transform. So I used Rebel Technology c++ fourier classes. Obviously, the result is way closer to earlevel’s result. After testing a few waveforms, only the real complex ones still alias at HF. A solution would be to filter and store every 5 or 6 semitones instead of every octave but it would take a lot more memory.
In order to do some morphing, I kept the same storage structure. I’m storing X oscillators, every of them contains Y waveform samples, morph X selects the oscillator while morph Y selects the waveform sample. Crossfading between them is made with linear interpolation. So there are always two oscillators and two waveforms running. This means the output is a mix of four waveforms (four different sounds).
An important thing to remember is that the final patch stores X * Y * 7 256bytes samples.

You can download waveedit from synthtech.com/waveedit/. Then you can design your own wavetable and “File/Save Bank As”. You need to add a .wav extension to the resulting file (after saving) and you have your 64 256bytes .wav waveform file. You can also download full 64 waveforms .wav files from https://waveeditonline.com. We then need to take static datas values from it. Those static datas are 256 float values for each waveforms. With owl actual config I can load 42 256bytes waveforms as header file. Which means I have 6 oscillators made of 7 waveforms. I made different solutions but the easiest way I found to distribute the waveform is to use a single header file that contain the 42 waveforms. I called those header files name10752.h and they contain the list of float datas: name42[0]
Morphing-RebelTech/extwaves at master · alexniger/Morphing-RebelTech · GitHub
In this repository you will find some different size .wav sample and static data .h files. But most import here is the program we use: wav2float.c
After compiling it with “gcc wav2float.c -o wav2float -lsndfile“ , run it with “./wav2float FULLSAMPLE.WAV name42 10752 1 > name10752.h” . As you can imagine, it won’t work if your FULLSAMPLE.WAV is smaller than 42 single 256 samples.
Note that all files (.c .o .wav .h) need to be in the same folder. Also you need to be careful about the capital letters when running.
You can now use your resulting header file instead of those I made. Don’t forget to change line 7 and 20 in the .hpp file.
Working with 48KHz samplerate and 256 unit samples means we won’t have frequencies over nyquist freq when playing the sample under 187.5 Hz. It is a good new because we don’t need to anti-alias under 187.5 but that means that playing a 256 unit sample at low frequencies such as 40Hz will give us harmonics only until 5120 Hz. We need bigger samples to reproduce bass sound properly with a full harmonic range.
I am not familiar with all different wavetable editor/plugins. If you know some you can choose your sample size (and define more than 128 harmonics), please share it here. It could be a good improvement to the patch.

2 Likes