Linking spectral descriptors to slices

Many thanks @tedmoore this looks great!
One fundamental question still: Arranging slices by any criteria will always presuppose analysis / ordering outside of synths which will have to rely on that prepared information? (as with arrays in this example etc.)

1 Like

Hi @jan,

Analysis can definitely be triggered from inside a synth. For buffer analysis, any of these FluidBuf* objects also have a .kr method that can trigger the analysis with an impulse inside a synth.

For finding the min and max of a list (i. e., buffer) on the server, check out the UGens BufMax, BufMin, ArrayMax, and ArrayMin. These return the extrema value and index!

Filtering items “in a synth” seems trickier–If you have the values in a control stream (possibly with FluidBufToKr), you could implement some > and < logic, possibly using Select.kr to route things different places. Another option is to pack the FluidBuf*.kr analyses into a FluidDataSet using FluidDataSetWr.kr and then using FluidDataSetQuery to do the filtering (this doesn’t have a .kr method, as it is more involved), either one of those seem a bit hairy to me–they would be prone to issues. But if you give it a try, it’d be great to see the code!

I hope that’s all helpful. Let me know how it develops!

T

Hi @tedmoore,
good to know, i wasn’t aware of this server-side, alternative approach! I will give the .kr methods a try in given time, my priority now is to manage the new terrain covered with all the previous inputs! The prospect of additional issues is a bit much atm :wink:
Thanks so much for your generous help!

1 Like

Hi @tedmoore,
i was trying to run one of the examples you’ve kindly provided on a new machine and bump into an error that i didn’t get before:

→ a Routine
ERROR: FluidBufSpecShp - Input buffer 10: not enough frames
ERROR: FluidBufStats - Can’t access input buffer
File ‘/Users/jan/Library/Application Support/SuperCollider/tmp/-1146700161’ could not be opened: Format not recognised.
done with anaylsis

would you know why this is?

heres the example again:

// load some audio (replace with a path to the audio you're interested in)
~audio = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

// create a buffer for holding slice points
~slicepoints_buffer = Buffer(s);

(
// slice according to spectral onsets, tweak the 'threshold' until you're getting about how many slice points you think you should be seeing (lower threshold will allow more slice points
FluidBufOnsetSlice.processBlocking(s,~audio,indices:~slicepoints_buffer,metric:9,threshold:0.001,action:{
	"done slicing!".postln;
	"found % slice ponits".format(~slicepoints_buffer.numFrames).postln;
});
)

(
// get the slice points out of the buffer and put them in an array
// notice that i'm putting "0" on the beginning of the array and the number of frames in the buffer at the end so that *all* of the audio in my buffer will be accounted for: the audio before the first slice point, and the audio after the last slice point;
~slicepoints_buffer.loadToFloatArray(action:{
	arg array;
	~slicepoints_array = [0] ++ array ++ [~audio.numFrames];
});
)

// inspect the slicepoints:
~slicepoints_array.postln;

// analysis:

(
// first we'll make a buffer for storing the spectral analyses in:
~features_buf = Buffer(s);
// and a buffer for storing the statistical analyses in:
~stats_buf = Buffer(s);
// and a list for collecting the average centroids:
~centroids = List.new;
)

(
fork{
	~slicepoints_array.doAdjacentPairs{
		arg start_frame, end_frame, i;
		var num_frames = end_frame - start_frame;
		FluidBufSpectralShape.processBlocking(s,~audio,start_frame,num_frames,features:~features_buf);

		// notice that we're only analysing the first channel, this is because that's were the
		// spectral centroid is and that's all we're interested in!
		FluidBufStats.processBlocking(s,~features_buf,startChan:0,numChans:1,stats:~stats_buf);

		// now we'll get index 0 out of the stats buf because thats where the average spectral centroid will be
		// if we wanted to get the median instead, we would look in index 5 (see the FluidBufStats helpfile for more
		// options)
		~stats_buf.loadToFloatArray(0,1,{
			arg mean_centroid;
			~centroids.add([i,mean_centroid[0]]); // here we'll save the mean centroid *along with* it's index so
			// that we can use it later to look up where it's start and end points are.
		});
	};
	s.sync;
	"done with anaylsis".postln;
}
)

// inspect centroids list:
~centroids.dopostln;

// sorting:
~centroids.sort({arg a, b; a[1] < b[1]}); // sorting by the value in index 1 because that's the centroid

// inspect again now that it is sorted:
~centroids.dopostln;

