3 *ar { | bus, numChannels = 2, offset = 0 |
4 ^this.getOutput(bus.asBus, 'audio', numChannels, offset);
7 *kr { | bus, numChannels=1, offset = 0 |
8 ^this.getOutput(bus.asBus, 'control', numChannels, offset);
11 *getOutput { | bus, argRate, numChannels, offset = 0 |
15 var startIndex = bus.index + offset;
16 var n = bus.numChannels;
17 if(n >= numChannels) {
18 index = startIndex.min(n + bus.index);
20 index = Array.fill(numChannels, { arg i; startIndex + (i % n) });
24 out = if(offset.isInteger) {
26 { InFeedback.ar(index, numChannels) }
27 { In.kr(index, numChannels) }
30 { XInFeedback.ar(index, numChannels) }
31 { XIn.kr(index, numChannels) }
34 ^if(argRate === rate) { out } { // if not the same rate, convert rates
35 if(argRate === 'audio') { K2A.ar(out) } { A2K.kr(out) }
46 ^XFade2.ar( // use equal power crossfading for audio rate
47 In.ar(which.round(2), n),
48 In.ar(which.trunc(2) + 1, n),
49 (which * 2 - 1).fold2(1)
55 ^LinXFade2.kr( // use linear crossfading for control rate
56 In.kr(which.round(2), n),
57 In.kr(which.trunc(2) + 1, n),
58 (which * 2 - 1).fold2(1)
68 InFeedback.ar(which.round(2), n),
69 InFeedback.ar(which.trunc(2) + 1, n),
70 (which * 2 - 1).fold2(1)
77 // listens on a fixed index (or several)
78 // plays out to various other indices.
81 classvar <>warnPlayN = true;
83 var <ins, <outs, <amps = #[1.0], <vol = 1.0;
84 var <group, synthIDs, synthAmps, <>fadeTime = 0.02;
86 var <usedPlayN; // default case
91 // [\noWarn, warnPlayN.not, \noInit, usedPlayN.isNil, \stays, usedPlayN == flag, \noOuts, outs.isNil].postln;
93 // normal case: init or stay the same
94 if (warnPlayN.not or: { usedPlayN.isNil or: { usedPlayN == flag } } /*or: { outs.isNil }*/) {
99 states = [\playN, \play];
100 #old, new = if (usedPlayN, states, { states.reverse });
101 warn("monitor switched from % to % - channels may be wrong! \n"
102 "\t Settings were: outs: % amps: % ins: % vol: %!".format(old, new, outs, amps, ins, vol)
107 play { | fromIndex, fromNumChannels=2, toIndex, toNumChannels,
108 target, multi=false, volume, fadeTime=0.02, addAction |
110 var server, inGroup, numChannels, bundle, divider;
112 inGroup = target.asGroup;
113 server = inGroup.server;
117 bundle, fromIndex, fromNumChannels, toIndex,
118 toNumChannels, inGroup, multi, volume, fadeTime, addAction
120 server.listSendBundle(server.latency, bundle);
121 this.usedPlayN_(false);
124 stop { | argFadeTime |
125 var oldGroup = group;
126 fadeTime = argFadeTime ? fadeTime;
129 if(oldGroup.isPlaying) {
130 oldGroup.release(fadeTime);
131 SystemClock.sched(fadeTime, { oldGroup.free })
134 group.isPlaying = false;
139 isPlaying { ^group.isPlaying }
140 numChannels { ^outs.size }
142 // multichannel support
144 playN { | out, amp, in, vol, fadeTime, target, addAction |
145 var bundle = List.new;
147 inGroup = target.asGroup;
148 server = inGroup.server;
150 this.playNToBundle(bundle, out, amp, in, vol, fadeTime, inGroup, addAction);
151 server.listSendBundle(server.latency, bundle);
152 this.usedPlayN_(true);
155 // setting volume and output offset.
156 // lists are only flat lists for now.
159 if(val == vol) { ^this };
164 // first channel interface
167 var offset = index - outs[0];
168 this.outs = outs + offset
175 // multi channel interface
179 "Monitor - initialising outs: %\n".postf(indices);
183 if (outs.collect(_.size) != indices.collect(_.size)) {
184 "new outs do not match old outs shape:".warn;
185 ("old:" + outs).postln;
186 ("new:" + indices).postln;
187 " use playN to change topology!".postln;
193 group.server.listSendBundle(group.server.latency,
194 [15, synthIDs, "out", outs.flat].flop
201 "Monitor - initialising amps: %\n".postf(values);
205 if (values.collect(_.size) != amps.collect(_.size)) {
206 "new amps do not match old amps shape:".warn;
207 ("old:" + amps).postln;
208 ("new:" + values).postln;
209 " use playN to change topology!".postln;
213 synthAmps = values.flat * vol;
215 if (this.isPlaying) {
216 group.server.listSendBundle(group.server.latency,
217 [15, synthIDs, "vol", synthAmps].flop
224 playNToBundle { | bundle,
225 argOuts = (outs ?? {(0..ins.size-1)}),
229 argFadeTime = (fadeTime),
232 defName = "system_link_audio_1" |
234 var triplets, server;
236 outs = argOuts; ins = argIns; amps = argAmps; vol = argVol; fadeTime = argFadeTime;
239 if (ins.size != outs.size)
240 { Error("wrong size of outs and ins" ++ [outs, amps, ins]).throw };
242 triplets = [ins, outs, amps].flop;
245 this.stopToBundle(bundle)
247 this.newGroupToBundle(bundle, inGroup, addAction)
252 inGroup = inGroup.asGroup;
253 server = group.server;
255 triplets.do { | trip, i |
257 #in, out, amp = trip;
261 var id = server.nextNodeID;
262 synthIDs = synthIDs.add(id);
263 synthAmps = synthAmps.add(amp[j]);
264 bundle.add([9, defName,
268 "vol", amp.clipAt(j) * vol
272 bundle.add([15, group.nodeID, "fadeTime", fadeTime])
275 // optimizes ranges of channels
277 playToBundle { | bundle, fromIndex, fromNumChannels=2, toIndex, toNumChannels,
278 inGroup, multi = false, volume, inFadeTime, addAction |
280 var server, numChannels, defname, chanRange, n;
282 toIndex = toIndex ?? { if(outs.notNil, { outs[0] }, 0) };
285 fadeTime = inFadeTime ? fadeTime ? 0.02; // remembers monitor fadeTime.
287 toNumChannels = toNumChannels ? fromNumChannels;
288 inGroup = inGroup.asGroup;
289 server = inGroup.server;
293 this.stopToBundle(bundle);
297 this.newGroupToBundle(bundle, inGroup, addAction);
298 if (multi.not) { outs = [] }
303 numChannels = max(fromNumChannels, toNumChannels);
304 chanRange = if(toNumChannels.even and: { fromNumChannels.even }, 2, 1);
305 defname = "system_link_audio_" ++ chanRange;
306 (numChannels div: chanRange).do { arg i;
307 var id = server.nextNodeID;
308 var out = toIndex + (i * chanRange % toNumChannels);
309 var in = fromIndex + (i * chanRange % fromNumChannels);
310 synthIDs = synthIDs.add(id);
311 outs = outs.add(out);
313 amps = amps.add(1.0);
314 bundle.add([9, defname, id, 1, group.nodeID, "out", out, "in", in]);
316 bundle.add([15, group.nodeID, "fadeTime", fadeTime, "vol", vol]);
320 playNBusToBundle { | bundle, outs, amps, ins, bus, vol, fadeTime, group, addAction |
322 outs = outs ?? {this.outs.unbubble} ? 0; // remember old ones if none given
323 if (outs.isNumber) { outs = (0 .. bus.numChannels - 1) + outs };
326 { ins.wrap(0, bus.numChannels - 1).asArray }
327 {(0..(bus.numChannels - 1)) }
330 ins = ins.wrapExtend(outs.size); // should maybe be done in playNToBundle, in flop?
331 this.playNToBundle(bundle, outs, amps, ins, vol, fadeTime, group, addAction: addAction)
335 newGroupToBundle { | bundle, target, addAction=(\addToTail) |
336 target = target.asGroup;
337 group = Group.basicNew(target.server);
338 group.isPlaying = true;
339 bundle.add(group.newMsg(target, addAction));
340 NodeWatcher.register(group);
344 stopToBundle { | bundle | // maybe with fade later.
345 bundle.add([15, group.nodeID, "gate", 0]);
351 if (outs.isNil) { ^true };
352 ^(outs.size < 1) or: { ^outs.differentiate.drop(1).every(_ == 1) };