How to plot minblep/hard sync?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

mystran wrote: Thu Nov 09, 2023 12:34 pm Another way to think of aliasing is as a sort of "false periodicity" and if the true period was say 654.3 samples and sometimes we sync at 654 samples and sometimes 655 samples, then you get various unwanted harmonics depending on how the 654 and 655 periods interleave.
Really can't get visually this, talking about minblep :)

It should compensate the gap wherever I insert discontinuity at 654, 655, 654.3, 654.9, and so on.
Different starting point, same target (i.e. 0).

The starting point is "the phase accumulate till the trigger" (which is an on/off), the ending point "the phase at 0".

Sort of a reset input (which reset phase to 0) triggered by square wave at some freq (which arrive at full sample in case of digital), isn't?

Post

Q8FuelDSP wrote: Thu Nov 09, 2023 3:11 pm Sort of a reset input (which reset phase to 0) triggered by square wave at some freq (which arrive at full sample in case of digital), isn't?
Which is basically what I'm doing as a sort of internal Hard Sync: when the phase of master at some freq > 1.0, I mod the phase of master osc and evaluate delta as lastPrev (which is -5) and current value (5), giving a delta of 0.5.

So I would deduce that's I'm doing it wrong so?

Post

To be practical, here's how I've change from sync using external signal:

Code: Select all

rack::simd::float_4 syncValue = [b]inputs[SYNC_EXT_INPUT].getPolyVoltageSimd<rack::simd::float_4>((uint8_t)c);[/b]
rack::simd::float_4 deltaSync = syncValue - mPrevSyncValues[c / 4];
rack::simd::float_4 syncCrossing = -mPrevSyncValues[c / 4] / deltaSync;
to internal/master osc:

Code: Select all

[b]rack::simd::float_4 syncValue = syncMask;[/b]
rack::simd::float_4 deltaSync = syncValue - mPrevSyncValues[c / 4];
rack::simd::float_4 syncCrossing = -mPrevSyncValues[c / 4] / deltaSync;
having syncMask basically set to 0.5 if phaseMaster > 1.0 or phaseMaster < -1.0f, otherwise -0.5. (so a sort of 1-sample trigger signal).

syncCrossing in this case is always 0.5 when pahse restart and cross the axis.

is that a correct approch? or am I missing somethings?

Post

I'm sure you could do it like this (with a very similar approach to the external sync) but in general you have much better information about the internal sync's timing than the external sync's timing. Having recently implemented internal hard sync for a VCV Rack oscillator myself, I just ended up doing something more or less like this:

Code: Select all

float_4 phaseAfterInc = phase + phaseInc;
float_4 doInternalSync = phaseAfterInc >= 1.f; // mask
float_4 phaseWrapped = phaseAfterInc - simd::floor(phaseAfterInc);
float_4 samplesAgoIfSync = phaseWrapped / phaseInc;
Since VCV Rack's MinBlepGenerator takes a negative value for `p` representing how many samples in the past the discontinuity happened, I just use `-samplesAgoIfSync` for that (for any voice that actually synced since it looks like you're doing a polyphonic module).

One thing to look into that's different from how VCV Rack's Fundamental VCO does it: make sure your discontinuity magnitude is the difference between the value after sync (often 0 but not always depending on your oscillator) and the value right at sync.

Post

rigatoni_modular wrote: Wed Nov 22, 2023 12:31 am I'm sure you could do it like this (with a very similar approach to the external sync) but in general you have much better information about the internal sync's timing than the external sync's timing. Having recently implemented internal hard sync for a VCV Rack oscillator myself, I just ended up doing something more or less like this:

Code: Select all

float_4 phaseAfterInc = phase + phaseInc;
float_4 doInternalSync = phaseAfterInc >= 1.f; // mask
float_4 phaseWrapped = phaseAfterInc - simd::floor(phaseAfterInc);
float_4 samplesAgoIfSync = phaseWrapped / phaseInc;
Thanks for the reply.

So basically, instead of linear interpolate between prev/current master osc value, you measure the exact delta from phase information (which you have, since its internal). Correct?

Post

Q8FuelDSP wrote: Wed Nov 22, 2023 8:21 am So basically, instead of linear interpolate between prev/current master osc value, you measure the exact delta from phase information (which you have, since its internal). Correct?
Yeah, exactly! It'll be slightly off if your oscillator is under FM but likely not as off as, for example, syncing to an external sawtooth (where interpolating between the min and max value to estimate when the sync happened can give you as much as half a sample of error)

Post

rigatoni_modular wrote: Wed Nov 22, 2023 7:29 pm
Q8FuelDSP wrote: Wed Nov 22, 2023 8:21 am So basically, instead of linear interpolate between prev/current master osc value, you measure the exact delta from phase information (which you have, since its internal). Correct?
(where interpolating between the min and max value to estimate when the sync happened can give you as much as half a sample of error)
Which is basically what my code does right now using -1/1 as ramp on master osc: it will always be 0.5, at every sync :P

