Using Ndef as instrument in Pbind

Hi!
Is there a way to use Ndef as an instrument of Pbind?
I’d like to use NdefMixer to control synth params while sending events via Ndef(\x, Pbind(...)).

You can reference the node id of the Ndef in a Pbind with a \set type. The basic setup would be like this:

(
Ndef(\a, {
	var freq = \freq.kr(220);
	var sig = SinOsc.ar(freq);
	sig = sig * 0.1;
	sig!2;
})
)

Ndef(\a).play;
(
x = Pbind(
	\type, \set, 
	\id, Pfunc({Ndef(\a).nodeID}), 
	\args, #[\freq], 
	\degree, Pwhite(0, 4)
).play;
)
x.stop;
Ndef(\a).stop;
4 Likes

Is there a way to make it work with gate?

Another option I’m looking at is using PdefGui + Pdef & Pbind.
But compared to NdefMixer I have to Pdef(\x).set(\freq, 200) to add freq slider to the gui while NdefGui automagically makes gui based on parameters of the Ndef.

In a Pbind the default behavior of \gate won’t retrigger an envelope in a monosynth. You will need to use a trigger instead. You can do so like this:

(
Ndef(\a, {
	var freq = \freq.kr(220);
	var sig = SinOsc.ar(freq);
	sig = sig * Env.perc.kr(gate:\trig.tr) * 0.1;
	sig!2;
})
)

Ndef(\a).play;
(
x = Pbind(
	\type, \set, 
	\id, Pfunc({Ndef(\a).nodeID}), 
	\args, #[\freq, \trig], 
	\trig, 1,
	\degree, Pwhite(0, 4)
).play;
)
x.stop;
Ndef(\a).stop;
1 Like

That’s actually desirable most of the time, i.e. not getting the kitchen sink in every Pdef; keep in mind that the Pdef object/source isn’t always a simple Pbind, so there’s not even a obvious way to do this in general as it’s not clear what the instrument is. (The Pbind could be wrapped in a Pchain, Ppar etc.)

When I got started I used to use Ndefs directly, but that’s actually less flexible than defining SynthDefs first. You can easily use syndefs in Ndefs, but getting the sythnDesc for a Ndef (defined directly) is a pain. I don’t even rember how it’s done, if I ever figured it out. On the other hand you can get the key/values for a Ndef. I.e. if you want to auto-import all the SynthDef (or Ndef) params into a Pdef.envir (or any Environment for that matter) you can do something like

Ndef(\bah, \default) // uses SynthDef \default

k = Ndef(\bah).controlKeysValues() // -> [ freq, 440.0, amp, 0.10000000149012, pan, 0.0 ]

e = ()

e.putAll(k) // -> ( 'freq': 440.0, 'pan': 0.0, 'amp': 0.10000000149012 )

Pdef(\meh).envir = e

Pdef(\meh).gui

// Getting them from the SynthDesc (directly) instead

e = ()

SynthDescLib.global.synthDescs.at(\default).controls do: { |c| e[c.name] = c.defaultValue };

e // -> ( 'out': 0.0, 'gate': 1.0, 'freq': 440.0, 'amp': 0.10000000149012,  'pan': 0.0 )

The latter approach has no “smarts” like Ndef.controlKeysValues which filters out some things like out and gate by default.

2 Likes

I remembered how to “officially” use a Ndef “directly” in a Pbind. You have to use the so-called “NodeProxy roles”, i.e. add 2nd source to the Pbind of an association type, with the key \set (or friends… xset, pset.) and value the Pbind. Example from the help page:

a = NodeProxy(s);
a[0] = { |freq = 440, dt=0.1, rate=2| Ringz.ar(Impulse.ar(rate * [1, 1.2]), freq, dt)*0.1 };
a.play;
(
a[1] = \set -> Pbind(
    \dur, Prand([1, 0.5], inf),
    \freq, Pwhite(200.0, 1000, inf),
    \rate, Pstutter(4, Prand([1, 3, 6, 10], inf)),
    \dt, Pwhite(0.01, 0.1, inf)
)
);
// modify the source in the meanwhile:
a[0] = { |freq = 440, dt=0.1, rate=2| Ringz.ar(Dust.ar(rate * 10.dup), freq, dt)*0.1 };

