minBLEPS once and for all
- KVRian
- 804 posts since 25 Apr, 2011
I don't know, the BLEP table looks reasonable to me. The 'step removal' stage is achieved at runtime, by using (in this example) 1.0-BLEP[tempIndex] A low-pass step is surely exactly what's required, with the cutoff frequency being effectively just below Nyquist once the BLEP is subsampled down to normal sample rate.
I agree about using a high level of oversampling and not using LERP. But bear in mind that quantising the start position will effectively introduce timing jitter into the resulting waveform, some cycles being up to 1 subsample's delay more or less than others. If you don't have enough supersampling then this will create audible noise.
Yes, make the circular buffer bigger whilst testing; there is no need for it to be exactly the same size as the subsampled BLEP.
Finally, it may make life easier initially to work with a square wave rather than a sawtooth, as this avoids problems with DC offset (alternate BLEPS are added and subtracted); and you can see the ringing without it being confused by the slope of the sawtooth.
I agree about using a high level of oversampling and not using LERP. But bear in mind that quantising the start position will effectively introduce timing jitter into the resulting waveform, some cycles being up to 1 subsample's delay more or less than others. If you don't have enough supersampling then this will create audible noise.
Yes, make the circular buffer bigger whilst testing; there is no need for it to be exactly the same size as the subsampled BLEP.
Finally, it may make life easier initially to work with a square wave rather than a sawtooth, as this avoids problems with DC offset (alternate BLEPS are added and subtracted); and you can see the ringing without it being confused by the slope of the sawtooth.
- KVRAF
- 8512 posts since 12 Feb, 2006 from Helsinki, Finland
I'm sorry, read the table wrong it appears.kryptonaut wrote: A low-pass step is surely exactly what's required, with the cutoff frequency being effectively just below Nyquist once the BLEP is subsampled down to normal sample rate.
Still, 4 points is garbage. Seriously, debug with at least 16 taps and AT MINIMUM 1k oversampling (branches). Also, if you get DC problems with saw, you are doing something wrong, so IMHO debugging saw is more useful.
- KVRian
- 804 posts since 25 Apr, 2011
Yes, 4 points is not enough.
I still think it's easier to debug with a square wave though, as the steps then ought to look like the same curve you can see in the BLEP table. And it nicely shows up any issues with scaling and offsets.
Amusesmile, are you still trying to get this working?
I still think it's easier to debug with a square wave though, as the steps then ought to look like the same curve you can see in the BLEP table. And it nicely shows up any issues with scaling and offsets.
Amusesmile, are you still trying to get this working?
-
- KVRist
- Topic Starter
- 67 posts since 17 Aug, 2012 from United States
Yeah I can't say when I'll solve this but I'm not going anywhere until I do. Thanks for the continued support. After what mystram said about the BLEP not looking right I was all mixed up so I decided to bother one of my local dsp wizards. He said he was also interested in implementing BLEPS/minBLEPS so it worked out and we're supposed to meet some time this week.
I guess it turns out that the BLEP data actually looks like it should from what you've been saying so I guess I'm not as far off as I thought I was. Since that's settled I'm going to try a larger ring buffer today to see if that helps. Should have an update soon.
While pondering the minBLEP problem I implemented feedback FM sawtooth/square waves because I had been interested in that approach for a while. They're far from perfect and still no hard sync but they do have a nice vintage sound. I figure it's good to have options.
And variable pulse width square/saw
I guess it turns out that the BLEP data actually looks like it should from what you've been saying so I guess I'm not as far off as I thought I was. Since that's settled I'm going to try a larger ring buffer today to see if that helps. Should have an update soon.
While pondering the minBLEP problem I implemented feedback FM sawtooth/square waves because I had been interested in that approach for a while. They're far from perfect and still no hard sync but they do have a nice vintage sound. I figure it's good to have options.
Code: Select all
//return a more or less bandlimited sawtooth wave created through feedback FM.
//taken from: http://scp.web.elte.hu/papers/synthesis1.pdf and the gen~.band_limited_saw_using_feedback_fm example from max/msp 6.0.7
//variable declaration
double phase = 0.0;
double frequency = 440.0;
double amplitude = 1.0;
double internalHistory1 = 0.0;
//put this inside your audio loop
double returnNextSample()
{
phaseIncrement = frequency/sampleRate; //normalize frequency
phase += phaseIncrement;
while(phase >= 1.0)
{
phase -= 1.0;
}
double scaling = 54.0*pow((0.5-phaseIncrement()), 6.0); //calculate scaling
//alternative scaling: scaling = 13.0*pow((0.5-phaseIncrement), 4.0);
double temp = (internalHistory1+sin(PI*(((2.0*phase)-1.0)+internalHistory1*scaling)))*0.5f;
// compensate HF rolloff
double const A0 = 2.5; //precalculated coeffs
double const A1 = -1.5; //for HF compensation
double output = (A0*temp)+(A1*internalHistory1);
double DC = 0.376-phaseIncrement*0.752; //calculate DC compensation
double norm = 1.0-(2.0*phaseIncrement); //calculate normalization (frequency dependent amplitude compensation)
output = (output+DC)*norm;
internalHistory1 = temp;
return output;
}
Code: Select all
//return a more or less bandlimited square-saw wave created through feedback FM.
//taken from: http://scp.web.elte.hu/papers/synthesis1.pdf and the gen~.band_limited_saw_using_feedback_fm example from max/msp 6.0.7
//variable declaration
double phase = 0.0;
double frequency = 440.0;
double amplitude = 1.0;
double squareAmount = 1.0; //0.0 = sawtooth, 1.0 = square
double pulseWidth = 1.0; //1.0 = perfect square
double internalHistory1 = 0.0;
double internalHistory2 = 0.0;
//put this inside your audio loop
double returnNextSample()
{
phaseIncrement = frequency/sampleRate; //normalize frequency
phase += phaseIncrement;
while(phase >= 1.0)
{
phase -= 1.0;
}
double scaling = 54.0*pow((0.5-phaseIncrement()), 6.0); //calculate scaling
double temp = (internalHistory1+sin(PI*(((2.0*phase)-1.0)+internalHistory1*scaling)))*0.5f;
double temp2 = squareAmount*(internalHistory2+sin(PI*(((2.0*phase)-1.0)+(internalHistory2*scaling)+pulseWidth)))*0.5f;
double square = temp-temp2; //you can make a square by subtracting two out of phase sawtooth waves
// compensate HF rolloff
double const A0 = 2.5; //precalculated coeffs
double const A1 = -1.5; //for HF compensation
double output = (A0*square)+(A1*(internalHistory1-internalHistory2));
double norm = 1.0-(2.0*phaseIncrement); //calculate normalization (frequency dependent amplitude compensation)
output = output*norm;
internalHistory1 = temp;
internalHistory2 = temp2;
return output;
}
-
- KVRian
- 653 posts since 4 Apr, 2010
It's been maybe 10 years since I looked at this (including generating my own minBLEPs, trying varying degree of oversampling), and unfortunately I lost that code years ago, but your table doesn't look right. (I copy-pasted the 128 values into a spreadsheet and graphed.) You should see some ringing after the step up—you've just got the first dip; it looks like the series is truncated, severely. It should look closer to Eli's figure 7: http://elthariel.free.fr/ebooks/icmc01-hardsync.pdfAmusesmile wrote:My BLEP[] table is 128 long, with 32 times oversampling, so my circular buffer is only 4 samples long (128/32). Not sure what's wrong exactly because conceptually I think I'm doing what both of you walked me through. I don't think this is the issue but just so we're on the same page, the BLEP[] looks like this:
Code: Select all
0.00153776 0.00301951 0.00443894 0.00579383 0.00708643 ...
And four samples won't be enough, as you know...I don't recall how many I settled on, but as a rough guess I'd start with 16 and see if ~8 would do, or something in between. But I'm pretty sure you'll get bad aliasing with that table regardless of resolution.
My audio DSP blog: earlevel.com
-
- KVRist
- Topic Starter
- 67 posts since 17 Aug, 2012 from United States
Oh thanks for chiming in. You're right about the data appearing truncated. The implementation I'm using allows you to specify the number of zero-crossings for the BLEP and I had that too low so it wasn't having time to ring. Now I'm using 24 zero-crossings and when I chart the output it looks like figure 7 in Eli's paper.earlevel wrote:It's been maybe 10 years since I looked at this (including generating my own minBLEPs, trying varying degree of oversampling), and unfortunately I lost that code years ago, but your table doesn't look right. (I copy-pasted the 128 values into a spreadsheet and graphed.) You should see some ringing after the step up—you've just got the first dip; it looks like the series is truncated, severely. It should look closer to Eli's figure 7: http://elthariel.free.fr/ebooks/icmc01-hardsync.pdfAmusesmile wrote:My BLEP[] table is 128 long, with 32 times oversampling, so my circular buffer is only 4 samples long (128/32). Not sure what's wrong exactly because conceptually I think I'm doing what both of you walked me through. I don't think this is the issue but just so we're on the same page, the BLEP[] looks like this:
Code: Select all
0.00153776 0.00301951 0.00443894 0.00579383 0.00708643 ...
And four samples won't be enough, as you know...I don't recall how many I settled on, but as a rough guess I'd start with 16 and see if ~8 would do, or something in between. But I'm pretty sure you'll get bad aliasing with that table regardless of resolution.

