Send value only when it changes

Hey guys! How are you?

I have a question about how to get only values from a list only when it changes. I’m using SendReply to send your values to OSCdef

OSCdef(\predictions,{
	arg msg;
	msg[3].postln;
},"/prediction");

var isPredicting = (loudness >= -40);
var trig = Impulse.kr(30);
SendReply.kr(trig * isPredicting,"/prediction", Array_of_Numbers);
SendReply.kr(trig * (1-isPredicting),"/prediction",-1);

and when I run the code (msg[3].postln;), I get a list of numbers.

Post Window example: -1, -1, -1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 0, 0, 0, -1, -1, -1, 1 1 1 etc…

However, I just wanted to get one number at a time only when it changes. I tried with Changed.kr(msg[3]) but I keep getting all the values. Something like this (following the Post Wundow example ):

-1, 0, 1, -1, 0, -1, 1, …

So for each of the changes I want to make a boolean to activate or play a specific SynthDef once per change (synthesis or sample for example). Something like this:

	OSCdef(\predictions,{
		arg msg;

		if(msg[3] == -1, {
			"doNothing".play;
		});

		if(msg[3] == 0, {
			"doSomething".play;
		 });
		
		 if(msg[3] == 1, {
			"doSomethingElse".play;
		 });

	},"/prediction");

If you can help me understand and implement this, I would greatly appreciate it.

Anyway, thank you so much for everything on this forum.

Cheers!

Could you post a minimal working example please? It’s a little hard to understand whats going on without knowing what the value of the array is; particularly the results of the post window, does the -1 come from the other trigger or is it in the array. Just use LFNoise or something to generate some dummy data.

1 Like

Thank you for the answer @jordan.
Sorry for that.

Could you post a minimal working example please?

Of course. I am trying to address different trigger according to MFCCs profiles using audio features extraction. Since

(
~mfccbuf = Buffer.alloc(s,13);
~predictions = Buffer.alloc(s,1);
~server_predictions = {
	arg buf;
	OSCdef(\predictions,{
		arg msg;

		 if(msg[3] == -1, {
		 	"doNothing".postln;
		 });
		 if(msg[3] == 0, {
		 	"doSomething1".postln;
		 });
		
		 if(msg[3] == 1, {
		 	"doSomething2".postln;
		 });

	},"/prediction");
	{
		var sig = PlayBuf.ar(1,buf,BufRateScale.ir(buf),doneAction:2);

// retrieve MFCCs coefficients
		var mfccs = FluidMFCC.kr(sig,13,startCoeff:1,maxNumCoeffs:13);

// loudness threshold separating 'silence' and 'sound'
		var loudness = FluidLoudness.kr(sig)[0];
		var thresh = -40;
		var isPredicting = (loudness >= thresh);
		var trig = Impulse.kr(30);
		FluidKrToBuf.kr(mfccs,~mfccbuf);

//send data to OSCdef
		SendReply.kr(trig * isPredicting,"/prediction",FluidBufToKr.kr(~predictions));
		SendReply.kr(trig * (1-isPredicting),"/prediction",-1);
		sig.dup;
	}.play;
};
)

~server_predictions.(~src);

~src is an audio file to analyze

I hope this could help me out to explain it a little better.
Thank you!

the post window example earlier

I am expecting single values just when changed
Thank you

So it looks like its working properly, but ~predictions is empty. Based on the post window, the sound starts out less than the threshold, for the first 3 calls, then is above it. You are only getting one value because ~predictions has only one value. You also aren’t doing anything with ~mfcc. At the moment the data you are sending can only be 0 or -1.

Or…
are you after sending a 0 continuously when its above a threshold, and a -1, once, when it dips below? That would be like this…

(
~server_predictions = {
	arg buf;
	OSCdef(\predictions,{
		|msg, t|
		[msg, t].postln;
	},"/prediction");
	
	
	{
		var sig = PlayBuf.ar(1, buf, BufRateScale.ir(buf), doneAction:2);
		var loudness = FluidLoudness.kr(sig)[0];
		var thresh = -30;
		var isPredicting = loudness >= thresh;
		var trig_there_is_data = Impulse.kr(30) * isPredicting;
		var trig_there_is_no_data = isPredicting < 0.01; 
		SendReply.kr(trig_there_is_data, "/prediction", 0);
		SendReply.kr(trig_there_is_no_data, "/prediction", -1);
		sig.dup;
	}.play;
};
)
1 Like

Thank you so much @jordan.

are you after sending a 0 continuously when its above a threshold, and a -1 , once, when it dips below?

Yes. I mean…

In that example, all the -1, 0 and 1 are string that are associated with ‘silence’, 'instrument1, 'instrument1´, respectively.
So when the PlayBuf is running the ~src audio file, it should return sometimes ‘-1’, ‘0’ or ‘0’ in ‘predictions’ OSCDef. But it does it at every Impulse, so a list of repetitive number.