a.nodeMap.postln; // the values are not set in the node map.
a.clear(3);

Note that this is a bit “backwards” than naming the instrument in the Pbind even by id (as in @droptableuser’s example), as you “link” to the Pbind in the Ndef/NodeProxy with “nodeproxy role”.

This is (of course) nothing too deep in terms of implementation (wrapForNodeProxy.sc):

			set: #{ | pattern, proxy, channelOffset = 0, index |
				var args = proxy.controlNames.collect(_.name);
				Pchain(
					(type: \set, \id: { proxy.group.nodeID }),
					pattern,
					(args: args)
				).buildForProxy( proxy, channelOffset, index )
			},

By the way, from there I see that pset and xset override the Event’s play

			pset: #{ | pattern, proxy, channelOffset = 0, index |
				Pbindf(
					pattern,
					\play, { proxy.set(*proxy.controlNames.collect(_.name).envirPairs.asOSCArgArray) }
				).buildForProxy( proxy, channelOffset, index )
			},

So some things will break when using those two, e.g. \callback (or \finish) will not work. And probably some other Event features (like some calculations).

a = NodeProxy(s);
a[0] = { |freq = 440, dt=0.1, rate=2| Ringz.ar(Impulse.ar(rate * [1, 1.2]), freq, dt)*0.1 };
a.play;
(
a[1] = \set -> Pbind(
    \dur, Prand([1, 0.5], inf),
    \freq, Pwhite(200.0, 1000, inf),
    \rate, Pstutter(4, Prand([1, 3, 6, 10], inf)),
    \dt, Pwhite(0.01, 0.1, inf),
	\callback, { "called back".postln }
)
);

a.nodeMap.postln; // the values are not set in the node map.

(
a[1] = \pset -> Pbind(
    \dur, Prand([1, 0.5], inf),
    \freq, Pwhite(200.0, 1000, inf).round(30),
    \rate, Pstutter(4, Prand([1, 3, 6, 10], inf)),
    \dt, Pwhite(0.01, 0.1, inf) + 1,
	\callback, { "NOT called back".postln }
)
);

a.nodeMap.postln; // the values are set in the node map.

a.clear(3);
2 Likes

Nodeproxy Role is usually the way to go but you could also make your own event type for setting it externally:

(
	// Create new Ndef setter event type
	Event.addEventType(\ndset, {|server|
		~type = \set;
		~id = Ndef(~key).group;
		~args = Ndef(~key).controlKeys(except: ~exceptArgs);

		currentEnvironment.play;
	})
)

// Make a dummy Ndef for testing
Ndef(\output, {|a1, a2| 
	a1.poll; 
	a2.poll;
})

// Test using pattern
Pbind(
	\type, \ndset, 
	\key, \output, 
	\a1, Pwhite(0.0,1.0), 
	\a2, Pwhite(0.0,1.0)
).play;
3 Likes

@madskjeldgaard There might a problem with this if you use Event callbacks and expect them to be called only once per event. With your approach you get them “doubled” because Event play calls itself again.

(Pbind(
	\type, \ndset,
	\key, \output,
	\a1, Pwhite(0.0,1.0),
	\a2, Pwhite(0.0,1.0),
	\callback, { ("called back" + Main.elapsedTime).postln }
).play;)

You’ll get something like

called back 142314.43081224
called back 142314.4308639
called back 142315.43046385
called back 142315.4305211

The \composite type added in 3.11 has the same issue (but there arguably it could be a feature).

Actually, that double-callback issue is easy to avoid even in this approach:

(
// avoid duoble play, so double-callbacks
Event.addEventType(\ndset, {|server|

	~id = Ndef(~nkey).nodeID;
	~args = Ndef(~nkey).controlKeys(except: ~exceptArgs);
	~eventTypes[\set].value(~server);
})
)


// Make a dummy Ndef for testing
(Ndef(\jpjp, {|a1, a2|
	[a1, a2].poll(3);
}))


