still work in progress, but you can check out the latest release here:
and test the current version with these plots and examples below (the SchedulerCycle is crisp now, phase starting at 0 and you still get an initial trigger).
EDIT: You can now search for “Event Scheduling” in the help browser and should find the first part of the guide there.
// ===== SHIFT REGISTER =====
(
{
var trig = Impulse.ar(1000);
var register = ShiftRegister.ar(
trig: trig,
chance: 1.0,
length: 8,
rotate: 1,
reset: 0,
);
[
register[\bit3],
register[\bit8]
];
}.plot(0.021).plotMode_(\plines);
)
///////////////////////////////////////////////////////////////////////////////////////
// ===== SCHEDULER CYCLE =====
(
{
//var rate = 1000 * (2 ** (SinOsc.ar(50) * 1));
var rate = 1000;
var events = SchedulerCycle.ar(rate);
[
events[\phase],
//events[\rate] / 1000,
events[\trigger],
//events[\subSampleOffset],
];
}.plot(0.0021).plotMode_(\plines);
)
// random durations
(
var getSubDivs = { |rate, randomness|
var subDiv = Ddup(2, (2 ** (Dwhite(-1.0, 1.0) * randomness))) / rate;
Duty.ar(subDiv, DC.ar(0), 1 / subDiv);
};
{
var subDiv, events;
subDiv = getSubDivs.(\tFreq.kr(500), \randomness.kr(1));
events = SchedulerCycle.ar(subDiv);
events[\phase];
}.plot(0.021).plotMode_(\plines);
)
// sequence of durations
(
var getSubDivs = { |rate, arrayOfDurations, numOfDurations|
var subDiv = Ddup(2, Dseq([Dser(arrayOfDurations, numOfDurations)], inf)) / rate;
Duty.ar(subDiv, DC.ar(0), 1 / subDiv);
};
{
var arrayOfDurations, numOfDurations, subDiv, events;
arrayOfDurations = [5, 1, 2, 4];
numOfDurations = 4;
subDiv = getSubDivs.(\tFreq.kr(500), arrayOfDurations, numOfDurations);
events = SchedulerCycle.ar(subDiv);
events[\phase];
}.plot(0.021).plotMode_(\plines);
)
// euclidean durations (needs Dbjorklund2 from f0 plugins)
(
var getSubDivs = { |rate, numHits, numSize, offSet|
var subDiv = Ddup(2, Dbjorklund2(numHits, numSize, offSet)) / rate;
Duty.ar(subDiv, DC.ar(0), 1 / subDiv);
};
{
var numHits, numSize, offSet, subDivs, events;
numHits = \numHits.kr(5);
numSize = \numSize.kr(8);
offSet = \offSet.kr(0);
subDivs = getSubDivs.(\tFreq.kr(500), numHits, numSize, offSet);
events = SchedulerCycle.ar(subDivs);
events[\phase];
}.plot(0.021).plotMode_(\plines);
)
///////////////////////////////////////////////////////////////////////////////////////
// ===== SCHEDULER BURST =====
// sequence of durations
(
var numOfSubDivs = 3;
var arrayOfSubDivs = [5, 1, 3].normalizeSum;
var getSubDivs = { |trig, arrayOfSubDivs, numOfSubDivs, duration|
var hasTriggered = PulseCount.ar(trig) > 0;
var subDiv = Ddup(2, Dseq(arrayOfSubDivs, numOfSubDivs)) * duration;
Duty.ar(subDiv, trig, subDiv) * hasTriggered;
};
{
var initTrigger, subDivs, events;
//initTrigger = Impulse.ar(50);
initTrigger = Trig1.ar(\trig.tr(1), SampleDur.ir);
subDivs = getSubDivs.(initTrigger, arrayOfSubDivs, numOfSubDivs, \sustain.kr(0.02));
events = SchedulerBurst.ar(
trig: initTrigger,
duration: subDivs,
cycles: numOfSubDivs
);
events[\phase];
}.plot(0.041).plotMode_(\plines);
)
// sequence of durations with voice allocation (plot)
(
var numOfSubDivs = 4;
var arrayOfSubDivs = [5, 1, 8, 3].normalizeSum;
var getSubDivs = { |trig, arrayOfSubDivs, numOfSubDivs, duration|
var hasTriggered = PulseCount.ar(trig) > 0;
var subDiv = Ddup(2, Dseq(arrayOfSubDivs, numOfSubDivs)) * duration;
Duty.ar(subDiv, trig, subDiv) * hasTriggered;
};
{
var numChannels = 5;
var initTrigger, subDivs, events, voices;
//initTrigger = Impulse.ar(50);
initTrigger = Trig1.ar(\trig.tr(1), SampleDur.ir);
subDivs = getSubDivs.(initTrigger, arrayOfSubDivs, numOfSubDivs, \sustain.kr(0.02));
events = SchedulerBurst.ar(
trig: initTrigger,
duration: subDivs,
cycles: numOfSubDivs
);
/*
[
events[\phase],
events[\trigger],
//events[\subSampleOffset]
];
*/
voices = VoiceAllocator.ar(
numChannels: numChannels,
trig: events[\trigger],
rate: events[\rate] / \overlap.kr(1),
subSampleOffset: events[\subSampleOffset]
);
voices[\phases];
}.plot(0.041).plotMode_(\plines);
)
// sequence of durations with voice allocation (example)
(
var getSubDivs = { |trig, arrayOfSubDivs, numOfSubDivs, duration|
var hasTriggered = PulseCount.ar(trig) > 0;
var subDiv = Ddup(2, Dseq(arrayOfSubDivs, numOfSubDivs)) * duration;
Duty.ar(subDiv, trig, subDiv) * hasTriggered;
};
SynthDef(\burst, {
var numChannels = 8;
var initTrigger, subDivs, numOfSubDivs, arrayOfSubDivs;
var duration, events, voices;
var grainPhases, grainWindows;
var sigs, sig;
initTrigger = Trig1.ar(\trig.tr(0), SampleDur.ir);
duration = \sustain.kr(1);
arrayOfSubDivs = \arrayOfSubDivs.kr(Array.fill(16, 1));
numOfSubDivs = \numOfSubDivs.kr(16);
subDivs = getSubDivs.(
initTrigger,
arrayOfSubDivs,
numOfSubDivs,
duration
);
events = SchedulerBurst.ar(
trig: initTrigger,
duration: subDivs,
cycles: numOfSubDivs
);
voices = VoiceAllocator.ar(
numChannels: numChannels,
trig: events[\trigger],
rate: events[\rate] / \overlap.kr(1),
subSampleOffset: events[\subSampleOffset]
);
grainWindows = ExponentialWindow.ar(
voices[\phases],
\windowSkew.kr(0.01),
\windowShape.kr(0)
);
grainPhases = RampIntegrator.ar(
trig: voices[\triggers],
rate: \freq.kr(440),
subSampleOffset: events[\subSampleOffset]
);
sigs = SinOsc.ar(DC.ar(0), grainPhases * 2pi);
sigs = sigs * grainWindows;
sigs = PanAz.ar(2, sigs, \pan.kr(0));
sig = sigs.sum;
sig = sig * \amp.kr(-15).dbamp;
sig = sig * Env.asr(0.001, 1, 0.001).ar(Done.freeSelf, \gate.kr(1));
sig = LeakDC.ar(sig);
sig = Limiter.ar(sig);
Out.ar(\out.kr(0), sig);
}).add;
)
// Pmono
(
//var arrayOfSubDivs = Array.fill(12, { 2 ** rrand(-1.0, 1.0) } ).normalizeSum;
//arrayOfSubDivs.debug(\arrayOfSubDivs);
var arrayOfSubDivs = [0.090043825988599, 0.10094508851196, 0.059173625247572, 0.078758510686157, 0.057549414766475, 0.085870514810413, 0.10684511179581, 0.082087147023147, 0.1207103511832, 0.047718306079275, 0.12704484171827, 0.043253262189117];
Pdef(\burst,
Pmono(\burst,
\trig, 1,
\legato, 0.8,
\dur, 4,
\freq, 440,
\overlap, 1,
\time, Pfunc { |ev| ev.use { ~sustain.value } / thisThread.clock.tempo },
\arrayOfSubDivs, [arrayOfSubDivs],
\numOfSubDivs, 12,
\amp, -15,
\out, 0,
),
).play;
)
// Routines
(
//var arrayOfSubDivs = Array.fill(12, { 2 ** rrand(-1.0, 1.0) } ).normalizeSum;
//arrayOfSubDivs.debug(\arrayOfSubDivs);
var arrayOfSubDivs = [0.090043825988599, 0.10094508851196, 0.059173625247572, 0.078758510686157, 0.057549414766475, 0.085870514810413, 0.10684511179581, 0.082087147023147, 0.1207103511832, 0.047718306079275, 0.12704484171827, 0.043253262189117];
Routine({
s.bind {
~synth = Synth(\burst, [
\trig, 0,
\freq, 440,
\overlap, 1,
\amp, -15,
\out, 0,
]);
};
s.sync;
loop {
s.bind {
~synth.set(
\trig, 1,
\sustain, 3.2,
\arrayOfSubDivs, arrayOfSubDivs,
\numOfSubDivs, 12,
);
};
4.wait;
};
}).play;
)
/////////////////////////////////////////////////////////////////////////////////////
// ===== SCHEDULER CYCLE & VOICE ALLOCATOR =====
// plot multichannel grain frequencies
(
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;
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 = SchedulerCycle.ar(tFreq, reset);
voices = VoiceAllocator.ar(
numChannels: numChannels,
trig: events[\trigger],
rate: events[\rate] / overlap,
subSampleOffset: events[\subSampleOffset],
);
grainWindows = HanningWindow.ar(voices[\phases], \skew.kr(0.5));
grainFreqMod = multiChannelDwhite.(voices[\triggers]);
grainFreqs = \freq.kr(800) * (2 ** (grainFreqMod * \freqMD.kr(2)));
grainPhases = RampIntegrator.ar(
trig: voices[\triggers],
rate: grainFreqs,
subSampleOffset: events[\subSampleOffset]
);
grainOscs = SinOsc.ar(DC.ar(0), grainPhases * 2pi);
grains = grainOscs * grainWindows;
}.plot(0.041);
)
// play multichannel grain frequencies + panning
(
var multiChannelDwhite = { |triggers|
var demand = Dwhite(-1.0, 1.0);
triggers.collect{ |localTrig|
Demand.ar(localTrig, DC.ar(0), demand)
};
};
var multiChannelDseq = { |triggers, reset, arrayOfItems, numOfItems, repeatItem|
var demand = Ddup(repeatItem, Dseq([Dser(arrayOfItems, numOfItems)], inf));
triggers.collect{ |localTrig|
Demand.ar(localTrig + reset, reset, demand)
};
};
var multiChannelDxrand = { |triggers, reset, arrayOfItems, numOfItems, repeatItem|
var demand = Ddup(repeatItem, Dxrand([Dser(arrayOfItems, numOfItems)], inf));
triggers.collect{ |localTrig|
Demand.ar(localTrig + reset, reset, demand)
};
};
var tuning = Tuning.new((0..12) * (3.ratiomidi / 13), 3.0, "Bohlen-Pierce").ratios;
var degrees = { rrand(0, 12) } ! 8;
var ratios = degrees.collect{ |degree| tuning[degree] };
{
var numChannels = 8;
var numSpeakers = 2;
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;
var trans, octave, note, pan, demand;
reset = Trig1.ar(\reset.tr(0), SampleDur.ir);
tFreqMD = \tFreqMD.kr(0);
tFreq = \tFreq.kr(10) * (2 ** (LFDNoise3.ar(0.3) * tFreqMD));
overlapMD = \overlapMD.kr(0);
overlap = \overlap.kr(4) * (2 ** (LFDNoise3.ar(0.1) * overlapMD));
events = SchedulerCycle.ar(tFreq, reset);
voices = VoiceAllocator.ar(
numChannels: numChannels,
trig: events[\trigger],
rate: events[\rate] / overlap,
subSampleOffset: events[\subSampleOffset],
);
grainWindows = HanningWindow.ar(
phase: voices[\phases],
skew: 0.03,
);
trans = multiChannelDxrand.(
voices[\triggers],
DC.ar(0),
[0, 2, -2, 7, -5],
5,
1
);
octave = multiChannelDxrand.(
voices[\triggers],
DC.ar(0),
[12, -12],
2,
1
);
grainFreqMod = multiChannelDseq.(
voices[\triggers],
DC.ar(0),
ratios,
\numOfItems.kr(8),
\repeatItem.kr(2)
);
grainFreqs = (89 + trans + octave).midicps * grainFreqMod;
grainPhases = RampIntegrator.ar(
trig: voices[\triggers],
rate: grainFreqs,
subSampleOffset: events[\subSampleOffset]
);
grainOscs = SinOsc.ar(DC.ar(0), grainPhases * 2pi);
grains = grainOscs * grainWindows;
pan = multiChannelDwhite.(voices[\triggers]);
grains = PanAz.ar(
numChans: numSpeakers,
in: grains,
pos: pan.linlin(-1, 1, -1 / numSpeakers, (2 * numSpeakers - 3) / numSpeakers);
);
sig = grains.sum;
sig = LeakDC.ar(sig);
sig * 0.1;
}.play
)
// play multichannel grain frequencies + panning 2
(
var multiChannelDwhite = { |triggers|
var demand = Dwhite(-1.0, 1.0);
triggers.collect{ |localTrig|
Demand.ar(localTrig, DC.ar(0), demand)
};
};
{
var numChannels = 8;
var numSpeakers = 2;
var reset, tFreq;
var events, voices, windowPhases;
var grainFreqMod, grainFreqs, grainPhases, grainWindows;
var grainOscs, grains, sig, pan;
reset = Trig1.ar(\reset.tr(0), SampleDur.ir);
tFreq = \tFreq.kr(12);
events = SchedulerCycle.ar(tFreq, reset);
voices = VoiceAllocator.ar(
numChannels: numChannels,
trig: events[\trigger],
rate: events[\rate] / \overlap.kr(0.5),
subSampleOffset: events[\subSampleOffset],
);
grainWindows = HanningWindow.ar(
phase: voices[\phases],
skew: \skew.kr(0.03)
);
grainFreqMod = multiChannelDwhite.(voices[\triggers]);
grainFreqs = \freq.kr(800) * (2 ** (grainFreqMod * \freqMD.kr(2)));
grainPhases = RampIntegrator.ar(
trig: voices[\triggers],
rate: grainFreqs,
subSampleOffset: events[\subSampleOffset]
);
grainOscs = SinOsc.ar(DC.ar(0), grainPhases * 2pi);
grains = grainOscs * grainWindows;
pan = multiChannelDwhite.(voices[\triggers]);
grains = PanAz.ar(
numChans: numSpeakers,
in: grains,
pos: pan.linlin(-1, 1, -1 / numSpeakers, (2 * numSpeakers - 3) / numSpeakers);
);
sig = grains.sum;
sig = LeakDC.ar(sig);
sig = sig * 0.1;
}.play;
)
// demonstration of multichnanel FM & PM
(
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(2);
tFreq = \tFreq.kr(10) * (2 ** (SinOsc.ar(0.3) * tFreqMD));
overlapMD = \overlapMD.kr(0);
overlap = \overlap.kr(1) * (2 ** (LFDNoise3.ar(0.1) * overlapMD));
events = SchedulerCycle.ar(tFreq, reset);
voices = VoiceAllocator.ar(
numChannels: numChannels,
trig: events[\trigger],
rate: events[\rate] / overlap,
subSampleOffset: events[\subSampleOffset],
);
grainWindows = HanningWindow.ar(
phase: voices[\phases],
skew: \skew.kr(0.05)
);
grainFreqMod = multiChannelDwhite.(voices[\triggers]);
grainFreqs = \freq.kr(440) * (2 ** (grainFreqMod * \freqMD.kr(1)));
fmods = ExponentialWindow.ar(
phase: voices[\phases],
skew: \pitchSkew.kr(0.03),
shape: \pitchShape.kr(0)
);
grainPhases = RampIntegrator.ar(
trig: voices[\triggers],
rate: grainFreqs * (1 + (fmods * \pitchMD.kr(0))),
subSampleOffset: events[\subSampleOffset]
);
modPhases = RampIntegrator.ar(
trig: voices[\triggers],
rate: grainFreqs * \pmRatio.kr(1.5),
subSampleOffset: events[\subSampleOffset]
);
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 = sig * 0.1;
}.play
)
// binding grain duration to grain frequency (pulsar synthesis) with phase shaping
(
var lfo = {
var measurePhase = Phasor.ar(DC.ar(0), \rate.kr(0.5) * SampleDur.ir);
var stepPhase = (measurePhase * \stepsPerMeasure.kr(2)).wrap(0, 1);
var measureLFO = HanningWindow.ar(measurePhase, \skewA.kr(0.75));
var stepLFO = GaussianWindow.ar(stepPhase, \skewB.kr(0.5), \index.kr(1));
stepLFO * measureLFO;
};
{
var numChannels = 8;
var reset, flux, tFreqMod, tFreq, windowRatio;
var events, voices, windowPhases, triggers;
var grainFreq, grainPhases, grainWindows;
var grainOscs, grains, sig;
reset = Trig1.ar(\reset.tr(0), SampleDur.ir);
flux = LFDNoise3.ar(\fluxMF.kr(1));
flux = 2 ** (flux * \fluxMD.kr(0.5));
tFreqMod = lfo.().linlin(0, 1, 1, 50);
tFreq = \tFreq.kr(20) * flux * tFreqMod;
grainFreq = \freq.kr(1200) * flux;
windowRatio = \windowRatio.ar(5); // this has to be audio rate, we will latch that later
events = SchedulerCycle.ar(tFreq, reset);
voices = VoiceAllocator.ar(
numChannels: numChannels,
trig: events[\trigger],
rate: grainFreq / windowRatio, // grain duration depending on grainFreq scaled by windowRatio
subSampleOffset: events[\subSampleOffset],
);
grainWindows = HanningWindow.ar(
phase: voices[\phases],
skew: \skew.kr(0.01)
);
// phase shaping for a frequency trajectory per grain:
// using normalized windowPhases into UnitCubic,
// then scaling to number of cycles by windowRatio before wrapping between 0 and 1
// important to latch windowRatio per trigger here!!!
grainPhases = UnitCubic.ar(voices[\phases], \shape.kr(0.45));
grainPhases = (grainPhases * Latch.ar(windowRatio, voices[\triggers])).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 = sig * 0.1;
}.play;
)
//////////////////////////////////////////////////////////////////////////////////
// ===== UNIT SHAPERS =====
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
UnitKink.ar(phase, \skew.kr(0.25));
}.plot(0.02);
)
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
UnitTriangle.ar(phase, \skew.kr(0.5));
}.plot(0.02);
)
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
UnitCubic.ar(phase, \index.kr(0.5));
}.plot(0.02);
)
// ===== WINDOW FUNCTIONS =====
// warped hanning window
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
HanningWindow.ar(phase, \skew.kr(0.5));
}.plot(0.02);
)
// warped raised cosine window
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
RaisedCosWindow.ar(phase, \skew.kr(0.5), \index.kr(5));
}.plot(0.02);
)
// warped gaussian window
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
GaussianWindow.ar(phase, \skew.kr(0.5), \index.kr(5));
}.plot(0.02);
)
// warped trapezoidal window
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
TrapezoidalWindow.ar(phase, \skew.kr(0.5), \width.kr(0.5), \duty.kr(1));
}.plot(0.02);
)
// warped tukey window
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
TukeyWindow.ar(phase, \skew.kr(0.5), \width.kr(0.5));
}.plot(0.02);
)
// warped exponential window
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
ExponentialWindow.ar(phase, \skew.kr(0.5), \shape.kr(0));
}.plot(0.02);
)
// ===== INTERP FUNCTIONS =====
// linear interpolation of quintic in and quintic out
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
var sigA = JCurve.ar(phase, \shapeA.kr(0));
var sigB = JCurve.ar(phase, \shapeB.kr(0.5));
var sigC = JCurve.ar(phase, \shapeC.kr(1));
[sigA, sigB, sigC];
}.plot(0.02).superpose_(true).plotColor_([Color.red, Color.blue, Color.magenta]);
)
// linear interpolation of quintic sigmoid and quintic seat
(
{
var phase = Phasor.ar(DC.ar(0), 50 * SampleDur.ir);
var sigA = SCurve.ar(phase, \shapeA.kr(0), \inflectionA.kr(0.25));
var sigB = SCurve.ar(phase, \shapeB.kr(0.5), \inflectionB.kr(0.50));
var sigC = SCurve.ar(phase, \shapeC.kr(1), \inflectionC.kr(0.75));
[sigA, sigB, sigC];
}.plot(0.02).superpose_(true).plotColor_([Color.red, Color.blue, Color.magenta]);
)