What I’m thinking is getting a single output number when it repeats (thinking here something like the [once] object in Pure Data ou even in the Changed.kr in SC].

Thank you very much for your patience and attention.
I’m also studying it and I’ll update here if I get any progress.

Cheers!

Is part of you code missing from the previous post? I don’t see any reference to instrument1 or anywhere else the empty buffer ~predictions gets filled with anything.

1 Like

Hi @Thor_Madsen

Is part of you code missing from the previous post?

yes! it is.

~poll_test = FluidLoadFolder("/Downloads/trompeteCello_NNClass/");
~poll_test.play(s, {"done".postln;});

~src = Buffer(s);
FluidBufCompose.processBlocking(s, ~poll_test.buffer,startChan:0, numChans:1, destination:~src, destStartChan:0, gain: -6.dbamp);

~celloPool = FluidLoadFolder("/Downloads/cello_train/");
~celloPool.play(s, {"done".postln;});

~celloTrain = Buffer(s);
FluidBufCompose.processBlocking(s, ~celloPool.buffer,startChan:0, numChans:1, destination:~celloTrain, destStartChan:0, gain: -6.dbamp);

~trumpetPool = FluidLoadFolder("/Downloads/trumpet_train/");
~trumpetPool.play(s, {"done".postln;});

~trumpetTrain = Buffer(s);
FluidBufCompose.processBlocking(s, ~trumpetPool.buffer,startChan:0, numChans:1, destination:~trumpetTrain, destStartChan:0, gain: -6.dbamp);

FluidWaveform(~trumpetTrain);

//training section autmatically triggered
(
~nmfccs = 13;
~mfccbuf = Buffer.alloc(s,13);
~timbredata = FluidDataSet(s);
~labels = FluidLabelSet(s);
~counter = 0;

~server_train = {
	arg buf, label;

	OSCdef(\predictions,{
		arg msg, innerLabel;
		var id = label ++ "%".format(~counter);

		if(msg[3] == 1, {
			innerLabel = label
		}, {
			innerLabel = "silence"
		});

		~timbredata.addPoint(id,~mfccbuf);
		~labels.addLabel(id, innerLabel);
		~counter = ~counter + 1;
		// ~counter.postln;
		innerLabel.postln;

	},"/prediction");

	{
		var sig = PlayBuf.ar(1,buf,BufRateScale.ir(buf),doneAction:2);
		var mfccs = FluidMFCC.kr(sig,~nmfccs,startCoeff:1,maxNumCoeffs:~nmfccs);
		var loudness = FluidLoudness.kr(sig)[0];
		var thresh = -40;
		var isPredicting = (loudness >= thresh);
		var trig = Impulse.kr(30);
		FluidKrToBuf.kr(mfccs,~mfccbuf);

		SendReply.kr(trig * isPredicting,"/prediction", 1);
		SendReply.kr(trig * (1-isPredicting),"/prediction",-1);
		sig.dup;
	}.play;
};
)

~server_train.(~celloTrain, "violoncello");

(
~nn = FluidMLPClassifier(s,[5], 1, 1000, 0.1, 0.9,5,0);
// hidden:[5],activation:1,maxIter:1000,learnRate:0.1,momentum:0.9,batchSize:5,validation:0);
)

// run this fitting function for as long as the error is not acceptable
(
~nn.fit(~timbredata,~labels,{
	arg loss;
	loss.postln;
});
)

(
~server_predictions = {
	arg buf;
	OSCdef(\predictions,{
		|msg, t|
		[msg, t].postln;
	},"/prediction");
	
	
	{
		var sig = PlayBuf.ar(1, buf, BufRateScale.ir(buf), doneAction:2);
		var loudness = FluidLoudness.kr(sig)[0];
		var thresh = -30;
		var isPredicting = loudness >= thresh;
		var trig_there_is_data = Impulse.kr(30) * isPredicting;
		var trig_there_is_no_data = isPredicting < 0.01; 
		SendReply.kr(trig_there_is_data, "/prediction", 0);
		SendReply.kr(trig_there_is_no_data, "/prediction", -1);
		sig.dup;
	}.play;
};
)

// run it to test sounds...
~server_predictions.(~src);

Hello,

I am not sure if I understood your question correctly. Could the following code example be what you want to have? (Sorry, I still have not installed FluidMFCC.kr.)

(
SynthDef(\test, {
	var trig, rand, changed;
	trig = Impulse.kr(5);
	rand = TIRand.kr(0, 2, trig);
	changed = Changed.kr(rand);
	SendReply.kr(changed, '/the_answer', rand);
}).add
)

