hey, here is a collection of functions, which are handy for sub-sample accurate granulation. You can already find a collection of unit shapers in this post A collection of unit shapers. These are meant to be used together.
To use them you need the ProtoDef class by @elgiano GitHub - elgiano/ProtoDef: Prototyping classes for SuperCollider.
EDIT: and the VariableRamp
class from Oversampling Oscillators
You can find some examples of possible use cases below:
(
ProtoDef(\grainFunctions) {
~init = { |self|
self.helperFunctions = IdentityDictionary.new();
self.multiChannel = IdentityDictionary.new();
self.oneShotRamps = IdentityDictionary.new();
self.masking = IdentityDictionary.new();
self.eventsCircular = IdentityDictionary.new();
self.eventsOneShot = IdentityDictionary.new();
self.getHelperFunctions;
self.getMultiChannelFunctions;
self.getOneShotRampFunctions;
self.getMaskingFunctions;
self.getEventsCircular;
self.getEventsOneShot;
};
~getHelperFunctions = { |self|
var rampToSlope = { |phase|
var history = Delay1.ar(phase);
var delta = phase - history;
delta.wrap(-0.5, 0.5);
};
var rampToTrig = { |phase|
var history = Delay1.ar(phase);
var delta = phase - history;
var sum = phase + history;
var trig = (delta / sum).abs > 0.5;
Trig1.ar(trig, SampleDur.ir);
};
var getSubSampleOffset = { |phase, slope, trig|
var sampleCount = phase - (slope < 0) / slope;
Latch.ar(sampleCount, trig);
};
var accumSubSample = { |trig, subSampleOffset|
var hasTriggered = PulseCount.ar(trig) > 0;
var accum = Duty.ar(SampleDur.ir, trig, Dseries(0, 1)) * hasTriggered;
accum + subSampleOffset;
};
var rampSubSample = { |trig, slope, subSampleOffset|
var hasTriggered = PulseCount.ar(trig) > 0;
var accum = Sweep.ar(trig, slope * SampleRate.ir) * hasTriggered;
accum + (slope * subSampleOffset);
};
self.helperFunctions.put(\rampToSlope, rampToSlope);
self.helperFunctions.put(\rampToTrig, rampToTrig);
self.helperFunctions.put(\subSampleOffset, getSubSampleOffset);
self.helperFunctions.put(\accumSubSample, accumSubSample);
self.helperFunctions.put(\rampSubSample, rampSubSample);
};
~getMultiChannelFunctions = { |self|
var multiChannelTrigger = { |numChannels, trig|
numChannels.collect{ |chan|
PulseDivider.ar(trig, numChannels, numChannels - 1 - chan);
};
};
var multiChannelAccum = { |triggers, subSampleOffsets|
triggers.collect{ |localTrig, i|
self.helperFunctions[\accumSubSample].(localTrig, subSampleOffsets[i]);
};
};
var multiChannelRamp = { |triggers, slopes, subSampleOffsets|
triggers.collect{ |localTrig, i|
self.helperFunctions[\rampSubSample].(localTrig, slopes[i], subSampleOffsets[i]);
};
};
var multiChannelDwhite = { |triggers|
var demand = Dwhite(-1.0, 1.0);
triggers.collect{ |localTrig|
Demand.ar(localTrig, DC.ar(0), demand)
};
};
var multiChannelDbrown = { |triggers|
var demand = Dbrown(-1.0, 1.0, 0.01);
triggers.collect{ |localTrig|
Demand.ar(localTrig, DC.ar(0), 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 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 multiChannelDseries = { |triggers, reset, numOfItems, repeatItem|
var demand = Ddup(repeatItem, Dseq([Dseries(0, 1, numOfItems)], inf));
triggers.collect{ |localTrig|
Demand.ar(localTrig + reset, reset, demand)
};
};
var multiChannelBufRd = { |triggers, phases, arrayOfBuffers, numOfBuffers, repeatBuffer, loop|
var bufferIndex = multiChannelDseries.(triggers, DC.ar(0), numOfBuffers, repeatBuffer);
var playbufs = arrayOfBuffers.collect{ |buffer|
BufRd.ar(1, buffer, phases * BufFrames.kr(buffer), loop: loop, interpolation: 4);
};
Select.ar(bufferIndex, playbufs);
};
self.multiChannel.put(\trigger, multiChannelTrigger);
self.multiChannel.put(\accumSubSample, multiChannelAccum);
self.multiChannel.put(\rampSubSample, multiChannelRamp);
self.multiChannel.put(\dwhite, multiChannelDwhite);
self.multiChannel.put(\dbrown, multiChannelDbrown);
self.multiChannel.put(\dxrand, multiChannelDxrand);
self.multiChannel.put(\dseq, multiChannelDseq);
self.multiChannel.put(\dseries, multiChannelDseries);
self.multiChannel.put(\bufrd, multiChannelBufRd);
};
~getOneShotRampFunctions = { |self|
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 rampOneShot = { |trig, duration, cycles = 1|
var hasTriggered = PulseCount.ar(trig) > 0;
var phase = Sweep.ar(trig, 1 / duration).clip(0, cycles);
phase * hasTriggered;
};
var oneShotRampToTrig = { |phase|
var compare = phase > 0;
var delta = HPZ1.ar(compare);
delta > 0;
};
var oneShotBurstToTrig = { |phaseScaled|
var phaseStepped = phaseScaled.ceil;
var delta = HPZ1.ar(phaseStepped);
delta > 0;
};
self.oneShotRamps.put(\subDivs, getSubDivs);
self.oneShotRamps.put(\rampOneShot, rampOneShot);
self.oneShotRamps.put(\rampToTrig, oneShotRampToTrig);
self.oneShotRamps.put(\burstToTrig, oneShotBurstToTrig);
};
~getMaskingFunctions = { |self|
var triggerMask = { |trig, maskOn, arrayOfBinaries, numOfBinaries|
var demand = Dseq([Dser(arrayOfBinaries, numOfBinaries)], inf);
var triggerMask = Demand.ar(trig, DC.ar(0), demand);
trig * Select.ar(maskOn, [DC.ar(1), triggerMask]);
};
var burstMask = { |trig, burst = 16, rest = 0|
var demand = Dseq([Dser([1], burst), Dser([0], rest)], inf);
trig * Demand.ar(trig, DC.ar(0), demand);
};
var probabilityMask = { |trig, prob = 1|
trig * CoinGate.ar(prob, trig);
};
var channelMask = { |trig, channelMask, centerMask, numSpeakers = 2|
var arrayOfPositions = Array.series(numSpeakers, -1 / numSpeakers, 2 / numSpeakers).wrap(-1.0, 1.0);
var channelPos = arrayOfPositions.collect { |pos| Dser([pos], channelMask) };
Demand.ar(trig, DC.ar(0), Dseq(channelPos ++ Dser([0], centerMask), inf));
};
self.masking.put(\trigger, triggerMask);
self.masking.put(\burst, burstMask);
self.masking.put(\probability, probabilityMask);
self.masking.put(\channel, channelMask);
};
~getEventsCircular = { |self|
var eventData = { |rate, reset|
var eventPhase, eventSlope, eventTrigger;
eventPhase = VariableRamp.ar(rate, reset);
eventSlope = self.helperFunctions[\rampToSlope].(eventPhase);
eventPhase = Delay1.ar(eventPhase);
eventTrigger = self.helperFunctions[\rampToTrig].(eventPhase);
(
phase: eventPhase,
slope: eventSlope,
trigger: eventTrigger
);
};
self.eventsCircular.put(\events, eventData);
};
~getEventsOneShot = { |self|
var eventData = { |initTrigger, duration, arrayOfSubDivs, numOfSubDivs|
var seqOfSubDivs, eventPhaseScaled, eventPhase, eventSlope, eventTrigger;
seqOfSubDivs = self.oneShotRamps[\subDivs].(
initTrigger,
arrayOfSubDivs,
numOfSubDivs,
duration
);
eventPhaseScaled = self.oneShotRamps[\rampOneShot].(
initTrigger,
seqOfSubDivs,
numOfSubDivs
);
eventTrigger = self.oneShotRamps[\burstToTrig].(eventPhaseScaled);
eventPhase = eventPhaseScaled.wrap(0, 1);
eventSlope = self.helperFunctions[\rampToSlope].(eventPhase);
(
phase: eventPhase,
slope: eventSlope,
trigger: eventTrigger
);
};
var measureData = { |initTrigger, duration|
var measurePhase, measureSlope, measureTrigger;
measurePhase = self.oneShotRamps[\rampOneShot].(initTrigger, duration);
measureSlope = self.helperFunctions[\rampToSlope].(measurePhase);
measureTrigger = self.oneShotRamps[\rampToTrig].(measurePhase);
(
phase: measurePhase,
slope: measureSlope,
trigger: measureTrigger
);
};
self.eventsOneShot.put(\events, eventData);
self.eventsOneShot.put(\measure, measureData);
};
};
)
~grainFunctions = Prototype(\grainFunctions);