hey, you could check out this basic granulation attempt (make sure you got the latest update here for the necessary functions: A collection of functions for sub-sample accurate granulation and A collection of unit shapers).
The length of the buffer is normalized between 0 and 1.
You can change the looping segment with posLo
and posHi
.
Just for testing:
with \posLo, 0.50
and \posHi, 0.72
:
(
SynthDef(\grains, { |sndBuf|
var numChannels = 8;
var reset, tFreqMD, tFreqMod, tFreq, events;
var subSampleOffsets, triggers, accumulator;
var overlapMod, overlap, maxOverlap;
var windowSlopes, windowPhases, grainWindows;
var posRate, posRateMod, pos;
var rate, rateMod, grainPhases;
var panMF, panMod, pan;
var sigs, sig;
reset = Trig1.ar(\reset.tr(0), SampleDur.ir);
tFreqMD = \tFreqMD.kr(0, spec: ControlSpec(0, 2));
tFreqMod = LFDNoise3.ar(\tFreqMF.kr(1, spec: ControlSpec(0.1, 5)));
tFreq = \tFreq.kr(1, spec: ControlSpec(1, 1000, \exp));
tFreq = tFreq * (2 ** (tFreqMod * tFreqMD));
events = ~grainFunctions.eventsCircular[\events].(tFreq, reset);
///////////////////////////////////////////////////////////////////////////////////
// distribute triggers round-robin across the channels
triggers = ~grainFunctions.multiChannel[\trigger].(numChannels, events[\trigger]);
// calculate sub-sample offset per multichannel trigger
subSampleOffsets = ~grainFunctions.helperFunctions[\subSampleOffset].(
events[\phase],
events[\slope],
triggers
);
// create a multichannel accumulator with sub-sample accuracy
accumulator = ~grainFunctions.multiChannel[\accumSubSample].(triggers, subSampleOffsets);
///////////////////////////////////////////////////////////////////////////////////
overlap = \overlap.kr(1, spec: ControlSpec(0.125, numChannels));
overlapMod = ~grainFunctions.multiChannel[\dwhite].(triggers);
overlap = overlap * (2 ** (overlapMod * \overlapMD.kr(0, spec: ControlSpec(0, 1))));
maxOverlap = min(overlap, 2 ** tFreqMD.neg * numChannels);
windowSlopes = Latch.ar(events[\slope] / max(0.001, maxOverlap), triggers);
windowPhases = (windowSlopes * accumulator).clip(0, 1);
///////////////////////////////////////////////////////////////////////////////////
grainWindows = ~unitShapers.windowFunctions[\gaussian].(
windowPhases,
\windowSkew.kr(0.5, spec: ControlSpec(0, 1)),
\windowIndex.kr(0, spec: ControlSpec(0, 5)),
);
///////////////////////////////////////////////////////////////////////////////////
posRate = \posRate.kr(1, spec: ControlSpec(0.125, 4));
posRateMod = SinOsc.ar(\posRateMF.kr(1, spec: ControlSpec(0.01, 1)));
posRate = posRate + (posRate * posRateMod * \posRateMD.kr(0, spec: ControlSpec(0, 2)));
pos = Phasor.ar(
trig: DC.ar(0),
rate: posRate * BufRateScale.kr(sndBuf) * SampleDur.ir / BufDur.kr(sndBuf),
start: \posLo.kr(0, spec: ControlSpec(0, 1)),
end: \posHi.kr(1, spec: ControlSpec(0, 1))
);
///////////////////////////////////////////////////////////////////////////////////
rate = \rate.kr(1, spec: ControlSpec(0.125, 4));
rateMod = SinOsc.ar(\rateMF.kr(1, spec: ControlSpec(0.01, 1)));
rate = rate * (2 ** (rateMod * \rateMD.kr(0, spec: ControlSpec(0, 2))));
///////////////////////////////////////////////////////////////////////////////////
grainPhases = Latch.ar(rate, triggers) * accumulator;
grainPhases = grainPhases + (Latch.ar(pos, triggers) * BufFrames.kr(sndBuf));
sigs = BufRd.ar(
numChannels: 1,
bufnum: sndBuf,
phase: grainPhases,
loop: 1,
interpolation: 4
);
sigs = sigs * grainWindows;
///////////////////////////////////////////////////////////////////////////////////
// panning
panMF = \panMF.kr(0.3, spec: ControlSpec(0.01, 1));
panMod = { |phase|
SinOsc.ar(panMF, phase * pi)
};
pan = ~unitShapers.helperFunctions[\modScaleBipolar].(
modulator: panMod.(0.5),
value: \pan.kr(0.5, spec: ControlSpec(0, 1)),
amount: \panMD.kr(0, spec: ControlSpec(0, 1)),
direction: \full
);
sigs = PanAz.ar(2, sigs, pan.linlin(0, 1, -0.5, 0.5));
sig = sigs.sum;
///////////////////////////////////////////////////////////////////////////////////
sig = sig * ~unitShapers.helperFunctions[\modScaleBipolar].(
modulator: SinOsc.ar(\ampMF.kr(5, spec: ControlSpec(1, 10))),
value: \amp.kr(-25, spec: ControlSpec(-35, -5)).dbamp,
amount: \ampMD.kr(0, spec: ControlSpec(0, 1)),
direction: \down
);
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;
)
~buffer = //... load your sample here ...
(
Synth(\grains, [
\tFreq, 1000,
\tFreqMF, 0.1,
\tFreqMD, 0,
\overlap, 8,
\overlapMD, 0,
\windowSkew, 0.5,
\windowIndex, 0,
\rate, 1,
\rateMF, 0.1,
\rateMD, 0,
\posRate, 1.0,
\posRateMF, 1,
\posRateMD, 0,
\posLo, 0.50,
\posHi, 0.72,
\sndBuf, ~buffer,
\pan, 0.5,
\panMF, 0.1,
\panMD, 0,
\amp, -10,
\ampMF, 0.3,
\ampMD, 0,
\out, 0,
]);
)