Exploratory analysis PlotXYColor example

Hi!

Firstly, thank you all fur such fantastic library and worderfull learning materials!!!

I am trying to run this example by @tedmoore which uses the external class PlotXYColor. However, before the external class call, I am not able to get the proper results.

The original code file was missing and as I rewrite it on my own I think there can be some silly typing error.

I was supposed to provide a dataset with 45297 point, but during the execution it randomly stops at middle, posting the debug variable i of something around 90 ~ 300. It does not crash, neither shows any error message.

I was afraid it could be some memory issue, but I got no warning and I also set:

s.options.memSize_(2**20);
s.options.numWireBufs = 128;
s.options.maxNodes = 2048 ;
s.options.numBuffers = 4096;

Any idea how to debug this?

(
s.waitForBoot{
	var mfccbuf = Buffer(s);
	var specbuf = Buffer(s);
	var pitchbuf = Buffer(s);
	var loudnessbuf = Buffer(s);
	var flat = Buffer(s);
	var point = Buffer(s);
	var counter = 0;
	var headers;

	var frameToChannel = {
		arg src, frame, startFrame;
		FluidBufFlatten.processBlocking(s,src,frame,1,destination:flat);
		FluidBufCompose.processBlocking(s,flat,destination:point,destStartFrame:startFrame);
	};

	var trainingData = FluidDataSet(s);
	var trainingLabels = FluidLabelSet(s);

	var buffers = [
		Buffer.readChannel(s, FluidFilesPath("Tremblay-AaS-AcBassGuit-Melo-M.wav"), channels:[0]);
		Buffer.readChannel(s, FluidFilesPath("Tremblay-CEL-GlitchyMusicBoxMelo.wav"), channels:[0]);
	];

	s.sync;
	
	buffers = buffers.collect{ 	// remove all the silence parts from a buffer
		arg src;
		var indices = Buffer(s);
		var temp = Buffer(s);
		FluidBufAmpGate.processBlocking(s,src,indices:indices,onThreshold:-30,offThreshold:-35,minSliceLength:4410);
		indices.loadToFloatArray(action:{
			arg fa; // fa == floatArray
			var curr = 0;
			fa.clump(2).do{
				arg arr;
				var start = arr[0];
				var num = arr[1] - start;		
				FluidBufCompose.processBlocking(s,src,start,num,destination: temp, destStartFrame: curr);
				curr = curr + num;
			};
			indices.free;
			src.free;
		});
		temp;
	};

	s.sync;

	"done stripping silence".postln;

	// analysis

	buffers.do{ // make every single fft frame a analysis (no stats) and can allow realtime latter
		arg buf, buffer_i;
		FluidBufMFCC.processBlocking(s,buf,features: mfccbuf);	
		FluidBufSpectralShape.processBlocking(s,buf,features: specbuf);	
		FluidBufPitch.processBlocking(s,buf,features: pitchbuf);	
		FluidBufLoudness.processBlocking(s,buf,features: loudnessbuf);

		s.sync;
		
		"done FluidBuf".postln;
		"numFrames: %".format(mfccbuf.numFrames).postln;

		mfccbuf.numFrames.do{
			arg i;
			var id = "analysis-%".format(counter);

			frameToChannel.(mfccbuf,i,0); // 12 values
			frameToChannel.(specbuf,i,13); // 7 values
			frameToChannel.(pitchbuf,i,20); // 2 values
			frameToChannel.(loudnessbuf,i,22); // 2 values

			trainingData.addPoint(id,point); // buffer called point with 23 features
			trainingLabels.addLabel(id,["bass","music-box"][buffer_i]);

			counter = counter + 1;

			i.postln;

			if(i % 100 == 99){s.sync};
		};
	};
	
	"Done frameToChannel".postln;

	// make headers
	headers = List.new;
	headers.addAll(13.collect{arg i; "mfcc-%".format(i.asString.padLeft(2,"0"))});
	headers.addAll(FluidSpectralShape.features);
	headers.addAll(FluidPitch.features);
	headers.addAll(FluidLoudness.features);

	s.sync;

	trainingData.print;
	trainingLabels.print;
	PlotXYColor.fromFluidDataSet(trainingData,trainingLabels,headerArray:headers.asArray);

}
)