I think I need to review it as suggested by you, using phase instead of interpolate signal's amp...

mystran wrote: Thu Nov 09, 2023 12:34 pm Another way to think of aliasing is as a sort of "false periodicity" and if the true period was say 654.3 samples and sometimes we sync at 654 samples and sometimes 655 samples, then you get various unwanted harmonics depending on how the 654 and 655 periods interleave. Solving for a more accurate approximation of the true period and then handling the hardsync so that the slave phase is allowed to accumulate for the partial sampling interval after the estimated sync point can help mitigate this somewhat.
Anyway, I've elaborated it a more bit. And I think what you are trying to said is not a dichotomy about aliasing (smooth edge vs false periodicity). But another characteristic that will introduce aliasing: edge AND/OR false periodicity.

So, while smoothing edge will be managed by minblep algo, the false periodicity is somethings that must be managed alongside it. Is it correct?

Post

Q8FuelDSP wrote: Thu Nov 23, 2023 9:28 am
mystran wrote: Thu Nov 09, 2023 12:34 pm Another way to think of aliasing is as a sort of "false periodicity" and if the true period was say 654.3 samples and sometimes we sync at 654 samples and sometimes 655 samples, then you get various unwanted harmonics depending on how the 654 and 655 periods interleave. Solving for a more accurate approximation of the true period and then handling the hardsync so that the slave phase is allowed to accumulate for the partial sampling interval after the estimated sync point can help mitigate this somewhat.
Anyway, I've elaborated it a more bit. And I think what you are trying to said is not a dichotomy about aliasing (smooth edge vs false periodicity). But another characteristic that will introduce aliasing: edge AND/OR false periodicity.

So, while smoothing edge will be managed by minblep algo, the false periodicity is somethings that must be managed alongside it. Is it correct?
@mystran: right? :)

Post

Q8FuelDSP wrote: Fri Dec 01, 2023 10:17 am So, while smoothing edge will be managed by minblep algo, the false periodicity is somethings that must be managed alongside it. Is it correct?
It's essentially about the same thing in both cases. The digital waveform represents the bandlimited signal (sum of sinusoids) that goes through the sampling points. With BLEPs you are trying to construct a signal where only harmonic partials are required, by sampling a continous-time signal that is band-limited to avoid aliasing. Because we need a band-limited signal, we need to lowpass filter, but that band-limited signal certainly also has to be periodic if you want simple partials.

Post

Pretty clear, thanks as usual.

Another thing I've noticed using hard sync (with external signal; but I'd say its the same with internal) is that's (obviously) it creates huge peak around the discontinuity point:

Image

Surely, this will introduce problems regard clipping, compression threshold, and so on.
I also believe these peaks are necessary given a bandlimited signal.

So, usually, what's the actions one can do to prevent this kind of unwanted transient? How do you treat is? Just "loweing" the volume? :)

Post

rigatoni_modular wrote: Wed Nov 22, 2023 12:31 am I'm sure you could do it like this (with a very similar approach to the external sync) but in general you have much better information about the internal sync's timing than the external sync's timing. Having recently implemented internal hard sync for a VCV Rack oscillator myself, I just ended up doing something more or less like this:

Code: Select all

float_4 phaseAfterInc = phase + phaseInc;
float_4 doInternalSync = phaseAfterInc >= 1.f; // mask
float_4 phaseWrapped = phaseAfterInc - simd::floor(phaseAfterInc);
float_4 samplesAgoIfSync = phaseWrapped / phaseInc;
Since VCV Rack's MinBlepGenerator takes a negative value for `p` representing how many samples in the past the discontinuity happened, I just use `-samplesAgoIfSync` for that (for any voice that actually synced since it looks like you're doing a polyphonic module).

One thing to look into that's different from how VCV Rack's Fundamental VCO does it: make sure your discontinuity magnitude is the difference between the value after sync (often 0 but not always depending on your oscillator) and the value right at sync.
I discover a strange artefact happens using this approch, when using (for example) square wave and add discontinuity on the same sample in the place:

Image

once I sync mult, it create "peaks" when resetting:

Image

while it should just process a "straight line" I believe. Somethings to do with minblep algo or should I prevent in some way this? Or is parto of the process of hardsync? How do you manage this?

Post

Q8FuelDSP wrote: Sat Dec 16, 2023 10:42 am I discover a strange artefact happens using this approch, when using (for example) square wave and add discontinuity on the same sample in the place:

Image

once I sync mult, it create "peaks" when resetting:

Image

