Find the close/ adjacent node

(
~adjacent = { | nactive |
 	var leftI=0, rightI=0;
 	var n=0;
	
	while{ n <= nactive} {
		if (n-1 < 0,
			{leftI = nactive-1},
			{leftI = n-1}
		);

		if(n + 1 > nactive-1,
			{rightI =0},
			{rightI = n+1}

		);

		n= n + 1;
	};

 	[leftI, rightI];

};
)

I want find the adjacent node in a graphic simulation, but for the first, I waant to write this function with integer, but It returns error. does anyone knows why?

I don’t know what you are trying to do … But this line is almost certainly wrong. There is no operator precidence in sc - you need to put bracket around the left hand side and right hand side of the > operator.

		if((n + 1) > (nactive-1),
			{rightI = 0},
			{rightI = n+1}

		);

It works with it thanks :slight_smile:

Just as more of an explanation, some of which you might already know.

SuperCollider operates sequentially on expressions, turning each operation into a message call.
…except for direct message calls (using .) which operate on their immediate receiver.

(the following code isn’t real and is just for illustration)

So 1 * 2 + 4.pow(2) * 2 is really…

1.times(2).plus( 4.pow(2) ).times(2)

This is very confusing and if supercollider were remade today this would be fixed as this is what is expected…

( 1.times(2) ).plus( 4.pow(2).times(2) )

The . operator should happens first, then *, and finally +.

In other words, Supercollider doesn’t obey the laws of BODMAS … instead it does something like… Brackets, Accessor, Everything-else (BAE).

Meaning your original code did this…

n.add(1).greaterThan(nactive).minus(1)

…which took 1 from a boolean value, when you wanted…

( n.add(1) ).greaterThan( nactive.minus(1) )

(Technically the brackets around the n + 1 are optional)

Defining exactly what an expression is and where they occur is a little complex.
One oddity is the assignment, =.

This …

b = a = 3 * 2

does not reduce to this …

b.assign(a).assign(3).times(2)

That is because = isn’t an operator but something else.

A bit off topic, but are you sure this is not what’s intended ?

I find operations ‘resolving’ from left to right, not only somehow funnier and simpler than what school taught me, but also very efficient when it’s about signal processing, i.e. when writing synthdefs. It feels like plugging effects one after an other, like I would do with my guitar. I never ask myself if I need to enclose my wah-wah pedal inside brackets to prevent it from modifying the sound before where it’s placed :stuck_out_tongue: .

But it’s true that it take a lot of times reinserting brackets where needed inside the pure ‘logical’ part of the code, and I even put brackets around some operations that indeed are going to be calculated first, just so any reader that doesn’t know how SC operates can understand directly.

And it’s misleading at first.

I definitely don’t know what was intended when supercollider was designed, but I imagine it was to speed up the parsing.

Defintily agree it makes sense when talking about signals, disagree that it should be the default. This is because its confusing as mentioned, and sometimes you don’t want a sequence of operations, having more explicit ways to deal with this might be nice.

A little while ago I posted about function chaining so you can do this…

SinOsc.ar(...) 
|> LFP.ar(_, 232) 
|> SomethingElse.ar(_)
...; 

If the Ugen class was redesigned, this could actually be written as…

SinOsc.ar()
.lpf(232)
.somethingElse()
...;

I like to think of the . operator (and |>) as a parallel to fmap in functional languages.
Point being, these operations are exactly what you want if you need a dependant sequence of operations — this makes them great for sequential signal processing.
If you need to use some other structure, you need another tool and usually, operators are that tool.

You could imagine some supercollider-like language that preserves this behaviour but does so explicitly…

SinOsc.ar() .+(1) .*(2);

There are also combinators, which fmap is just one of…
In my thing, I use |>.fork for doing things in parallel and recombining them …

|>.fork ['++', _.reduce('+'), _.reduce('-')] // mid side conversion

…and another is tap, meaning, do this function but return the old result — useful for IO.

SinOsc.ar([324, 224])
|>.fork ['++', _.reduce('+'), _.reduce('-') ]
|>.tap BufWr.ar(_, \bufNum.kr)   
|> LPF.ar(_, 345)
2 Likes

nice!

I have also found my way to |> and |>.tap (or equivalent!) and use them absolutely everywhere…

can you share your code for |>.fork please ? any other methods on your pipe operator?

