# Order of execution

This may sound like a stupid question , but is there a certain order of execution ?
In the first example , the decay takes Dust as input , but the dust u-gen is one line lower , which I believe is the reason why the console gives an error .
The second example works , first dust , then decay ( taking dust as input )
What puzzles me is that the console reports ;The preceding error dump is for ERROR: Decay first input is not audio rate; for the first example
Afaik it does take an audio rate input , namely dust â€¦or is it just another way of saying that it canâ€™t execute the order correctly ?

``````///
(
{
var a,b;
a= Decay.ar(b,0.064);
b= Dust.ar(15);
SinOsc.ar(880)!2*a
}
.play
)
///
(
{
var a,b;
b=Dust.ar(10);
a=Decay.ar(b,0.064);
SinOsc.ar(880)!2*a*0.5
}
.play)
///``````

Edit
I just remembered that I made a similar topic about serial processing when using all sig variables , guess this is related.
And just found out about the amazing ctrl+shift + cursor keys or swapping lines

You have to create a UGen before you use it as an input somewhere else.

You canâ€™t flip lines around into a random order and expect them to keep working.

Dependency is the key concept here.

The Decay unit depends on whatever `b` is.

If `b` is not yet valid, then the Decay expression will fail.

So, whenever you have one expression Z that depends on one or more other expressions (say, X and Y), then X and Y must be written first. There is no way to write Z X Y and expect it to work.

(There are languages such as Haskell where calculations are described as networks of relationships and the compiler determines the order. These languages are rare. SC is not one of them. These are temporal sequences of operations and the order is your responsibility to get right.)

hjh

Makes sense , just like max msp or loomer architect which have top,bottom left right approach etcâ€¦

Who said I fliiped lines around in a random order ?
I am learning and experimenting in supercollider , adhering the rules
By flipping lines around I meant that the first (wrong example ) can easily be turned into a working example by putting my cursor on the line where decay u-gen resides , holding crl+shift and press the down arrow

Experimenting is a valid way of learning, sure. What I meant was, you actually ran into this situation before (â€śSadly there is no way to mute individual lines to get to the bottom of thisâ€ť @ Chains of reassignment), where you tried to use a signal as an input to something else, but the signal hadnâ€™t been prepared. Itâ€™s the same thing. If youâ€™re using a signal input, there must be a signal to input.

So, in the first example here, either you expected it to work, or you expected it to fail. If you expected it to fail, then you already understand the principle (and you already know the answer â€“ so, you could start to have a bit more confidence).

If you expected it to work, then it suggests to me that you might be under-generalizing from the experimental results â€“ seeing the earlier question and this question as distinct, separate situations when in fact they are variants of the same principle. This is a tricky thing â€“ nobody knows, at the beginning, how much to generalize findings. From where I sit, it looks to me like you tend to view the results of your experiments as small-scale ideas when, often, they point to broader principles â€“ IMO, you could afford to generalize a bit more.

For instance, if you use patterns later on, eventually youâ€™ll find Pkey, which allows reuse of previously calculated values:

``````// this is ok
p = Pbind(
\a, Pwhite(1, 10, inf),
\b, Pkey(\a) * 2
);

// this will throw an error when played
p = Pbind(
\b, Pkey(\a) * 2,
\a, Pwhite(1, 10, inf)
);
``````

The surface appearance is different but itâ€™s the same principle.

hjh

hmmm it would sure be nice if Pkey could look forwardâ€¦ or maybe that would be a can of wormsâ€¦

If you want to make changes to an Event based on its â€śfinalâ€ť state, but before its type Function is evaluated, you can use the `\finish` key, which takes a Function into which you can pass the Event. As with Pfunc, you can alter or remove existing keys in the Event, add new ones, etc in this Function:

``````(
p = Pbind(
\finish, {|ev|
ev.degree = ev.degree % 8 + [0, 2]; // alter a key
ev.dur = 0.1; // add a key
ev.removeAt(\legato) // remove a key
},
\legato, 20,
\degree, Pseries()
).play
)
``````

This is a contrived example and in practice I probably wouldnâ€™t have `\finish` first in the interest of readability; just wanted to illustrate that itâ€™s possible.

thatâ€™s super useful! thanks for the tip!

where is this documented? not findingâ€¦ thx!

Itâ€™s mentioned here, in â€śA Practical Guideâ€ť and here, in â€śUnderstanding Streams, Patterns, and Eventsâ€ť. Actual implementation here, with an excellent plain english explanation in this comment.

Hope that helps! It was quite eye opening for me when I discovered it.

Thereâ€™s no way to evaluate an expression containing Pkey if the referent isnâ€™t available â€“ thatâ€™s impossible.

But the situation youâ€™re asking about is, what if the referent is defined in the Pbind, just, later?

Technically that would (usually) be possible if we could determine the event-key dependencies of every key-pattern pair in the Pbind, and reorder the keys such that itâ€™s always â€śkeys being referred toâ€ť first, then the Pkey(s).

