Using a boolean value to determine if a ctlbus value is set

I’m still trying to get a grasp on sclang, especially when it comes to conditionals. My SynthDef takes a ctlbus and deviceId as args. It reads a bunch of digital IOs from a Bela mini and uses most of the bits for velocity and 1 bit to determine which device sent it. I’m able to send the velocity part on the control bus, but I don’t know how to integrate the device ID. The goal is that if the device digital IO matches the deviceID argument, then it should set the bus signal.

You can see I’m trying to use if statements, but I’m pretty sure these are only getting used during the initialization of the SynthDef. I tried using |==| as I saw in another post, but that fails with an error. This is sclang 3.12.1, though it’s running on the Bela. I’m guessing there’s a uGen for this purpose, but I’m not sure how to find it. Help?

	var digitalPins = [ 0, 1, 2, 3, 4, 5, 6, 7];
	var triggerCount = 1;

  	SynthDef(\trigDetector, {
		arg ctlBus, deviceId;

		var all = Array.fill(digitalPins.size, { |i|
		    DigitalIn.ar(digitalPins[i]);
		});
		
		// Convert all the bit array to a binary value
		var allId = all.reverse.reduce({ arg total, bit; total * 2 + bit }, 0);

		// Make sure they are set by delaying a little bit
		var delay_allId = TDelay.ar(in: allId, dur: 0.003);

		// Create bitmasks.  
		//  velMask is how many bit used for velocity
		//  devMask used for devices, of which there are only 2
		var velMask = pow(2, digitalPins.size - triggerCount) -1;
		var devMask = pow(2, triggerCount) - 1;

		// Use the masks to get the velocity and device that was activated
		var velocity = allId.bitAnd(velMask);
		var device = (allId >> (digitalPins.size - triggerCount)) & devMask;

                // Various attempts to understand how this works.  But the goal is to Out.ar(ctrlbus, velocity)
                // if the deviceId matches the device that is derived from the digtial in.


		// Determine which device is set, left=0 or right foot=1
		var devtest = (device == deviceId);

		// scale button between 0 and 1 based on velMask
		var button = velocity / velMask;

		var signal = Trig.ar( delay_allId * button, 0.4);

		// if(device == deviceId, 
		// 	{ SendTrig.ar(HPZ1.ar(delay_allId), 23, 123); }
		// 	, {SendTrig.ar(HPZ1.ar(delay_allId), 23, 321);  }
		// );

		if(device == deviceId,
			{ ("device="+device+" id="+deviceId).postln; }
			, { ("f device="+device+" id="+deviceId).postln; }
		);		
		
		if(device == deviceId, 
			{ Out.ar(ctlBus, signal); Out.kr(ctlBus, signal); }
			,{ Out.ar(ctlBus, signal); Out.kr(ctlBus, signal); }
		);
	}).send(s);

You can use Select as an ad hoc if statement:

{
    var device = 0;
    var deviceID = 0;

    var outSig = Select.kr(device==deviceID, [0,1]).poll;

    Out.kr(0, outSig);
}.play

{
    var device = 0;
    var deviceID = 1;

    var outSig = Select.kr(device==deviceID, [0,1]).poll;

    Out.kr(0, outSig);
}.play

So, you select the value or signal which will be sent to the control bus with Select, then you send it to the control bus. You only use 1 Out Ugen, and you are always sending a value to that bus.

edit: Hopefully it is clear that the reason this works is because false results in a 0 and true results in a 1. So a false boolean chooses the first element in the array and a true chooses the second.

Sam

Thanks @Sam_Pluta! I tried adding this snippet to the end of my example above, but I get an error (one of those confusing sclang erros :))

...
		var signal = Trig.ar( delay_allId * button, 0.4);

		var outSignal = Select.ar(device==deviceId, [signal, 0]);
		
		Out.ar(ctlBus, outSignal);

The part of the error is below. I can add more if needed.

Select input was not audio rate:  0
 ARGS:
   which: false False
   array: a Trig Trig
   2: 0 Integer
SynthDef trigDetector build failed
ERROR: Select input was not audio rate:  0

When I used Out.ar(ctlBus, signal) it works though obviously ignores the deviceId, which is what we are attempting to fix. In the end I want to send to both audio and control bus, but for now we can focus on the audio bus. What did I do wrong? Is 0 not a signal I guess?

0 is not audio rate. So you need to make it audio rate:

var outSignal = Select.ar(device==deviceId, [signal, K2A.ar(0)]);

Sam

Hmm, still getting an error with this updated code using K2A.

		var signal = Trig.ar( delay_allId * button, 0.4);

		var outSignal = Select.ar(device==deviceId, [signal, K2A.ar(0)]); 
		
		Out.ar(ctlBus, outSignal); // Fails
		//Out.ar(ctlBus, signal); // Works

This is the first part of the error:

Select arg: 'which' has bad input: false
 ARGS:
   which: false False
   array: a Trig Trig
   2: a K2A K2A
SynthDef trigDetector build failed
ERROR: Select arg: 'which' has bad input: false

could you post the complete def you are using with the changes?

Here’s the complete SynthDef

	var digitalPins = [ 0, 1, 2, 3, 4, 5, 6, 7];
	var triggerCount = 1;

  	SynthDef(\trigDetector, {
		arg ctlBus, deviceId;

		var all = Array.fill(digitalPins.size, { |i|
		    DigitalIn.ar(digitalPins[i]);
		});
		
		// Convert all the bit array to a binary value
		var allId = all.reverse.reduce({ arg total, bit; total * 2 + bit }, 0);

		// Make sure they are set by delaying a little bit
		var delay_allId = TDelay.ar(in: allId, dur: 0.003);

		// Create bitmasks.  
		//  velMask is how many bit used for velocity
		//  devMask used for devices, of which there are only 2
		var velMask = pow(2, digitalPins.size - triggerCount) -1;
		var devMask = pow(2, triggerCount) - 1;

		// Use the masks to get the velocity and device that was activated
		var velocity = allId.bitAnd(velMask);
		var device = (allId >> (digitalPins.size - triggerCount)) & devMask;

		// Determine which device is set, left=0 or right foot=1
		var devtest = (device == deviceId);

		// scale button between 0 and 1 based on velMask
		var button = velocity / velMask;

		var signal = Trig.ar( delay_allId * button, 0.4);

		var outSignal = Select.ar(device==deviceId, [signal, K2A.ar(0)]); 
		
		Out.ar(ctlBus, outSignal); // Fails
		//Out.ar(ctlBus, signal); // Works
	}).send(s);

I don’t have DigitalIn installed, but substituting In.ar this synthDef compiles without errors over here…

I tried the same change as a test, and still got the same error. I modified this part. I believe it does build, but perhaps this is a runtime error?

		// var all = Array.fill(digitalPins.size, { |i|
		//     DigitalIn.ar(digitalPins[i]);
		// });
		
		// // Convert all the bit array to a binary value
		// var allId = all.reverse.reduce({ arg total, bit; total * 2 + bit }, 0);
		
		var allId = In.ar(0);

What does the error mean?

Select arg: 'which' has bad input: false

it means that the which arg for Select.new is boolean value false - the required type is Float - what happens if you use >= instead of == ? IIRC == does not produce a BinaryOpUGen so doesn’t work cheerfully in this context (an annoying gotcha).

Yes, >= doesn’t produce an error, though it doesn’t produce a sound either so I think it must be taking the K2A.ar(0) route? Should I have my channels reversed since 0 means signal and 1 means K2A.ar(0)?

I tested, and yes it works. But I need equivalency. How can I do an == like test?

I’m sure there is a better way to do this but if these are integer values you could do something like (x - y).abs < 0.9 ?

Try using |==|, that will create a BinaryOpUGen which will check for equivalency on the server.

Hooray, |==| worked! I had tried that before in a different context and it failed so I thought it wasn’t supported, but I guess it is. Thanks all for the help!

Hmm, wonder if I spoke too soon. 0 |==| 0 worked, but 1 |==| 1 doesn’t seem to work. Adding SendTrig I can see they are both 1. What happened?

	var digitalPins = [ 0, 1, 2, 3, 4, 5, 6, 7];
	var triggerCount = 1;

  	SynthDef(\trigDetector, {
		arg ctlBus, deviceId;

		var all = Array.fill(digitalPins.size, { |i|
		    DigitalIn.ar(digitalPins[i]);
		});
		
		// Convert all the bit array to a binary value
		var allId = all.reverse.reduce({ arg total, bit; total * 2 + bit }, 0);
		
		// Make sure they are set by delaying a little bit
		var delay_allId = TDelay.ar(in: allId, dur: 0.003);

		// Create bitmasks.  
		//  velMask is how many bit used for velocity
		//  devMask used for devices, of which there are only 2
		var velMask = pow(2, digitalPins.size - triggerCount) -1;
		var devMask = pow(2, triggerCount) - 1;

		// Use the masks to get the velocity and device that was activated
		var velocity = allId.bitAnd(velMask);
		var device = (allId >> (digitalPins.size - triggerCount)) & devMask;

		// scale button between 0 and 1 based on velMask
		var button = velocity / velMask;

		var signal = Trig.ar( delay_allId * button, 0.4);

		var outSignal = Select.ar(device |==| deviceId, [K2A.ar(0), signal]); 
		
		Out.ar(ctlBus, outSignal);
		Out.kr(ctlBus, outSignal);
		
		SendTrig.ar(HPZ1.ar(delay_allId), 23, device);
		SendTrig.ar(HPZ1.ar(delay_allId), 21, deviceId);
	}).send(s);

Output:

[ 189.401508982, [ /tr, 1001, 21, 1.0 ] ]
[ 189.402132955, [ /tr, 1001, 23, 1.0 ] ]
[ 190.399960345, [ /tr, 1001, 21, 1.0 ] ]
[ 190.400606776, [ /tr, 1001, 23, 1.0 ] ]

Just guessing, but maybe it’s due to a small fractional difference between the two floats which doesn’t show up in the posted representation. Could something like this be an option, outputting 1 when the difference is small enough?

Select.ar((device - deviceId).abs < 1e-06, [K2A.ar(0), signal]);

Though this does work. However, I would like to understand why |==| didn’t work.

		var outSignal = Select.ar(device - deviceId < 0.1, [K2A.ar(0), signal]); 
(0.1 + 0.1 + 0.1)
-> 0.3

(0.1 + 0.1 + 0.1) == 0.3
-> false

Basically: Floating point numbers lie to you. In binary, the only time they’re exact is if the denominator is a power of two. Otherwise, they’re approximations and == isn’t quite trustworthy with them.

Better keep the abs: abs(device - deviceId) < 0.1. Or write device absdif: deviceId.

hjh

OK, thanks for the explanation and code fix/tweak. Suppose when I do my bit twiddling the value I thought was 1.0 was not, though for whole numbers I thought it wasn’t an issue. Obviously, it is :slight_smile:

One thing with sclang that bites me all the time is the weak typing. Is there any way to make it strongly type so I can deal with integers instead? Or is everything a float for a reason?