IMHO the best way to handle BLEP oscillators is to treat them as state machines where every sample you check for the next state transition and then if there is one, repeat until there is none.So for example in the "Alpha Juno" code, if I wanted to antialias the PWM gate where it's extremely likely that you can miss a very narrow pulse (because at 48kHz you start to increment the counter by more than 1 every sample at the low hundreds of Hz output frequency), you'd need to check if the counter wrapped and then see if there was a need for a "blep up" before putting in the "blep down" again?
Something to keep in mind is that if you allow the oscillator frequency to get high enough, then you can have multiple BLEP transitions within the same sampling interval ... more so with narrow pulse widths and such (where the pulse can go high, then low, or vice versa within a single sampling interval).
For example, if we have a pulse wave, then we might have states "low" and "high" with "low" being the initial state after a reset. Every sample you increment the phase and check if it has crossed the current PWM threshold. If the threshold has been crossed, that's a state transition, you solve for the exact transition time and insert a BLEP, then set "high" as the current oscillator state.
Then we repeat the same thing for the new state, this time checking if we're past the point where the phase should be reset. If that threshold has been crossed as well, then again we solve for the exact time offset and insert a BLEP, subtract the reset threshold from the phase and set "low" as the current oscillator state.. and then we repeat, again checking if the PWM threshold has been met again.. and so on, until we get to the point where we observe that there aren't any more transitions during the current sample.
Next sample, we look at which state we were left in and carry on doing the same thing we did the previous sample, again looping until there are no more transitions. Now, for most samples (at reasonable oscillator frequency) there's no transitions at all. Sometimes there's a single transition. But if we have a highish frequency and narrow pulse-width, we might get two transitions. If we allow a ridiculous frequency, then we might have 500 transitions; that'd obviously be very slow to process, so you want to cap the frequency at some sensible maximum, but the point is.. if you loop for "are we at the next transition already" until there's no more transitions for the current sample, it "just works" and with large enough kernel you can run at 44.1kHz and have the oscillator at 1MHz and it's outputting a crapton of BLEPs every sample and the output is the expected silence (and in practice a bit of noise).
The code in the very first post of this thread uses this strategy: there's a while(true) loop for solving BLEPs and in each stage of the oscillator, we break out of the loop if the next transition cannot be during the current sample. It doesn't care about how many BLEPs we need, it just keeps inserting them until it's gone through all the possible transitions and it just works(tm).
For hard-sync, the only complication is that every time you have a "natural" transition, you check whether that natural transition or the sync transition comes first. If it's the natural transition, you proceed normally. If it's the sync transition, you perform sync, then check for natural transitions again after the reset.
Statistics: Posted by mystran — Wed Jan 15, 2025 11:30 pm