Code for band-limited PWM squarewave

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

(My google-fu is obviously weak: several searches through this forum and elsewhere have yielded "answers" of the form, "Just implement a (insert obscure algorithm here) and then apply a (insert another obscure algorithm here), rinse, lather, repeat", generating an ever-expanding search tree. Where I come from, that's called a "scavenger hunt". But I digress...)

Can anyone point me towards some C++ (or Delphi/F77/whatever: I can translate) code for generating band-limited PWM squarewaves?

I've got Laurent deSoras' 'resampler' code, which appears to be a viable approach for bandlimiting any static waveform. And it looks like I can use it to generate squarewaves of arbitrary pulse-width by using two phase-shifted saws. But I'm having trouble wrapping my head around a method for continuously changing the phase/delay of a signal generated that way, especially when it may also be changing more-or-less continuously in frequency. I'm assuming any simple brute-force approach to that will introduce (non-bandlimited) discontinuities into the result, yes?

Post

This code is a simple nice way to do a band-limited PWM:

http://musicdsp.org/archive.php?classid=4#221

It uses two self phase modulated sine waves to produce an approximation of a band-limited saw wave. Taking the phase shifted difference gives a roughly band-limited square.

The other options are using pre-generated band-limited saw-wave tables and doing the phase shifted difference again. You need quite a number of tables (with varying numbers of partials) and at least linear interpolation (between both tables and samples) to get good results. Synth1 is very highly regarded and uses this method.

You can also generate a naive PWM at a very high sample rate and run it through a high order low pass filter (WinFilter will generate the code for you). According to a post (probably pure speculation) I read somewhere the Nord Lead (what Synth1 is emulating) works like this.

Finally you can use the BLIT/BLEP methods, but these are probably the obscure algorithms you speak of. I find them a little complicated for my taste but I tend to sacrifice too much in the quest for simplicity. I have not seen any good clear example code of this anywhere.

Post

i'm also using the "two saw waves" method for my synth
since it already needs to have a Saw waveform, i did my wavetables using a technique better known in games/textures - "mip-map" where i start from a fundamental table (the bigest one, for low frequencies) and then each next table is exactly twice smaller than the previous
this way, if my fundamental table is 2048 samples, i would need not more than 4096 samples for all the tables (including this one)

then i interpolate both between the tables, and samples (like 2x2 interpolation)

for a Pulse i just use two of these Saws (one of them inverted) with some phase offset, works perfect ;]
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

GameSmith wrote:This code is a simple nice way to do a band-limited PWM:

http://musicdsp.org/archive.php?classid=4#221

It uses two self phase modulated sine waves to produce an approximation of a band-limited saw wave. Taking the phase shifted difference gives a roughly band-limited square.
Looks like a good place to start. Thanks!
GameSmith wrote:The other options are using pre-generated band-limited saw-wave tables and doing the phase shifted difference again. You need quite a number of tables (with varying numbers of partials) and at least linear interpolation (between both tables and samples) to get good results. Synth1 is very highly regarded and uses this method.
That's basically what I was alluding to with the de Soras package, but as I said, I don't see any way to vary the phase of one of the table sweeps without inducing artifacts. (Unless you mean using a separate set of tables for each of a largish set of pulse-width values? :o )
GameSmith wrote:Finally you can use the BLIT/BLEP methods, but these are probably the obscure algorithms you speak of.
Geeze, was I that obvious?!?

Post

antto wrote:i'm also using the "two saw waves" method for my synth
since it already needs to have a Saw waveform, i did my wavetables using a technique better known in games/textures - "mip-map" where i start from a fundamental table (the bigest one, for low frequencies) and then each next table is exactly twice smaller than the previous
this way, if my fundamental table is 2048 samples, i would need not more than 4096 samples for all the tables (including this one)

then i interpolate both between the tables, and samples (like 2x2 interpolation)

for a Pulse i just use two of these Saws (one of them inverted) with some phase offset, works perfect ;]
That (if I understand the terminology) is the method used in the de Soras functions. But how do you vary the phase offset (i.e. pulse-width) smoothly?

Post

you can tweak your SAW tables so they don't have too much harmonics near Nyquist (anti-gibbs effect, or something)
you can find a small piece of code on music-dsp, where i've posted how i generate my Saw table, look for "Band-Limited <something> My way" and read the comment since i had a typo in the original post ;P~
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

antto wrote:you can tweak your SAW tables so they don't have too much harmonics near Nyquist (anti-gibbs effect, or something)
you can find a small piece of code on music-dsp, where i've posted how i generate my Saw table, look for "Band-Limited <something> My way" and read the comment since i had a typo in the original post ;P~
One of us is missing something here, probably me.

I know how to generate a block of samples for a band-limited saw, and I know I can subtract out a phase-shifted copy to get a square-wave of arbitrary duty cycle/pulse-width. That's fine for a static pulse-width.

