{
   "name" : "Paulstretch for SuperCollider",
   "author" : "jpdrecourt",
   "description" : "This is a port of the basic Paulstretch algorithm to SuperCollider (no onset detection). Mono version, for stereo, use 2 instances hard panned. The sound buffer needs to be mono too, so use Buffer.readChannel to extract separate channels.\r\nThe stretch parameter is modulatable. That allows for phasing effects if using more than one instance.\r\n\r\nThanks to Paul for his feedback!\r\nCheck his work at http://www.paulnasca.com/",
   "ancestor_list" : [],
   "labels" : [
      "ambient",
      "drone",
      "stretching"
   ],
   "is_private" : null,
   "id" : "1-5d6",
   "code" : "(\r\nSynthDef(\\paulstretchMono, { |out = 0, bufnum, envBufnum, pan = 0, stretch = 50, window = 0.25, amp = 1|\r\n\t// Paulstretch for SuperCollider\r\n\t// Based on the Paul's Extreme Sound Stretch algorithm by Nasca Octavian PAUL\r\n\t// https://github.com/paulnasca/paulstretch_python/blob/master/paulstretch_steps.png\r\n\t//\r\n\t// By Jean-Philippe Drecourt\r\n\t// http://drecourt.com\r\n\t// April 2020\r\n\t//\r\n\t// Arguments:\r\n\t// out: output bus (stereo output)\r\n\t// bufnum: the sound buffer. Must be Mono. (Use 2 instances with Buffer.readChannel for stereo)\r\n\t// envBufnum: The grain envelope buffer created as follows:\r\n\t//// envBuf = Buffer.alloc(s, s.sampleRate, 1);\r\n\t//// envSignal = Signal.newClear(s.sampleRate).waveFill({|x| (1 - x.pow(2)).pow(1.25)}, -1.0, 1.0);\r\n\t//// envBuf.loadCollection(envSignal);\r\n\t// pan: Equal power panning, useful for stereo use.\r\n\t// stretch: stretch factor (modulatable)\r\n\t// window: the suggested grain size, will be resized to closest fft window size\r\n\t// amp: amplification\r\n\tvar trigPeriod, sig, chain, trig, pos, fftSize;\r\n\t// Calculating fft buffer size according to suggested window size\r\n\tfftSize = 2**floor(log2(window*SampleRate.ir));\r\n\t// Grain parameters\r\n\t// The grain is the exact length of the FFT window\r\n\ttrigPeriod = fftSize/SampleRate.ir;\r\n\ttrig = Impulse.ar(1/trigPeriod);\r\n\tpos = Demand.ar(trig, 0, demandUGens: Dseries(0, trigPeriod/stretch));\r\n\t// Extraction of 2 consecutive grains\r\n\t// Both grains need to be treated together for superposition afterwards\r\n\tsig = [GrainBuf.ar(1, trig, trigPeriod, bufnum, 1, pos, envbufnum: envBufnum),\r\n\t\tGrainBuf.ar(1, trig, trigPeriod, bufnum, 1, pos + (trigPeriod/(2*stretch)), envbufnum: envBufnum)]*amp;\r\n\t// FFT magic\r\n\tsig = sig.collect({ |item, i|\r\n\t\tchain = FFT(LocalBuf(fftSize), item, hop: 1.0, wintype: -1);\r\n\t\t// PV_Diffuser is only active if its trigger is 1\r\n\t\t// And it needs to be reset for each grain to get the smooth envelope\r\n\t\tchain = PV_Diffuser(chain, 1 - trig);\r\n\t\titem = IFFT(chain, wintype: -1);\r\n\t});\r\n\t// Reapply the grain envelope because the FFT phase randomization removes it\r\n\tsig = sig*PlayBuf.ar(1, envBufnum, 1/(trigPeriod), loop:1);\r\n\t// Delay second grain by half a grain length for superposition\r\n\tsig[1] = DelayC.ar(sig[1], trigPeriod/2, trigPeriod/2);\r\n\t// Panned output\r\n\tOut.ar(out, Pan2.ar(Mix.new(sig), pan));\r\n}).add;\r\n)\r\n\r\n// Example\r\n({\r\n\tvar envBuf, envSignal, buffer;\r\n\tbuffer = Buffer.read(s, Platform.resourceDir +/+ \"sounds/a11wlk01.wav\");\r\n\t// The grain envelope\r\n\tenvBuf = Buffer.alloc(s, s.sampleRate, 1);\r\n\tenvSignal = Signal.newClear(s.sampleRate).waveFill({|x| (1 - x.pow(2)).pow(1.25)}, -1.0, 1.0);\r\n\tenvBuf.loadCollection(envSignal);\r\n\ts.sync();\r\n\t// Runs indefinitely\r\n\tSynth(\\paulstretchMono, [\\bufnum, buffer.bufnum, \\envBufnum, envBuf.bufnum]);\r\n}.fork;\r\n)"
}
