Deterministic sync of oscillator phase

Hi. Newbie question: Is there a way to deterministically sync the phase and timing between oscillators?
If I run the code below, you can often but not always, hear a distortion, which I assume comes from the oscillators starting almost at the same time with aligned phases, leading to high peaks that get clipped. But it seems there is a delay in execution, causing the oscillator to by out of sync. Are my assumptions correct, and can I control it deterministically? What I want is for the sine waves to have the same phase and start at the same time.

Thanks,
HĂĄkan

(
{
{ [SinOsc.ar(110, 0, 0.6), SinOsc.ar(110, 0, 0.6)] }.play;
{ [SinOsc.ar(220, 0, 0.6), SinOsc.ar(220, 0, 0.6)] }.play;
{ [SinOsc.ar(440, 0, 0.5), SinOsc.ar(440, 0, 0.5)] }.play;
{ [SinOsc.ar(880, 0, 0.45), SinOsc.ar(880, 0, 0.45)] }.play;
}.play;
)

Short answer, you can easily synchronize them, but the code you have doesn’t.

The reason this is happening is your outer function is trying to send four other different functions to the server and you’ll end up with an error. You can fix some syntax to get it to not throw an error, but the timing will be off since you have to make sure the UGen functions are synchronized with s.bind.

{}.play is a convenience method to build a temporary SynthDef with the UGen graph function and play it, so this is interpreted as “make and play a SynthDef that makes and plays these four SynthDefs,” which you can’t do.

By default, the UGens in a function will be synchronized as long as they all play on the same function. So if you could evaluate what you have, the first {}.play would have two oscillators in sync, but they would not be in sync with the second {}.play oscillators because they wouldn’t start at the same time. Observe:

{ [SinOsc.ar(110), SinOsc.ar(110)] }.plot; 

{ [SinOsc.ar(400), SinOsc.ar(110)] }.plot;

They all have an initial phase of 0. If you change the phase argument, they’ll be at that phase.

{ [SinOsc.ar(110, pi), SinOsc.ar(110)] }.plot; 

{ [SinOsc.ar(400, pi), SinOsc.ar(110)] }.plot;

A few other things about your code. If you want to just use a function to make sure this works, it looks like this, but as you had it before it’s way too loud:

({
{ [SinOsc.ar(110, 0, 0.6), SinOsc.ar(110, 0, 0.6), SinOsc.ar(220, 0, 0.6), SinOsc.ar(220, 0, 0.6), SinOsc.ar(440, 0, 0.5), SinOsc.ar(440, 0, 0.5), SinOsc.ar(880, 0, 0.45), SinOsc.ar(880, 0, 0.45)].sum * 0.01
}.play)

Several ways to improve this:

  1. Make this a SynthDef and then play it. That way it’s already been built on the server.
(
SynthDef(\mySynth, {
	arg out = 0;
	var sig = [
		SinOsc.ar(110, 0, 0.6), 
		SinOsc.ar(110, 0, 0.6), 
		SinOsc.ar(220, 0, 0.6), 
		SinOsc.ar(220, 0, 0.6), 
		SinOsc.ar(440, 0, 0.5), 
		SinOsc.ar(440, 0, 0.5), 
		SinOsc.ar(880, 0, 0.45), 
		SinOsc.ar(880, 0, 0.45)].sum * 0.01;
	Out.ar(out, sig);
}).add;
)

Synth(\mySynth);
  1. You can (and it’s far more idiomatic to) use iteration instead of typing out the same thing multiple times. You also don’t need duplicates of the same UGen with the same arguments. I assume you’re doing this to get stereo playback, but there’s a better way with .collect. See Eli Fieldsteel’s tutorial on iteration for a more detailed explanation.
(
SynthDef(\mySynth, {
	arg out = 0, freq = 110;
	var sig = 4.collect{
		arg i; //index value
		var ratio = 2.pow(i);
		SinOsc.ar(ratio * freq, 0);
	};
	sig = sig * [0.6, 0.6, 0.5, 0.45].normalizeSum; //so it's not so loud
	sig = sig.sum.dup;
	Out.ar(out, sig);
}).add;
)

Synth(\mySynth);
  1. You could use Pan2 instead of .dup or !2 to control the panning.
(
SynthDef(\mySynth, {
	arg out = 0, freq = 110, pan = 0; //add pan argument set to stereo center
	var sig = 4.collect{
		arg i; //index value
		var ratio = 2.pow(i);
		SinOsc.ar(ratio * freq, 0);
	};
	sig = sig * [0.6, 0.6, 0.5, 0.45].normalizeSum;
	sig = Pan2.ar(sig, pan);
	Out.ar(out, sig);
}).add;
)

x = Synth(\mySynth);
x.set(\pan, -1);
x.set(\pan, 1);
x.free;
1 Like

Thanks a lot mjsyts!