PolyBLEP oscillators

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

left_sample is the sample before the discontinuity. right_sample is the one after it.

For starters try playing only at the original/root frequency of the waveform. Because if you try any other frequency, you'll get aliasing NOT because of the discontinuity, but because of other reasons.

I'm not sure how many wavetables per octave your using. If your using only one table for all frequencies. Then it will still alias bad even if you smooth the discontinuity when you play it far off the root frequency.
Last edited by S0lo on Thu Jul 09, 2020 1:58 am, edited 2 times in total.
www.solostuff.net
Advice is heavy. So don’t send it like a mountain.

Post

Sorry wrong post.
www.solostuff.net
Advice is heavy. So don’t send it like a mountain.

Post

So for example, (Edit: assuming the discontinuity is between the end and start of the wave array)

Code: Select all

float getSample (float currentIndex)
{
  float x, f;

  if (currentIndex > lastIndex)
  {	x = currentIndex - lastIndex;
  	if (x<0.5) f = 2*x*x;
	else  f = 4*x -2*x*x -1;
	return (1-f) * tableWaveform[lastIndex] + f * tableWaveform[0];
  }
  else 
  	// You better also do linear or cubic interpolation between two samples here to accommodate for fractional currentIndex.
	return tableWaveform[currentIndex];	
	
}
Last edited by S0lo on Thu Jul 09, 2020 10:15 am, edited 1 time in total.
www.solostuff.net
Advice is heavy. So don’t send it like a mountain.

Post

New to this subject so, I would like to know if the "sum-of-saws" method mentioned in W.C.Pirkle's book "Designing Software Synthesizer Plug-Ins in C++:..." (chapter 5.12-->) is introduced here at some point ...? Note! You be able to read the hidden pages by 1st scrolling enough towards the beginning of the book ... .

Post

juha_p wrote: Thu Jul 09, 2020 6:33 am New to this subject so, I would like to know if the "sum-of-saws" method mentioned in W.C.Pirkle's book "Designing Software Synthesizer Plug-Ins in C++:..." (chapter 5.12-->) is introduced here at some point ...? Note! You be able to read the hidden pages by 1st scrolling enough towards the beginning of the book ... .
Assuming this refers to generation of pulse waves by subtracting two saws with opposite polarity, it is a well-known technique that can be applied to pretty much any method of generating saws (eg. works great with wavetables). When it comes to BLEPs though, there isn't really any obvious advantage (unless you want the DC cancellation, but you can easily compute that directly as well) over just generating a pulse-wave directly.

Post

mystran wrote: Thu Jul 09, 2020 9:44 amAssuming this refers to generation of pulse waves by subtracting two saws with opposite polarity
Clearing throat... :D

Post

mystran wrote: Thu Jul 09, 2020 9:44 amAssuming this refers to generation of pulse waves by subtracting two saws with opposite polarity, it is a well-known technique that can be applied to pretty much any method of generating saws (eg. works great with wavetables). When it comes to BLEPs though, there isn't really any obvious advantage (unless you want the DC cancellation, but you can easily compute that directly as well) over just generating a pulse-wave directly.
I was just curious why Pirkle suggested here to use sum-of-saws over BLEP (assuming he means polyBLEP as what that thread is about) ... though, he references Andy Leary from Korg R&D been behind his choice...

Post

Z1202 wrote: Thu Jul 09, 2020 11:36 am
mystran wrote: Thu Jul 09, 2020 9:44 amAssuming this refers to generation of pulse waves by subtracting two saws with opposite polarity
Clearing throat... :D
?

Oh.. I see... well right.. let's say mixing two saws with opposite polarity then.

Post

juha_p wrote: Thu Jul 09, 2020 12:12 pm
mystran wrote: Thu Jul 09, 2020 9:44 amAssuming this refers to generation of pulse waves by subtracting two saws with opposite polarity, it is a well-known technique that can be applied to pretty much any method of generating saws (eg. works great with wavetables). When it comes to BLEPs though, there isn't really any obvious advantage (unless you want the DC cancellation, but you can easily compute that directly as well) over just generating a pulse-wave directly.
I was just curious why Pirkle suggested here to use sum-of-saws over BLEP (assuming he means polyBLEP as what that thread is about) ... though, he references Andy Leary from Korg R&D been behind his choice...
I have no idea. In my humble opinion, most of the PolyBLEP implementations I've seen on the internet are rather silly and half of them won't even work correctly in various edge cases.