// Test using pattern
(Pbind(
	\type, \ndset,
	\nkey, \jpjp,
	\a1, Pwhite(0.0,1.0, 4),
	\a2, Pseq((10..15)),
	\callback, { ("called back" + Main.elapsedTime).postln }
).play;)

Although, there’s the minor nagging issue that you still have to use a different event name…

The one disadvantage with the NodeProxy role is I don’t think you can then include the pattern in a Pchain or Ppar. At least, I couldn’t figure that one out.

1 Like

If that’s somehow undesirable (e.g. double callback at least with that implementation), one can hook \finish instead for roughly the same effect

// Make a dummy Ndef for testing
(Ndef(\jpjp, {|a1, a2|
	[a1, a2].poll(0.5);
}))

(Pbind(
	\type, \set,
	\ndef, \jpjp,
	\finish, { ~id = Ndef(~ndef).group.nodeID; ~args = Ndef(~ndef).controlKeys }, 
	\a1, Pwhite(0.0,1.0, 3),
	\a2, Pwhite(0.0,1.0),
	\callback, { (~id.asString + "called back" + Main.elapsedTime).postln }
).play;)

Note that setting the args field is essential here, as Event type \set can’t seem to figure out the list from (actually group) nodes produced by Ndef.

An annoying thing is that \set doesn’t eval ~id as a function, so changing it is a bit difficult (without patching \set) or some other precalculation method (\finish, a Pfunc, another event [type] etc.), An addParentType (to \set) doesn’t alas help in any way with this.

			bndl = ([15 /* \n_set */, ~id] ++  bndl).flop.asOSCArgBundle;

Actually that is really confusing. It turns out that ~id can be a function because

{ 23 }.asOSCArgBundle // actually evals -> 23... because it calls
{ 23 }.asControlInput // which is defined in AbstractFunction to ^this.value

However it is the args which can’t eval’d that way

(Event.addParentType(\set, (
	id: { ("parent set ndef" + ~ndef).postln; ~ndef !? { |n| Ndef(n).group.nodeID } },
	args: { "NOT CALLED".postln; ~ndef !? { |n| Ndef(n).controlKeys(except: ~exceptArgs)} ? ~args }
).parent_(Event.default.parent)))

// Make a dummy Ndef for testing
(Ndef(\jpjp, {|a1, a2|
	[a1, a2].poll(0.5);
}))


(Pbind(
	\type, \set,
	\ndef, \jpjp,
	\a1, Pwhite(0.0,1.0, 2),
	\a2, Pwhite(0.0,1.0),
	\callback, { ("called back" + [Main.elapsedTime, ~id !? (_.value)]).postln; }
).play;)

that outputs something like

-> Ndef('jpjp')
UGen Array [0]: 0
UGen Array [1]: 0
UGen Array [0]: 0
UGen Array [1]: 0
-> an EventStreamPlayer
parent set ndef jpjp
parent set ndef jpjp
called back [ 182969.19091744, 1104 ]
parent set ndef jpjp
parent set ndef jpjp
called back [ 182970.19082074, 1104 ]
UGen Array [0]: 0
UGen Array [1]: 0
UGen Array [0]: 0
UGen Array [1]: 0

It’s the args which are a little harder to have eval’d as a function. I think ~msgFunc (called by ~getMsgFunc) is for that. It gets called by Event type \set … but only if args is of size zero.

Anyway, that parent even type setting works for the id, but you still need to set the args in Pbing that way

(Pbind(
	\type, \set,
	\ndef, \jpjp,
	\a1, Pwhite(0.0,1.0, 2),
	\a2, Pwhite(0.0,1.0),
	\args, [\a1, \a2], // still needed
	\callback, { ("called back" + [Main.elapsedTime, ~id !? (_.value)]).postln; }
).play;)

Actually, it turns out that envirPairs which Event type set calls on args is only defined in Array (but not in Object) so one can hack it from an event like:

(envirPairs: {"foo"}).envirPairs // -> foo

So it might be possible to do it this way too, but this is a brittle mechanism, e.g. if envirPairs gets defined as an object method, this will stop working… A (second) attempt to make that work turn out okish

