One approach to troubleshooting is to inspect the components of the sound. There are many ways to do this (polling, scoping etc.)… in this case I was curious what is the result of each Blip and each PMOsc.
There are five of each, so I allocated a 10-channel bus and scoped it. Also I wanted a look at some of the random numbers, so I moved those into variables (no difference in behavior – just to check out the numbers).
b = Bus.audio(s, 10);
b.scope;
(
thisThread.randSeed = 12;
Ndef(\pmtest, {
var a, c, mb, mt;
var rand5 = Array.rand(5, 9, 150);
var rand2 = Array.rand(2, 10, 2210.1);
var rand4 = Array.rand(4, 1, 20);
rand5[0].debug("rand5[0]");
rand2[0].debug("rand2[0]");
rand4[0].debug("rand4[0]");
a = 0.7;
c = Blip.ar([ 17.645452179362, 17.742460335684 ], rand5, 0.3);
Out.ar(b, c);
c = PMOsc.ar(0.01, (c*32), rand2, (c / rand4).mod(2pi), 0.1);
Out.ar(b.index + 5, c);
Splay.ar(c) * -6.dbamp;
}).play;
)
The Blips turned out to be unsurprising. After a few minutes, some of the PMOscs had become almost DC:
PMOsc is actually not a C++ UGen – it’s made of a pair of SinOscs.
PMOsc {
*ar { arg carfreq,modfreq,pmindex=0.0,modphase=0.0,mul=1.0,add=0.0;
^SinOsc.ar(carfreq, SinOsc.ar(modfreq, modphase, pmindex),mul,add)
}
...
}
Then I remembered something. SinOsc has a limitation – from its help file: “Phase values should be within the range ±8pi. If your phase values are larger then simply use .mod(2pi) to wrap them.”
Now in PMOsc: The modulator is fed directly into the carrier’s phase input, after being multiplied by pmindex
, so the modulator signal will range -pmindex to +pmindex.
If the user supplies a pmindex > 8pi, then the modulator signal will go outside the bounds that SinOsc can handle for the phase input. At that point, correct behavior isn’t guaranteed.
Tracing the synth does reveal some SinOsc phases that are out of bounds… it might take a bit of luck to catch one in the act, but here, phase 67.2665 definitely exceeds 8pi!
unit 16 SinOsc
in 0.01 67.2665
out 0.0456926
I’d suggest to log this as a bug at github – the modulator should have .mod(2pi)
after it, but doesn’t.
As a workaround, you can unpack PMOsc’s logic and write the SinOscs directly:
(
thisThread.randSeed = 12;
Ndef(\pmtest, {
var a, c, mb, mt;
var modulators;
var rand5 = Array.rand(5, 9, 150);
var rand2 = Array.rand(2, 10, 2210.1);
var rand4 = Array.rand(4, 1, 20);
a = 0.7;
c = Blip.ar([ 17.645452179362, 17.742460335684 ], rand5, 0.3);
Out.ar(b, c);
modulators = SinOsc.ar(c * 32, (c / rand4).mod(2pi), rand2);
c = SinOsc.ar(0.01, modulators.mod(2pi), 0.1);
Out.ar(b.index + 5, c);
Splay.ar(c) * -6.dbamp;
}).play;
)
I’ve been running this one for probably 10 minutes now and the behavior isn’t degrading.
hjh