Hard-Sync with MinBlep / How to manage edge cases

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

I try to make a hard sync saw oscillator with a phase start parameter [0..1]. This means the phase jumps to phase start when it syncs with the hard sync. It works pretty well in the general case, but there are edge cases that introduce unwanted frequencies (aliasing?).

The problem appears when the phase jump is one sample before or at the same sample like the hard sync.

I tried to ignore the phase jump when the sub oscillator hard sync happens within the next one or two samples. But this produces new edge cases where the phase jump sometimes happens and sometimes not. This introduces also some unwanted frequencies.

Code: Select all

        phaseIncrement = frequency * sampleRateInv;
        phase += phaseIncrement;
        
        if (hardSync)
        {
            float newPhase = phaseStart + phaseHardSync * phaseIncrement / phaseHardSyncInc;
            mixInBlep(phaseHardSync / phaseHardSyncInc, phase - newPhase);
            phase = newPhase;
        }
        
        if (phase >= 1.0f)
        {
            phase -= 1.0f;
            
            mixInBlep(phase/phaseIncrement, 1.0f);
        }
        
        return getNextBlep() + phase - 0.5f;
Is there a solution for this issue. Any ideas are welcome!!

Post

If both transitions occur within one sample you have to time-sort them and process one after another. Mind that the sync transition occurring before the coming phase wrap will remove the phase wrap transition.

PS. IMHO minBLEPs are a questionable alternative to simply using linBLEPs

Post

Z1202 wrote:If both transitions occur within one sample you have to time-sort them and process one after another. Mind that the sync transition occurring before the coming phase wrap will remove the phase wrap transition.
Thanks for the response. Thats exactly what i do not understand. Why do i have to sort them. I add both bleps to the same buffer with different fractional values at the same sample position. Maybe i have to skip the the normal phase wrap when the fractional part is bigger than the one of the hard sync? Do i need to calculate the real waveform value for both bleps and take the average?

Post

+1 for MinBLEPs being a bad idea, the minimum-phase will cause more problems than it solves.

The edge cases are usually something like "natural" slave reset (or transition for things like pulses with multiple stages) being missed during the same sample as a master reset being missed if you process sync first, or a "false" slave resets/transitions if you process slave past the master reset point first.

General algorithm that will get the ordering right without explicit sorting:

1. advance master in time until you reach a reset point or end-of-sample
2. advance slave so it matches the time-point you reached at step 1
3. if you had a master-reset, perform the sync and go back to step 1
4. when master reaches end of sample, advance slave for the remaining part of the sample

So essentially you want to treat the "sample" as a time inverval rather a discrete step, then sub-divide the time-interval at master reset-points so you can process the sub-intervals for the slave separately before and after reset.

Post

mystran wrote:General algorithm that will get the ordering right without explicit sorting:
I didn't mean that the sorting has to be explicit. What I meant is that you need to do different decisions for different relative orders of the wraparound and sync transitions. Sorry for causing the confusion.

Although, if you have more than 2 transition sources (like two masters + own wraparound), you'd need to sort them more or less explicitly :wink:

Post

