2 summary:: Base class for UGens that alter FFT chains
6 "PV_ChainUGen" is an abstract class - in other words, a class that you do not use directly. Instead, use one of its subclasses. It represents phase-vocoder UGens - i.e. UGens which apply some kind of transformation to the frequency-domain signal produced by FFT.
8 (Strictly, this class encompasses all units whose output is an FFT chain. This is why FFT is in this group but IFFT is not - the IFFT unit outputs ordinary time-domain audio.)
10 For more info on using these units, see link::Guides/FFT-Overview::.
17 Process the bins of an FFT chain
20 chain = chain.pvcalc(numframes, func, frombin, tobin, zeroothers)
22 pvcalc applies function code::func:: to the frequency-domain data of an FFT chain.
23 code::func:: should be a function that takes two arrays as inputs (magnitude, and phase) and returns a resulting pair of arrays code::[magnitude, phase]::.
25 frombin, tobin, and zeroothers are optional arguments which limit the processing to a specified integer range of FFT bins. If zeroothers is set to 1 then bins outside of the range being processed are silenced.
27 See code::pvcollect:: below for discussion of efficiency considerations. See also code::pvcalc2:: below, and link::Classes/UnpackFFT::.
30 Process the bins of two FFT chains
33 chain = chain.pvcalc2(chain2, numframes, func, frombin, tobin, zeroothers)
35 pvcalc2 is just like pvcalc but can combine two FFT chains together. See code::pvcalc:: above for more information.
37 code::func:: should be a function that takes four arrays as inputs (magnitudes1, phases1, magnitudes2, phases2) and returns a resulting pair of arrays code::[magnitude, phase]::.
40 Process each bin of an FFT chain, separately
43 chain = chain.pvcollect(numframes, func, frombin, tobin, zeroothers)
45 pvcollect applies function code::func:: to each bin of an FFT chain. func should be a function that takes code:: magnitude, phase, bin, index :: as inputs and returns a resulting code::[magnitude, phase]::.
47 The "bin" is the integer bin number, starting at 0 for DC, while "index" is the iteration number, always starting with 0. You can optionally ignore the phase and only return a single (magnitude) value, in which case the phase is assumed to be left unchanged.
49 frombin, tobin, and zeroothers are optional arguments which limit the processing to a specified integer range of FFT bins. If zeroothers is set to 1 then bins outside of the range being processed are silenced.
51 Note that this procedure can be relatively CPU-heavy, depending on how you use it.
52 Using pvcollect (or its components, UnpackFFT & PackFFT) is usually less efficient than using a single "PV_" unit generator to process an FFT chain, because it involves the creation of quite a large graph of demand-rate unit generators.
54 If you wish to reduce the CPU impact of using this approach, try the following:
56 ## Use the frombin and tobin arguments to limit the number of FFT bins that will be included in the calculation. Often the lower FFT bins contain the loudest and/or most relevant information, so perhaps your effect sounds very similar if you ignore the higher-up bins (either leave them unprocessed, or discard them by setting the zeroothers argument to 1, which has the effect of a band-pass frequency-domain filter).
57 ## Use a smaller FFT buffer size.
58 ## Avoid creating ugens inside your calculation function if at all possible. For example, a deterministic ugen such as LFPar.kr(0.5, 0, 1) will be replicated once for each bin if specified inside the function, despite the fact that the output is always the same. Define it outside the calculation function and then reference it by variable name.
59 ## Avoid unused calculations! For example, uncommenting all the different lines in the above will waste effort because many values will be calculated but not used. This cannot be optimised away during compilation. It is particularly important because all calculations are duplicated (once for each bin) so can have a significant impact on efficiency.
60 ## If you find yourself calling pvcollect on an FFT chain more than once in series, you should definitely try to combine your processing into a single pvcollect function, to avoid unneccessary unpacking-then-packing-then-unpacking-then-packing.
69 c = Buffer.read(s, Help.dir +/+ "sounds/a11wlk01.wav");
76 in = PlayBuf.ar(1, c, BufRateScale.kr(c), loop: 1);
77 chain = FFT(LocalBuf(1024), in);
79 chain = chain.pvcalc(1024, {|mags, phases|
80 //////// Try uncommenting each of these lines in turn and re-running the synth:
81 [mags * {1.5.rand}.dup(mags.size), phases + {pi.rand}.dup(phases.size)]; // Arbitrary filter, arbitrary phase shift
82 //[mags.reverse, phases.reverse]; // Upside-down!
83 //[mags.differentiate, phases.differentiate]; // Differentiate along frequency axis
84 //[mags[30..] ++ mags[..30], phases[30..] ++ phases[..30]]; // ".rotate" doesn't work directly, but this is equivalent
85 }, frombin: 0, tobin: 250, zeroothers: 0);
87 Out.ar(0, 0.5 * IFFT(chain).dup);
97 c = Buffer.read(s, Help.dir +/+ "sounds/a11wlk01.wav");
104 var in, chain, in2, chain2, out;
105 in = PlayBuf.ar(1, c, BufRateScale.kr(c), loop: 1);
106 chain = FFT(LocalBuf(fftsize), in);
108 // in2 = PlayBuf.ar(1, e, BufRateScale.kr(e), loop: 1);
109 // JMcC babbling brook
111 RHPF.ar(OnePole.ar(BrownNoise.ar, 0.99), LPF.ar(BrownNoise.ar, 14)
112 * 400 + 500, 0.03, 0.003)}!2)
113 + ({RHPF.ar(OnePole.ar(BrownNoise.ar, 0.99), LPF.ar(BrownNoise.ar, 20)
114 * 800 + 1000, 0.03, 0.005)}!2
117 chain2 = FFT(LocalBuf(fftsize), in2);
119 chain = chain.pvcalc2(chain2, fftsize, {|mags, phases, mags2, phases2|
120 [mags * mags2 / 10, phases2 + phases]
121 }, frombin: 0, tobin: 125, zeroothers: 0);
124 Out.ar(0, 0.5 * out.dup);
130 subsection:: pvcollect
134 c = Buffer.read(s, Help.dir +/+ "sounds/a11wlk01.wav");
141 in = PlayBuf.ar(1, c, BufRateScale.kr(c), loop: 1);
142 chain = FFT(LocalBuf(1024), in);
144 v = LFPar.kr(0.5).range(0.1, 1);
146 chain = chain.pvcollect(1024, {|mag, phase, index|
147 //////// Try uncommenting each of these lines in turn and re-running the synth:
153 //[mag, LFNoise0.kr.range(0, 3.14)];
154 //[mag * Dseq([1, 0, 0, 1, 1, 0, 1, 0].stutter(8), 999999999999)]; // Can even use Demand ugens! One val demanded each frame
155 //[mag.sqrt, 3.14.rand];
156 //if(index % 7 == 0, mag, 0); // Comb filter
157 //if(LFNoise0.kr(10) > 0.5, mag, 0);
158 //mag + DelayN.kr(mag, 1, v); // Spectral delay
159 if((index-LFPar.kr(0.1).range(2, 1024/20)).abs < 10, mag, 0); // Swept bandpass
160 }, frombin: 0, tobin: 250, zeroothers: 0);
162 Out.ar(0, 0.5 * IFFT(chain).dup);