Advanced Synthesis - Oscillator Sync

I have succesfully implemented the anti aliased saw with hard sync from the go book, i have posted above:

(
var rampToTrig = { |phase|
	var history = Delay1.ar(phase);
	var delta = (phase - history);
	var sum = (phase + history);
	var trig = (delta / sum).abs > 0.5;
	Trig1.ar(trig, SampleDur.ir);
};

var rampToSlope = { |phase|
	var history = Delay1.ar(phase);
	var delta = (phase - history);
	delta.wrap(-0.5, 0.5);
};

var getSubSampleOffset = { |phase, trig|
	var slope = rampToSlope.(phase);
	var sampleCount = phase - (slope < 0) / slope;
	Latch.ar(sampleCount, trig);
};

var hardSyncSubSample = { |carrierFreq, syncFreq|

	var syncPhase, syncTrigger, syncSubSampleOffset;
	var carrierSlope, carrierPhaseCurrent, carrierPhaseLast;
	var loopSyncSubSample, mix, triggerTrans;
	var carrierPhaseBeforeTrans, carrierPhaseAfterTrans;
	var replaceCarrierPhaseBeforeTrans, replaceCarrierPhaseAfterTrans, sig;

	syncPhase = (Phasor.ar(DC.ar(0), syncFreq * SampleDur.ir) - SampleDur.ir).wrap(0, 1);
	syncTrigger = rampToTrig.(syncPhase);
	syncSubSampleOffset = getSubSampleOffset.(syncPhase, syncTrigger);

	carrierSlope = carrierFreq * SampleDur.ir;
	carrierPhaseCurrent = (Phasor.ar(syncTrigger, carrierSlope) + (carrierSlope * syncSubSampleOffset)).wrap(0, 1);
	carrierPhaseLast = Delay1.ar(carrierPhaseCurrent);

	loopSyncSubSample = (carrierPhaseCurrent.wrap(-0.5, 0.5) + (carrierSlope * 0.5)) / carrierSlope;
	mix = Select.ar(syncTrigger, [loopSyncSubSample, syncSubSampleOffset]);
	triggerTrans = (mix >= 0) * (mix < 1);

	carrierPhaseBeforeTrans = (carrierPhaseLast - (carrierSlope * mix)).wrap(0, 1);
	carrierPhaseAfterTrans = (carrierPhaseCurrent - (carrierSlope * (mix - 1))).wrap(0, 1);

	replaceCarrierPhaseBeforeTrans = Select.ar(triggerTrans, [
		carrierPhaseCurrent,
		carrierPhaseBeforeTrans
	]);

	replaceCarrierPhaseAfterTrans = Select.ar(triggerTrans, [
		carrierPhaseCurrent,
		carrierPhaseAfterTrans
	]);

	// throw in unit shapers here!!!!!

	sig = LinXFade2.ar(
		replaceCarrierPhaseBeforeTrans,
		replaceCarrierPhaseAfterTrans,
		mix.clip(0, 1) * 2 - 1;
	);

	sig * 2 - 1;
};

{
	var freq, fmod, fmFreq, sig;
	fmFreq = \fmFreq.kr(3);
	fmod = SinOsc.ar(fmFreq) * \index.kr(0);
	fmod = fmod - OnePole.ar(fmod, exp(-2pi * fmFreq * SampleDur.ir));
	freq = \freq.kr(270);
	sig = hardSyncSubSample.(freq + (freq * fmod), \syncFreq.kr(50));
	sig = LeakDC.ar(sig);
	sig!2 * 0.1;
}.play;
)

here an audio example of a naive phasor at 2000 hz and the subsample accurate phasor at 2000 hz

Here a plot for carrier freq at 273 hz and sync freq at 517 hz for the anti-aliased and the naive version:
grafik
grafik

I think adding 2x oversampling would already be enough.

1 Like