Waveform tables

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

mistertoast wrote:Does that seem reasonable?
No. The higher harmonics by their nature want larger wavetables, and I suspect the noise introduced by using low quality tables will be broadband, i.e. will add noise to the pristine low harmonics you're generating.

Is the point here to reduce memory usage? 4096 samples * 128 (harmonic extents or MIDI notes) * 4 bytes per single precision float sample = 2MB per waveform. Suck it up. The tables with fewer harmonics can be a lot smaller, also. You can go to 16-bit integer instead of float and cut the size in half again if you need to, or go to 16-bit and interleave deltas to speed up the interpolation for the same size.
Image
Don't do it my way.

Post

Daniel used to have a complete article, rather than just the code.. hmm.. WaybackMachine gives http://web.archive.org/web/200602130427 ... ?article.1 which has more explanation, but there was a bigger version once...
Image

Post

The text is still there, it's just set to background color in firefox.

Post

>>No. The higher harmonics by their nature want larger wavetables, and I suspect the noise introduced by using low quality tables will be broadband, i.e. will add noise to the pristine low harmonics you're generating.

I was expecting you to say that. That's really the question--whether the noise on the small ones will ruin my pretty ones. Isn't the noise on the small ones going to be on that small scale? I can make a quick test.
Last edited by mistertoast on Thu Dec 07, 2006 12:03 am, edited 3 times in total.

Post

>>The text is still there, it's just set to background color in firefox.

Haha! I didn't see the text, but it showed up when I clipped the contents of the page to Evernote.

Post

http://web.archive.org/web/200412090435 ... nbleps.php

That's how I remember it!!!

Oh, and you might be interested in this thread as well... http://www.kvraudio.com/forum/viewtopic.php?t=56891

DSP
Image

Post

mistertoast wrote:Chris,

Actually, I'd prefer the "how it works."
I might get the terminology and a few details wrong, but I blame it on having taught this myself and/or bugging people on IRC to help me understand :D.

Ok. Make sure you know about time-based convolution (http://www.dspguide.com/ch6.htm), and sinc filters (http://www.dspguide.com/ch16.htm). You probably already know anyway but I'm just being safe.

Integration and Differentiation act as opposites. Integrating the Differentiation, or vice versa, is an identity. In the discrete domain, Integration means the running sum, and Differentiation means the running difference. I'm pretty certain you knew this as well.

Ok, beef time. BLEP is an acronym for Band-Limited Step. So the principle behind it is that steps are bandlimited. A step is pretty much a discontinuity. And of course, at such discontinuities, we always rack up a bunch of harmonics.

Lets start off with a simple square oscillator - does a few cycles at -1, then does a few cycles at 1, and then repeats. One way to attempt to bandlimit this signal is through massive oversampling with a filter. What this does is make sure that a good portion of what would be aliasing is actually not aliasing. Then, we can go in with a Linear Phase Windowed Sinc filter, cut out everything above 22050Hz (or whatever), and downsample again. This approach works pretty well, because its easy to increase the oversampling factor or the filter kernel length to cut out the bad frequencies.

The problem, however, is that it is slow. Not only does the entire signal path have to be oversampled a large amount, it also requires a per-sample time domain convolution of the windowed sinc filter, which isn't cheap. However, there is an optimization waiting for us.

Notice earlier how I said integration and differentiation are opposites. If you convolve A with B, is it the same as convolving the differentiation of A with the integration of B? Why, it certainly is! (but don't expect a proof from me, I couldn't write one if my life depended on it). So, with this knowledge, this gives us a few good ideas we could try to optimize this bunch. Differentiating a square wave gives us a 1 sample, several 0 samples, a -1 sample, and several 0 samples again, repeated. Basically, an impulse train. Now, integrating the filter kernel gives us something that looks like a stairstep with the carpet not correctly attached. It has a special name though, its called a "step response". That is because it describes what the filter will do if it encounters a step from 0 to 1.

So, lets do what we did a few paragraphs ago, except this time we integrate the filter kernel and differentiate the square. We pretty much get the same result as before, but there is a striking difference - there are a bunch of 0 samples being convolved. That can be optimized, by simply not convolving them. So now, instead of having, say, 102 convolutions per cycle, you know only have 2. Drastic difference, innit?

That right there is pretty much the whole idea behind BLEP, but here are a few other things worth pointing out:

To think of the whole issue from a different paradigm perspective, you are taking a square wave and replacing the instant step with a bandlimited one. My implementation actually subtracts the instant delta step from my bandlimited delta step beforehand, so all I have to do is scale and add to a circular buffer at the right time.

The oversampling can be drastically cut, to the point of not even needing it (depending on what you do and what you want). If you were to "downsample" a continuous impulse train, the impulses would be at fraction times. You can take advantage of this in blep. Generate the square without oversampling, but at the discontinuities, also pass the blep-adding routine this fractional amount. The filter is oversampled to the amount, and the fractional amount is used to pick which branch to pick (which basically is just an offset added to the array index).

You can also synthesize triangles and probably a whole bunch of other things with BLEP, but I haven't done it so I won't comment further. I will say though that the basis behind synthesizing triangles with BLEPs is another layer of integrate/differentiate ;)

