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.
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.getHelperFunctions;
self.getMultiChannelFunctions;
self.getOneShotRampFunctions;
self.getMaskingFunctions;
};
~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 accumulatorSubSample = { |trig, subSampleOffset|
var hasTriggered = PulseCount.ar(trig) > 0;
var accum = Duty.ar(SampleDur.ir, trig, Dseries(0, 1)) * hasTriggered;
accum + subSampleOffset;
};
self.helperFunctions.put(\rampToSlope, rampToSlope);
self.helperFunctions.put(\rampToTrig, rampToTrig);
self.helperFunctions.put(\subSampleOffset, getSubSampleOffset);
self.helperFunctions.put(\accumSubSample, accumulatorSubSample);
};
~getMultiChannelFunctions = { |self|
var multiChannelTrigger = { |numChannels, trig|
numChannels.collect{ |chan|
PulseDivider.ar(trig, numChannels, numChannels - 1 - chan);
};
};
var multiChannelAccumulator = { |triggers, subSampleOffsets|
triggers.collect{ |localTrig, i|
self.helperFunctions[\accumSubSample].(localTrig, subSampleOffsets[i]);
};
};
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 multiChannelBufRd = { |triggers, phase, arrayOfBuffers, numOfBuffers, repeatBuffer|
var demand = Ddup(repeatBuffer, Dseq([Dseries(0, 1, numOfBuffers)], inf));
var bufferIndex = triggers.collect{ |localTrig|
Demand.ar(localTrig, DC.ar(0), demand);
};
var playbufs = arrayOfBuffers.collect{ |buffer|
BufRd.ar(1, buffer, phase * BufFrames.kr(buffer), loop: 1, interpolation: 4);
};
Select.ar(bufferIndex, playbufs);
};
self.multiChannel.put(\trigger, multiChannelTrigger);
self.multiChannel.put(\accumSubSample, multiChannelAccumulator);
self.multiChannel.put(\dwhite, multiChannelDwhite);
self.multiChannel.put(\dseq, multiChannelDseq);
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, [K2A.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 = Dswitch1(arrayOfPositions,
Dser(Dseries(0, 1, numSpeakers), 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);
};
};
)
~grainFunctions = Prototype(\grainFunctions);