1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
|
/**
"Unpack FFT" UGens (c) 2007 Dan Stowell.
Magical UGens for treating FFT data as demand-rate streams.
*/
// Actually this just wraps up a bundle of Unpack1FFT UGens
UnpackFFT : MultiOutUGen {
*new { | chain, bufsize, frombin=0, tobin |
var upperlimit = bufsize/2;
tobin = if(tobin.isNil, upperlimit, {tobin.min(upperlimit)});
^[Unpack1FFT(chain, bufsize, (frombin..tobin), 0),
Unpack1FFT(chain, bufsize, (frombin..tobin), 1)].flop.flatten;
}
}
Unpack1FFT : UGen {
*new { | chain, bufsize, binindex, whichmeasure=0 |
//("bufsize:"+bufsize).postln;
^this.multiNew('demand', chain, bufsize, binindex, whichmeasure);
}
}
// This does the demanding, to push the data back into an FFT buffer.
PackFFT : PV_ChainUGen {
*new { | chain, bufsize, magsphases, frombin=0, tobin, zeroothers=0 |
tobin = tobin ?? {bufsize/2};
^this.multiNewList(['control', chain, bufsize, frombin, tobin, zeroothers, magsphases.size] ++ magsphases.asArray)
}
fftSize {^this.inputs[1]}
}
// Conveniences to apply calculations to an FFT chain
PV_ChainUGen : WidthFirstUGen {
// Give it a func to apply to whole set of vals: func(mags, phases)
pvcalc { |numframes, func, frombin=0, tobin, zeroothers=0|
var origmagsphases, magsphases, ret;
origmagsphases = UnpackFFT(this, numframes, frombin, tobin).clump(2).flop;
magsphases = func.value(origmagsphases[0], origmagsphases[1]);
// Add phases back if they've been ignored
magsphases = magsphases.size.switch(
1, {magsphases ++ origmagsphases[1]},
2, {magsphases},
// any larger than 2 and we assume it's a list of magnitudes
{[magsphases, origmagsphases[1]]}
);
magsphases = magsphases.flop.flatten;
^PackFFT(this, numframes, magsphases, frombin, tobin, zeroothers);
}
// The same but for two chains together
pvcalc2 { |chain2, numframes, func, frombin=0, tobin, zeroothers=0|
var origmagsphases, origmagsphases2, magsphases, ret;
origmagsphases = UnpackFFT(this, numframes, frombin, tobin).clump(2).flop;
origmagsphases2 = UnpackFFT(chain2, numframes, frombin, tobin).clump(2).flop;
magsphases = func.value(origmagsphases[0], origmagsphases[1], origmagsphases2[0], origmagsphases2[1]);
// Add phases back if they've been ignored
magsphases = magsphases.size.switch(
1, {magsphases ++ origmagsphases[1]},
2, {magsphases},
// any larger than 2 and we assume it's a list of magnitudes
{[magsphases, origmagsphases[1]]}
);
magsphases = magsphases.flop.flatten;
^PackFFT(this, numframes, magsphases, frombin, tobin, zeroothers);
}
// Give it a func to apply to each bin in turn: func(mag, phase, index)
pvcollect { |numframes, func, frombin=0, tobin, zeroothers=0|
var magsphases, ret;
magsphases = UnpackFFT(this, numframes, frombin, tobin).clump(2);
magsphases = magsphases.collect({ |mp, index|
ret = func.value(mp[0], mp[1], index + frombin, index).asArray;
ret = if(ret.size==1, {ret ++ mp[1]}, ret); // Add phase if it's been ignored
}).flatten;
^PackFFT(this, numframes, magsphases, frombin, tobin, zeroothers);
}
addCopiesIfNeeded {
var directDescendants, frames, buf, copy;
// find UGens that have me as an input
directDescendants = buildSynthDef.children.select ({ |child|
var inputs;
child.isKindOf(PV_Copy).not and: { child.isKindOf(Unpack1FFT).not } and: {
inputs = child.inputs;
inputs.notNil and: { inputs.includes(this) }
}
});
if(directDescendants.size > 1, {
// insert a PV_Copy for all but the last one
directDescendants.drop(-1).do({|desc|
desc.inputs.do({ arg input, j;
if (input === this, {
frames = this.fftSize;
frames.widthFirstAntecedents = nil;
buf = LocalBuf(frames);
buf.widthFirstAntecedents = nil;
copy = PV_Copy(this, buf);
copy.widthFirstAntecedents = widthFirstAntecedents ++ [buf];
desc.inputs[j] = copy;
buildSynthDef.children = buildSynthDef.children.drop(-3).insert(this.synthIndex + 1, frames);
buildSynthDef.children = buildSynthDef.children.insert(this.synthIndex + 2, buf);
buildSynthDef.children = buildSynthDef.children.insert(this.synthIndex + 3, copy);
buildSynthDef.indexUGens;
});
});
});
});
}
// return a BufFrames
// any PV UGens which don't take the chain as first arg will need to override
fftSize { ^inputs[0].fftSize }
}
|