What I don't know is how to change that phase-shift (or delay or pulse-width or duty cycle or whatever you want to call it) once I start generating samples without causing the same problems the band-limiting is intended to avoid.

Post

I don't think modifying the phase offset introduces significant aliasing artifacts. Usually the phase shift is modulated at sub-audible frequencies. Are you wanting to modulate it much faster?

Post

GameSmith wrote:I don't think modifying the phase offset introduces significant aliasing artifacts. Usually the phase shift is modulated at sub-audible frequencies. Are you wanting to modulate it much faster?
"Wanting"? More like "anticipating": LFOs hooked to pulse-width controls and LFOs that get up into audible range (30-50Hz). (I admit that combination isn't necessarily pleasant to the ear, but it happens.)

I'm willing to be convinced it's a non-problem, I'm even willing to be convinced it's an unsolvable problem. I just don't want to hand-wave away something that might be a problem if there's a reasonably easy solution available.

But yeah, in this case "don't know how to" and "don't know if I need to" are pretty much the same thing, at least for me.

Post

The faster you move it, the more bandlimited it has to be. Obviously, you can't get too close to Nyquist.

Your extreme case is using a square as your LFO modifying your pulse width. To avoid aliasing, the two saws being subtracted have to be bandlimited and the square moving that second saw has to be bandlimited, too.

I ran into this when doing audiorate oscillator mixing. I had a table which had a version of saw for every midi note (with the correct number of partials). Worked fine, but init time was awful.

Right now I'm just doing all that stuff at 8x oversampling and filtering down.
Swing is the difference between a drum machine and a sex machine.

Post

GameSmith wrote: The other options are using pre-generated band-limited saw-wave tables and doing the phase shifted difference again. You need quite a number of tables (with varying numbers of partials) and at least linear interpolation (between both tables and samples) to get good results. Synth1 is very highly regarded and uses this method.
This is the approach that I use. It's cool because once the algorithm is in place, you can apply it to other waveforms beside the sawtooth. For example, using it with a bell type waveform gives you a nice shimmer effect.

Post

mistertoast wrote:Your extreme case is using a square as your LFO modifying your pulse width. To avoid aliasing, the two saws being subtracted have to be bandlimited and the square moving that second saw has to be bandlimited, too.
I think we're inching towards the "not a problem" state (or at least a "trivial solution" substate). It's not like anybody cares about precisely how "square" the LFO waveform is, and lord knows, we know how to generate that bandlimited squarewave.
mistertoast wrote:I ran into this when doing audiorate oscillator mixing. I had a table which had a version of saw for every midi note (with the correct number of partials). Worked fine, but init time was awful.
(scratches head) What constitutes "awful"?

I'm envisioning a one-time-only init, either at startup or, better yet, installation. (That's how SynthMaster 2 does its wave tables.)

Post

deraudrl wrote:I think we're inching towards the "not a problem" state (or at least a "trivial solution" substate). It's not like anybody cares about precisely how "square" the LFO waveform is, and lord knows, we know how to generate that bandlimited squarewave.
I was mixing oscs at audiorate speeds. Even down to the sub-cycle. Surely that's the worst case.
deraudrl wrote:(scratches head) What constitutes "awful"?

I'm envisioning a one-time-only init, either at startup or, better yet, installation. (That's how SynthMaster 2 does its wave tables.)
Awful for development at least. Many seconds. By "at installation," I assume you mean the tables are written to files which are loaded. That's what I was going to do when I decided to just bag it and go many-times-oversampled.
Swing is the difference between a drum machine and a sex machine.

Post

Bandlimited Pulse: Subract a bandlimited Saw from another which is at a different phase. The phase difference between the two determines your "Pulse Width"

That's how I implement pulse waveforms in SynthMaster 1.0/2.0

Of course, then the question might change into how to create bandlimited saws?
Works at KV331 Audio
SynthMaster voted #1 in MusicRadar's "Best Synth of 2019" poll
SynthMaster One voted #4 in MusicRadar's "Best Synth of 2019" poll

Post

well this is exactly what i was talking about all the time
http://musicdsp.org/showArchiveComment. ... hiveID=268
here is how i generate the Saw into a table (in C++)
a tip about the PW
the easies way you probably gonna implement the PW is something like this:

// per sample
phase1 = phase1 + omega;
phase2 = phase1 + offset;
out = get_saw(phase1) - get_saw(phase2);

while this is simple and works, it actually might not be what you want
the first saw would always be runing at the right frequency, while changing the PWM will only "detune" the second saw (if you modulate the PWM)

another way is to make both saws detune equaly in both directions, tho it's a bit more calculation

phase = phase + omega;
phase1 = phase + 0.5 * offset;
phase2 = phase - 0.5 * offset;
out = get_saw(phase1) - get_saw(phase2);

this is what i do for my pulse ;]
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post Reply

Return to “DSP and Plugin Development”