Years ago, I wrote this tutorial on how to do BLEPs in a sane way: viewtopic.php?t=398553

Post

S0lo wrote: Thu Jul 09, 2020 12:34 am So for example, (Edit: assuming the discontinuity is between the end and start of the wave array)

Code: Select all

float getSample (float currentIndex)
{
  float x, f;

  if (currentIndex > lastIndex)
  {	x = currentIndex - lastIndex;
  	if (x<0.5) f = 2*x*x;
	else  f = 4*x -2*x*x -1;
	return (1-f) * tableWaveform[lastIndex] + f * tableWaveform[0];
  }
  else 
  	// You better also do linear or cubic interpolation between two samples here to accommodate for fractional currentIndex.
	return tableWaveform[currentIndex];	
	
}
Thanks again for taking the time to assist it is MUCH appreciated, however I think perhaps my variable name is a cause of a misunderstanding.

currentIndex is a pointer (angle) to the current sample in a wavetable. It is a float between 0 and 4095.999. It in incremented by a delta after retrieving every sample.

Your code above, if the condition is true, therefore returns a float between -4096 and 4096, instead I think of your intention between -1 and 1.
"Scuba divers work best under pressure!"

https://www.youtube.com/user/DKDiveDude (Original music and hardware videos)

Post

DKDiveDude wrote: Thu Jul 09, 2020 3:45 pm Thanks again for taking the time to assist it is MUCH appreciated,
Never mention it. I like this subject :)
DKDiveDude wrote: Thu Jul 09, 2020 3:45 pm currentIndex is a pointer (angle) to the current sample in a wavetable. It is a float between 0 and 4095.999. It in incremented by a delta after retrieving every sample.
Yes, Thats exactly my understanding of your currentIndex. I'm glad you confirmed. So on this. lastIndex should always be equal to 4095.0
DKDiveDude wrote: Thu Jul 09, 2020 3:45 pm Your code above, if the condition is true, therefore returns a float between -4096 and 4096, instead I think of your intention between -1 and 1.
When the outer condition is true. x should result in a value between 0.0 to 0.999. Then f will also result in the same range. Then the return of the whole function will be a value between tableWaveform[lastIndex] to tableWaveform[0]. i.e -1 to 1 if thats the contents of the wave.
www.solostuff.net
Advice is heavy. So don’t send it like a mountain.

Post

S0lo wrote: Thu Jul 09, 2020 4:11 pm
DKDiveDude wrote: Thu Jul 09, 2020 3:45 pm Thanks again for taking the time to assist it is MUCH appreciated,
Never mention it. I like this subject :)
DKDiveDude wrote: Thu Jul 09, 2020 3:45 pm currentIndex is a pointer (angle) to the current sample in a wavetable. It is a float between 0 and 4095.999. It in incremented by a delta after retrieving every sample.
Yes, Thats exactly my understanding of your currentIndex. I'm glad you confirmed. So on this. lastIndex should always be equal to 4095.0
DKDiveDude wrote: Thu Jul 09, 2020 3:45 pm Your code above, if the condition is true, therefore returns a float between -4096 and 4096, instead I think of your intention between -1 and 1.
When the outer condition is true. x should result in a value between 0.0 to 0.999. Then f will also result in the same range. Then the return of the whole function will be a value between tableWaveform[lastIndex] to tableWaveform[0]. i.e -1 to 1 if thats the contents of the wave.
And your "lastIndex" is like a pointer to my wavetable, delayed by one sample right or in other words previous sample index?
"Scuba divers work best under pressure!"

https://www.youtube.com/user/DKDiveDude (Original music and hardware videos)

Post

DKDiveDude wrote: Thu Jul 09, 2020 5:37 pmAnd your "lastIndex" is like a pointer to my wavetable, delayed by one sample right or in other words previous sample index?
lastIndex points to the last sample in the tableWaveform[] array.

Talking in a float manner. lastIndex is a float that points to the beginning of the last sample in the tableWaveform[] array. Since as you know, each sample is an interval.

BTW, the code above does the smoothing during that last sample. (not two samples, for simplicity).
www.solostuff.net
Advice is heavy. So don’t send it like a mountain.

Post

S0lo wrote: Thu Jul 09, 2020 6:37 pm
DKDiveDude wrote: Thu Jul 09, 2020 5:37 pmAnd your "lastIndex" is like a pointer to my wavetable, delayed by one sample right or in other words previous sample index?
lastIndex points to the last sample in the tableWaveform[] array.