I also set the ring buffer size to 1000 to have more room.
Unfortunately the signal still aliases or distorts in a weird way. It's definitely better than before but not nearly good enough. How many zero-crossings should I be using? It's currently set to 24 with 16x oversampling.
-
- KVRian
- 653 posts since 4 Apr, 2010
24 zero crossings is more than enough—I seem to recall using 16. As for oversampling...it's been a long time since i've looked at the paper, but I believe you're just building a table and doing linear interpolation. If so, memory is cheap—I think 64x should be plenty, as the function is fairly smooth (and monotonic between samples as long as the oversampling ratio is an even number); 16x might be close enough for rock 'n roll, but a 4x increase is table size is a small price to pay for more precision.Amusesmile wrote:Unfortunately the signal still aliases or distorts in a weird way. It's definitely better than before but not nearly good enough. How many zero-crossings should I be using? It's currently set to 24 with 16x oversampling.
While you're battling this distortion problem, you'll want to pick numbers that you know should give you good results, and I'm pretty sure that 24 zero crossings and 64x oversampling will give excellent results. Then when you've got that going, you can always back off on the 24 zero crossings and see what you can live with, and get some cpu cycles back.
Still, while I think the 16x you're doing now is a bit low, I wouldn't expect it to be terrible distortion-wise, so you may have a coding problem. So bump to 64x and then focus on the code if it's still a problem.
My audio DSP blog: earlevel.com
-
- KVRist
- Topic Starter
- 67 posts since 17 Aug, 2012 from United States
Eureka!
I had a stupid mistake in the exact cross time calculation.
should be
Hot damn. Alright this fixes the aliasing but I'm still having pretty bad DC offset making this unusable over like 6kHz. I'll have to figure out how to correct that and then clean up the code and actually test some hard sync. If/once I get it working I'll post some examples as promised. Many thanks all around.
By the way earlevel- I've run across your blog quite a few times and it's been very helpful. The wavetable stuff especially.
I had a stupid mistake in the exact cross time calculation.
Code: Select all
double exactCrossTime = (phaseIncrement-phase)/phaseIncrement;
Code: Select all
double exactCrossTime = 1.0-((phaseIncrement-phase)/phaseIncrement);
By the way earlevel- I've run across your blog quite a few times and it's been very helpful. The wavetable stuff especially.
-
- KVRian
- 653 posts since 4 Apr, 2010
Great!Amusesmile wrote:Eureka!
Thanks for saying so—it helps motivate me to write if I know people are reading...By the way earlevel- I've run across your blog quite a few times and it's been very helpful. The wavetable stuff especially.
My audio DSP blog: earlevel.com
-
- KVRist
- 98 posts since 26 May, 2005 from Winterthur - Switzerland
-
- KVRian
- 653 posts since 4 Apr, 2010
Thanks Frank! Oh wow, taking a trip in the way-back machine, step sequencers...At the USC electronic music lab, I used the Moog 960 sequencer with regularity...later we added Emu modular digital (step) sequencer...at home I had three (they told me I had more than anyone else) Aries 8x2 modules and a sequential switch. And later, my home-brew digital keyboard with step sequencer via my SYM-1 (single board 6502)...fun stuff...Frank123 wrote:I was reading....
My audio DSP blog: earlevel.com
-
- KVRist
- 98 posts since 26 May, 2005 from Winterthur - Switzerland
Nice to hear from another step sequencer enthusiast.earlevel wrote:Thanks Frank! Oh wow, taking a trip in the way-back machine, step sequencers..Frank123 wrote:I was reading....
And later, my home-brew digital keyboard with step sequencer via my SYM-1 (single board 6502)...fun stuff...
Take care.
Frank
- KVRAF
- 3212 posts since 17 Apr, 2010 from Slovenia
Now, I know this one is an ancient thread, but I just have to thank you, Amusesmile! It's currently my favorite little saw/pulsewidth code up there! 
I keep studying it and see where else it might take me, but from what I've seen thus far it's really elegant and sounds beautiful.
I am working on a completely different synthesis concept, which I will share, once I'm through with it, but for my first little "work horse" vst, yours is really pushing it forward. I hope, you don't mind me using it or something heavily inspired by it.
Even if it may well end up a powerful inspiration "only", it already brings me massive joy!
My goal is to create a free vst for the OSCs here, which "just works" without any obvious weaknesses. We shall see!
Anyway, this is a 3 year late (even if only minutes for me as I just discovered it) THANK YOU!
I keep studying it and see where else it might take me, but from what I've seen thus far it's really elegant and sounds beautiful.
I am working on a completely different synthesis concept, which I will share, once I'm through with it, but for my first little "work horse" vst, yours is really pushing it forward. I hope, you don't mind me using it or something heavily inspired by it.
Even if it may well end up a powerful inspiration "only", it already brings me massive joy!
My goal is to create a free vst for the OSCs here, which "just works" without any obvious weaknesses. We shall see!
Anyway, this is a 3 year late (even if only minutes for me as I just discovered it) THANK YOU!
-
- KVRist
- Topic Starter
- 67 posts since 17 Aug, 2012 from United States
Cool, I'm very happy you found it useful! It was my hope to make things easier for everyone trying to figure this out in the future. Three years isn't so long in the esoteric music dsp timeline, so your belated thank you is much appreciated. Good luck with what you're working on and be sure to post your findings/creations. I'd love to see what comes of it.
Josh, Co-Founder of Unfiltered Audio:
http://www.unfilteredaudio.com
http://www.unfilteredaudio.com
- KVRAF
- 3212 posts since 17 Apr, 2010 from Slovenia
UUuuhhh, you're still around, that's WONDERFUL! 
Yeah, you bet I will post my first vst with a good deal of pride on top, haha (Or so I hope!)!
I'm still gathering information and figure stuff out, but I've done some great leaps the last few days and should have something done in the coming few days at the going rate.
It's rough, of course, filling my brains with all this stuff. The most painful was all vst sdk related, of course, but now I gained control over chunks to handle my own data more freely and gui matters, which were the main things that kept me busy for the last two weeks, I think. Before then I never had dealt with them.
The pure dsp stuff is far more fun, of course, because there are the exciting brain teasers. But there are the exciting brain teasers!
It's quite amazing to wrestle frequency and amplitude matters, bringing values within understandable, predictable and controllable ranges, pondering over just how fast a speaker can vibrate, hahahaha... man, it's crazy stuff! Not to mention the math, of course...sheesh.
I noticed that my filter's resonance can create vicious aliasing now, which keeps me on my toes for a moment. I allow the tracking of frequencies rather than midi notes, making it really fantastic to play, but causing this additional challenge, of course.
Anyhow...your oscillator is great fun, while I'd love to get on top of it enough to tweak it to my ideals. If it was only about how it sounds, it's pretty darn sweet, though. I'm just afraid that it's really unique and someone, who'd expect a saw, for example, would hear or- at the latest- see what comes out instead. With the slow recovering sine it gets a lot of "bottom" or "body", which can be considered uncommon or even undesirable for that, you know. (Yes, YAHA, yes, the gift-horse thing, haha, don't get me wrong, I'm still hyper-thrilled!)

