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:
I think adding 2x oversampling would already be enough.