I got some OK results using a time-domain pitch-shifting UGen to post-process the iFFT.
// Thread here:
// https://scsynth.org/t/ifft-freeze-scrub/3802
(
var resultbuf, inbuf;
var fftSize = 1024;
p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";
q = "~/pvtest.wav".standardizePath;
// get duration
f = SoundFile.openRead(p);
f.close;
f.duration; // 4.2832879818594
z = Server(\nrt, NetAddr("127.0.0.1", 57110),
ServerOptions.new
.numOutputBusChannels_(2)
.numInputBusChannels_(2)
.sampleRate_(44100)
);
inbuf = Buffer(z, 65536, 1);
resultbuf = Buffer(z, f.duration.calcPVRecSize(fftSize, 0.5, z.options.sampleRate), 1);
x = Score([
[0, inbuf.allocMsg],
[0, resultbuf.allocMsg],
[0, inbuf.readMsg(p, leaveOpen: true)],
[0, [\d_recv, SynthDef(\pv_ana, {
var sig = VDiskIn.ar(1, inbuf, f.sampleRate / SampleRate.ir);
var fft = FFT(LocalBuf(fftSize, 1), sig);
fft = PV_RecordBuf(fft, resultbuf, run: 1);
Out.ar(0, sig);
}).asBytes]],
[0, Synth.basicNew(\pv_ana, z).newMsg],
[f.duration + (fftSize / z.options.sampleRate),
resultbuf.writeMsg(q, "wav", "float")
]
]);
x.recordNRT(
outputFilePath: if(thisProcess.platform.name == \windows) { "NUL" } { "/dev/null" },
headerFormat: "wav", sampleRate: z.options.sampleRate,
options: z.options,
duration: f.duration + (fftSize / z.options.sampleRate),
action: { "encoded file".postln }
);
z.remove;
)
s.boot;
(
// this is the pv_rec result
b = Buffer.read(s, q);
SynthDef(\pvmouse, { |out = 0, recBuf = 1, fftSize =1024|
var in, chain, bufnum, mY, result;
bufnum = LocalBuf.new(fftSize);
chain = PV_BufRd(bufnum, recBuf, MouseX.kr(0, 1));
// Render FFT
result = IFFT(chain).dup;
// Pitch-shift
result = PitchShift.ar(
result, // stereo audio input
0.1, // grain size
(MouseY.kr(-12, 12).round).midiratio, // mouse x controls pitch shift ratio
0, // pitch dispersion
0.004 // time dispersion
);
Out.ar(out, result);
}).add;
)
a = Synth(\pvmouse, [recBuf: b]);
a.free;
I think I can live with the audio quality of this solution. Plus, I can have some fun messing with the parameters of PitchShift.ar()