Experience implementing DSF?

Hi All,

Just a quick query as to whether anyone has any experience implementing Moorer’s DSF (Discrete Summation Formulas) for synthesis in the form of UGen graphs, or directly in sclang?

Strangely, I’m not returning the expected results… and am beginning to doubt my sanity! I’m seeing two things:

  1. unexpectedly steep rolloff of higher partials for non-bandlimited synthesis with Moorer’s eqs. (2) & (4)
  2. what looks like phase drift (?) of cancellation components for bandlimited synthesis with Moorer’s eqs. (1) & (3)

I see unexpected rolloff with UGen graphs using both SinOsc and FSinOsc for sinusoid synthesis. And when using Signal:-sineFill2 in sclang, I see something similar. (The resulting spectrum appears low passed.)

The phase drift-like issue appears in the UGen graph context; phase of the bandlimiting cancellation components seems to slowly drift out of sync with the main summation components. The effect is that after a certain amount of time, the partials that are to be suppressed gradually fade in. (I’m not completely convinced that phase drift is what generates this effect.)

I’m rather puzzled here, particularly as Moorer illustrates MUSIC-V implementations for synthesis… so I’d think an SC3 UGen graph would be suitable.

My initial suspicion was that wavetable oscillators with separate phasors may not what we need for the job. With sclang implementations, interpolation and/or phase sync/drift shouldn’t matter—especially if we’re only attempting to generate a single periodic cycle.

Any thought on the matter? Maybe I’m missing something?

… I can throw up some code to illustrate… but thought I’d start with my initial observations / presumed incompetencies.


This sounds right to me: if Phasor is based on an increment, then it’s subject to floating point rounding error (even if it’s double precision internally).

Are the phasor periods in relatively simple integer ratios? If so, I’d consider running the phasor at the GCD of the phasor frequencies, and then doing a multiply and % 1.0 to get the different rates (which will then always be phase-synced). (Max/MSP has an object for that, but I didn’t use it before.)

Like, if you need 220 Hz and 330 Hz, I bet this would work (untested):

var main = Phasor.ar(0, 110 * SampleDur.ir, 0, 1);
var ramps = (main * [2, 3]) % 1.0;

It would get trickier without a usable GCD but I’m guessing DSFs favor harmonic relationships.

@dietcv here has done a lot of work with phasor magic – well worth searching the forum for those threads. Maybe some of it applies to the problem of deriving new phasor ramps from a main cycle.

Edit: For fun, I tried this synth:

(
a = {
	var pulses = HPZ1.ar(Phasor.ar(0, [11, 10] * SampleDur.ir, 0, 1)) < 0;
	var every11th = Demand.ar(pulses[0], 0, Dseq([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], inf));
	var time = Sweep.ar(pulses[0], 1);
	time.poll(pulses[1] * every11th);
	Silent.ar(1);
}.play;
)

a.free;

At the beginning, it reports 0.00907029; after 30-40 minutes, 0.00900227. this suggests that the 10 cps phasor is running slightly faster than the 11 cps (shorter delay over time). I’m not completely sure this is a valid test – for one thing, I don’t quite understand why the initial Sweep value is not 0 – if there were no rounding error in the phasors’ accumulation, then the value would be stable, wouldn’t it?

hjh

Hello @jamshark70,

The test case I was initially reviewing is a sawtooth wave. The non-bandlimited version(s) require only two oscillators (at the same frequency) in quadrature.*

Interestingly enough, desmos has implemented a direct evaluation of Moorer’s DSF equations. Here’s a “snapshot” of the arguments set to return a saw:

The bottom waveform is the single side-band version while the top is the double side-band variant. Both are non-bandlimited, i.e., Moorer’s eqs (2) & (4).

This desmos example directly evaluates a phasor, so there are no oscillator implementation issues here. Visually inspecting we see significant curviness in the two waveforms—which corresponds to the low pass filtering I was seeing in my sclang and synthDef tests.

This leads me expect that Moorer’s DSF really doesn’t end up matching the ideal sinusoid sum targeted after all! (One would hope there would be some mention of this in the literature. I’ll have to do some further reading.)

The phase drift with the bandlimited versions will be a different matter…


*Dodge & Jerse 2nd edition illustrate a non-bandlimited single side-band implementation, Moorer’s eq (2), in figure 5.35, p. 163.

The bandlimited solutions require two extra oscillators. The frequency ratio of these vary depending on the number of target partials to synthesize as well as the c:m ratio.

There is certainly the possibility that floating point rounding error leads to the observed drift. The thing that is a bit weird is that the drift doesn’t seem to continue. Once the higher partials are no longer suppressed, there is no further drift back to suppression—which is what I’d expect if that really is what is happening.