I will keep playing with it all...ahm...probably till the end of days, but I'll try to hurry something out for my first release!
Again: You Rock! That's undeniable in my book!
I should probably cyber stalk you a little to see what you've done in the last 3 years, haha, but if you don't mind me asking: Have you released any synths since then? (Man, that could be such a dumb question...I'm already bracing myself!)
Yeah, you bet I will post my first vst with a good deal of pride on top, haha (Or so I hope!)!
I'm still gathering information and figure stuff out, but I've done some great leaps the last few days and should have something done in the coming few days at the going rate.
It's rough, of course, filling my brains with all this stuff. The most painful was all vst sdk related, of course, but now I gained control over chunks to handle my own data more freely and gui matters, which were the main things that kept me busy for the last two weeks, I think. Before then I never had dealt with them.
The pure dsp stuff is far more fun, of course, because there are the exciting brain teasers. But there are the exciting brain teasers!
It's quite amazing to wrestle frequency and amplitude matters, bringing values within understandable, predictable and controllable ranges, pondering over just how fast a speaker can vibrate, hahahaha... man, it's crazy stuff! Not to mention the math, of course...sheesh.
I noticed that my filter's resonance can create vicious aliasing now, which keeps me on my toes for a moment. I allow the tracking of frequencies rather than midi notes, making it really fantastic to play, but causing this additional challenge, of course.
Anyhow...your oscillator is great fun, while I'd love to get on top of it enough to tweak it to my ideals. If it was only about how it sounds, it's pretty darn sweet, though. I'm just afraid that it's really unique and someone, who'd expect a saw, for example, would hear or- at the latest- see what comes out instead. With the slow recovering sine it gets a lot of "bottom" or "body", which can be considered uncommon or even undesirable for that, you know. (Yes, YAHA, yes, the gift-horse thing, haha, don't get me wrong, I'm still hyper-thrilled!)
I will keep playing with it all...ahm...probably till the end of days, but I'll try to hurry something out for my first release!
Again: You Rock! That's undeniable in my book!
I should probably cyber stalk you a little to see what you've done in the last 3 years, haha, but if you don't mind me asking: Have you released any synths since then? (Man, that could be such a dumb question...I'm already bracing myself!)
