1Bus {
2
3	var <rate, <index, <numChannels, <server;
4	var mapSymbol;
5
6	*control { arg server, numChannels=1;
7		var alloc;
8		server = server ? Server.default;
9		alloc = server.controlBusAllocator.alloc(numChannels);
10		if(alloc.isNil) {
11			error("Meta_Bus:control: failed to get a control bus allocated."
12				+ "numChannels:" + numChannels + "server:" + server.name);
13			^nil
14		};
15		^this.new(\control, alloc, numChannels, server)
16	}
17
18	*audio { arg server, numChannels=1;
19		var alloc;
20		server = server ? Server.default;
21		alloc = server.audioBusAllocator.alloc(numChannels);
22		if(alloc.isNil) {
23			error("Meta_Bus:audio: failed to get an audio bus allocated."
24			+ "numChannels:" + numChannels + "server:" + server.name);
25			^nil
26		};
27		^this.new(\audio, alloc, numChannels, server)
28	}
29
30	*alloc { arg rate, server, numChannels=1;
31		^this.perform(rate ? \audio, server, numChannels)
32	}
33
34	*new { arg rate=\audio, index=0, numChannels=2, server;
35		^super.newCopyArgs(rate, index, numChannels, server ? Server.default)
36	}
37
38	*newFrom { arg bus, offset, numChannels=1;
39		if(offset > bus.numChannels or: { numChannels + offset > bus.numChannels }) {
40			Error("Bus:newFrom tried to reach outside the channel range of %".format( bus )).throw
41		};
42		^this.new(bus.rate, bus.index + offset, numChannels)
43	}
44
45	isSettable {
46		^rate != \audio
47	}
48
49	set { arg ... values; // shouldn't be larger than this.numChannels
50		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
51		if(this.isSettable) {
52			server.sendBundle(nil, (["/c_set"]
53				++ values.collect({ arg v, i; [index + i , v] }).flat))
54		} {
55			error("Cannot set an audio rate bus")
56		}
57	}
58
59	setMsg { arg ... values;
60		if(index.isNil) { Error("Cannot construct a % for a % that has been freed".format(thisMethod.name, this.class.name)).throw };
61		if(this.isSettable) {
62			^["/c_set"] ++ values.collect({ arg v, i; [index + i , v] }).flat
63		} {
64			error("Cannot set an audio rate bus")
65		}
66	}
67
68	setn { arg values;
69		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
70		// could throw an error if values.size > numChannels
71		if(this.isSettable) {
72			server.sendBundle(nil,
73				["/c_setn", index, values.size] ++ values)
74		} {
75			error("Cannot set an audio rate bus")
76		}
77	}
78
79	setnMsg { arg values;
80		if(index.isNil) { Error("Cannot construct a % for a % that has been freed".format(thisMethod.name, this.class.name)).throw };
81		if(this.isSettable) {
82			^["/c_setn", index, values.size] ++ values
83		} {
84			error("Cannot set an audio rate bus")
85		}
86	}
87
88	setAt { |offset ... values|
89		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
90		// shouldn't be larger than this.numChannels - offset
91		if(this.isSettable) {
92			server.sendBundle(nil, (["/c_set"]
93				++ values.collect({ arg v, i; [index + offset + i , v] }).flat))
94		} {
95			error("Cannot set an audio rate bus")
96		}
97	}
98
99	setnAt { |offset, values|
100		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
101		// could throw an error if values.size > numChannels
102		if(this.isSettable) {
103			server.sendBundle(nil,
104				["/c_setn", index + offset, values.size] ++ values)
105		} {
106			error("Cannot set an audio rate bus")
107		}
108	}
109
110	setPairs { | ... pairs|
111		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
112		if(this.isSettable) {
113			server.sendBundle(nil, (["/c_set"]
114				++ pairs.clump(2).collect({ arg pair; [pair[0] + index, pair[1]] }).flat))
115		} {
116			error("Cannot set an audio rate bus")
117		}
118	}
119
120	get { arg action;
121		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
122		if(numChannels == 1) {
123			action = action ? { |vals| "Bus % index: % value: %.\n".postf(rate, index, vals); };
124			OSCFunc({ |message|
125				// The response is of the form [/c_set, index, value].
126				// We want "value," which is at index 2.
127				action.value(message[2])
128			}, \c_set, server.addr, argTemplate: [index]).oneShot;
129			server.listSendMsg([\c_get, index])
130		} {
131			this.getn(numChannels, action)
132		}
133	}
134
135	getn { arg count, action;
136		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
137		action = action ? { |vals| "Bus % index: % values: %.\n".postf(rate, index, vals) };
138		OSCFunc({ |message|
139			// The response is of the form [/c_set, index, count, ...values].
140			// We want the values, which are at indexes 3 and above.
141			action.value(message[3..])
142		}, \c_setn, server.addr, argTemplate: [index]).oneShot;
143		server.listSendMsg([\c_getn, index, count ? numChannels])
144	}
145
146	getMsg {
147		if(index.isNil) { Error("Cannot construct a % for a % that has been freed".format(thisMethod.name, this.class.name)).throw };
148		^[\c_get, index]
149	}
150
151	getnMsg { arg count;
152		if(index.isNil) { Error("Cannot construct a % for a % that has been freed".format(thisMethod.name, this.class.name)).throw };
153		^[\c_getn, index, count ? numChannels]
154	}
155
156	getSynchronous {
157		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
158		if (this.isSettable.not) {
159			Error("Bus-getSynchronous only works for control-rate busses").throw
160		} {
161			^server.getControlBusValue(index)
162		}
163	}
164
165	getnSynchronous { |count|
166		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
167		if (this.isSettable.not) {
168			Error("Bus-getnSynchronous only works for control-rate busses").throw
169		} {
170			^server.getControlBusValues(index, count ? numChannels)
171		}
172	}
173
174	setSynchronous { |... values|
175		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
176		if (this.isSettable.not) {
177			Error("Bus-setSynchronous only works for control-rate busses").throw
178		} {
179			if (values.size == 1) {
180				server.setControlBusValue(index, values[0])
181			} {
182				server.setControlBusValues(index, values)
183			}
184		}
185	}
186
187	setnSynchronous { |values|
188		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
189		if (this.isSettable.not) {
190			Error("Bus-setnSynchronous only works for control-rate busses").throw
191		} {
192			server.setControlBusValues(index, values)
193		}
194	}
195
196	fill { arg value, numChans;
197		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
198		// could throw an error if numChans > numChannels
199		server.sendBundle(nil, ["/c_fill", index, numChans, value])
200	}
201
202	fillMsg { arg value;
203		if(index.isNil) { Error("Cannot construct a % for a % that has been freed".format(thisMethod.name, this.class.name)).throw };
204		^["/c_fill", index, numChannels, value]
205	}
206
207	free { arg clear = false;
208		if(index.isNil) { (this.class.name ++ " has already been freed").warn; ^this };
209		if(rate == \audio) {
210			server.audioBusAllocator.free(index)
211		} {
212			server.controlBusAllocator.free(index);
213			if(clear) { this.setAll(0) }
214		};
215		index = nil;
216		numChannels = nil;
217		mapSymbol = nil;
218	}
219
220	// allow reallocation
221
222	alloc {
223		if(rate === 'audio') {
224			index = server.audioBusAllocator.alloc(numChannels)
225		} {
226			index = server.controlBusAllocator.alloc(numChannels)
227		};
228		mapSymbol = nil;
229	}
230
231	realloc {
232		var r, n;
233		if(index.notNil) {
234			r = rate;
235			n = numChannels;
236			this.free;
237			rate = r;
238			numChannels = n;
239			this.alloc;
240		} {
241			Error("Cannot % a % that has been freed".format(thisMethod.name, this.class.name)).throw
242		}
243	}
244
245	// alternate syntaxes
246	setAll { arg value;
247		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
248		this.fill(value, numChannels)
249	}
250
251	value_ { arg value;
252		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
253		this.fill(value, numChannels)
254	}
255
256	printOn { arg stream;
257		stream << this.class.name << "(" <<*
258			[rate, index, numChannels, server] <<")"
259	}
260
261	storeOn { arg stream;
262		stream << this.class.name << "(" <<*
263			[rate.asCompileString, index, numChannels, server.asCompileString] <<")"
264	}
265
266	== { arg aBus;
267		^this.compareObject(aBus, #[\index, \numChannels, \rate, \server])
268	}
269
270	hash {
271		^this.instVarHash(#[\index, \numChannels, \rate, \server])
272	}
273
274	isAudioOut { // audio interface
275		^(rate === \audio and: { index < server.options.firstPrivateBus})
276	}
277
278	ar { arg numChannels=(this.numChannels), offset=0;
279		if(rate == \audio) {
280			^In.ar(index + offset, numChannels)
281		} {
282			^K2A.ar( In.kr(index + offset, numChannels) )
283		}
284	}
285
286	kr { arg numChannels=(this.numChannels), offset=0;
287		if(rate == \audio) {
288			^A2K.kr( In.ar(index + offset, numChannels) )
289		} {
290			^In.kr(index + offset, numChannels)
291		}
292	}
293
294	play { arg target=0, outbus, fadeTime, addAction=\addToTail;
295		if(index.isNil) { Error("Cannot call % on a % that has been freed".format(thisMethod.name, this.class.name)).throw };
296		if(this.isAudioOut.not) {
297			// returns a Synth
298			^{ this.ar }.play(target, outbus, fadeTime, addAction)
299		}
300	}
301
302	asUGenInput { ^this.index }
303	asControlInput { ^this.index }
304
305	asMap {
306		^mapSymbol ?? {
307			if(index.isNil) { MethodError("bus not allocated.", this).throw };
308			mapSymbol = if(rate == \control) { "c" } { "a" };
309			mapSymbol = (mapSymbol ++ index).asSymbol;
310		}
311	}
312
313	subBus { arg offset, numChannels=1;
314		^this.class.newFrom(this, offset, numChannels)
315	}
316
317}
318