Comparing distances between MFCCs

If anyone wants to try my (supercollider) (dirty) patch, I have done a subjective comparison of nearest-neighbour, group-scaled vs dimension-scaled, 2d (via UMAP) vs 13d for each. The idea is that I browse for a point in 2d. When I have found a target that way, I retrieve with the ID the original value in each representation and find the nn in the said representation. it finds itself, then I compare #2

The conclusions so far:

  1. the difference is subtle.
  2. when there is a difference, both 13d are better (aka the sample sounds nearer) than either 2d
  3. when there is a difference between the 13d, the group-scaled sound nearer.

Next, I’m tempted to do an autoencoding on both 13d and see which space makes more sense… also, I’ll retrieve more than one NN to see if the difference gets better, worse, or is just noise (i.e. same ballpark for the 4 first nn, just a in different order)

I am sharing as I go, in case that is inspiring for anyone, both challenges, anecdotes and encouragments welcome.

1 Like

Hello,

I’m eager to read ( not sure I totally get the concept right now ) the code and make a pd version so I can test it!

1 Like

ok I’ll clean and comment it (and maybe add the autoencoding too to see, and maybe a few more neighbours)

stay tuned!

1 Like

That reminds me of a methodology @tutschku showed in early FluCoMa stuff over in this thread (sadly his original vids are no longer available).

If memory serves me right he had an automated thing where he would pick a sound (don’t remember how, if it was 2d grid or via distance to an input sound) and then the playback engine would quickly play back the nearest n amount of them, with the idea being that you could get a sense of what was matching better.

Actually looks like my vid is still up which was mirroring his:

1 Like

indeed there is also still a test file in the SC repos doing it. but it is ugly so I’m trying to make it more user friendly, and GUIs are not SC’s forte…

2 Likes

Here we go, a bit cleaned, and 3 NN per type of data. It looks like that:

and that is the code:

(
// We'll load all the Fluid Corpus Manipulation audio example files
~path = FluidFilesPath();
~loader = FluidLoadFolder(~path);
~monosource = Buffer(s);

~loader.play(s,action:{ |dataDictionary|
	"Done loading".postln;
	FluidBufCompose.process(s,~loader.buffer,numChans: 1,destination: ~monosource, action: {\done.postln});
});
)

// make the MFCCs
~mfccs = Buffer(s);

FluidBufMFCC.process(s, ~monosource, features: ~mfccs, startCoeff: 1, minFreq: 40, maxFreq: 10000, windowSize: 8192, padding: 0, action: {\done.postln})

~mfccDS = FluidDataSet(s).fromBuffer(~mfccs, action: {\done.postln})

~mfccDS.print

/// make the scaler and the normaliser

(
~norm = FluidNormalize(s,min: -1);
~scale = FluidStandardize(s).load(Dictionary.newFrom([\std, 120.dup(13), \mean, 0.dup(13), \cols, 13]));

~normedMFCCs = FluidDataSet(s);
~scaledMFCCs = FluidDataSet(s);

~norm.fitTransform(~mfccDS, ~normedMFCCs);
~scale.transform(~mfccDS, ~scaledMFCCs);
)

/// make 2d versions of each
(
~normed2d = FluidDataSet(s);
~scaled2d = FluidDataSet(s);
~umap = FluidUMAP(s);
)

~umap.fitTransform(~normedMFCCs, ~normed2d, action: {\done.postln}) //wait

~umap.fitTransform(~scaledMFCCs, ~scaled2d, action: {\done.postln}) //wait

(
~normed2d.print;
~scaled2d.print;
~normedMFCCs.print;
~scaledMFCCs.print;
)
///now plot and query

