@Moddmo said:
I guess a better question is if pd has disadvantages here compared to other DSP programming languages.
Granular synthesis is sometimes imagined as this magical, complex thing, but the fundamentals are quite simple. Get the fundamentals right, and the sound quality follows from that (and is mainly a matter of parameter tuning).
So you're playing back a block of audio from a buffer or a delay line. (I used a delay line because that's the best way to implement a circular buffer in Pd vanilla. You could also use cyclone [count~] to generate phase for use with [tabwrite~] and [tabread4~].) The important points here are to get the boundaries of the audio segment correct, and to modulate the starting position intelligently. "Boundaries" includes concepts of: how many grains per second should be triggered, how many of them overlap, how fast will the audio be played.
And each grain needs an envelope matching the grain duration.
In my example, I've already tuned it for one specific use case (pitch shifting). But there's no magic here -- it really is just linear audio playback with envelopes, overlapped and added.
Comparison with other DSP environments, then, is just a matter of implementation. E.g., SuperCollider has UGens (single objects) TGrains and GrainBuf that do the audio segment and envelope and overlap/add for you, so that you can concentrate on the parameters:
s.boot; // sort of like "; pd dsp 1"
(
var rateSl;
a = { |inbus, rate = 1, trigFreq = 66.66667, overlap = 4|
var sr = SampleRate.ir;
var maxDelaySamps = 2 * sr;
var src = In.ar(inbus, 1);
// "delwrite" part (rolling my own circular buffer)
var buf = LocalBuf(maxDelaySamps, 1).clear;
var phase = Phasor.ar(0, 1, 0, maxDelaySamps);
var writer = BufWr.ar(src, buf, phase);
// *all* of the rest of it
var trig = Impulse.ar(trigFreq);
var grainDur = overlap / trigFreq;
var delayBound = max(0, grainDur * (rate - 1));
// grain position in samples, for now
var pos = phase - (sr * (delayBound + TRand.ar(0.0, 0.003, trig)));
GrainBuf.ar(2,
trig, grainDur, buf,
rate,
pos: (pos / maxDelaySamps) % 1.0, // normalized pos in GrainBuf
interp: 4 // cubic
) * 0.4;
}.play(args: [inbus: s.options.numOutputBusChannels]);
rateSl = Slider(nil, Rect(800, 200, 200, 50))
.value_(0.5)
.orientation_(\horizontal)
.action_({ |view|
a.set(\rate, view.value.linexp(0, 1, 0.25, 4))
})
.onClose_({ a.free })
.front
)
The DSP design here is the same as in the Pd patch (rate-scaled audio segments under Hann windows [GrainBuf gives you Hann windows for free], with a 3 ms randomized timing offset for each grain) so the sound should be basically identical. Personally I find the SC way to be easier to read and write, but I wouldn't expect everyone on a graphical patching forum to feel the same 
hjh