y = Synth(\test)

o = OSCFunc({ |msg| msg.postln }, '/the_answer');

o.free; y.free

Hi @prko

You are right.

Maybe I’m not able to express myself properly what I want.
So I’m sorry guys

prko’s suggestion is as close as possible for now.
The idea is that instead of

rand = TIRand.kr(0,2, trig);

the input number is a list [-1,0,1,2] retrieved from

SendReply.kr(trig * isPredicting,"/prediction",FluidBufToKr.kr(~predictions));
SendReply.kr(trig * (1-isPredicting),"/prediction",-1);

Anyway, thank you all guys for the help and support
I will keep studying it

Best!

something like that:

(
~predictions = Buffer.alloc(s,1);
~server_predictions = {
	arg buf;

	OSCdef(\test, {
		arg msg2;
		
		msg2[3].postln;
	}, '/the_answer');
	
	
	OSCdef(\predictions,{
		arg msg;

		var trig, rand, changed;
		// trig = Impulse.kr(5);
		// rand = TIRand.kr(0, 2, trig);
		changed = Changed.kr(msg[3]);
		SendReply.kr(changed.value, '/the_answer', msg[3]);

		// msg[3].postln;

	},"/prediction");

	{
		var sig = PlayBuf.ar(1,buf,BufRateScale.ir(buf),doneAction:2);
		var mfccs = FluidMFCC.kr(sig,~nmfccs,startCoeff:1,maxNumCoeffs:~nmfccs);
		var loudness = FluidLoudness.kr(sig)[0];
		var thresh = -40;
		var isPredicting = (loudness >= thresh);
		var trig = Impulse.kr(30);
		FluidKrToBuf.kr(mfccs,~mfccbuf);
		~nn.kr(trig * isPredicting,~mfccbuf,~predictions);
		SendReply.kr(trig * isPredicting,"/prediction",FluidBufToKr.kr(~predictions));
		SendReply.kr(trig * (1-isPredicting),"/prediction",-1);
		sig.dup;
	}.play;
};
)

Seems like you are still only posting a partial code, nothing happens when I run this, seems like same stuff which was missing previously.

Hi @Thor_Madsen
Thank you!
I will post it again. It was missing neural networks parts (~nn)

s.reboot;

~poll_test = FluidLoadFolder("/Downloads/trompeteCello_NNClass/");
~poll_test.play(s, {"done".postln;});

~src = Buffer(s);
FluidBufCompose.processBlocking(s, ~poll_test.buffer,startChan:0, numChans:1, destination:~src, destStartChan:0, gain: -6.dbamp);

~celloPool = FluidLoadFolder("/Downloads/cello_train/");
~celloPool.play(s, {"done".postln;});

~celloTrain = Buffer(s);
FluidBufCompose.processBlocking(s, ~celloPool.buffer,startChan:0, numChans:1, destination:~celloTrain, destStartChan:0, gain: -6.dbamp);

~trumpetPool = FluidLoadFolder("/Downloads/trumpet_train/");
~trumpetPool.play(s, {"done".postln;});

~trumpetTrain = Buffer(s);
FluidBufCompose.processBlocking(s, ~trumpetPool.buffer,startChan:0, numChans:1, destination:~trumpetTrain, destStartChan:0, gain: -6.dbamp);

FluidWaveform(~trumpetTrain);

//training section autmatically triggered
(
~nmfccs = 13;
~mfccbuf = Buffer.alloc(s,13);
~timbredata = FluidDataSet(s);
~labels = FluidLabelSet(s);
~counter = 0;

~server_train = {
	arg buf, label;

	OSCdef(\predictions,{
		arg msg, innerLabel;
		var id = label ++ "%".format(~counter);

		if(msg[3] == 1, {
			innerLabel = label
		}, {
			innerLabel = "silence"
		});

		~timbredata.addPoint(id,~mfccbuf);
		~labels.addLabel(id, innerLabel);
		~counter = ~counter + 1;
		// ~counter.postln;
		innerLabel.postln;

	},"/prediction");

	{
		var sig = PlayBuf.ar(1,buf,BufRateScale.ir(buf),doneAction:2);
		var mfccs = FluidMFCC.kr(sig,~nmfccs,startCoeff:1,maxNumCoeffs:~nmfccs);
		var loudness = FluidLoudness.kr(sig)[0];
		var thresh = -40;
		var isPredicting = (loudness >= thresh);
		var trig = Impulse.kr(60);
		FluidKrToBuf.kr(mfccs,~mfccbuf);

		SendReply.kr(trig * isPredicting,"/prediction", 1);
		SendReply.kr(trig * (1-isPredicting),"/prediction",-1);
		sig.dup;
	}.play;
};
)

~server_train.(~celloTrain, "cello");