while it should just process a "straight line" I believe. Somethings to do with minblep algo or should I prevent in some way this? Or is parto of the process of hardsync? How do you manage this?
This happens if you just insert a BLEP without computing the correct magnitude. What you need to do is compute the value "just before" and "just after" the sync in the limit sense (ie. at the point where the sync happens, before it has happened.. and then at the beginning of the waveform), then subtract one from the other to get the scale factor for the BLEP. In case of a square (or pulse) that's still in it's first segment, both values come up the same, so difference (and hence BLEP scaling) is zero.

One important detail th at I want to emphasize with regards to the "limit sense" is that you actually need a state machine (two states, high and low in the case of a square) for piece-wise waveforms to do this correctly, because if the slave square is at exactly half-way phase, then whether or not you need to insert a BLEP for sync depends on whether or not you already added a BLEP to transition to the second state and you need to track this. If the sync happens at exactly half-way and you have already transitioned, your BLEP becomes the inverse of the regular transition and cancels exactly (well, up to numerical precision, but that's not a huge concern)... and if you have not yet transitioned, then your BLEP scale comes as zero. So to really do this correctly, it's not enough to simply evaluate the waveform value (or derivatives in the higher order cases), but also track which side of the limits you should be looking at.

More generally, when dealing with waveforms where the derivatives also jump and we should insert higher order BLEPs for anti-aliasing those, we'd also need to do the same: solve each (non-constant) derivative at both sides and scale by difference. For example, in the case of a triangle, we'd almost always have some amount of step, but whether or not the slope changes is similar to the square case.... but make sure you have your squares and saw-waves perfected before you start messing with anything more complicated, because getting the derivative stuff correct is way more difficult.

Post

mystran wrote: Sat Dec 16, 2023 4:47 pm This happens if you just insert a BLEP without computing the correct magnitude.
Note that this happens without external/signal sync, but with the internal one, processing basically only the phase, not the magnitude/interpolation of external signal anymore.

Somethings like suggested by @rigatoni_modular:

Code: Select all

phaseMaster += phaseIncMaster;
rack::simd::float_4 syncInt = phaseMaster >= 1.0f;
phaseMaster -= rack::simd::floor(phaseMaster);
rack::simd::float_4 syncCrossingInt = -phaseMaster / phaseIncMaster;

...

// insert minblep for sync serially
mPhases[c / 4] = rack::simd::ifelse(syncInt, (1.0f - syncCrossing) * phaseInc, mPhases[c / 4]);
for (int cc = 0; cc < ccs; cc++) {
	if (syncMask & (1 << cc)) {
		float index = mPhases[c / 4][cc] * (float)mMultiWaveTable.mWaveLen * (float)mMultiWaveTable.mQuality;
		float out1 = getWave(index, pos[cc], octave[cc]);
		float p = syncCrossing[cc] - 1.0f;
		rack::simd::float_4 mask = rack::simd::movemaskInverse<rack::simd::float_4>(1 << cc);
		rack::simd::float_4 x = mask & (out1 - out[cc]);
		mSyncMinBleps[c / 4].insertDiscontinuity(p, x);
	}
}
Or this is valid in both case?

Post

Q8FuelDSP wrote: Sat Dec 16, 2023 6:46 pm
mystran wrote: Sat Dec 16, 2023 4:47 pm This happens if you just insert a BLEP without computing the correct magnitude.
Note that this happens without external/signal sync, but with the internal one, processing basically only the phase, not the magnitude/interpolation of external signal anymore.
The signal you use for the sync should NOT matter. Whatever kind of sync signal you have, all you're taking from that signal is the time offset where the sync happens. At that time offset the synced "slave" oscillator has some value and after reset it has some other value and it is this difference that gives the scaling for the BLEP, which is the magnitude of the step that results from the reset.

Post

mystran wrote: Sat Dec 16, 2023 7:38 pm
Q8FuelDSP wrote: Sat Dec 16, 2023 6:46 pm
mystran wrote: Sat Dec 16, 2023 4:47 pm This happens if you just insert a BLEP without computing the correct magnitude.
Note that this happens without external/signal sync, but with the internal one, processing basically only the phase, not the magnitude/interpolation of external signal anymore.
The signal you use for the sync should NOT matter. Whatever kind of sync signal you have, all you're taking from that signal is the time offset where the sync happens. At that time offset the synced "slave" oscillator has some value and after reset it has some other value and it is this difference that gives the scaling for the BLEP, which is the magnitude of the step that results from the reset.
Well, but that's what happens :O I got the phase when the sync happens, and I resolve the discontinuity given that exact phase moment:

Code: Select all

mPhases[c / 4] = rack::simd::ifelse(syncInt, (1.0f - syncCrossing) * phaseInc, mPhases[c / 4]);
So in the case of square prev magn before sync was 1, while the magn after the sync is still 1 (phase 0 start at 1).

Can't get where am I missing... or maybe do you mean to check the insertDiscontinuity() function?

Post Reply

Return to “DSP and Plugin Development”