Help me understand minBlep implementation

I’m struggling to successfully implementing minBlep anti-aliasing into my oscillator. In particular, I’m not quite understanding what I should use as an argument for the time parameter of the addDiscontinuity function.

My oscillator is built based on a float_4 class that has an internal float_4 Phase parameter that increments from 0 to 1, adding the a scaled sampleTime based on the frequency once per process. When the phase exceeds 1, I fold it back by subtracting -1, thus retaining the fraction.

		Phase += sampleTime * Frequency;
		for (int p = 0; p < Phase.size; p++){
			if(Phase[p]>=1){
				Phase[p] -= 1;
			}
		}

(Sidenote, I am aware this is an ugly and very non-simd implementation. I’ve not quite figured out how to use the masks and ifelse methods in the simd library.)

In addition, I have built functions that take the Phase and generate the corresponding point on a waveform, where each waveform is centered around 0, and scaled between -1 and 1.

Example:

	float SawWave(float Phase){
		return (0.5-Phase)*2;
	}

In this case, the discontinuity occurs when whenever the Phase folds back, and it goes into the if statement. Thus, my line of thinking was that as I know exactly when the fold occurs, I should then be able to just be able to carry a bool to the waveform generator that sets to true on phase foldback, so that the addDiscontinuity function executes exactly once, exactly when the phase foldback occurs, and use 0, -2 as arguments.

	float SawWave(float Phase, bool Blep){
		float t = (0.5-Phase)*2;
		if(Blep){
			minBlepper.addDiscontinutity(0,-2);
			return t+minBlepper.process();
		else{
			return (0.5-Phase)*2;
		}
	}

This didn’t do…well, anything. So clearly, I’m missing something in how I’m approaching and thinking about this. Does anyone have any insight?

On a conceptual level the min blep will be to affect more than one sample. My understanding is you want to turn a hard step function into this https://images.app.goo.gl/61vasj4JctT9xFPT9

The blep bool you describe above is only true for one sample per cycle, which isn’t enough to have your desired effect.

Correct, minBlepper.process() must be added to every sample.

Ahh, okay, so the process function must be run every sample, but the addDiscontinuity function should only run when a discontinuity occurs?

	float SawWave(float Phase, bool Blep){
		float t = (0.5-Phase)*2;
		if(Blep){
			minBlepper.addDiscontinutity(0,-2);
		}	
		return (0.5-Phase)*2+minBlepper.process();
	}

Something like this?

I’d still like to know a bit more about how I’m supposed to use the first input parameter of the addDiscontinuity() function, though. Am I supposed to predict where discontinuities occur?

Looks good.

It is some time ago I did DSP and used the minBlep, but yes, you have to estimate the point of the discontinuity, for example by linear interpolation.

The documentation says p ranges from (-1,0] relative to the current time. I’m guessing units are in samples. So maybe this allows you to place the blep with sub-sample precision. So if you wanted the blep to occur half way between the last sample and this one, you would put in -0.5.

You can look at the code for it here. I think that is what its doing.

This stuff can be hard. Most people copy the VCV Fundamental code, then tinker from there. Or at least copy the way it does minBlep. When I’ve had to “roll my own” I’ve found it frustratingly difficult to get right. In any case make sure to look on a spectrum analyzer that you got it right. If you get it wrong it will not reduce the alisaing at all.

What do you mean by a “sample” here? In my mind, a sample is a single execution of the process() method, and as such you can’t have anything occuring in-between samples.

Your definition of sample is correct.

So the thing to remember is this is all discrete emulation of an under continuous effect. The MinBlep is a continuous wave (or function) and it has a defined value for all real points of time. If you offset the function by 0.5 a sample, then when you ask for the 50th sample you get the value at time = 50.5, rather than time = 50

In your case lets say last call of process phase was 0.995 and this call the phase rolled over to 0.002 The sample side moved the phase forward 0.007 units and the MinBlep started 0.002 units ago. You need to convert from units to seconds by dividing by 0.007, so you know that the MinBlep started 0.002/0.007 samples ago.

Oh okay, I think I understand. Thank you for the explanation. I’ll attempt an implementation tomorrow. :slight_smile:

1 Like

I’m bumping this up again to request some help:

@Squinky I’ve got good minBlep implementations working for saw and square waves. It produces satisfactory, unaliased sounds. However, I’m at a conceptual loss for how to use it with triangle waves. Do you have any insight? As triangle waves are continuous, I’m sort of at a loss about how to approach the problem.

My intuition is to think of the triangle wave as the sum of two waves. One wave is the output of minblep and another wave is the one you create.

I could help more if you answer this question: does the minblep return to 0 after some time or does you have to add 2nd discontinuity when the square wave falls low again?

I’m not sure I understand what a square wave has to do with this?

I am generating a triangle wave by simply copying the phase and subtracting the delta instead of adding it after it reaches 0.5. So obviously, this creates a continuous wave with no obvious discontinuities for me to use the minBlep on. So how do I anti-alias a continuous wave?

To answer your question: in my implementation of DSP::minblep, I am alternating adding one discontinuity when the wave goes high, and one (negative) discontinuity when the wave goes low.

Yes, I think @Patheros is on the right track. A few things:

  1. Many ppl don’t worry about the triangle, as the aliasing isn’t so bad. Fundamantal doesn’t.
  2. Some ppl do worry. EvenVCO and my BasicVCO do. Those generate a minBlep square, then integrate it to get a minBLEP triangle. I think in BasicVCO there is a choice of both? Anyway, look at the minBLEP in there. It’s super easy to understand (I think).

Yeah, just took a look. There are two triangles in BasicVCO.

So my understanding is the following:

One big caveat is idk if the MinBlep when skewed like that has the desired properties? Idk I don’t know anything about audio anti-aliasing, I just like drawing pretty(ish) graphs :stuck_out_tongue:

But assuming the above is true, I think you want this…

So the trick is to offset the triangle wave’s phase by half a cycle and then flip the min bleep’s sign every time the B resets. Idk if that is possible with the DSP code directly but you can just store a boolean and do addition or subtraction depending on if the boolean is true or false.

Or you could use my way that for sure works and you can get the working code from GitHub and not reinvent this particular wheel.

Btw, I don’t see any triangle waves on that nice drawing?

Doh. I always get sawtooth and triangle confused. They both are triangles!!!

Yeah I’m not sure what an anti-aliased triangle wave would look like…

I guess I can answer my own question

And zooming into the tip

But I don’t know enough about audio signals to know which is correct or best or desirable?

Did you pick the clean triangle? Only one is anti aliased. Look at this stuff on a spectrum analyzer, that will tell you. In my demo repo there is an article on using analyzer to spot aliasing.