Message 'def' not understood when declaring function inside Environment

Compare these three variants of similar code:

// 1
SynthDef(\test, { Out.ar(0, SinOsc.ar) });

// 2
a = { Out.ar(0, SinOsc.ar) };
SynthDef(\test, a);

// 3
a = ();
a.function = { Out.ar(0, SinOsc.ar) };
SynthDef(\test, a.function);
// ERROR: Message 'def' not understood.

Why does the third example generate an error? What is the difference between putting the function in its own variable vs. the event namespace variable?

This works

// This works
(
~myfunction = { |synthName|
  var myenv = ();
  myenv.defaultParams = [];
  myenv.synthFunction = { Out.ar(0, SinOsc.ar) };
  myenv.init = { fork {
    SynthDef(synthName, { value(myenv.synthFunction) }).add; s.sync;
    myenv.synthInstance = Synth(synthName.asSymbol, myenv.defaultParams)
  } };
};
)

~myenv = ~myfunction.value('Test');
~myenv.init;

These two are passing a function into SynthDef.new.

This one is passing the result of the function, but not the function itself.

If you change it to read SynthDef(\test, a[\function]); then it should work. .xxx and [\xxx] are not interchangeable even though many examples use them as such.

hjh

1 Like

Thank you. Is this documented somewhere? It was particularly obvious although it makes sense now.
I guess [\xxx] is like at and .xxx is like a method call.

This is where its implemented - don’t think its documented.

In Smalltalks you can look at the message (bit after the full-stop) and redirect it if the underlying class doesn’t understand it using the doesNotUnderstand method.

Yes.

https://doc.sccode.org/Classes/Environment.html#Using%20Environments%20as%20object%20prototypes

If the Environment item is a function, it is evaluated as if it were a method definition. The first argument passed into the function is the Environment that holds the function; arguments to the method call follow as the second, third etc. arguments passed into the function.

e.printMe("Oh hai wrldz");

// same as
e['printMe'].value(e, "Oh hai wrldz");

… where e was defined at the top of this section.

hjh

Related-ish question:

I want to compose SynthDefs out of functions, in the way you might use a templating engine. In the example below, I am defining a “footer” called outs which takes an audio rate signal which I would like to define once and re-use.

I am reading the Environment docs but I’m having trouble wrapping my head around the scopes. The code below generates an error.

~test = ();
~test.outs = { arg sig; Out.ar(\out.kr(0), sig * \gate.kr(0) ) };
~test.signalPath = { var sig = SinOsc.ar; ~test[\outs].valueEnvir };
~test.signalPath.play;
// ERROR: Message 'addKr' not understood.

I also tried some variants using ~test.make and ~test.use.
Any suggestions here?

Here is one way to do it where the sound elements are stored in the event as functions using the ~event[\name → { }) syntax.

// Three different variants of the samme thing

(
~test = ();
~test.add(\outs -> { arg sig; Out.ar(\out.kr(0), sig * \gate.kr(1) ) });
~test.add(\signalPath -> { var sig = SinOsc.ar(200); ~test[\outs].(sig * 0.1) });
~test[\signalPath].play;
);

(
~test = ();
~test.add(\outs -> { arg sig; Out.ar(\out.kr(0), sig * \gate.kr(1) ) });
{ var sig = SinOsc.ar(200); ~test[\outs].(sig * 0.1) }.play;
)


(
~test = ();
~test.add(\outs -> { arg sig; Out.ar(\out.kr(0), sig * \gate.kr(1) ) });
SynthDef(\test, {
	var sig = SinOsc.ar(200);
	~test[\outs].(sig * 0.1)
}).play
)
1 Like

What about this case? Below is a class method which will iterate through a directory, call the .load method on the file and try and create a synthDef from it.

  loadSynthDefs { 
    var path = "%/../SynthFunctions/*"
    .format(File.realpath(this.class.filenameSymbol)
    .dirname.withTrailingSlash);
    pathMatch(standardizePath(path)).do { |filepath|
      if(filepath.splitext.last == "scd") {
        var a = ();
        a.outs = { arg sig; Out.ar(\out.kr(0), sig); Out.ar(\record.kr(0),sig * \recordEnable.kr(0)) }.inEnvir;
        a.synthGraph = filepath.load;
        "loading synthDef %_%"
        .format(this.class, PathName(filepath).fileNameWithoutExtension)
        .postln;
        SynthDef("%_%".format(this.class, PathName(filepath).fileNameWithoutExtension).asSymbol, a[\synthGraph]).add;
      }
    };
  }

The context of filepath is a graph function where I reference the parent environment:

a.[\outs].value(sig);
// ^^ ERROR: Message 'at' not understood.`

Is there a way to bind the envars inside of the scope of the function in this context?

Let me ask a different question:

// The compiler provides a shortcut syntax where ~ is a placeholder for currentEnvironment.
// This makes the expression ~myvariable; equivalent to currentEnvironment.at(\myvariable); and the expression ~myvariable = 888; equivalent to currentEnvironment.put(\myvariable, 888);

a = 'foo';
~a = 'bar';
currentEnvironment[\a] // bar
topEnvironment[\a] // bar

What is the Environment of a? Does a have an environment? Is there a method to discern it?

How about this?

a = [1, 2, 3];
b = a;

a[0]  // 1
b[0]  // 1

What is the array of 0?

hjh

So there’s a reference/pointer but is there a global context/namespace/environment for a that can be referenced?

That should be a[\outs].value(sig), no fullstop between a and [.

Assuming that is ‘a’ without a tilde then there is no environment, this is stored in the Interpreter class.
If however, you declare ‘a’ like this … var a … then a exists in the local scope (surrounding curly braces) and will be destroyed when the code leaves the scope and there are no other references to the variable. If you want to use ‘a’ outside of the scope just return it or append it to something.

It’s actually in the code you quoted:

currentEnvironment[\a]
topEnvironment[\a]

You could try it by yourself – what do you see if you remove the “at” operation? (What is the value of currentEnvironment? Of topEnvironment?)

Why are there two names? Because currentEnvironment may change but topEnvironment doesn’t. But the fact that currentEnvironment can change means that ~a is not truly global. It acts like it’s global in most usages but if you push or use a different environment, then you have a different ~a. So “global” is not quite the right concept here.

hjh