You will often see the term "minBlep". What I demonstrated here was actually "linearBlep", because the filter used is a linear phase filter. My implementation uses linearBlep (but then again, its just a proof of concept). The problem with the linearBLEP is the group delay - it is half of the filter kernel length, divided by oversampling. That may potentially be unacceptable, and it may add a bit of circular buffer overhead. minBLEP means that the filter has gone through a minimum phase reconstruction, which basically means that the group delay is minimized (~4 samples for length 32 * 16 oversampling, but it varies with cutoff frequency). There are some advantages and disadvantages to each, but in the end, its all up to you. ;)

Finally, I need to mention sync. Sync with BLEP is ridiculously easy. When a sync occurs, just figure out the size of the discontinuity, and add a blep scaled to that size there. Done! :D

NOTE: I take no responsibility if I'm full of shit and what I say makes no sense or is even wrong. Use at your own risk. ;)

-- Chris

Post

Thanks Chris. Next synth for sure. Very helpful.

Post

mistertoast wrote:I have tables of 4096 values for my waveforms. My current plan is to have those for each of the first 12 harmonics, these are the "big," "loud" harmonics, and the others trail off in relative quietness.
It almost sounds like you are having only one partial in one table.

You should have one table with the first partial. Another table table with a few partials, yet another table with even more partials mixed together. Like this:

Code: Select all

Table Partials
-----+--------
1    | 1
2    | 1,2
3    | 1,2,3
Or something like that (you might have the tables in another order) and you might have a few partials added for each table...

Jonas

Post

Jonas, that's what I have. Currently 1, 1.2, 1.2.3, 1.2.3.4, up to 1..16 at the moment, which takes me through the range of midi note 127 down through, I think, midi note 88. At 44100, of course. So that's 16 tables that when blended correctly, perfectly handle 39 midi notes. I can just do the rest in 88 tables, but I'm still thinking about it.

The high notes take a while to get through harmonics. The low notes run through a bunch of them at a time.

Post

One way to do it...

harmonics = roundDown(power(2,semitone/12));

that will convert the table bandwidth in semitones to the bandwidth in harmonics.

Eg.. 0..11 will give you 1
12..19 will give you 2
20..23 will give you 3
24..27 will give you 4

and so on..

So set up a lookup table with 128 entrys.. Start at 0 and run thru initializing the tables.. but any tables that have the same harmonic width as the previous table you just point at the previous tables data.

So.. 0..11 will all point to one table. 12..19 likewise

ect..

I do it sort of like that but rather than using semitone as the lookup i use the phase step of the oscillator.

So.. harmonic width = 0.5 / phasestep

That works out how many harmonics can fit up to the nyquist with the given phase step per sample.

I clip that width to 0..511 which i then use to lookup the correct mip map. Generating the table spacing is more involved this way.. but it does simplify the mip map lookup in the actual oscillator code.

chris

Post

bslf wrote: NOTE: I take no responsibility if I'm full of shit and what I say makes no sense or is even wrong. Use at your own risk. ;)

-- Chris
Thats the clearest easiest to understand explanation of Blit/Bleps i've seen.

good post :D

chris

Post

chris,

That's nice. Seems like a good solution.

I'm interpreting between tables now. Is that worth it? I didn't want the top harmonic to "pop" in all at once for the high notes. But I personally can't tell the difference anyhow when I drop out the highest harmonic. I know my hearing is bad up in that range, so I'm still doing it for now.

Is there a way to know when your host is "rendering" as opposed to playing live? Because I can imagine I'd do some things differently if I knew that time was not an issue.

Post

mistertoast wrote:chris,
Is there a way to know when your host is "rendering" as opposed to playing live? Because I can imagine I'd do some things differently if I knew that time was not an issue.
Yep, getCurrentProcessLevel although I suspect its still not that widely supported by hosts.

Post

mistertoast wrote:chris,

That's nice. Seems like a good solution.

I'm interpreting between tables now. Is that worth it? I didn't want the top harmonic to "pop" in all at once for the high notes. But I personally can't tell the difference anyhow when I drop out the highest harmonic. I know my hearing is bad up in that range, so I'm still doing it for now.
There's no need to interpolate between tables if you have them so closely spaced. I've tried 3 semitone spacing and couldn't hear any difference in switching tables.. probably because even at 3 semitones the harmonics coming in or out will all be above 18khz anyway.. well out of most peoples hearing range, and at the least out of the range where anyone would notice it. (imo of course)

chris

Post Reply

Return to “DSP and Plugin Development”