Spectral centroid vs rolloff

I’m not entirely sure what the relationship between these two are because the reference is a bit confusing.

Spectral centroid: “This is the point that splits the spectrum in 2 halves of equal energy.”

Rolloff: “This indicates the bin under which 95% of the energy is included.”

(p.s. both still reference bin numbers instead of Hz, which is no longer correct)

So that makes sense until I look at the numbers. For just about all the audio I can feed into the algorithm the spectral rolloff is lower (often, much lower) than the centroid. Now it could be me just not understanding the maths of “two halves” vs “95%”, but since both are talking about energy, I interpret centroid being the point “under which 50% of the energy is included”, which would seemingly put the rolloff always above it.

A quick way to test/compare is just running this and turning off the ezdac~ randomly:


----------begin_max5_patcher----------
1070.3ocwXssiihCD84juBDOM6prH7EtMus6S6+vpQibClzdZBFYL8sQS912
xFS5jNtSS5lUKJhXbJ6iOmpJ6h7y0qBuQ9HuOL3qA+SvpU+b8pU1tLcrx87p
vcrGKaX8VyB663kZ0vtJE6g8gaFsnV1pqYkbiEwt9DUV6k27i+H4X65EOasC
gilLscXmnsgqsHPeoS4fdpWzQyPKamcFB+Skf0LM2cLc4sh1seWAKvQFgwo.
FAzjhnD3q3XySnT3qfu4F0HD5m53iCILL3ale4WqWatsYlpRK+AfnSKEM+Q6
JHbnsiUdWPbz4eBOWlRC8oGHu5Q1ayZJMh.WTRg6JyvciZGjPyMegwWRApaj
Lc3lOTiOj3UIJ0Q2K3O3QSPYWgnDekhxXbAJADE3hfhcWvugfADujgBVRZiF
J4sZkTT8k+94e6qA8cJNap8c7GZ488eQwzBIzwcCJsrW7RGJYSirtdz75Fl1
Zd0eAOUp38ZaSexn+XqrKlqMWYjL+XKiJHjsL0SKZd1uuGxnPd3M0KswWKsI
YYVdmcf2XfoT69IT76S6dw1VXmpkjxsRQOeuGJSthDlKQYRw4TFkXIKM8+EJ
2ee8dXcEC95Tew3dINwKwoWKwyQFJmMehu4hsVRY44lfxaYsa4dzj7jqHA.e
c48jLjY6yzYnIFlunbttYPTEMVKBqo+VVGeObHWgu3Br2pOH9q9.c0JC4UQK
EfxPRrUZ.muXjFxkjlFQu9iKO7mqXk91EHM9Jb7SJQirzDa9NaElDke1Vg4j
wBslteBYrjGHZ6qqzzN8l9Okg8xAU4j93xrCvGjvJ3bNQq4.w1irA7xAGIyp
JtxRXuh5RBbhefQ+mCL0OvweNfSOYV8ironrf2p5n4hDdF.kmr..QlCiV.bn
y.GSZY.ZocZyF4EON0Dsj9dZqynOMPIyAnjE.H5b.ht..QlCPjE.H7b.Bu..
glCPnE.n34.zmMkNOYNYVmAz3Adrtt64pdmwVLfCu+gzl1kuw9nnc7Q6AtgJ
98hI6Q1CkCYJ3.XMb56fZ7+73wzw2oIbmDxfaGDtjXfd.l85mZd8orG96RfJ
GRMkSXYCPlZ1Pi9TEvTfUorQp9tn0r3chg8Ecd4F5nBZtYq09Q6Pte0cKN5k
TVVo48dOx13nbiE4jBLJ0zJKtHOl5l8Q0biyaofgZolkGNBrCV8hoMOO16dB
gs09bIJexRhhII3BypgjgSn1V3LZBZoWV6f2amMUx9x4KpEMMGHzpCyELYtJ
NC2pXUBXc6JOzwezAeRJbkswaqifxML7zvxxyAkai2VmOroQgKhQEVeOw46M
sftPuZTva2LJx3r3o51AGfR1IUSYivPKNX+fVdfnSGuNlc9ltuSyNlmWbrF5
YGcY8YTHrJFaaAbFVyezHqwcYruKfYQ0249CXsuxv5es9e0BEEfD
-----------end_max5_patcher-----------

nice catch!

Indeed that feels counterintuitive. I’ll need to test further to swear what we have is right, maybe @groma knows the reason right away… I see in the code that there is a slight nuance so I’ll raise a ‘unexpected feature’ test request.

in term of their relation, you can imagine a dual peak spectrum (let’s say around 200 and 2000k) which will have a very perceptually false centroid, but a rolloff is still good to know how ‘dark’ the sound is.

1 Like

I figured there might be some edge cases where something like that could happen (though it’s hard for me to imagine what they could be), but as I said, for everything I tested, the rolloff was always like 1/4 of the centroid.

I think that’s all over the reference files, as I feel like I’ve seen that elsewhere. So may be worth doing a find/replace on the documentation as I think only a few objects return bin numbers these days.

You’ll be happy to know that so far, there is no bug - I get the same value in SC’s native implementation.

b = Buffer.alloc(s, 2048, 1);
(
x = {
    var in, chain, shape;
	in = Blip.ar(110, MouseX.kr(1,100));
	shape= FluidSpectralShape.kr(in,2048);
    chain = FFT(LocalBuf(2048), in);
	SpecCentroid.kr(chain).poll(2,"scCen");
	shape[0].poll(2,"flCen");
	SpecPcile.kr(chain,0.95,1).poll(2,"sc95");
	shape[4].poll(2,"fl95");
    in.dup * 0.1
}.play
)

ok I think I found a bug with noise. I will refer to the authorities, nice catch!

b = Buffer.alloc(s, 2048, 1);
(
x = {
    var in, chain, shape;
	// in = Blip.ar(110, MouseX.kr(1,100));
	in = BPF.ar(WhiteNoise.ar, 333);
	shape= FluidSpectralShape.kr(in,2048);
    chain = FFT(LocalBuf(2048), in);
	SpecCentroid.kr(chain).poll(2,"scCen");
	shape[0].poll(2,"flCen");
	SpecPcile.kr(chain,0.95,1).poll(2,"sc95");
	shape[4].poll(2,"fl95");
    in.dup * 0.1
}.play
)
1 Like

lol, an emotional roller coaster!

So the SC version returns different results for noise-y input? (I don’t have SC installed to test those examples).

I get the same behaviour I cannot explain in SC that you get in max on noisy material, so I’ve asked the algoboss to let me know if it is a real bug or one of understanding (by you and I now :slight_smile:

1 Like