I’m trying to write something that works like the rate~ object in Max in order to create a phasor synced system. This is what i’ve come up with so far (based on a Max patch found here), and it almost works, but the two phasors in this example are not in sample correct sync (obviously). I suspect i need to compensate for block sizes introduced by the Delays and LocalIns and LocalOuts, possible roundoff errors or similar? Or are there better ways to implement this in SC? Oh, and I need everything to be audio rate.
The original rate~ object time stretches or time compress a phasor. So, i guess this works for scaling factors that are <1 (and divide the rate by powers of 2) but not >1 because they would restart phasorB from 0 when phasorA restarts.
you can get the slope of your original ramp, run an accumulator and subdivide by a factor and wrap between 0 and 1. Your subdivided phase can now be slower then your original phasor, but might get out of sync.
Using the factor and wrap, you could derive any number of phasors from a non-resetting master phase that continuously increments, unless you run the system for a very long time (days?). Using your original sonification above, it would look like:
(
Server.default.waitForBoot({
play {
var phase;
var derivedSlow, derivedUnity, derivedFast;
var a, u, b;
phase = Phasor.ar(DC.ar(1), SampleRate.ir().reciprocal(), (60 * 60 * 24), inf); // starting 24 hours in seems fine
derivedUnity = (phase * 1).wrap(0, 1);
derivedSlow = (phase * (1/8)).wrap(0, 1);
derivedFast = (phase * 2).wrap(0, 1);
phase.poll();
a = Saw.ar(300 + (derivedSlow * 300), 0.1);
u = Saw.ar(300 + (derivedUnity * 300), 0.1);
b = Saw.ar(300 + (derivedFast * 300), 0.1);
Out.ar(0, Splay.ar([a, u, b]));
};
});
)
Hm. Yes, i actually started out this project with a Sweep as a master, which i guess is similar to a Phasor with inf as end value. The thing i’m trying to do here is a modular system of phasors where i can build a tree of sub-phasors (which i used to do quite often during my Max days using rate~). So, what i should’ve told you is that the slavePhasor in my example need to be able to work as master to other sub-phasors and i guess they all need to have a range between 0-1.
Do you have the book on gen~ “generating sound & organizing time” ?
There it is said: “Unlike the ramp multiplication we explored earlier in this chapter, we can work with divisions that are longer than the input ramp without beeing reset. However, even with simple divisions, with this patch, there is no guarantee that the output ramps cycle will remain phase-synchronized with the input ramp. Even if they start synchronized, modulations to the ratio can cause them to drift”
If you dont modulate the rate of your main ramp or the division / multiplication of your subramps you might be fine. The patch from the book has a sync logic to sync the derived ramps to the main ramp when the ratio changes by an significant amount by detecting a proportional change above a certain threshold. This issue has nothing to do with the programming language you are using.
Sounds like an interesting book. I don’t have that. Will check it out.
I have tried implementing a sync logic that resets the phase using a Latch and subtraction. It seems the resets causes slight offsets to the sub-ramps though. I will have to investigate this further.
Thanks! This might work as a basis for what i’m trying to do i think. But is there a way to compensate for the offset caused by a rate change? I can’t figure out what’s happening here because the offset is different with every change it seems (looking at my phase oscilloscope).
(
Server.default.waitForBoot({
var derive;
derive = { |phaseIn, factor|
var phasor, phaseOut;
phaseOut = phaseIn * factor;
phasor = phaseOut.wrap(0, 1);
[phasor, phaseOut]
};
x = play {
var phase;
var derivedAPhasor, derivedAPhase;
var derivedBPhasor, derivedBPhase;
phase = Phasor.ar(DC.ar(1), SampleRate.ir().reciprocal(), 0, inf);
#derivedAPhasor, derivedAPhase = derive.(phase, 1);
#derivedBPhasor, derivedBPhase = derive.(derivedAPhase, \rate.ar(1));
a = Saw.ar(300 + (derivedAPhasor * 300), 0.1);
b = Saw.ar(300 + (derivedBPhasor * 300), 0.1);
Out.ar(0, [a, b]);
};
});
)
x.set(\rate, 0.5);
x.set(\rate, 1);
Great! Thank you!
I tried this, which kind of works, if you ignore the constant offset that i mention above: rate = Latch.ar(\rate.ar(1), mstPhasor < 0.5);
have you considered using a different function to get triggers from ramps then using mstPhasor < 0.5?
This will not give you a single sample trigger on the phasors wrap, but a gate signal:
The phasor should start one sample earlier, because the calculation for getting the delta is based on the current and the last sample. If your phase starts at 0, the rampToTrig function will still output a trigger but its one sample late from the phasors wrap. Here i have zoomed in on the phasors wrap and you see that the trigger is aligned with the phasors wrap if you start your phase one sample earlier.
Another option is to use HPZ1.ar(phase) < 0 and add an additional initial trigger with Impulse.ar(0)
This caused me some inaccurancy if you additionally calculate the slope with last and current sample using Delay1 and accumulate new phases by using that slope. They are one sample off from the main ramp, so i decided to base both the trigger and the slope calculation on Delay1 and start my main ramp one sample earlier. But using HPZ1 with an additional initial trigger might work for you as well.