Thanks!

Just tested this code. Works for me. Have you downloaded this? GitHub - tedmoore/PlotXYColor: class for easily plotting multidimensional data in 3 dimensions: x, y, color

Maybe i’m not understanding the error you’re seeing. Maybe you can post some images of the errors or point us to the dataset to try to find it better?

T

1 Like

Thanks! I think it is a windows bug. I’ve now tested the same code on a old mac and I almost manage to achieve the final plot. On a windows machine I do not even get the trainingData.print and trainingLabels.print executed on the post window. It simply stops during the process. Any idea on how to solve this? I will make a short video because it is clearer.

On a mac, however, I am getting an error at the last line PlotXYColor.fromFluidDataSet(trainingData,trainingLabels,headerArray:headers.asArray); Can your reproduce this?

(...........)
853
854
Done frameToChannel
Done headers.addAll
Done trainingLabels.print
DataSet 6932: 
rows: 855 cols: 24
analysis-0     -15.91    30.844    19.644       ...   0.40918   -35.312   -25.649
analysis-1     -133.1    86.756    13.382       ...   0.61484   -32.737   -25.649
analysis-2    -150.86    97.355    5.5525       ...     0.631   -33.623   -27.423
       ...
analysis-852       -192     71.68   -1.9663       ...   0.94081   -31.424   -27.155
analysis-853    -197.69    77.771   -3.7297       ...   0.94084   -31.818   -28.082
analysis-854    -94.887    62.529   -13.824       ...   0.92076   -33.288   -28.209

LabelSet 6933: 
rows: 855 cols: 1
analysis-0       bass
analysis-1       bass
analysis-2       bass
       ...
analysis-852       bass
analysis-853       bass
analysis-854       bass

ERROR: Message 'dopostln' not understood.
Perhaps you misspelled 'postln', or meant to call 'dopostln' on another receiver?
RECEIVER:
Instance of Dictionary {    (0x7f99e38f4df8, gc=48, fmt=00, flg=00, set=02)
  instance variables [2]
    array : instance of Array (0x7f99e89f9978, size=32, set=5)
    size : Integer 1
}
ARGS:
CALL STACK:
      DoesNotUnderstandError:reportError
            arg this = <instance of DoesNotUnderstandError>
      Nil:handleError
            arg this = nil
            arg error = <instance of DoesNotUnderstandError>
      Thread:handleError
            arg this = <instance of Thread>
            arg error = <instance of DoesNotUnderstandError>
      Object:throw
            arg this = <instance of DoesNotUnderstandError>
      Object:doesNotUnderstand
            arg this = <instance of Dictionary>
            arg selector = 'dopostln'
            arg args = [*0]
      < FunctionDef in Method Meta_PlotXYColor:makeColorArray >
            arg labelsDict = <instance of Dictionary>
            var colorArray = [*855]
            var catColors = <instance of Routine>
            var colorTrackerDict = <instance of Dictionary>
      < FunctionDef in Method FluidDataObject:dump >  (no arguments or variables)
      OSCFuncAddrMessageMatcher:value
            arg this = <instance of OSCFuncAddrMessageMatcher>
            arg msg = [*2]
            arg time = 903.727838511
            arg testAddr = <instance of NetAddr>
            arg recvPort = 57120
      Dictionary:keysValuesArrayDo
            arg this = <instance of IdentityDictionary>
            arg argArray = [*32]
            arg function = <instance of Function>
            var i = 30
            var j = 1
            var key = nil
            var val = nil
            var arraySize = nil
      Dictionary:keysValuesDo
            arg this = <instance of IdentityDictionary>
            arg function = <instance of Function>
      FluidOSCPatternInversion:value
            arg this = <instance of FluidOSCPatternInversion>
            arg msg = [*2]
            arg time = 903.727838511
            arg addr = <instance of NetAddr>
            arg recvPort = 57120
            var msgpath = '/FluidLabelSet/write'
      < FunctionDef in Method Collection:collectInPlace >
            arg item = <instance of FluidOSCPatternInversion>
            arg i = 2
      ArrayedCollection:do
            arg this = [*4]
            arg function = <instance of Function>
            var i = 2
      Collection:collectInPlace
            arg this = [*4]
            arg function = <instance of Function>
      FunctionList:value
            arg this = <instance of FunctionList>
            arg args = [*4]
            var res = nil
      Main:recvOSCmessage
            arg this = <instance of Main>
            arg time = 903.727838511
            arg replyAddr = <instance of NetAddr>
            arg recvPort = 57120
            arg msg = [*2]
