Playbuf for FM in GrainSin

Are you latching the envelope selection per grain? I think that came up earlier in the thread.

It’s mandatory – if you’ve got one envbuf control being split out to multiple simultaneous grains, then you have to hold the value steady for the complete duration of one grain. If you don’t do this, then the envelope selection for grain 2 will also change grain 1 in the middle. This will sound like “sliding out of time” but it may be a totally different issue.

So, taking your kr version:

(
SynthDef(\pulseDivider, {

	var trig = K2A.ar(\trig.tr(0));
	var tFreq = \tFreq.kr(1);
	var grainRate = tFreq / \overlap.kr(1);

	var maxOverlap = 4;
	var envBufNums = 4;
	var envBufs = \envBufs.kr(Array.fill(envBufNums, 0));
	var envIndex = \localEnvBufIndex.kr(1);

	var sig = Array.fill(maxOverlap, { |i|

		var localTrig, hasTriggered;
		var phase, grainEnvs, grainEnv;

		localTrig = PulseDivider.ar(trig, maxOverlap, i);
		hasTriggered = PulseCount.ar(localTrig) > 0;

		phase = Sweep.ar(localTrig, grainRate * hasTriggered);

		// because this is all kr, you don't need the multiplex
		// also -- here -- you must not allow envIndex to change mid-grain
		grainEnv = BufRd.ar(1, Select.kr(envBufs, Latch.kr(envIndex, localTrig)), phase * BufFrames.kr(envBuf), loop: 0, interpolation: 4);

		sig = SinOsc.ar(\freq.kr(440));

		sig = sig * grainEnv;

	}).sum;

	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.10));
	Out.ar(\out.kr(0), sig);
}).add;
)

If you’re going to use Demand units to choose envelopes, then this is instead of the pattern doing it. It strikes me as not quite meaningful to try to do both at once. (That is, don’t put synth controls inside the demand structure.)

(
SynthDef(\pulseDivider, {

	var tFreq = \tFreq.kr(1);
	var trig = Impulse.ar(tFreq);
	var grainRate = tFreq / \overlap.kr(1);

	var maxOverlap = 4;
	var envBufs = \envBufs.kr(#[1,1,1,1]);

	// here: use this 100% to choose envelopes
	// now think about this... you want *one* stream
	// of envelope selection. So this must be outside the loop
	var envIndex = Demand.ar(trig, 0, Dseq([0, 1], inf));

	var sig = Array.fill(maxOverlap, { |i|

		var localTrig, hasTriggered;
		var phase, grainEnvs, grainEnv, envBufSelect;

		localTrig = PulseDivider.ar(trig, maxOverlap, i);
		hasTriggered = PulseCount.ar(localTrig) > 0;

		phase = Sweep.ar(localTrig, grainRate * hasTriggered);

		grainEnvs = envBufs.collect { |envBuf|
			BufRd.ar(1, envBuf, phase * BufFrames.kr(envBuf), loop: 0, interpolation: 4)
		};

		// it is still necessary to latch
		grainEnv = Select.ar(Latch.ar(envBufSelect, localTrig), grainEnvs);

		sig = SinOsc.ar(\freq.kr(440));

		sig = sig * grainEnv;

	}).sum;

	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.10));
	Out.ar(\out.kr(0), sig);
}).add;
)

hjh

thanks for your help.
ive been confused by which values have to be latched and which not.

If its possible to use Impulse.ar, Latch.ar and sequence the Buffer switching via Patterns, then this would be the optimal solution since the beginning. I was just following the Demand Ugen Path because i thought its not possible otherwise.

envBufSelect works with Demand and Latch.ar perfectly outside the Loop, thank you very much.
Would it be possible to use \envBufSelect.kr instead of the Demand Ugens and Sequence the Buffer switching via Patterns? ive tried it out, but the Latch.ar got in the way. Also tried out Latch.kr instead.

Sorry for beeing annoying with this topic.

(
SynthDef(\pulseDivider, {

	var tFreq = \tFreq.kr(1);
	var trig = Impulse.ar(tFreq);
	var grainRate = tFreq / \overlap.kr(1);

	var maxOverlap = 4;
	var envBufs = \envBufs.kr(#[1,1,1,1]);
	//var envBufSelect = \envBufSelect.kr(1);
	
	var envBufSelect = Demand.ar(trig, 0, Dseq([0, 1], inf));

	var sig = Array.fill(maxOverlap, { |i|

		var localTrig, hasTriggered;
		var phase, grainEnvs, grainEnv;

		localTrig = PulseDivider.ar(trig, maxOverlap, i);
		hasTriggered = PulseCount.ar(localTrig) > 0;

		phase = Sweep.ar(localTrig, grainRate * hasTriggered);

		grainEnvs = envBufs.collect { |envBuf|
			BufRd.ar(1, envBuf, phase * BufFrames.kr(envBuf), loop: 0, interpolation: 4)
		};

		grainEnv = Select.ar(Latch.ar(envBufSelect, localTrig), grainEnvs);
		
		sig = SinOsc.ar(\freq.kr(440));

		sig = sig * grainEnv;

	}).sum;

	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.10));
	Out.ar(\out.kr(0), sig);
}).add;
)

(
x = Pmono(\pulseDivider,

		\freq, 440,

		\envBufSelect, Pseq([0, 1], inf),
		\envBufIndex, [0, 1],
		\envBufs, Pfunc{ |ev| ~customEnvs[ev[\envBufIndex]] }.collect(`_),

		\dur, 0.5,
		\tFreq, 1 / Pkey(\dur),
		\overlap, 0.5,

		\amp, 0.1,
		\out, 0,
).play;
)

Timing is likely to be delicate this way. It might work some of the time but not always.

If it were my project, I’d just use Demand and be done with it. I would not try to synchronize rapid audio rate triggers with control rate data coming from the language.

I apologize but I’m not willing to do any testing on the pattern approach.

hjh

okay, i think then i will either go for the .kr approach or demand Ugens + Impulse.ar which is compositionally a bit restricted. thanks alot for your help :slight_smile: