GrainUtils - sub-sample accurate EventScheduler and dynamic VoiceAllocator

now you can use the RampIntegrator and the WindowFunctions in the following example to do FM and PM for the grainPhases:

(
var multiChannelDwhite = { |triggers|
	var demand = Dwhite(-1.0, 1.0);
	triggers.collect{ |localTrig|
		Demand.ar(localTrig, DC.ar(0), demand)
	};
};

{
	var numChannels = 8;

	var reset, tFreqMD, tFreq;
	var overlapMD, overlap;
	var events, voices, windowPhases, triggers;

	var grainFreqMod, grainFreqs, grainPhases, grainWindows;
	var grainOscs, grains, sig;
	var fmods, modPhases, pmods;

	reset = Trig1.ar(\reset.tr(0), SampleDur.ir);

	tFreqMD = \tFreqMD.kr(1);
	tFreq = \tFreq.kr(10) * (2 ** (SinOsc.ar(0.3) * tFreqMD));

	overlapMD = \overlapMD.kr(1);
	overlap = \overlap.kr(2) * (2 ** (LFDNoise3.ar(0.1) * overlapMD));

	events = EventScheduler.ar(triggerRate: tFreq, reset: reset);

	voices = VoiceAllocator.ar(
		numChannels: numChannels,
		trig: events[0],
		rate: events[1] / overlap,
		subSampleOffset: events[2],
	);
	windowPhases = voices[0..numChannels - 1];
	triggers = voices[numChannels..numChannels * 2 - 1];

	grainWindows = HanningWindow.ar(windowPhases, \skew.kr(0.05));

	grainFreqMod = multiChannelDwhite.(triggers);
	grainFreqs = \freq.kr(440) * (2 ** (grainFreqMod * \freqMD.kr(1)));

	fmods = ExponentialWindow.ar(windowPhases, \pitchSkew.kr(0.03), \pitchShape.kr(0));

	grainPhases = RampIntegrator.ar(
		trig: triggers,
		rate: grainFreqs * (1 + (fmods * \pitchMD.kr(2))),
		subSampleOffset: events[2]
	);

	modPhases = RampIntegrator.ar(
		trig: triggers,
		rate: grainFreqs * \pmRatio.kr(1.5),
		subSampleOffset: events[2]
	);
	pmods = SinOsc.ar(DC.ar(0), modPhases * 2pi);

	grainPhases = (grainPhases + (pmods * \pmIndex.kr(1))).wrap(0, 1);

	grainOscs = SinOsc.ar(DC.ar(0), grainPhases * 2pi);
	grains = grainOscs * grainWindows;

	grains = PanAz.ar(2, grains, \pan.kr(0));
	sig = grains.sum;

	sig = LeakDC.ar(sig);

	sig * 0.1;

}.play;
)

here via the plot:

(
var multiChannelDwhite = { |triggers|
	var demand = Dwhite(-1.0, 1.0);
	triggers.collect{ |localTrig|
		Demand.ar(localTrig, DC.ar(0), demand)
	};
};

{
	var numChannels = 5;

	var reset, tFreqMD, tFreq;
	var overlapMD, overlap;
	var events, voices, phases, triggers;
	var grainFreqMod, grainFreqs, grainPhases, grainWindows;
	var grainOscs, grains;

	reset = Trig1.ar(\reset.tr(0), SampleDur.ir);

	tFreqMD = \tFreqMD.kr(0);
	tFreq = \tFreq.kr(400) * (2 ** (SinOsc.ar(50) * tFreqMD));

	overlapMD = \overlapMD.kr(0);
	overlap = \overlap.kr(5) * (2 ** (SinOsc.ar(50) * overlapMD));

	events = EventScheduler.ar(tFreq, reset);

	voices = VoiceAllocator.ar(
		numChannels: numChannels,
		trig: events[0],
		rate: events[1] / overlap,
		subSampleOffset: events[2],
	);
	phases = voices[0..numChannels - 1];
	triggers = voices[numChannels..numChannels * 2];

	grainWindows = HanningWindow.ar(phases, \skew.kr(0.5));

	grainFreqMod = multiChannelDwhite.(triggers);
	grainFreqs = \freq.kr(800) * (2 ** (grainFreqMod * \freqMD.kr(2)));

	grainPhases = RampIntegrator.ar(
		trig: triggers,
		rate: grainFreqs,
		subSampleOffset: events[2]
	);
	
	grainOscs = SinOsc.ar(DC.ar(0), grainPhases * 2pi);
	grains = grainOscs * grainWindows;

}.plot(0.041);
)
1 Like