(
// playback in a sorted order
fork{
	~centroids.do{
		arg array;
		var index = array[0];
		var centroid = array[1];
		var start_frame = ~slicepoints_array[index];
		var end_frame = ~slicepoints_array[index + 1];
		var num_frames = end_frame - start_frame;
		var dur_seconds = num_frames / ~audio.sampleRate;

		"playing index: %\tcentroid: %".format(index,centroid).postln;

		{
			var sig = PlayBuf.ar(1,~audio,BufRateScale.ir(~audio),startPos:start_frame);
			var env = EnvGen.kr(Env([0,1,1,0],[0.03,dur_seconds-0.06,0.03]),doneAction:2);
			sig.dup * env;
		}.play;

		(dur_seconds).wait;
		1.wait;
	};
};
)

Thanks,

Jan

Hmm. My guess is that the buffer ~audio doesn’t actually have anything in it? Maybe call ~audio.play just to take a listen just to check?

just did, the audio is there and plays!

1 Like

Ah. It looks like with this audio file and a threshold = 0.001 , FluidBufOnsetSlice returns that it finds a slice point at frame 0, so when we add a 0 to the beginning of the buffer the first “slice” now looks like it goes from frame 0 to frame 0, so of course FluidBufSpectralShape will report back “not enough frames”.

I think just not doing the [0] ++ to the array will solve the issue!

Ah, yes that was it (also with higher thresholds)! I also realised that before it would only work with metrics 2 end 7 and report errors with all the others!
Thanks @tedmoore!

Hmm. This is interesting. I’ll loop @weefuzzy in on this. Was it always reporting errors or just when thresholds were too low? Can you recreate one of the errors and send it along? Thanks!

so for example this case gives the aforementioned error with some metrics set, and it seems to confirm thats its when theres consecutive 0s at beginning:

// load some audio (replace with a path to the audio you're interested in)
~audio = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
~audio.play
~audio
// create a buffer for holding slice points
~slicepoints_buffer = Buffer(s);

(
// slice according to spectral onsets, tweak the 'threshold' until you're getting about how many slice points you think you should be seeing (lower threshold will allow more slice points
FluidBufOnsetSlice.processBlocking(s,~audio,indices:~slicepoints_buffer,metric:1,threshold:0.3,action:{
	"done slicing!".postln;
	"found % slice ponits".format(~slicepoints_buffer.numFrames).postln;
});
)

(
// get the slice points out of the buffer and put them in an array
// notice that i'm putting "0" on the beginning of the array and the number of frames in the buffer at the end so that *all* of the audio in my buffer will be accounted for: the audio before the first slice point, and the audio after the last slice point;
~slicepoints_buffer.loadToFloatArray(action:{
	arg array;
	~slicepoints_array = [0] ++ array ++ [~audio.numFrames];
});
)

// inspect the slicepoints:
~slicepoints_array.postln;

// analysis:

(
// first we'll make a buffer for storing the spectral analyses in:
~features_buf = Buffer(s);
// and a buffer for storing the statistical analyses in:
~stats_buf = Buffer(s);
// and a list for collecting the average centroids:
~centroids = List.new;
)

(
fork{
	~slicepoints_array.doAdjacentPairs{
		arg start_frame, end_frame, i;
		var num_frames = end_frame - start_frame;
		FluidBufSpectralShape.processBlocking(s,~audio,start_frame,num_frames,features:~features_buf);

		// notice that we're only analysing the first channel, this is because that's were the
		// spectral centroid is and that's all we're interested in!
		FluidBufStats.processBlocking(s,~features_buf,startChan:0,numChans:1,stats:~stats_buf);

		// now we'll get index 0 out of the stats buf because thats where the average spectral centroid will be
		// if we wanted to get the median instead, we would look in index 5 (see the FluidBufStats helpfile for more
		// options)
		~stats_buf.loadToFloatArray(0,1,{
			arg mean_centroid;
			~centroids.add([i,mean_centroid[0]]); // here we'll save the mean centroid *along with* it's index so
			// that we can use it later to look up where it's start and end points are.
		});
	};
	s.sync;
	"done with anaylsis".postln;
}
)

→ a Routine
ERROR: FluidBufSpecShp - Input buffer 37: not enough frames
ERROR: FluidBufStats - Can’t access input buffer
File ‘/Users/jan/Library/Application Support/SuperCollider/tmp/-281274951’ could not be opened: Format not recognised.
done with anaylsis

Thanks for reporting it! We’ll take a look at this!

1 Like