Talking in a float manner. lastIndex is a float that points to the beginning of the last sample in the tableWaveform[] array. Since as you know, each sample is an interval.

BTW, the code above does the smoothing during that last sample. (not two samples, for simplicity).
Ok thanks for that clarification. I must still be doing something wrong, because this seems to have no anti-alias effect, whereas "my" previously posted code, appears to virtually eliminate aliasing for all notes for a saw at least. Let me show you my actual code, as I had previously simplified some things (variables), however which could lead to errors "translating" back and forth.

1) First my function with my originally posted PolyBlep code. Here I get a sample from my wavetable, containing waveform with values between -1 and 1, and linear interpolate, then PolyBlep.

Variable description;
"tg" is the current tone generator (number) of which I have several.
phaseLastVoice is the last index in the wavetable. I keep that in a variable because I allow the size, actually start and end of the wavetable to be changed at any time, either during user edit of waveform or during playback as a form of modulation.
phaseStartVoice is the first index, usually 0, in the wavetable. Reason in a variable, same as above.
tgActiveWaveform current tone generator's wavetable in use, as each tone generator can have it's own unique wavetable waveform.

Code: Select all

forcedinline float getSampleAndInterpolateLinear (int& tg, float& currentIndex) noexcept
	{
		float alpha, sample;
		int i, index;

		// Linear Interpolation
		i = (int) currentIndex;
		alpha = currentIndex - (float) i;
		index = i + 1;
		if (index > phaseLastVoice[tg]) index = phaseStartVoice[tg];
		sample = tgActiveWaveform[tg][i] + (tgActiveWaveform[tg][index] - 
		              tgActiveWaveform[tg][i]) * alpha;

		// Poly Blep - To my ear does a tremendous job in eliminating aliasing, even with 
		    high notes. Problem is it only works with a saw :(
		float dt, t, correction;
		t = (currentIndex - phaseStartVoice[tg]) / phaseSizeVoice[tg];
		dt = phasePositiveDelta[tg][0] / phaseSizeVoice[tg];
				
		if (t < dt)
		{
			t /= dt;
			correction = t + t - t * t - 1;
		}
		else if (t > 1 - dt)
		{
			t = (t - 1) / dt;
			correction = t * t + t + t + 1;
		}
		else
			correction = 0;

		sample += correction;
				
		return sample;		
	}
2) Same function as above, but with your PolyBlep instead of mine.

Code: Select all

forcedinline float getSampleAndInterpolateLinear (int& tg, float& currentIndex) noexcept
	{
		float alpha, sample;
		int i, index;

		// Linear Interpolation
		i = (int) currentIndex;
		alpha = currentIndex - (float) i;
		index = i + 1;
		if (index > phaseLastVoice[tg]) index = phaseStartVoice[tg];

		// Poly Blep
		float x, f;

		if (currentIndex > phaseLastVoice[tg])
		{
			x = currentIndex - phaseLastVoice[tg];
					
			if (x < 0.5f) 
				f = 2 * x * x;
			else  
				f = 4 * x - 2 * x * x - 1;

			sample = (1 - f) * tgActiveWaveform[tg][phaseLastVoice[tg]] + 
			               f *  tgActiveWaveform[tg][phaseStartVoice[tg]];
		}
		else
			sample = tgActiveWaveform[tg][i] + (tgActiveWaveform[tg][index] - 
                                      tgActiveWaveform[tg][i]) * alpha;*
				
		return sample;		
	}
"Scuba divers work best under pressure!"

https://www.youtube.com/user/DKDiveDude (Original music and hardware videos)

Post

On a quick look at it, I'd say your doing it correctly since you've also added linear interpolation to the ELSE part. Although linear interpolation will still produce some aliasing, it's better than none. cubic is better.

Let me ask, Where is the waveform coming from? The waveform it self has to be band limited. If the user is simply drawing the waveform on screen then it's most certainly not band limited.

Try a waveform taken from a wave file or song. And play it at it's root frequency that it was recorded on. Any other frequencies will produce higher and higher aliasing as you go far from the root.

The reason PolyBLEP works well for saw/square is because you are generating those waveforms on the fly through formulas that produce only 1 discontinuity at that edge which PolyBLEP fixes. It's not because saw/square is special. To prove that, record a saw to a wavetable then play it again with PolyBLEP at a far frequency than what it was recorded on. You will hear aliasing with saw and square
www.solostuff.net
Advice is heavy. So don’t send it like a mountain.

Post Reply

Return to “DSP and Plugin Development”