Client representation of rate-modulated Phasor

Hi, guys!

I’m trying to make a client representation of GrainBuf, where position of the grain is controlled by rate-modulated Phasor
something like this:


	phasor = Phasor.ar(
		trig: 0.0, 
		rate: posRate + SinOsc.ar(posRateMF, [1.51471, 2.58218], mul: posRateMD), 
		start: 0.0, 
		end: bufFrames, 
		resetPos: 0.0
	);

	gran = GrainBuf.ar(
		numChannels: 1, 
		trigger: t, 
		dur: tFreq.reciprocal * overlap, 
		sndbuf: bufnum, 
		rate: rate, 
		pos: phasor / bufFrames, 
		interp: 2, 
		pan: 0, 
		envbufnum: -1, 
		maxGrains: 512, 
	);

Now I can’t figure out if there is a way to recreate this rate-modulated phasor behaviour. The simple approach to modulate rate would be to multiply by modulating function the time deltas between grain onset and routine start and use it for position, but that’s a different behaviour. It would be like Phasor’s output is multiplied by mod.
The rate parameter though works as increment value (level += rate), so modulating it gives different result.

So far the only aproach that’s working for me is just to have a synth with Phasor writing to a bus, and read it with grain nodes. Maybe it’s a best way to do it, but maybe someone knows a better way )?

(
SynthDef(\pbmono, {arg buf;
	var phasor, play, e;
	var dur = \dur.kr(0.2);
	var delay = \delay.kr(0.0);
    phasor = Phasor.ar(
		rate: BufRateScale.kr(buf) * \rate.kr(1),
		// start: Gate.ar(In.ar(\bus.kr), DC.ar(1) - Delay1.ar(DC.ar(2))), // not getting any values
		start: Gate.ar(In.ar(\bus.kr), DC.ar(1) - Delay2.ar(DC.ar(2))), // looks accurate
		end: BufFrames.kr(buf)
	);
	play = BufRd.ar(1, buf, phasor, 0, 2);
	e = EnvGen.ar(Env.new([0, \amp.kr(0.1), 0], [dur * 0.5, dur * 0.5], curve: \sin),
		doneAction: 2);
	play = play * e;
	OffsetOut.ar(\out.kr(0), play);
}).add
)

~bus = {Bus.audio(s,1)}.dup;
b = Buffer.read(s, Platform.resourceDir ++ "/sounds/a11wlk01.wav");


(
~func = { arg tFreq, overlap, buffer, niter, stretch, rate;
	{
		var posRatePhase = [1.51471, 2.58218];
		var dur;
		
		{
			var sig = Phasor.ar(
				rate: stretch + SinOsc.ar(
					0.5,
					phase: posRatePhase,
					mul: 0.05
				) * BufRateScale.kr(buffer.bufnum),
				end: buffer.numFrames
			);
			~bus.do { |it i| OffsetOut.ar(it, sig[i])};
		}.play(addAction: \addToHead);

		2.do({arg j;
			Routine({
				niter.do{ arg i;
					dur = (1 / abs(tFreq));
						s.sendBundle(0.2,
							["/s_new",
								'pbmono',
								-1,
								1, // add to tail
								1,
								\bus, ~bus[j].index,
								\dur, dur * overlap,
								\amp, 0.3,
								\buf, buffer.bufnum,
								\rate, rate,
								\out, j,
							]
						);
					dur.wait;
				}				
			}).play})

	}
};

Routine(~func.value(
	tFreq: 20,
	overlap: 2,
	buffer: b,
	niter: 10000,
	stretch: 0.5,
	rate: 1,
)).play;


)

Because these are two different timescales, i think the bus approach is the best way to do it.

probably yeah, I was just already having it organized with other parameters like
having a mod

 mod = {arg i, phase, mf, md;
			(2pi * mf / sr * i + phase).sin * md
		};

and then using it for other parameters like

...
startTime = thisThread.seconds;

		2.do({arg j;
			Routine({
				niter.do{ arg i;
					var timeDelta;
					
					timeDelta = thisThread.seconds - startTime * sr;
					i.postln;
					dur[j] = tFreq + mod.value(timeDelta, durPhase[j], mf: 0.1, md: 0);
					dur[j] = (1 / abs(dur[j]));
						s.sendBundle(0.2,

...

and then stuck with this rate thing, so it got me thinking )