File: FFTUnpacking.sc

package info (click to toggle)
supercollider 1%3A3.10.0%2Brepack-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 45,496 kB
  • sloc: cpp: 283,513; lisp: 74,040; ansic: 72,252; sh: 23,016; python: 7,175; makefile: 1,087; perl: 766; java: 677; yacc: 314; lex: 175; ruby: 136; objc: 65; xml: 15
file content (116 lines) | stat: -rw-r--r-- 4,343 bytes parent folder | download
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 }
}