(
var query2d = Buffer.alloc(s,2);
var query13ds = Buffer.alloc(s,2);
var query2dn = Buffer.alloc(s,2);
var query13dn = Buffer.alloc(s,2);
var previous = -1;

var w = Window(bounds: Rect(width: 1900, height: 450)).front;

var source = TextField(w,Rect(5,420,80,20));
var dist2ds = 3.collect{|i|TextField(w,Rect(165+(i*80),420,55,20))};
var dist13ds = 3.collect{|i|TextField(w,Rect(665+(i*80),420,55,20))};
var dist2dn = 3.collect{|i|TextField(w,Rect(1165+(i*80),420,55,20))};
var dist13dn = 3.collect{|i|TextField(w,Rect(1665+(i*80),420,55,20))};

var pl1 = FluidPlotter(w,Rect(left: 0,width: 400,height: 400), xmin: -20, xmax: 20, ymin: -20, ymax: 20, mouseMoveAction: {
	arg view, x, y, modifiers;
	query2d.setn(0, [x,y]);
	~scaled2d.kNearest(query2d, 1, {
		arg nearest;
		if (nearest != previous) {
			Synth(\pointplayer, [\point, nearest.asInteger]);
			previous = nearest;
			defer{source.string = nearest};
			~scaled2d.getPoint(nearest, query2d, {
				~scaled2d.kNearest(query2d, 4, {
					arg nearestO;
					pl1.highlight = nearestO;
					defer{nearestO[1..].do{|label, i| dist2ds[i].string = label}};
				});
			});
			~scaledMFCCs.getPoint(nearest, query13ds, {
				~scaledMFCCs.kNearest(query13ds, 4, {
					arg nearestS;
					pl2.highlight = nearestS;
					defer{nearestS[1..].do{|label, i| dist13ds[i].string = label}};
				});
			});
			~normed2d.getPoint(nearest, query2dn, {
				~normed2d.kNearest(query2dn, 4, {
					arg nearestN;
					pl3.highlight = nearestN;
					defer{nearestN[1..].do{|label, i| dist2dn[i].string = label}};
				});
			});
			~normedMFCCs.getPoint(nearest, query13dn, {
				~normedMFCCs.kNearest(query13dn, 4, {
					arg nearestN13;
					pl4.highlight = nearestN13;
					defer{nearestN13[1..].do{|label, i| dist13dn[i].string = label}};
				});
			});
		};
	});
});
var pl2 = FluidPlotter(w,Rect(left: 500,width: 400,height: 400), xmin: -20, xmax: 20, ymin: -20, ymax: 20);
var pl3 = FluidPlotter(w,Rect(left: 1000,width: 400,height: 400), xmin: -20, xmax: 20, ymin: -20, ymax: 20);
var pl4 = FluidPlotter(w,Rect(left: 1500,width: 400,height: 400), xmin: -20, xmax: 20, ymin: -20, ymax: 20);

~scaled2d.dump{|x|pl1.dict = x; pl2.dict = x};
~normed2d.dump{|x|pl3.dict = x; pl4.dict = x};

SynthDef(\pointplayer, {
	arg point, out = 0;
	var start = point * 4096;
	var dur = 8192 / SampleRate.ir;
	Out.ar(out, PlayBuf.ar(1, ~monosource, startPos: start).dup * EnvGen.ar(Env.sine(dur),doneAction: 2));
}).send;

Button(w, Rect(85,420,20,20)).action_{Synth(\pointplayer, [\point, source.string.postln.asInteger])};
3.do{|i|Button(w, Rect(220+(i*80),420,20,20)).action_{Synth(\pointplayer, [\point, dist2ds[i].string.postln.asInteger])}};
3.do{|i|Button(w, Rect(720+(i*80),420,20,20)).action_{Synth(\pointplayer, [\point, dist13ds[i].string.postln.asInteger])}};
3.do{|i|Button(w, Rect(1220+(i*80),420,20,20)).action_{Synth(\pointplayer, [\point, dist2dn[i].string.postln.asInteger])}};
3.do{|i|Button(w, Rect(1720+(i*80),420,20,20)).action_{Synth(\pointplayer, [\point, dist13dn[i].string.postln.asInteger])}};

StaticText(w, Rect(5,400,100,20)).string_("source:");
StaticText(w, Rect(165,400,100,20)).string_("2D-scaled:");
StaticText(w, Rect(665,400,100,20)).string_("13D-scaled:");
StaticText(w, Rect(1165,400,100,20)).string_("2D-normalised:");
StaticText(w, Rect(1665,400,100,20)).string_("13D-normalised:");
)
1 Like

I think this should read "~scale.transform(~mfccDS, ~scaledMFCCs); "

otherwise the loaded dict gets crushed.

There seems to be a number of dimensions error in the scaler ( fluid.standardize) if there’s 13 mfcc shouldn’t the scaler be loaded with 13 cols not 40 ?

1 Like

Hello,

I reproduced your experiment in pd

mfccDistCompare.zip (4.2 KB) zip contains the json file to load into fluid.standardize

2 Likes

2 posts were split to a new topic: [pd] Bug in fluid.plotter

and

Nice catches for both (the former much more important), and a good proof that I can see what I want in the difference (confirmation bias in action here)… and now, to my surprise, the results are much worse: the normalised MFCCs (i.e. each dimension being full range) provide more convincing NNs… @weefuzzy can you help us with your deep understanding of distances and covariate datasets?

In all cases, a good example to compare. Now I’ll try autoencoding again…

(ps: the code above is corrected now, thanks again @yogi )

2 Likes

ok actually the result of the scaled is more often nearer. I am puzzled.

Moreover, the autoencoded space converges better than when normalized or standardised, but the latent space emerging is not yielding as convincing NN as any of them.

I’ve tested the mfcc scaling comparison ( individual vs group ) in a specific case, the classification of attacks of percussive sounds, I noticed the success rate was a tiny bit better with group scaling. I could take some time to extract that part of the patch and make a pd demonstration like yours whenever I have spare time.

1 Like