A couple of counterexamples:

``````// algebraically correct, but also un-resolvable
Pbind(
\a, Pkey(\b) + 1,
\b, Pkey(\a) - 1
)

// Plazy, or Pfunc for that matter: no reliable way to find dependencies
Pbind(
\a, Plazy {
Pkey(("a" ++ 3.rand).asSymbol)
},
\a0, Pwhite(0, 9, inf),
\a1, Pwhite(10, 19, inf),
\a2, Pwhite(20, 29, inf)
)
``````

OTOHâ€¦ hmmâ€¦ Iâ€™m thinking of something now. Itâ€™s not fully formed in my mind, so I wonâ€™t go deep into it, but it might loosely look like a â€śLazyEventâ€ť where Pbind would put all the streams into the event and then any `at` into the event would a/ if it finds a stream that hasnâ€™t been resolved, evaluate it or b/ return a previously evaluated result. It might work, though my first counterexample (a and b being mutually dependent) would hang the interpreter under this alternate model (where it throws an error now).

hjh

Had a bit of time at lunch, so I POCâ€™ed something.

The â€śsomethingâ€ť I was thinking of involves the difference between push and pull models of computation (which James McCartney mentioned in passing at the first SC symposium, years ago). Pbind and SynthDef building are currently â€śpushâ€ť models â€“ you start at the top and push values down. In this model, calculations have to be written in the right order â€“ if B depends on A, you canâ€™t do B if A happened to be written later.

But an alternate model is to â€śpullâ€ť a value from the root node of a tree of operations. If this operation depends on any values that havenâ€™t been resolved yet, then the operation itself can call for them to be resolved â€“ effectively, a tree traversal.

The advantage in the context of this thread is, when â€śpullingâ€ť from a root node, the order in which the nodes are defined doesnâ€™t matter, because values are resolved as needed, on demand. (This is definitely inspired by Haskell.) So this could support â€śPkey [to] look forwardâ€ť and UGens declared later in the code than their usage.

``````// class definitions
// proof of concept - hjh 2020-09-11

// basically Thunk, possibly could fold this into Thunk
LazyResult : AbstractFunction {
var func, value;
var originalFunc;
*new { |func| ^super.newCopyArgs(func, nil, func) }

value { |... args|
^if(this.isResolved) {
value
} {
value = func.value(*args);
func = nil;
value
}
}

isResolved { ^func.isNil }

reset { func = originalFunc; value = nil }
}

LambdaEnvir {
var envir;
var resolvingKeys;

*new { |env|
^super.new.init(env);
}

init { |env|
envir = Environment.new;
env.keysValuesDo { |key, value|
envir.put(key, LazyResult(value));
};
}

at { |key|
var entity = envir.at(key);
if(entity.isResolved) {
// should be an already-calculated value,
// but we need to return it below
entity = entity.value;
} {
if(resolvingKeys.isNil) {
resolvingKeys = IdentitySet.new;
};
if(resolvingKeys.includes(key)) {
Error("LambdaEnvir: Circular reference involving '%'".format(key)).throw;
};
protect {
if(currentEnvironment === this) {
entity = entity.value(this);
} {
this.use {
entity = entity.value(this);
};
};
} {
resolvingKeys.remove(key);
};
};
^entity
}

put { |key, value| envir.put(key, value) }

use { |func|
var saveEnvir = currentEnvironment;
var result;
protect {
currentEnvironment = this;
result = func.value;
} {
currentEnvironment = saveEnvir;
};
^result
}

keysValuesDo { |func|
envir.keysValuesDo(func)
}

reset {
this.keysValuesDo { |key, value| value.reset }
}
}

+ Object {
isResolved { ^true }
}
``````

With this in your extensions directory, then:

``````(
l = LambdaEnvir((
a: { 10.rand },
b: { ~a + 1 }
));

l[\b]  // between 1 and 10
)

l[\b]  // same value again -- this LambdaEnvir is already resolved

l.reset; l[\b]  // new value

// can you crash it?
(
l = LambdaEnvir((
a: { ~b + 1 },
b: { ~a - 1 }
));

l[\b]
)

-->
ERROR: LambdaEnvir: Circular reference involving 'b'
``````

And even SynthDef building:

``````(
l = LambdaEnvir((
out: { Out.ar(\out.kr, (~filter * (~eg * \amp.kr(0.1))).dup) },
eg: { EnvGen.kr(Env.perc, doneAction: 2) },
filter: { RLPF.ar(~osc, \ffreq.kr(2000), \rq.kr(1)) },
osc: { Saw.ar(\freq.kr(440)) }
));

)

// or even
(
l = LambdaEnvir((
synthdef: { SynthDef(\test, { ~out }) },
out: { Out.ar(\out.kr, (~filter * (~eg * \amp.kr(0.1))).dup) },
eg: { EnvGen.kr(Env.perc, doneAction: 2) },
filter: { RLPF.ar(~osc, \ffreq.kr(2000), \rq.kr(1)) },
osc: { Saw.ar(\freq.kr(440)) }
));

)
``````

