Buffered unipolar / bipolar signals for FM per grain

hey, yet another thread about granular / pulsar synthesis and fm…

This time about buffered signals used as FM modulators. Ive been thinking about this for some days already and tried out buffered unipolar and bipolar signals as frequency modulators per grain.

The first attempt fmod_LFO is using a bipolar signal and gets triggered once per grain and is more like an oscillator when you turn on loop = 1, therefore it has its own frequency here grainFreq binded to the trigger frequency, not a must but ive been trying out if its better to use frequency independent from duration or if they should be binded together for pulsar synthesis. Probably better here to not use the trigger at all and have it totally free running, otherwise you get a discontinuity when the next triggers arrives in the middle of a cycle.

The second attempt fmod_Env is using a unipolar signal and its duration is scaled to the grain duration, its behaving like a frequency envelope. Probably good to scale it with .linlin like i have done below.

My question would be: is it possible to have both approaches at the same time and choose its behaviour for unipolar and bipolar signals as needed.

I think the fmod_LFO approach doesnt make so much sense. either trigger it and scale it to grainDur or have it totally free running with its own frequency with the possibilty of using a buffer crossfading approach Crossfade between Buffers with BufRd Vosc style - #2 by jamshark70.

Im not so sure how to formulate the question any better, its just about trying to have a most flexible SynthDef expression, which could then be used with patterns in different ways. Maybe something to consider are envelopes and additional LFOs with Oscillators to get complex FM shapes without any buffered signals.

~freqBuf_bipolar = Buffer.loadCollection(s, Signal.sineFill(512, [1]), 1);
~freqBuf_unipolar = Buffer.sendCollection(s, Env.perc(0.01, 1, 1, -4).discretize(2048), 1);
~envBuf = Buffer.sendCollection(s, Signal.hanningWindow(2048), 1);


	var tFreq = \tFreq.kr(4);
	var grainRate = tFreq / \overlap.kr(1);
	var grainFreq = grainRate * \grainFreqRatio.kr(2);
	var trig = Impulse.ar(tFreq);

	var phase, freqPhase, hasTriggered;
	var sig, grainEnv, fmod_LFO, fmod_Env, iEnv;

	//var grainFreq = \freq.kr(50);

	hasTriggered = PulseCount.ar(trig) > 0;
	phase = Sweep.ar(trig, hasTriggered);

	grainEnv = BufRd.ar(
		numChannels: 1,
		bufnum: ~envBuf,
		phase: grainRate * phase * BufFrames.kr(~envBuf),
		loop: 0,
		interpolation: 4

	fmod_LFO = BufRd.ar(
		numChannels: 1,
		bufnum: ~freqBuf_bipolar,
		phase: grainFreq * phase * BufFrames.kr(~freqBuf_bipolar),
		loop: 0,
		interpolation: 4

	fmod_Env = BufRd.ar(
		numChannels: 1,
		bufnum: ~freqBuf_unipolar,
		phase: grainRate * phase * BufFrames.kr(~freqBuf_unipolar),
		loop: 0,
		interpolation: 4
	//freqEnv = grainFreq + freqEnv.linlin(0, 1, 0, \freqEnvAmount.kr(220));

	iEnv = IEnvGen.ar(Env(
		[0, 1, 0],
		[\iAtk.kr(0.25), \iRel.kr(0.75)],
		[\iAtkCurve.kr(4.0), \iRelCurve.kr(-2.0)]
	), phase * grainRate);
	iEnv = \index.kr(0) + iEnv.linlin(0, 1, 0, \iEnvAmount.kr(1));

	sig = SinOsc.ar(grainFreq * (1 + (fmod_LFO * iEnv)));

	sig = sig * grainEnv;

	[grainEnv, fmod_LFO, fmod_Env, iEnv, sig]