«NodeProxy PlayControl for parallel sounds» by LFSaw

on 04 Aug'22 11:38 in jitlibrecipe

NodeProxy rule to create parallel versions of the same sound definition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
(
// AbstractPlayControl-par
//
// create parallel streams of the provided function filling the NodeProxy channels.
// requires the functions output channels to be a multiple of the NodeProxy's numChannels.
AbstractPlayControl.proxyControlClasses.put(\par, SynthDefControl);
AbstractPlayControl.buildMethods.put(\par, #{ | func, proxy, channelOffset = 0, index |
	var funcNumChannels;

	func.isKindOf(ArrayedCollection).if({
		funcNumChannels = func[1];
		func = func.first;
	}, {
		try({
			funcNumChannels = (func.value.shape ?? {[1]}).first;
		},{|exeption|
			(exeption.selector == \addKr).if({
				// assume mono
				"NodeProxy: NamedControl in function, please provide numChannels explicitely:".postln;
				"\t \par -> [{...}, <numChannels>]".postln;
				funcNumChannels = 1;
			}, {
				exeption.throw
			})
		})
	});

	if ((proxy.numChannels % funcNumChannels) != 0) {
		Error("NodeProxy input (par): number of proxy channels need to be multiple of function output channels").throw;
	};

	{ | out |
		var env, ctl = NamedControl.kr(("mix" ++ (index ? 0)).asSymbol, 1.0);
		var funcControls, outSnd;

		// create named controls '<argname>s' whilst retaining order of function args
		funcControls = func.def.keyValuePairsFromArgs.clump(2).collect{|args, i|
			var key, val;
			#key, val = args;

			// \idx arg is used to hand index into function
			if (key == \idx, {
				Array.series((proxy.numChannels/funcNumChannels).asInteger)
			}, {
				NamedControl.kr(
					name: (key ++ $s  ++ (index ? 0)),
					values: (val ? 0).dup(proxy.numChannels/funcNumChannels)
				)
			})

			//
			// NamedControl.kr(
			// 	(args[0] ++ $s ++ (index ? 0)),
			// 	(args[1] ? 0).dup(proxy.numChannels/funcNumChannels)
			// )
		};

		// embed function synthesis
		if (funcControls.isEmpty, {
			// no args given
			outSnd = (proxy.numChannels/funcNumChannels).asInteger.collect{
				SynthDef.wrap(func, nil);
			}
		}, {
			// args given
			outSnd = funcControls.flop.collect{|args, i|
				// args.postln;
				SynthDef.wrap(func, nil, args);
			}.flatten;
		});


		if(proxy.rate === 'audio') {
			env = ctl * EnvGate(i_level: 0, doneAction: 2, curve: \sin);
			Out.ar(out, env * outSnd)
		} {
			env = ctl * EnvGate(i_level: 0, doneAction: 2, curve: \lin);
			Out.kr(out, env * outSnd)
		};
	}.buildForProxy( proxy, channelOffset, index )
});

// specs

(0..1000).do{|i|
	Spec.add(
		("wet"++i).asSymbol, [0, 1].asSpec
	);
	Spec.add(
		("mix"++i).asSymbol, [0, 1].asSpec
	);
}
)


////////////// example usage

// make n 8-channel Ndef
Ndef(\a).ar(8)


// show edit window
Ndef(\a).edit(30)


// play on two channels (this wraps the 8 channels onto 2)
Ndef(\a).play(0, numChannels: 2, vol: 0.2)

// create an 8-channel sound by instantiating 8 times the below monosound
Ndef(\a)[0] = \par -> {|freq = 444, amp = 0.1, lpFreq = 1200, lpRq = 0.1| RLPF.ar(Blip.ar(freq.lag(4), 5), lpFreq.lag(4), lpRq.lag(4)) * amp };

// set some arbitrary parameters of the sound
Ndef(\a).setn(\freqs0, {exprand(100, 400)}!8);
Ndef(\a).setn(\lpFreqs0, {exprand(1000, 4000)}!8);
Ndef(\a).setn(\lpRqs0, {exprand(1, 0.1)}!8);


// add a variation of the first sound in 2nd slot
Ndef(\a)[1] = \par -> {|freq = 444, amp = 0.1, lpFreq = 1200, lpRq = 0.1| RLPF.ar(Blip.ar(freq.lag(4), 5), lpFreq.lag(4), lpRq.lag(4)) * amp };

// set its mix param (amplitude)
Ndef(\a).set(\mix1, 0.3);

// set some arbitrary parameters of the 2nd sound
Ndef(\a).setn(\freqs1, {exprand(1000, 4000)}!8);
Ndef(\a).setn(\lpFreqs1, {exprand(1000, 4000)}!8);
Ndef(\a).setn(\lpRqs1, {exprand(1, 0.1)}!8);


// filter sound globally
Ndef(\a)[10] = \filter -> {|in, lpFreq = 1000, lpRq = 0.1| RLPF.ar(in, lpFreq, lpRq)}

Ndef(\a).release
Ndef(\a).clear
raw 3828 chars (focus & ctrl+a+c to copy)
reception
comments