(Event.addParentType(\set, (
	id: { ("parent set ndef" + ~ndef).postln; ~ndef !? { |n| Ndef(n).nodeID;  } }, // group
	args: (envirPairs: { var rv = ~ndef !? { |n|  Ndef(n).controlKeys(except: ~exceptArgs).envirPairs }; ("HACK args" + rv).postln; rv }) // ? ~args.envirPairs
).parent_(Event.default.parent)))

(Pbind(
	\type, \set,
	\ndef, \jpjp,
	\a1, Pwhite(0.0,1.0, 2),
	\a2, Pwhite(0.0,1.0),
	//\args, [\a1, \a2],
	\callback, { ("called back" + [Main.elapsedTime, ~id !? (_.value)]).postln; }
).play;)

Output like

-> Ndef('jpjp')
UGen Array [0]: 0
UGen Array [1]: 0
-> an EventStreamPlayer
HACK args [ a1, 0.89903783798218, a2, 0.11174464225769 ]
parent set ndef jpjp
parent set ndef jpjp
called back [ 556.56184814, 1006 ]
UGen Array [0]: 0.899038
UGen Array [1]: 0.111745
HACK args [ a1, 0.5106920003891, a2, 0.66889154911041 ]
parent set ndef jpjp
parent set ndef jpjp
called back [ 557.561878161, 1006 ]
UGen Array [0]: 0.510692
UGen Array [1]: 0.668892
UGen Array [0]: 0.510692
UGen Array [1]: 0.668892

A slightly cleaned-up version of that that doesn’t break the default Event args (tested):

(Event.addParentType(\set, (
	id: { ~ndef !? { |n| Ndef(n).nodeID; } },
	args: (envirPairs: { ~ndef !? { |n|  Ndef(n).controlKeys(except: ~exceptArgs).envirPairs } ? Event.default.parent.args.envirPairs; })
).parent_(Event.default.parent)))
1 Like

Probably not impossible but annoying, e.g. via Pbind data sharing (Penvir, Plambda, PSdup etc.) with a “stub” Pbind in the Ndef \set role that get the actual data from elesewhere.

Were you setting args? Because this is a common gotcha:

y = Synth(\default)

(type: \set, id: y.nodeID, freq: 400, args: [\freq]).play // args is actually redundant here

(type: \set, id: y.nodeID, gate: 0, args: [\gate]).play // but not here!

y = Synth(\default)

(type: \set, id: y.nodeID, gate: 0).play // this won't turn it off, but will reset freq!

That’s because the default Event args do not include gate (as a settable parameter).

Also now that I remembered to look back at this:

// Make a dummy Ndef for testing
(Ndef(\jpjp, {|a1, a2|
	[a1, a2].poll(0.5);
}))

Ndef(\jpjp).objects // -> Order[ a SynthDefControl ]
Ndef(\jpjp).objects[0].asDefName // -> temp__0jpjp-33677435_0
Ndef(\jpjp).objects[0].synthDesc // -> nil 

There is a method for getting the SynthDesc from a SynthDefControl (which is what NodeProxy wraps its synths in) but alas it seem to always return nil for the nodeproxy built via functions (as opposed to synthdef names).

But it is actually possible to build those Descs “on demand”

Ndef(\jpjp).objects[0].synthDef.asSynthDesc // -> SynthDesc 'temp__0jpjp-33677435_0' 
Ndef(\jpjp).objects[0].synthDesc // not nil now

That also gives us a way to auto-generate the msgFunc that we can use in Event.

Ndef(\jpjp).objects[0].synthDesc.msgFunc.cs
// ->
-> #{ arg a1, a2, fadeTime, out;
	var	xA6F415B2 = Array.new(8);
	a1 !? { xA6F415B2.add('a1').add(a1) };
	a2 !? { xA6F415B2.add('a2').add(a2) };
	fadeTime !? { xA6F415B2.add('fadeTime').add(fadeTime) };
	out !? { xA6F415B2.add('out').add(out) };
	xA6F415B2

And the following does work by using msgFunc instead of args:

(Pbind(
	\type, \set,
	\ndef, \jpjp,
	\a1, Pwhite(0.0,1.0, 2),
	\a2, Pseq([100, 101]),
	\args, #[], // this will use msgFunc instead...
	\msgFunc, Ndef(\jpjp).objects[0].synthDesc.msgFunc,
	\callback, { ("called back" + [Main.elapsedTime, ~id !? (_.value)]).postln; }
).play;)

You can also set the instrument instead which is used to lookup the msgFunc, but this is hardly an improvement here

(Pbind(
	\type, \set,
	\ndef, \jpjp,
	\a1, Pwhite(0.0,1.0, 2),
	\a2, Pseq([100, 101]),
	\args, #[], // this will use msgFunc instead... directly or via...
	\instrument, Ndef(\jpjp).objects[0].asDefName, // used to lookup msgFunc
	\callback, { ("called back" + [Main.elapsedTime, ~id !? (_.value)]).postln; }
).play;)

Unfortunately I wasn’t able to get that to work inside an addParentType. \set alas ignores ~synthDefName, even though it implicitly depends on \instrument in its \getMsgFunc. And trying to override the latter resulted in some wierd error I cannot figure out right now:

(Event.addParentType(\set, (
	id: { ("parent set ndef" + ~ndef).postln; ~ndef !? { |n| Ndef(n).nodeID; } },
	getMsgFunc: { |instrument| ~ndef !? {|n| instrument = ~instrument = Ndef(n).objects[0].asDefName }; ("gMF" + instrument).postln;
		Event.default.parent.getMsgFunc(instrument) } 
).parent_(Event.default.parent)))

// Make a dummy Ndef for testing
(Ndef(\jpjp, {|a1, a2|
	[a1, a2].poll(0.5);
}))

Ndef(\jpjp).objects[0].synthDef.asSynthDesc

(Pbind(
	\type, \set,
	\ndef, \jpjp,
	\a1, Pwhite(0.0,1.0, 2),
	\a2, Pseq([100, 101]),
	\args, #[], // this will use msgFunc instead...
	\callback, { ("called back" + [Main.elapsedTime, ~id !? (_.value)]).postln; }
).play;)

Barfs

gMF temp__0jpjp-33677435_0
ERROR: Cannot convert this object to a SynthDef:  ( 'instrument': default, 'db': -20.0, .....

It’s probably easier to just change \set rather than figure out how to plug all the right bits into the Event architecture to change \set’s lookups the kosher way.

So… I’ve actually settled on this approach:

// one can actually copy the old \set to a different key
Event.addEventType(\stdset, Event.default.eventTypes[\set])


// and make set "smarter"
(Event.addEventType(\set, {|server|

	var id;
	if(~nkey.notNil and: { (id = Ndef(~nkey).nodeID).notNil }) {
		~id = id;
		// use \set semantics of only generating args if args.size == 0
		// although this is rather annoying, it's easily overriden by an
		// addParentType setting args to []
		if(~args.size == 0) {
			~args = Ndef(~nkey).controlKeys(except: ~exceptArgs);
		};
	};
	~eventTypes[\stdset].value(~server);
})
)

// Make a dummy Ndef for testing
(Ndef(\jpjp, {|a1, a2|
	[a1, a2].poll(3);
}))

// Tests
(Pbind(
	\type, \set,
	\nkey, \jpjp,
	\a1, Pwhite(0.0,1.0, 4),
	\a2, Pseq((10..15)),
	\args, #[], // autogen
	\callback, { ("called back" + Main.elapsedTime).postln }
).play;)

(Pbind(
	\type, \set,
	\nkey, \jpjp,
	\a1, Pwhite(0.0,1.0, 4),
	\a2, Pseq((10..15)),
	\args, #[\a2], // specify
	\callback, { ("called back" + Main.elapsedTime).postln }
).play;)

Event.addParentType(\set, (args: #[])) // auto by default

(Pbind(
	\type, \set,
	\nkey, \jpjp,
	\a1, Pwhite(0.0,1.0, 4),
	\a2, Pseq((10..15)),
	//\args, #[], // autogen is auto now
	\callback, { ("called back" + Main.elapsedTime).postln }
).play;)

// Check that some standard \set stuff still works the same, omitted here.
2 Likes