ServerTree.add

in a question from @Peter the following code block was being repeatedly evaluated:

(
~init = { Synth(\default) };
ServerTree.add(~init);
Synth(\default);
)

ServerTree.add(object) checks whether aFunction is already in ServerTree’s List using list.includes(object). But { Synth(\default } != { Synth(\default) } so you end up with multiple copies in the List…

If ServerTree checked using list.collect(_.cs.asSymbol).includes(function.cs.asSymbol) this small gotcha would be avoided – of course maybe there is some situation where people actually do want multiple copies of a function in this situation?

another maybe better thought would be for ServerTree and similar to use a dictionary instead of a list so that entries could be replaced?

…or at least some improvement to the docs is needed?, currently:

object
Can either be a Function to be evaluated (as first arg the server is
passed in), or an Object that implements the message returned by
-functionSelector. One object is only registered once, so that multiple
additions don’t cause multiple calls.

I think the existing behavior is correct, as it allows you to see if that function is in the servertree. You cannot assume equality based upon the symbol alone.

Can you not just check to see if a copy of that function with your symbol exists in the server tree? Or even store your function in your code, and check to see if it has been created?

it allows you to see if that function is in the servertree

But how? When we pass is most Objects we check to see if they are in the list via identity (===) - this doesn’t work for Functions though. So the check only works if you store the Function in a variable.

But even then, reassigning the same variable to the same function declaration results in a new instance so the check doesn’t work in that case either.

You can still check by comparing compileStrings as Symbols { 3 }.cs.asSymbol === { 3 }.cs.asSymbol

You could argue that passing in raw Functions should be avoided unless you take care not to repeat the operation, in which case this could maybe be mentioned in the docs, which refer to specifically functions as arguments I guess… maybe all that’s needed are some examples that point out the gotchas (and some improvement to the help paragraph above…). Likewise for CmdPeriod etc.

Actually there’s no way what you’re proposing could work. My fault for not looking closely enough.

You’re just adding a function. There’s no way (without a lot of work) for SuperCollider to know what the function does. It doesn’t know that it creates a synth called \default.

You’ll have to manage this separately. Store ~init somewhere (maybe another class, or a dictionary).

So for example with the code above you should just do something like:

if (~init == nil){
    ~init = {Synth...};
    ServerTree.ad(~init);
};

Obviously that could be improved, but something along those lines should work.

I mostly don’t use SCLang (prefer Lisp), so the syntax may be slightly off, but hopefully that should give you the gist of it.

It would be nice if you could use an Fdef. But it seems Fdef is not a complete substitute for a Function…

ServerTree.add( Fdef(\func, {  \here.postln  }  ) );
2 Likes

upon further investigation, if you create an extension as such:

+ Fdef {
    doOnStartUp { this.value }
    doOnCmdPeriod { this.value }
    doOnShutDown { this.value }
    doOnError { this.value }
    doOnServerBoot { arg server; this.value(server) }
    doOnServerQuit { arg server; this.value(server) }
    doOnServerTree { arg server; this.value(server) }
}

You can use an Fdef in ServerTree.add without creating duplicates of your function

2 Likes

Comparing the byte code rather than the compile string would be better here f.def.code.

Why is this code being evaluated many times? is this avoidable?

ah yes this is the way!

not sure - I was responding to a user’s question, but I remember bumping into this problem while learning.

So wondering if ServerTree (and the other AbstractServerActions) shouldn’t check when adding a Function that a function with the same byte code is not already in the list? And likewise if a Function is passed to remove, so that users can do ServerTree.add({ Synth(\default) }) (possibly multiple times) and then ServerTree.remove({Synth(\default)}) ?

something like:

	*add { arg object, server;
		// ... //
		if (
			(object.isFunction and: list.select(_,isFunction).includes(object.def.code).not) 
			or:  
			list.includes(object).not
		) { list.add(object) };
	}

seems to have been the design intention since list.includes(object).not is already in the method

It seems to me that this could be a file management issue. The bit of code that adds something to the ServerTree should only be evaluated once while some other code wants to be evaluated multiple times, probably after a ctrl+..

If that’s the case, then more guides on working with multiple files could alleviate this difficulty.

That may be valid if the function is closed. If it’s open, then no.

When I was working on a cache for temp SynthDefs in my ddwPlug quark, I ended up not trying to match open functions. Maybe it’s possible, but matching byte codes isn’t sufficient because the variables in the surrounding scope may have different values during different invocations.

hjh

1 Like

I think they would only produce the same bytecode if the variables name was the same name, had the same frame offset, and the same variable index. Making it quite unlikely — but not impossible!

Here are some examples:

(
var foo;
{ foo } .def.dumpByteCodes
)
  0   21 00    PushTempVar 'foo'
  2   F2       BlockReturn

1 frame offest (21), var index of 0 (00)

(
var foo;
{ 
	{ foo } .def.dumpByteCodes
}.()
)
  0   22 00    PushTempVar 'foo'
  2   F2       BlockReturn

2 frame offsets (22), var index of 0 (00)

(
var bar;
var foo;

{ foo } .def.dumpByteCodes

)
  0   21 01    PushTempVar 'foo'
  2   F2       BlockReturn

1 frame offset (21), var index of 2 (01)


But just to be clear, I don’t think this is a good solution and I suspect the problem points to a file management issue instead.

Here’s the problem I mean:

(
~makeFunc = { |value = 0|
	{ value + 1 }
};

f = ~makeFunc.(1);
g = ~makeFunc.(2);

"f code: ".post;
f.def.code.postln;
"f value: ".post;
f.value.postln;

"g code: ".post;
g.def.code.postln;
"g value: ".post;
g.value.postln;
)

f code: Int8Array[33, 0, 107, -14]
f value: 2
g code: Int8Array[33, 0, 107, -14]
g value: 3

If you were checking g.def.code to match f.def.code, then g would be considered a match, but it isn’t – it evaluates differently with the same .value parameters (here, no parameters).

Back to the thread topic:

~addSynthRebuild = { |defname, args, target, addAction|
    ServerTree.add({
        Synth(defname, args, target, addAction)
    })
};

You might call this multiple times for different synths, but only one would actually be added if you’re using .def.code to identify “same” functions – then “I added a bunch of funcs to ServerTree but most of them aren’t working.”

That’s why I said the .def.code approach in and of itself isn’t enough. It’s fine for closed functions but not safe for open functions.

hjh

1 Like

Ah yes you are right, I didn’t consider that case of a helper function like that!

Mildly OT but function matching came up for me in my ddwPlug quark, where you can write e.g.

// Boards-of-Canada-ify
\freqPlug, { |freq| Plug({ |freq| freq * (1.02 ** LFDNoise3.kr(0.8)) }, [freq: freq]) }

In this form, the function given to Plug makes a SynthDef, and because the function is closed, the SynthDef can be cached and reused for subsequent events.

Taking a shortcut and writing it like this…

\freqPlug, { |freq| Plug { freq * (1.01 ** LFDNoise3.kr(0.8)) } }

… prevents caching because the SynthDef has a hardcoded freq, which won’t be valid for later events.

I was just working on this function-matching problem a few weeks ago; that’s why it was fresh on my mind.

hjh