^^ ERROR: Message 'dopostln' not understood.
Perhaps you misspelled 'postln', or meant to call 'dopostln' on another receiver?
RECEIVER: Dictionary[ ([ bass ] -> Color(0.12156862745098, 0.46666666666667, 0.70588235294118)) ]

This is what I am getting on Windows 11 and SC 3.13.0 :

sc-compressed2

Now I tested the same code on a more powerful windows desktop and everything is working properly… :confounded:

I am getting this strange behavior when running on my laptop… I’ve checked the thermal and energy plan, set it to ultra performance, but I still getting it…

I will test the behavior on other laptops to see if this is the problem, thanks a lot again!!!

dopostln is part of the wslib quark.

bummer. i wish i had some insights. what are the specs of the two machines?

I tested on 3 different machines three different laptops and one desktop. I also tried to downgrade the SC version, but no good results…

Laptops:
Hp Probook 6450b i7 M620 2.67GHz - 8GB RAM - Windows 10
Dell XPS 9570 i7-8750H CPU @ 2.20GHz - 16GB RAM - Windows 11
Macbook Pro mid 2012 - i7 dual core, 2,9GHz - 8GB RAM
Dell g15 5520 - 12th Gen i5-12500H @ 2.50 GHz - 16gb RAM

Desktop (the only one working):
i5-10600K @ 4.10GHz - 16GB RAM - Windows 11

I’ve just managed to solve it… I inserted a s.sync at the beggining of the mfccbuf.numFrames.do loop…

Do you have some explanation for this behavior so I can avoid it in the future?

I am running into a similar issue when trying the PlotXYColor example.scd, the post window counter stops at a random point and stays there forever. I’ve tried to insert s.sync in various point of the example but with no success…

Firstly it said that MinMaxScaler was missing the initRanges , so I search for the older commits at github and added this to MinMaxScaler.sc :

initRanges {
		arg size;
		ranges = ControlSpec(inf,-inf).dup(size);
	}

Then it gave the following warning WARNING: keyword arg 'feature' not found in call to Meta_FluidBufNoveltySlice:process. So I removed feature:1 argument from the line

