Similar to Adverbs (see J concepts in SC), roles allow to specify how a source for a NodeProxy is being used. A role is an association of a Symbol and the new proxy source object.
The below examples can equally be used for Ndef and in ProxySpace.
// Thus, the following expressions behave in an equivalent way:
a = NodeProxy(s);
a[0] = ...
ProxySpace.push(s);
~a[0] = ...
Ndef(\a, ...)
\set
. Old values are kept, only those explicitly provided are overridden.xxxxxxxxxx
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, Pdup(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);
xxxxxxxxxx
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] = \pset -> Pbind(
\dur, Prand([1, 0.5], inf),
\freq, Pwhite(200.0, 1000, inf).round(30),
\rate, Pdup(4, Prand([1, 3, 6, 10], inf)),
\dt, Pwhite(0.01, 0.1, inf) + 1
)
);
a.nodeMap.postln; // the values are set in the node map.
xxxxxxxxxx
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] = \xset -> Pbind(
\dur, Prand([1, 0.5], inf),
\freq, Pwhite(200.0, 1000, inf).round(30),
\rate, Pdup(4, Prand([1, 3, 6, 10], inf)),
\dt, Pwhite(0.01, 0.1, inf) + 1
)
);
a.fadeTime = 2;
// 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.clear(3);
\set
. Contrary to other roles it must be applied separately to each channel of the proxy.xxxxxxxxxx
// 5-channel NodeProxy
a = NodeProxy.audio(s).mold(5);
// output will be stereo
b = NodeProxy.audio(s).play;
(
a[0] = {
Blip.ar(
\freq.kr([200, 201, 202, 204, 205]),
\harms.kr([100, 100, 100, 100, 100])
)
};
b[0] = {
// mix 5 channel input to stereo panorama
Splay.ar(\in.ar([0, 0, 0, 0, 0]), level: \amp.kr(0.7))
};
)
// route a out to b in
b <<> a;
// apply the role(s)
(
a.numChannels.do { |i|
// a[0] holds the source!
a[i+1] = \seti -> Pbind(
\freq, Prand(#[62, 64, 67, 70, 72].midicps, inf),
\harms, Pexprand(50, 1000),
\dur, Prand(#[0.1, 0.2, 0.4], inf),
// channelOffset is an offset, not a channel index
\channelOffset, i
)
}
)
// remove roles again
// either all at once or one by one
a.numChannels.do { |i| a[i+1] = nil };
a.clear; b.clear;
\c_set
xxxxxxxxxx
a = NodeProxy(s);
b = NodeProxy(s).play;
b[0] = { SinOsc.ar(a.kr(4)).sum * 0.2 };
(
a[0] = \setbus -> Pbind(
\dur, Prand([1, 0.5], inf),
\value, Pfunc { var z = rrand(300, 4000); [300, 400, z, z + 30.rand2] }
)
);
// modify the other source in the meanwhile:
b[0] = { Pulse.ar(a.ar(4)).sum * 0.2 };
a.clear; b.clear;
xxxxxxxxxx
a = NodeProxy(s);
a.play;
(
a[0] = \setsrc -> Pbind(
\dur, Prand([1, 0.5, 2], inf),
\source, Prand ([
{ SinOsc.ar(SinOsc.ar({5.rand}.dup + 4) * 50 + 400 + 50.rand) * 0.1 },
{ SinOsc.ar(LFSaw.ar({5.rand}.dup + 4) * 50 + 400 + 50.rand) * 0.1},
{ LFSaw.ar(SinOsc.ar({5.rand}.dup + 4) * 50 + 400 + 50.rand) * 0.1 },
{ LFSaw.ar(LFSaw.ar({5.rand}.dup + 4) * 50 + 400 + 50.rand) * 0.1 }
], inf)
)
);
a.clear(3);
\wet1
, and it crossfades between the incoming sound source and the effect (wet) signal output.xxxxxxxxxx
a = NodeProxy(s).play;
a[0] = { RLPF.ar(Dust2.ar(5!2), LFNoise2.kr(2!2).exprange(200, 5000), 0.05) };
a[1] = \filter -> { |in| CombL.ar(in, 0.2, LFNoise2.kr(0.5!2).exprange(0.01, 0.2), 3) };
a.set(\wet1, 0.2); // set dry/wet mix level to less combs
a.set(\wet1, 0.0); // wet 0 is all dry - cuts combs instantly.
a.clear(3);
\filter
, but the \wet
control now sets the filter input level, rather than its output. This lets time-based effects like delays, combs, filters with long ringtimes continue to sound even when the input is already turned off.xxxxxxxxxx
a = NodeProxy(s).play;
a[0] = { RLPF.ar(Dust2.ar(5!2), LFNoise2.kr(2!2).exprange(200, 5000), 0.05) };
a[1] = \filterIn -> { |in| CombL.ar(in, 0.2, LFNoise2.kr(0.5!2).exprange(0.01, 0.2), 3) };
a.set(\wet1, 0.5); // set mix level to less effect signal
// wet 0 is all dry - input is off, but comb decay still sounds.
a.set(\wet1, 0.0);
a.clear(3);
xxxxxxxxxx
a = NodeProxy(s);
a[0] = { PinkNoise.ar(0.1.dup) };
a.play;
a[1] = \mix -> { Dust.ar(30.dup) };
a.set(\mix1, 0.2);
a.clear(3);
Roles can be added on the fly. They are kept in a dictionary ( buildMethods ) in AbstractPlayControl. A second dictionary ( proxyControlClasses ) provides the wrapper class for a given key.
Here is a new role that allows you to set a control rate node proxy with the help of an event pattern. The new values are in a key named \value.
xxxxxxxxxx
// add the new role:
(
AbstractPlayControl.proxyControlClasses.put(\stream, PatternControl);
AbstractPlayControl.buildMethods.put(\stream,
#{ arg pattern, proxy, channelOffset=0, index;
Pbindf(
pattern,
\type, \bus,
\id, Pfunc { proxy.group.nodeID },
\array, Pkey(\value),
\out, Pfunc { proxy.index }
).buildForProxy( proxy, channelOffset, index )
});
)
// test:
a = NodeProxy.control(s);
a.source = \stream -> Pbind(\value, Pseq([1, 2, 3], inf), \dur, 1.5).trace;
b = NodeProxy(s);
b.source = { SinOsc.ar([340, 440] * a.kr) * 0.1 };
b.play;
a.source = \stream -> Pbind(\value, Pseq([1, 2, 3], inf) + Pwhite(0.0, 0.2, inf), \dur, 0.05);
b.source = { SinOsc.ar([5.6, 10.3] ** a.kr + 300) * 0.1 };