Pipe_UTIL {
	*getFunc {|a|
		^a.isKindOf(Symbol).if(
			{ {|...b| a.applyTo(*b)} },
			{ a }
		)
	}
}

+Object {
	identityFunc { ^this } // unlike _.value, this always does nothing.
	|> { |f, adverb|
		^case
		{adverb.isNil} {Pipe_UTIL.getFunc(f).(this)}
		{adverb == \fork}{
			if(f.size < 3, {Error("|>.fork must have an array of 3 or more functions").throw});

			f[1..].collect{|o| Pipe_UTIL.getFunc(o).(this) }
			.reduce(Pipe_UTIL.getFunc(f[0]))
		}

		{adverb == \tap} { Pipe_UTIL.getFunc(f).(this); this }
	}
}
+Function {
	|> { |f, adverb|
		^case
		{adverb.isNil} {
			{ |...a| Pipe_UTIL.getFunc(f).(this.(*a)) }
		}
		{adverb == \fork}{
			if(f.size < 3, {Error("|>.fork must have an array of 3 or more functions").throw});
			{ |...a|
				f[1..].collect{|o| Pipe_UTIL.getFunc(o).(this.(*a)) }
				.reduce(Pipe_UTIL.getFunc(f[0]))
			}
		}
		{adverb == \tap} {
			{ |...a|
				var r = this.(*a);
				Pipe_UTIL.getFunc(f).(r);
				r
			}
		}
	}
}

So here is an example…

SynthDef(\pipes, {
	WhiteNoise.ar() 
	|>.fork ['+',
		_.identityFunc, 
		BHiShelf.ar(_, 5000, 4) |> _.tanh
		BLowShelf.ar(_, 500, 4) |> _.tanh
	]
	|>.tap ReplaceOut.ar(\rout.kr, _)
	|> (_ * -10.dbamp) |> _.tanh
	|> Out.ar(\out.kr, _)
})

SynthDef(\noPipes, {
	var sig = WhiteNoise.ar();
	var sig2 = sig + (BHiShelf.ar(sig, 5000, 4).tanh) + (BLowShelf.ar(sig, 500, 4).tanh);
	var attenuatedSig = (sig * -10.dbamp).tanh;
	ReplaceOut.ar(\rout.kr, sig2);
	Out.ar(\out.kr, attenuatedSig);
})

A few things to note…

It is often better to write (_ + 1) rather than just + 1 as the latter does not work when you are building a chain of Functions, but does work if you are immediately applying them.

f = (_ * 2) |> (_ + 1);
g = (_ * 2) |> _ + 1;
f.(1) // returns 3
g.(1) // returns a BinaryOpFunction

|>.fork takes the combining function as its first array element, but there must be more than 2.
Internally it uses reduce to combine them.

When using function composition in this way, some of supecollider’s more questionable design choices start to rear their heads.
One thing that always catches me is that ++ doesn’t actually do array concatenation (I blame String for decaying into an Array) if the left hand side isn’t already an array like thing (but nil works and so do, importantly, Ugens).
Here is the same midside conversion on numbers which don’t turn into arrays.
[0.2, -0.7] |>.fork ['++', _.reduce('-') |> _.asArray, _.reduce('+') |> _.asArray]

In the past I have had many different adverbs on |>, there is a whole branch of logic call combinatory logic that deals with these sorts of operators. I think these are the only useful ones as supercollider has good functional support already — might be wrong though.

1 Like

This is an excellent habit!

In systems that allow user defined operators, and where they’re widely used for different kinds of behaviours (math, logic, control flow, sequencing, set operations &etc.) it’s not obvious what precedence rules are nicest.

The approach of Smalltalk (and Apl and J and K) has the definite virtues of simplicity and clarity!

Fortress had nice rules giving a partial ordering, but they’re rather complicated, as the report acknowledges (see below).

Swift has nice rules too.

The Haskell model is also very simple, but gives a total order…

While the rules of precedence are complicated, they are intended to
be both unsurprising and conservative. Note that operator precedence
in Fortress is not always transitive; for example, while + has
higher precedence than < (so you can write a + b < c without
parentheses), and < has higher precedence than ∨ (so you can write a
< b ∨ c < d without parentheses), it is not true that + has higher
precedence than ∨ — the expression a ∨ b + c is not permitted, and one
must instead write (a ∨ b) + c or a ∨ (b + c).

https://www.ccs.neu.edu/home/samth/fortress-spec.pdf
“16.2 Operator Precedence and Associativity”, p.102

1 Like