FluidBufNoveltySlice.process(s,buf,indices:indices,feature:1,threshold:0.55,action:{
			indices.loadToFloatArray(action:{

Now it does run, but does not complete the analysis as in the demonstration video… Any idea on how to solve this?

(
// s.options.device_("Soundflower (2ch)");
//Window.closeAll
//~audioFile = "/Users/ted/Desktop/plot test.wav";
~audioFile = FluidFilesPath("Nicol-LoopE-M.wav"); 
s.waitForBoot({

	Task({
		var buf, indices;

		//Buffer.freeAll;
		//s.sync;

		buf = Buffer.readChannel(s,~audioFile,channels:[0]);
		indices = Buffer(s);
		s.sync;

		FluidBufNoveltySlice.process(s,buf,indices:indices,threshold:0.55,action:{
			indices.loadToFloatArray(action:{
				arg novelties;
				var n_slices = novelties.size - 1; // how many slices there actually are
				//var n_slices = 2;
				var data = Array.newClear(n_slices); // this will end up being the master data set
				var headers, normedData;
				n_slices.do({
					arg i;
					var n_frames = novelties[i+1] - novelties[i];
					var features = Buffer(s);
					FluidBufSpectralShape.process(s,buf,novelties[i],n_frames,features:features,action:{
						var stats = Buffer(s);
						FluidBufStats.process(s,features,stats:stats,numDerivs:0,action:{
							stats.loadToFloatArray(action:{
								arg sts;
								i.postln;

								sts = sts.clump(7); // clump all the means together, all the std devs together, etc.

								/*
								each row is a param of stats analysis
								[0] means
								[1] standard deviations
								[2] skewnesses
								[3] kurtoses
								[4] mins
								[5] medians
								[6] maxes

								each column is a feature of the spec shape
								[0] centroid
								[1] spread
								[2] normalized skewness
								[3] normalized kurtosis
								[4] rolloff
								[5] flatness
								[6] crest
								*/

								data[i] = sts[0]; // just look at the means for now, add to data set
								s.sync;
								stats.free;
							});
						});
						features.free;
					});
				});

				headers = ["centroid","spread","skewness","kurtosis","rolloff","flatness","crest"];
				//data.postln;
				//data.shape.postln;
				normedData = MinMaxScaler.fit_transform(data);
				defer{
					PlotXYColor(normedData,{
						arg hoverIndex, x, y, xindex, yindex;
						// use index that is passed back to find what frame in the buffer this slice starts at
						var startFrame = novelties[hoverIndex];
						// how long is this slice in seconds--for playback
						var dur_sec = (novelties[hoverIndex + 1] - startFrame) / s.sampleRate;
						"index:\t\t%".format(hoverIndex).postln; // post a bunch of stuff
						data[hoverIndex].do({
							arg val, hi;
							var extra_tab = "";
							if((hi == 1).or(hi == 6),{extra_tab = "\t"});
							"%:\t%%".format(headers[hi],extra_tab,val).postln;
						});
						"dur:\t\t%".format(dur_sec).postln;
						"point:\t\t% , %".format(x.round(0.01),y.round(0.01)).postln;
						"indices:\t% , %".format(xindex,yindex).postln;
						"".postln;
						// playback that slice
						{
							var sig = PlayBuf.ar(1,buf,1,0,startPos:startFrame);
							sig = sig * EnvGen.kr(Env([0,1,1,0],[0.03,dur_sec-0.06,0.03]),doneAction:2);
							sig.dup;
						}.play;
					},headers);
				}
			});
		});
	},AppClock).play;
});
)

The same "performance " behaviour is happening: on my laptop it goes only until the 4~5 iteration and on my more powerfull desktop it goes until the 21 iteration but stops.

Hey,

Just passing through, so I can only offer an impressionistic answer for now, but will try and come back and probe more systematically later.

Getting this kind of loop-based processing to be robust without too much user troubleshooting is a source of lasting angst for me. The trade-offs are between reasonable speed and not expecting folk to have to think about how the various bits of the client-server traffic work. The speed bit is about not calling sync too many times. The gory details are about queues on both client and server, as well as the propensity of UDP to drop packets (which makes problems like this a PITA to reason about).

My gut says that if this works on some computers but not others then it’s most likely a packet drop problem (rather than, e.g., the server FIFO queue overflowing). Unfortunately, unless you switch to a TCP connection, the only remedy I know of is to try and find a reasonable density of syncs to put into the loop. Putting one in every iteration is sad-making because everything slows down horribly*, so it’s worth trying to see if one in every N produces reasonable reliability with not too much slow down (backing off with larger numbers of N until it’s reliable).

* it slows things down because it introduces a 100ms or so pause into traffic whilst the client waits for all server business to clear. If you’re processing hundreds or thousands of little jobs, that adds up rapidly.

2 Likes

As a follow up, today I learned that the UDP buffers on SC Windows are smaller, so more prone to this problem:

3 Likes

@weefuzzy thanks!! I think I will mention this post on the github issue, maybe it will also elucidate other issues.

2 Likes

I am now trying to run the example using TCP (which seems to solve the problem).

The example included the argument feature:1 in FluidBufNoveltySlice however this argument does not exist at the current version of Flucoma’s FluidBufNoveltySlice . What should I change here?

FluidBufNoveltySlice.process(s,buf,indices:indices,feature:1,threshold:0.55,action:{

That argument has been renamed to “algorithm”. Check the docs for more details!