mystran wrote:+1 for MinBLEPs being a bad idea, the minimum-phase will cause more problems than it solves.
In the past I've used a tabulated BLEP derived from an IIR filter response - that way it's always causal (so you don't have to anticipate a step N samples ahead) and by adjusting the filter parameters you can experiment with different roll-offs, cutoff frequency, etc. for various effects and/or trading off BLEP length vs antialiasing.

Post

kryptonaut wrote: In the past I've used a tabulated BLEP derived from an IIR filter response - that way it's always causal (so you don't have to anticipate a step N samples ahead) and by adjusting the filter parameters you can experiment with different roll-offs, cutoff frequency, etc. for various effects and/or trading off BLEP length vs antialiasing.
The problem is that a minimum-phase BLEP is not zero-phase and trying to pretend so leads to all kinds of funky DC offset issues and other crap that BLEPs are specifically supposed to avoid. Since you can have arbitrary overlaps of any number of BLEPs anyway, your best strategy is to mix them into a buffer right-away when you find the relevant transition point, at which point moving the "naive" output around in the buffer to compensate for linear-phase BLEP latency is trivial.

edit: in short, if you think MinBLEPs make something easier in terms of implementation, then your implementation is almost certain broken in the first place!

Post

Z1202 wrote: Although, if you have more than 2 transition sources (like two masters + own wraparound), you'd need to sort them more or less explicitly :wink:
As far as I can see, you can use the "implicit" sorting from splitting time intervals as long as your sync-graph is DAG (just pick an arbitrary total ordering that doesn't violate the DAG's partial ordering and recurse in this order).

Ps. you can even use the same approach for cyclic graphs, but in that case you need the ability to "cancel" an earlier scheduler resets .. but one can argue that this them amounts to a sorting algorithm, so like.. whatever
Last edited by mystran on Fri Aug 18, 2017 3:26 pm, edited 1 time in total.

Post

mystran wrote:
Z1202 wrote: Although, if you have more than 2 transition sources (like two masters + own wraparound), you'd need to sort them more or less explicitly :wink:
As far as I can see, you can use the "implicit" sorting from splitting time intervals as long as your sync-graph is DAG (just pick an arbitrary total ordering that doesn't violate the DAG's partial ordering and recurse in this order).
Hmmm, you mean like Osc1 syncing Osc2 syncing Osc3? In that case Osc2 can have two transitions per sample and you need to de-facto sort the in order to have correct splitting of time intervals for Osc3. And then in the Osc3 you'll need to compare your wraparound time in turn to the boundaries of the intervals, which constitutes one run of the (quadratic complexity) sorting process. I wouldn't call this sorting "very" implicit :wink: :D

Post

Z1202 wrote:Hmmm, you mean like Osc1 syncing Osc2 syncing Osc3? In that case Osc2 can have two transitions per sample and you need to de-facto sort the in order to have correct splitting of time intervals for Osc3.
All you need to do is order the DAG: you process Osc1 until reset, then process Osc2 until this point or until an earlier reset point, then process Osc3 for the split intervals from Osc2, then go back to Osc2 and so on. If you you want Osc1 to sync Osc3 (with Osc2 also syncing) it still works, you just skip the sync of Osc2, but otherwise it's the same.

You get worst-case exponential number of time-intervals, but you process them in linear order. The asymptotic complexity is not very important though, because for realistic frequencies the number of time-intervals you process per sample on average is still roughly 1 (eg. most samples don't need to be split at all, unless your frequencies are very high).

Post

mystran wrote:+1 for MinBLEPs being a bad idea, the minimum-phase will cause more problems than it solves.
Thanks for the help :) Ok, maybe i have to choose the symmetric one. I can imagine that the behaviour is more natural and this way i maybe can avoid ugly side effects.
mystran wrote: General algorithm that will get the ordering right without explicit sorting:

1. advance master in time until you reach a reset point or end-of-sample
2. advance slave so it matches the time-point you reached at step 1
3. if you had a master-reset, perform the sync and go back to step 1
4. when master reaches end of sample, advance slave for the remaining part of the sample
Not sure if i understand that the right way. My english maybe isn't the best... just pre calculate the master sync position and increment the slave until i reach that position?
mystran wrote: So essentially you want to treat the "sample" as a time inverval rather a discrete step, then sub-divide the time-interval at master reset-points so you can process the sub-intervals for the slave separately before and after reset.
Ok, this makes sense. I really liked the step approach, but i see that steps maybe do not lead to a solution. I try that. I will report if i have something useful.

Post

mystran wrote:
Z1202 wrote:Hmmm, you mean like Osc1 syncing Osc2 syncing Osc3? In that case Osc2 can have two transitions per sample and you need to de-facto sort the in order to have correct splitting of time intervals for Osc3.
All you need to do is order the DAG: you process Osc1 until reset, then process Osc2 until this point or until an earlier reset point, then process Osc3 for the split intervals from Osc2, then go back to Osc2 and so on. If you you want Osc1 to sync Osc3 (with Osc2 also syncing) it still works, you just skip the sync of Osc2, but otherwise it's the same.

You get worst-case exponential number of time-intervals, but you process them in linear order. The asymptotic complexity is not very important though, because for realistic frequencies the number of time-intervals you process per sample on average is still roughly 1 (eg. most samples don't need to be split at all, unless your frequencies are very high).
It looks to me like essentially this is exactly the same as sorting, just differently expressed. Even the order of processing resembles the "triangular loop" of sorting by direct insertion: Osc1, Osc2, Osc3, Osc2, Osc3. :wink: The same applies to the complexity, as with the absence of transient collisions, the number of intervals will stay equal to 1.

Post

mystran wrote:
kryptonaut wrote: In the past I've used a tabulated BLEP derived from an IIR filter response - that way it's always causal (so you don't have to anticipate a step N samples ahead) and by adjusting the filter parameters you can experiment with different roll-offs, cutoff frequency, etc. for various effects and/or trading off BLEP length vs antialiasing.
The problem is that a minimum-phase BLEP is not zero-phase and trying to pretend so leads to all kinds of funky DC offset issues and other crap that BLEPs are specifically supposed to avoid. Since you can have arbitrary overlaps of any number of BLEPs anyway, your best strategy is to mix them into a buffer right-away when you find the relevant transition point, at which point moving the "naive" output around in the buffer to compensate for linear-phase BLEP latency is trivial.

edit: in short, if you think MinBLEPs make something easier in terms of implementation, then your implementation is almost certain broken in the first place!
Yes, mixing into a buffer is IMHO the right way to do it.

But if I understand you correctly, you are suggesting a linear-phase BLEP which has an oscillatory 'lead-in', a step, and an oscillatory 'lead-out', with a delayed subtracted naive step timed according to the lead-in?

I can see this working ok for a rectangular pulse without introducing a DC offset, but wouldn't it mean that for a sawtooth you'd end up with the ramp effectively starting and ending a number of samples (the lead-in) later than expected, so introducing a frequency-dependent DC offset as a result? Or have I misunderstood?

Post

mystran wrote: The edge cases are usually something like "natural" slave reset (or transition for things like pulses with multiple stages) being missed during the same sample as a master reset being missed if you process sync first, or a "false" slave resets/transitions if you process slave past the master reset point first.
I have made some more tests and this seems to be the case. I miss some steps all x intervals also with sorting. Think because i trigger the slave oscillator when the master did reach the sample end (when the discrete step happened)... maybe that's too late because of the fractional part above 1.
mystran wrote: So essentially you want to treat the "sample" as a time inverval rather a discrete step, then sub-divide the time-interval at master reset-points so you can process the sub-intervals for the slave separately before and after reset.
Not sure how to do this. How can i sub-divide the time interval at reset points?

Post

xhy3 wrote: Not sure how to do this. How can i sub-divide the time interval at reset points?
The way I'd normally do it for the 2-oscillator case is to just bump the phase of both oscillators, then process the transitions (essentially solving the time-stamps "backwards") for the master until I find a reset. At that point I start processing slave events, but with the additional condition that if the next event has a time-stamp later than the time-stamp of the master reset, I break out of the transition processing loop, which then returns control back to the master reset code, which does the sync reset (by solving the exact phase of the slave at the sync time-point and then using that to solve the change in level and derivatives to insert the correct BLEPs). Once I'm done with processing master, the slave then gets processed once more, this time with the "limit" time at the end of the sampling interval.

There's probably other ways to do the same thing, but I find this results in fairly nice and simple code.

Post Reply

Return to “DSP and Plugin Development”