(
~nn = FluidMLPClassifier(s,[5], 1, 1000, 0.1, 0.9,5,0);
// hidden:[5],activation:1,maxIter:1000,learnRate:0.1,momentum:0.9,batchSize:5,validation:0);
)

// run this fitting function for as long as the error is not acceptable
(
~nn.fit(~timbredata,~labels,{
	arg loss;
	loss.postln;
});
)

// define a querying function on the synth
(
~predictions = Buffer.alloc(s,1);
~server_predictions = {
	arg buf;

	OSCdef(\predictions,{
		arg msg;

		var trig, rand, changed;
		// trig = Impulse.kr(5);
		// rand = TIRand.kr(0, 2, trig);
		changed = Changed.kr(msg[3].value.postln);
		SendReply.kr(changed.value, '/the_answer', msg[3]);

		// msg[3].postln;

	},"/prediction");

	{
		var sig = PlayBuf.ar(1,buf,BufRateScale.ir(buf),doneAction:2);
		var mfccs = FluidMFCC.kr(sig,~nmfccs,startCoeff:1,maxNumCoeffs:~nmfccs);
		var loudness = FluidLoudness.kr(sig)[0];
		var thresh = -40;
		var isPredicting = (loudness >= thresh);
		var trig = Impulse.kr(30);
		FluidKrToBuf.kr(mfccs,~mfccbuf);
		~nn.kr(trig * isPredicting,~mfccbuf,~predictions);
		SendReply.kr(trig * isPredicting,"/prediction",FluidBufToKr.kr(~predictions));
		SendReply.kr(trig * (1-isPredicting),"/prediction",-1);
		sig.dup;
	}.play;
};
)

// run it with cello or trumpet test sounds...
~server_predictions.(~src);

I believe that is complete.
I run it again and I can get results…
Once again, thank you!

Unfortunately I cannot run your code as it is dependent on local files on your HD. You code is a bit hard for me to read. Are you getting the result you are after? the ~nn global variable is confusing as it is not a function but simply pointing to a Ugen, this is quite unusual - it would either turn it into a function with arguments OR simply spell out the Ugen it is pointing to when you need it, which makes the code far more readable. I would probably also consider using SynthDefs rather than the { }.play method unless you have a specific reason for not using SynthDefs - this is more of a personal preference but in my experience it makes things more flexible. I don’t think there is a way you can stop the ~server_predictions without cmd + period as the code is written now (maybe I am overlooking something), wether you use function + play or a SynthDef, I would always assign such a synth (one that does not stop by itself) to a variable so that is possible to stop, free etc. without cmd + period.

1 Like

Actually, this is quite common with flucoma.

@Ivan_Simurra
I no longer know what this thread is about. If the issue isn’t about flucoma, then you don’t need to mention it here. Please make a minimal example that will generate the problem, then clearly outline what you expect it to do. If you need to simulate data, use LFNoise2, or for a buffer, just fill it with random noise. That way, we will be able to see exactly what is wrong and how to help as quickly as possible. You also keep referring to ‘it’, and it is difficult to know exactly what that is.

If you are only after sending some data once when a threshold is met and once when it isn’t, you can do this…

{
	var sig = ....
	var loudness = FluidLoudness.kr(sig)[0];
	var thresh = -30;
	var isPredicting = loudness >= thresh;
	SendReply.kr(isPredicting , "/prediction", 0);
	SendReply.kr(1 - isPredicting , "/prediction", -1);

}
1 Like

@Thor_Madsen and @jordan Thank you guys for the patience and flexibility.

Thank you for your patience and for your attention and flexibility in being able to guide this thread.

As I’m exploring the idea of working with FluCoMa and performance, I still don’t have an exact idea of what I’m proposing. So I keep exploring the potential outcomes. This may contribute to my confusion in making the discussion clearer.

@Thor_Madsen

I cannot run your code as it is dependent on local files on your HD.

Sorry once again. These local files are simply some short audio files (from music instrument recordings). They are just .wav files.

I would probably also consider using SynthDefs

Agreed! I believe I will update this code by using SynthDef instead. Much more flexible

@jordan

if the issue isn’t about flucoma, then you don’t need to mention it here.
You are completely right. At first I did not use FluCoMa functions because my question is not about the results of these functions indeed.

What I’m getting so far from our conversations is something like:

(
~server_predictions = {
		OSCdef(\test, {
			arg msg;
			
			msg[3].postln;
	}, "/anything");
		
		{
			arg imp = 1;
			var impulse = Impulse.kr(imp);
			var sig = TIRand.kr(0, 2, impulse);
			
			var changed = Changed.kr(sig);
			SendReply.kr(changed, "/anything", sig);
			
		}.play;
}
)

~server_predictions.(1);

Thank you so much guys!