Forums > MaxMSP

Interpolation of Oscillators


Sym
December 22, 2012 | 8:30 pm

Hey,

I just stumbled upon the term Interpolation of Oscillators and whether it is better to use a wavetable using 4096 samples for each midi note or to use a smaller wavetable and better interpolation .

I searched upon the web but I have a real problem on understanding what interpolation exactly is.

Would be awesome if someone could help me out with a simple explanation to get me started!


December 22, 2012 | 10:45 pm

Interpolation is a mathematical equation to find the value of a point in between two ore more known points. For example, if you know (0,0) and (1,2) and you want to know the y value at point 0.5, you use an equation to find this out. A linear equation is always in the form of y=mx+b and you can find the value for m (the slope of the line) by m=(y1-y2)/(x1-x2).

The way that wavetable oscillators work is you generate a single cycle of a wave using a certain number of samples (like 4096). By reading this table at different rates you can produce higher and lower frequencies than the original wave stored in the table. The problem here is the rate you want to read the table at will, more often than not, not be at a known sample in the table. Your options are to truncate, use the closest known samples value, or you can interpolate the x value at that point.

Using interpolation (linear, cubic, quadratic, or hermite), will decrease the amount of distortion at the cost of more processing. The higher the number of samples in the wavetable the more memory it takes up but the more accurate it is. The use of interpolation decreases distortion and increases the number of cpu cycles.

I hope that answers your question.
GW



Sym
December 22, 2012 | 11:53 pm

Thank you, that answered my question. Awesome explanation!


December 23, 2012 | 2:44 am

A small addition to the quite great explanation above: as it was pointed out, a bigger wave table takes up more memory. In fact, this also means that a huge wavetable might also require more processing. Since an average CPU has only a couple of MBs of internal cache, the bigger your wavetable is, the more often your CPU will run into cache misses, which at the end of the day will result in more processing time required by your oscillator. I remember from older days some experimenting with different sized wave tables. On my MacBookPro from 2006, I noticed that a wave table of approx. 16K with no interpolation required actually more CPU than a small table with 512 samples and interpolation. Maybe these days I’d get better results, but the point is, that simply by increasing your wavetable and disabling interpolation, you won’t necessarily save processing time.

HTH,
Ádám


December 23, 2012 | 3:31 am

@Adam – Thank you for the comment on my explanation, but I would have to respectfully disagree. I recent built a wavetable class in c++ from scratch and did extensive testing on the benifits/drawbacks to many different permutations of wavetable size and interpolation methods. Granted I could be dead wrong because you had tested this on a much older laptop than I did my testing on.

From my investigation I found significant improvement in quality of produced waves from a wavetable as I increased the table size. I didn’t notice any increase in cpu, other than during initialization creating the table, but I did notice that any size larger than 16K yielded little to no benefit. This is also highly dependent on the way the wavetable is accessed as well and how the class is structured to generate sample values of a wave.

I should also say that it’s certainly possible that as I increased the table size it was requiring more cpu but I couldn’t notice. I’m running a 2.4 GHz i7 quad core processor and 2x8GB RAM. But at the same time, when you have higher table sizes you have to "wrap" around the table less frequently using while loops, which can take up a lot of cpu.

A small history lesson, the guy that invented wavetables was trying to find a quicker way to generate sine waves because trigonometry functions take up a whole lot of cpu. If you think about how wavetables work, reading a single cycle of a waveform at different speeds to produce different frequencies, it’s quite ingenious.

GW


December 24, 2012 | 12:05 am

Hm, then it might actually have to do with my old CPU (it was a 32bit Dual Core with 2MB cache). My actual ‘test’ was, that at that time I was developing an optimized additive synth and I was wondering whether to use a small wavetable with linear interpolation or a bigger one without any interpolation. I just remember that with around a thousand partials, the solution using the big wavetable without interpolation performed worse than the one with a small wavetable and a linear interpolation. Of course, one also needs to take into account that with a thousand partials, the importance of cache misses is much bigger than with a single oscillator, since you’re practically drawing from a uniform distribution of phases all the time.

But of course, this has little to do with the original question about interpolation, which was quite well answered by the first reply.

Best,
Ádám

P.S. I’m not sure that I really understood what you meant by ‘wrapping’ the table. If you mean the phase wrapping, I guess that by using properly scaled integer phases, the integer overflow will wrap the phase of the table in a natural way, thus the need for if-s and while-s can be avoided.


December 24, 2012 | 1:01 am

Yes that’s what I ment by wrapping. But I’m unclear, and very interested, in how you could avoid if’s and while’s. Here’s basically my truncating generator code:

int index = static_cast(osc->tickInfo->curvPhase);

// advance curvPhase and wrap
osc->tickInfo->curvPhase = wrap(osc->tickInfo->curvPhase + osc->tickInfo->increment);

return currentTable[index] * osc->amp;

and the wrap function does this:

while (val >= osc->tableSize) val -= osc->tableSize;
while (val < 0.0) val += osc->tableSize;
return val;

So I’m curious to know how you would get curvPhase to wrap without if’s and/or while’s, if you don’t mind sharing.

GW


December 24, 2012 | 9:09 am

Hi,

of course, I don’t mind sharing. But I won’t take credit for something that I did not invent. ;-)

The idea was taken from this thread (it took me a wile to find it again, but I was lucky today): http://www.kvraudio.com/forum/viewtopic.php?t=272457 The code sample was submitted by a user called ‘nollock’ and it’s at the very end of the thread. I copy that original code here. I hope the guy wouldn’t mind…

So, here it goes:

const int WAVEBITS = 10;
const int WAVESIZE = 1 < < WAVEBITS;
const int FRACBITS = 32 - WAVEBITS;
const int FRACMASK = ( 1 << FRACBITS ) - 1;
const double FRACSCALE = 1.0 / ( 1 << FRACBITS );

// For the wave lookup repeat very first entry at the end,
// so we dont need to mask the index, thats why the array
// size is one larger.

float wavetable [ WAVESIZE + 1 ];
// Here you need to fill your wavetable with data

// Omega calc
int omega = int ( ( freq / samplerate ) * 4294967296.0 ); // The constant here is 2^32

// Osillator code
unsigned int phase += omega;
int idx = phase >> FRACBITS;
float tmp = wavetable [ idx ];
output = tmp + ( phase & FRACMASK ) * FRACSCALE * ( wavetable [ idx + 1 ] - tmp );

In this code, the number of samples in your wavetable will be WAVESIZE+1, but the last sample must match with the 0th for the interpolation to work properly (just as in [cycle~]). The wavetable initialization is actually not part of this code, so you’ll need to take care of that yourself (I inserted a comment at the proper place).

The main idea behind this solution is the use of integer overflow: if you have an (unsigned) integer that holds the biggest positive value that it could represent, then the result of adding 1 to this integer will actually be 0. Therefore, if you scale down the interval [0, MAX_INT] to [0, 2PI], then you can use an integer as the phase of the oscillator and the phase wrapping will be done automatically by the integer overflow mechanism.

Hope this helps,
Ádám


December 24, 2012 | 10:59 pm

This is genius! I can’t wait to poke around the code and try it out.

GW


Viewing 9 posts - 1 through 9 (of 9 total)