It needs a little more work before it could run transparently with Pkey, but I can see in my head how to do it. Also, currently LambdaEnvir is not a drop-in replacement for environments or events â€“ for this POC, I didnâ€™t bother to mimic the entire class interface. (Also, for SynthDef building, this currently doesnâ€™t deal so well with non-pure UGens like BufWr, SendReply, FreeSelf etc, which are often used without plugging their output into another unitâ€™s input â€“ Iâ€™ve got an idea for that, but wonâ€™t do it today.)

But I think this is promisingâ€¦ itâ€™s probably fiddly in ways that I havenâ€™t realized yet, but thereâ€™s something appealing hereâ€¦

hjh

``````LazyPbind : Pbind {
embedInStream { |inevent|
var proto = Event.new;
var lambdaEnvir;
var event;

// these will become LazyResults later
// this is OK because both streams and functions respond to 'value'
patternpairs.pairsDo { |key, value|
proto.put(key, value.asStream)
};

loop {
if(inevent.isNil) { ^nil.yield };
// passing 'proto' here populates all the LazyResult objects
lambdaEnvir = LambdaEnvir(proto)
.parent_(inevent.parent).proto_(inevent.proto);
event = inevent.copy;
lambdaEnvir.keysValuesDo { |key|
var value = lambdaEnvir[key];  // this is what resolves the LazyResult
if(value.isNil) { ^inevent };
if(key.isSequenceableCollection) {
if(key.size > value.size) {
("the pattern is not providing enough values to assign to the key set:" + key).warn;
^inevent
};
key.do { |key1, i|
event.put(key1, value[i]);
};
} {
event.put(key, value);
};
};
inevent = event.yield;
}
}
}

// oh, and LambdaEnvir needs to add:
+ LambdaEnvir {
parent { ^envir.parent }
proto { ^envir.proto }
parent_ { |parent| envir.parent_(parent) }
proto_ { |proto| envir.proto_(proto) }
}
``````

Then:

``````(
p = LazyPbind(
\instrument, \default,
\freq, Pkey(\dur).linexp(0.05, 0.25, 200, 800),
\dur, Pwhite(0.05, 0.25, inf)
).play;
)

p.stop;
``````

Hereâ€™s `\freq` depending on `\dur` that is defined later, andâ€¦ it works.

Soâ€¦ are we onto something here?

hjh

2 Likes

this is very appealing Iâ€™m excited to play with itâ€¦

love the SynthDef building example - stays legible without the annoyance of a big variable declaration block at the top. Also seems like a handy way to get a modular arrangement going perhaps.

I donâ€™t want to oversell that â€“ LambdaEnvir doesnâ€™t support arguments being passed to the functionally-defined values, so it wouldnâ€™t support reusable components (or recursion, for that matter). You could declare reusable components outside the LambdaEnvir and use them inside (but you can do that in normal SynthDef style too). But it would go some distance toward relieving the need to handle calculation dependencies by hand.

hjh

My next question would be whether LazyPbind shouldnâ€™t replace just plain Pbind - is there a downside?

I can think of one downside.

``````p = Pbind(
// begin a calculation
\a, Pwhite(0, 9, inf),

// now get a limiting value from somewhere
\b, Pfunc { aControlBus.getSynchronous },

// and finish the calculation
\a, min(Pkey(\a), Pkey(\b))
)
``````

LazyPbind can have only one `\a`, so it would break compatibility with the above. Iâ€™m pretty sure I have code like that lying around somewhere.

hjh

â€¦didnâ€™t know you could do that! Iâ€™ve only ever repeated keys to A/B possibilitiesâ€¦ could have â€śnew Pbindâ€ť check for repeated keys and fall back to â€ślegacy Pbindâ€ť if soâ€¦

Another:

``````p = Pbind(
\degree, Pwhite(0, 7, inf),
\dur, 0.5,
\callback, { |event|
funcToRegisterIDs.value(event[\id])
}
).play;
``````

In the current Pbind, a function assigned to a key is neither a pattern nor a stream (responds to `asStream` with itself), so the function is stored in the event as-is and not evaluated until event-play time (`\callback` and `\finish` in particular).

The LambdaEnvir used in this POC evaluates functions as lazy values, so `\callback` would be evaluated during Pbind processing (which is early â€“ the IDs wonâ€™t be populated yet).

It should work to write `\callback, { { /* code */ } }` but that breaks compatibility with existing code.

I suppose there could be a LazyStreamResult that calls `next` instead of `value` (which, I think, Iâ€™d have to check, should evaluate streams but pass functions through).

hjh

Iâ€™m going to use this in work for a bit and see how it goes. I think it solves some long standing annoyances (for me!) Perhaps this merits its own thread?