3 Spectral statistics UGens for SuperCollider, by Dan Stowell.
4 Copyright (c) Dan Stowell 2006-2007.
5 Now part of SuperCollider 3, (c) James McCartney.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "SC_PlugIn.h"
24 #include "SCComplex.h"
25 #include "FFT_UGens.h"
29 //////////////////////////////////////////////////////////////////////////////////////////////////
32 struct FFTAnalyser_Unit : Unit
36 // Not always used: multipliers which convert from bin indices to freq vals, and vice versa.
37 // See also the macros for deriving these.
38 float m_bintofreq; // , m_freqtobin;
41 struct FFTAnalyser_OutOfPlace : FFTAnalyser_Unit
47 struct SpecFlatness : FFTAnalyser_Unit
51 struct SpecPcile : FFTAnalyser_OutOfPlace
56 struct SpecCentroid : FFTAnalyser_Unit
61 //////////////////////////////////////////////////////////////////////////////////////////////////
64 // for operation on one buffer
65 // just like PV_GET_BUF except it outputs unit->outval rather than -1 when FFT not triggered
66 #define FFTAnalyser_GET_BUF \
67 float fbufnum = ZIN0(0); \
68 if (fbufnum < 0.f) { ZOUT0(0) = unit->outval; return; } \
70 uint32 ibufnum = (uint32)fbufnum; \
71 World *world = unit->mWorld; \
73 if (!(ibufnum < world->mNumSndBufs)) { \
74 int localBufNum = ibufnum - world->mNumSndBufs; \
75 Graph *parent = unit->mParent; \
76 if(!(localBufNum > parent->localBufNum)) { \
77 buf = parent->mLocalSndBufs + localBufNum; \
79 buf = world->mSndBufs; \
82 buf = world->mSndBufs + ibufnum; \
85 int numbins = buf->samples - 2 >> 1;
87 // Copied from FFT_UGens.cpp
88 #define GET_BINTOFREQ \
89 if(unit->m_bintofreq==0.f){ \
90 unit->m_bintofreq = world->mFullRate.mSampleRate / buf->samples; \
92 float bintofreq = unit->m_bintofreq;
94 #define GET_FREQTOBIN \
95 if(unit->m_freqtobin==0.f){ \
96 unit->m_freqtobin = buf->samples / world->mFullRate.mSampleRate; \
98 float freqtobin = unit->m_freqtobin;
101 //////////////////////////////////////////////////////////////////////////////////////////////////
105 void SpecFlatness_Ctor(SpecFlatness *unit);
106 void SpecFlatness_next(SpecFlatness *unit, int inNumSamples);
108 void SpecPcile_Ctor(SpecPcile *unit);
109 void SpecPcile_next(SpecPcile *unit, int inNumSamples);
110 void SpecPcile_Dtor(SpecPcile *unit);
112 void SpecCentroid_Ctor(SpecCentroid *unit);
113 void SpecCentroid_next(SpecCentroid *unit, int inNumSamples);
118 SCPolarBuf* ToPolarApx(SndBuf *buf)
120 if (buf->coord == coord_Complex) {
121 SCComplexBuf* p = (SCComplexBuf*)buf->data;
122 int numbins = buf->samples - 2 >> 1;
123 for (int i=0; i<numbins; ++i) {
124 p->bin[i].ToPolarApxInPlace();
126 buf->coord = coord_Polar;
129 return (SCPolarBuf*)buf->data;
132 SCComplexBuf* ToComplexApx(SndBuf *buf)
134 if (buf->coord == coord_Polar) {
135 SCPolarBuf* p = (SCPolarBuf*)buf->data;
136 int numbins = buf->samples - 2 >> 1;
137 for (int i=0; i<numbins; ++i) {
138 p->bin[i].ToComplexApxInPlace();
140 buf->coord = coord_Complex;
142 return (SCComplexBuf*)buf->data;
147 void init_SCComplex(InterfaceTable *inTable);
149 //////////////////////////////////////////////////////////////////////////////////////////////////
151 void SpecFlatness_Ctor(SpecFlatness
*unit
)
153 SETCALC(SpecFlatness_next
);
154 ZOUT0(0) = unit
->outval
= 0.;
155 unit
->m_oneovern
= 0.;
158 void SpecFlatness_next(SpecFlatness
*unit
, int inNumSamples
)
161 if(unit
->m_oneovern
== 0.)
162 unit
->m_oneovern
= 1./(numbins
+ 2);
164 SCComplexBuf
*p
= ToComplexApx(buf
);
166 // Spectral Flatness Measure is geometric mean divided by arithmetic mean.
168 // In order to calculate geom mean without hitting the precision limit,
169 // we use the trick of converting to log, taking the average, then converting back from log.
170 double geommean
= std::log(sc_abs(p
->dc
)) + std::log(sc_abs(p
->nyq
));
171 double mean
= sc_abs(p
->dc
) + sc_abs(p
->nyq
);
173 for (int i
=0; i
<numbins
; ++i
) {
174 float rabs
= (p
->bin
[i
].real
);
175 float iabs
= (p
->bin
[i
].imag
);
176 float amp
= std::sqrt((rabs
*rabs
) + (iabs
*iabs
));
177 if(amp
!= 0.f
) { // zeroes lead to NaNs
178 geommean
+= std::log(amp
);
183 double oneovern
= unit
->m_oneovern
;
184 geommean
= exp(geommean
* oneovern
); // Average and then convert back to linear
187 // Store the val for output in future calls
188 unit
->outval
= geommean
/ mean
;
190 ZOUT0(0) = unit
->outval
;
193 ////////////////////////////////////////////////////////////////////////////////////
195 void SpecPcile_Ctor(SpecPcile
*unit
)
197 SETCALC(SpecPcile_next
);
199 unit
->m_interpolate
= ZIN0(2) > 0.f
;
201 ZOUT0(0) = unit
->outval
= 0.;
205 void SpecPcile_next(SpecPcile
*unit
, int inNumSamples
)
209 // Used to be MAKE_TEMP_BUF but we can handle it more cleanly in this specific case:
210 if (!unit
->m_tempbuf
) {
211 unit
->m_tempbuf
= (float*)RTAlloc(unit
->mWorld
, numbins
* sizeof(float));
212 unit
->m_numbins
= numbins
;
213 unit
->m_halfnyq_over_numbinsp2
= ((float)unit
->mWorld
->mSampleRate
) * 0.5f
/ (float)(numbins
+2);
214 } else if (numbins
!= unit
->m_numbins
) return;
216 // Percentile value as a fraction. eg: 0.5 == 50-percentile (median).
217 float fraction
= ZIN0(1);
218 bool interpolate
= unit
->m_interpolate
;
220 // The magnitudes in *p will be converted to cumulative sum values and stored in *q temporarily
221 SCComplexBuf
*p
= ToComplexApx(buf
);
222 float *q
= (float*)unit
->m_tempbuf
;
224 float cumul
= sc_abs(p
->dc
);
226 for (int i
=0; i
<numbins
; ++i
) {
227 float real
= p
->bin
[i
].real
;
228 float imag
= p
->bin
[i
].imag
;
229 cumul
+= std::sqrt(real
*real
+ imag
*imag
);
231 // A convenient place to store the mag values...
235 cumul
+= sc_abs(p
->nyq
);
237 float target
= cumul
* fraction
; // The target cumul value, stored somewhere in q
239 float bestposition
= 0; // May be linear-interpolated between bins, but not implemented yet
240 // NB If nothing beats the target (e.g. if fraction is -1) zero Hz is returned
242 for(int i
=0; i
<numbins
; ++i
) {
243 //Print("Testing %g, at position %i", q->bin[i].real, i);
244 if(!(q
[i
] < target
)){ // this is a ">=" comparison, done more efficiently as "!(<)"
245 if(interpolate
&& i
!=0) {
246 binpos
= ((float)i
) + 1.f
- (q
[i
] - target
) / (q
[i
] - q
[i
-1]);
248 binpos
= ((float)i
) + 1.f
;
250 bestposition
= binpos
* unit
->m_halfnyq_over_numbinsp2
;
251 //Print("Target %g beaten by %g (at position %i), equating to freq %g\n",
252 // target, p->bin[i].real, i, bestposition);
257 // Store the val for output in future calls
258 unit
->outval
= bestposition
;
260 ZOUT0(0) = unit
->outval
;
264 void SpecPcile_Dtor(SpecPcile
*unit
)
266 if(unit
->m_tempbuf
) RTFree(unit
->mWorld
, unit
->m_tempbuf
);
269 ////////////////////////////////////////////////////////////////////////////////////////////////////////
271 void SpecCentroid_Ctor(SpecCentroid
*unit
)
273 SETCALC(SpecCentroid_next
);
274 ZOUT0(0) = unit
->outval
= 0.;
276 unit
->m_bintofreq
= 0.f
;
279 void SpecCentroid_next(SpecCentroid
*unit
, int inNumSamples
)
283 SCPolarBuf
*p
= ToPolarApx(buf
);
288 double num
= sc_abs(p
->nyq
) * (numbins
+1);
289 double denom
= sc_abs(p
->nyq
);
291 for (int i
=0; i
<numbins
; ++i
) {
292 num
+= sc_abs(p
->bin
[i
].mag
) * (i
+1);
293 denom
+= sc_abs(p
->bin
[i
].mag
);
296 ZOUT0(0) = unit
->outval
= denom
== 0.0 ? 0.f
: (float) (bintofreq
* num
/denom
);
299 //////////////////////////////////////////////////////////////////////////////////////////////////
301 void load(InterfaceTable *inTable)
305 //(*ft->fDefineUnit)("SpecFlatness", sizeof(FFTAnalyser_Unit), (UnitCtorFunc)&SpecFlatness_Ctor, 0, 0);
306 //(*ft->fDefineUnit)("SpecPcile", sizeof(SpecPcile_Unit), (UnitCtorFunc)&SpecPcile_Ctor, (UnitDtorFunc)&SpecPcile_Dtor, 0);
308 DefineSimpleUnit(SpecFlatness);
309 DefineDtorUnit(SpecPcile);
310 DefineSimpleUnit(SpecCentroid);