1title:: Multichannel Expansion 2summary:: Explaining multichannel expansion and representation 3categories:: Server>Nodes, UGens>Multichannel 4 5section:: Multiple channels as Arrays 6Multiple channels of audio are represented as link::Classes/Array::s. 7code:: 8s.boot; 9// one channel 10{ Blip.ar(800,4,0.1) }.play; 11 12// two channels 13{ [ Blip.ar(800,4,0.1), WhiteNoise.ar(0.1) ] }.play; 14:: 15Each channel of output will go out a different speaker, so your limit here is two for a stereo output. If you have a supported multi channel audio interface or card then you can output as many channels as the card supports. 16 17All link::Classes/UGen::s have only a single output. This uniformity facilitates the use of array operations to perform manipulation of multi channel structures. 18 19In order to implement multichannel output, UGens create a separate UGen known as an link::Classes/OutputProxy:: for each output. An OutputProxy is just a place holder for the output of a multichannel UGen. OutputProxies are created internally, you never need to create them yourself, but it is good to be aware that they exist so you'll know what they are when you run across them. 20code:: 21// look at the outputs of Pan2: 22Pan2.ar(PinkNoise.ar(0.1), FSinOsc.kr(3)).dump; 23 24play({ Pan2.ar(PinkNoise.ar(0.1), FSinOsc.kr(1)); }); 25:: 26 27section:: Multichannel expansion 28When an link::Classes/Array:: is given as an input to a unit generator it causes an array of multiple copies of that unit generator to be made, each with a different value from the input array. This is called multichannel expansion. All but a few special unit generators perform multichannel expansion. Only Arrays are expanded, no other type of Collection, not even subclasses of Array. 29code:: 30{ Blip.ar(500,8,0.1) }.play // one channel 31 32// the array in the freq input causes an Array of 2 Blips to be created : 33{ Blip.ar([499,600],8,0.1) }.play // two channels 34 35Blip.ar(500,8,0.1).postln // one unit generator created. 36 37Blip.ar([500,601],8,0.1).postln // two unit generators created. 38:: 39Multichannel expansion will propagate through the expression graph. When a unit generator constructor is called with an array of inputs, it returns an array of instances. If that array is the input to another constructor, then another array is created, and so on. 40code:: 41{ RLPF.ar(Saw.ar([100,250],0.05), XLine.kr(8000,400,5), 0.05) }.play; 42 43// the [100,250] array of frequency inputs to Saw causes Saw.ar to return 44// an array of two Saws, that array causes RLPF.ar to create two RLPFs. 45// Both RLPFs share a single instance of XLine. 46:: 47When a constructor is parameterized by two or more arrays, then the number of channels created is equal to the longest array, with parameters being pulled from each array in parallel. The shorter arrays will wrap. 48 49for example, the following: 50code:: 51Pulse.ar([400, 500, 600],[0.5, 0.1], 0.2) 52:: 53is equivalent to: 54code:: 55[ Pulse.ar(400,0.5,0.2), Pulse.ar(500,0.1,0.2), Pulse.ar(600,0.5,0.2) ] 56:: 57A more complex example based on the Saw example above is given below. In this example, the link::Classes/XLine:: is expanded to two instances, one going from 8000 Hz to 400 Hz and the other going in the opposite direction from 500 Hz to 7000 Hz. 58These two XLines are 'married' to the two Saw oscillators and used to parameterize two copies of link::Classes/RLPF::. So on the left channel a 100 Hz Saw is filtered from 8000 Hz to 400 Hz and on the right channel a 250 Hz Saw is filtered from 500 Hz to 7000 Hz. 59code:: 60{ RLPF.ar(Saw.ar([100,250],0.05), XLine.kr([8000,500],[400,7000],5), 0.05) }.play; 61:: 62 63subsection:: Expanding methods and operators 64Many operators and methods also multichannel expand. For example all common math operators: 65code:: 66{ Saw.ar([100,250]) * [0.5,0.8] }.play; 67{ Saw.ar(LFNoise1.kr(1).range(0,100) + [100,250]) }.play; 68:: 69Also the various UGen convenience functions like code::.clip2::, code::.lag:: and code::.range:: : 70code:: 71{ Saw.ar(LFNoise1.kr(1).range(100,[200,300])) }.play; 72{ Saw.ar(LFPulse.kr(1).range(100,[200,300]).lag([0.1,0.2])) }.play; 73:: 74The expansion is handled by wrapper-methods defined in link::Classes/SequenceableCollection::. 75 76You can use link::Classes/Object#-multiChannelPerform:: to do multichannel expansion with any method on any kind of object: 77code:: 78["foo","bar"].multiChannelPerform(\toUpper); 79:: 80The shorter arrays wrap: 81code:: 82["foo","bar","zoo"].multiChannelPerform('++', ["l","ba"]) 83:: 84 85subsection:: Using flop for multichannel expansion 86The method flop swaps columns and rows, allowing to derive series of argument sets: 87code:: 88( 89SynthDef("help_multichannel", { |out=0, freq=440, mod=0.1, modrange=20| 90 Out.ar(out, 91 SinOsc.ar( 92 LFPar.kr(mod, 0, modrange) + freq 93 ) * EnvGate(0.1) 94 ) 95}).add; 96) 97:: 98code:: 99( 100var freq, mod, modrange; 101 102freq = Array.exprand(8, 400, 5000); 103mod = Array.exprand(8, 0.1, 2); 104modrange = Array.rand(8, 0.1, 40); 105 106fork { 107 [\freq, freq, \mod, mod, \modrange, modrange].flop.do { |args| 108 args.postln; 109 Synth("help_multichannel", args); 110 0.3.wait; 111 } 112}; 113) 114:: 115 116Similarly, link::Classes/Function#flop#Function:flop:: returns an unevaluated function that will expand to its arguments when evaluated: 117code:: 118( 119SynthDef("blip", { |out, freq| 120 Out.ar(out, 121 Line.ar(0.1, 0, 0.05, 1, 0, 2) * Pulse.ar(freq * [1, 1.02]) 122 ) 123}).add; 124 125a = { |dur=1, x=1, n=10, freq=400| 126 fork { n.do { 127 if(x.coin) { Synth("blip", [\freq, freq]) }; 128 (dur / n).wait; 129 } } 130}.flop; 131) 132 133a.value(5, [0.3, 0.3, 0.2], [12, 32, 64], [1000, 710, 700]); 134:: 135 136subsection:: Multichannel expansion in Patterns 137Multichannel expansion does not quite follow the scheme one might expect from the previously described. E.g. the following doesn't multichannel-expand properly: 138code:: 139( 140SynthDef(\help_multichannel, { |out=0, freq=#[342, 145]| 141 var env = EnvGate.new; 142 Out.ar(out, [SinOsc.ar(freq[0]), Saw.ar(freq[1])] * env * 0.2) 143}).add 144) 145 146// not working as expected 147// only freq in the left channel gets set correctly 148( 149a = Pbind( 150 \instrument, \help_multichannel, 151 \freq, Pseq([[342, 145], [187, 564], [234, 135]], inf), 152 \dur, 0.5 153).play; 154) 155:: 156 157Instead wrap arrayed args in an extra pair of square brackets: 158 159code:: 160a.stop; 161 162// freq in both channels set as expected 163( 164a = Pbind( 165 \instrument, \help_multichannel, 166 \freq, Pseq([[[342, 145]], [[187, 564]], [[234, 135]]], inf), 167 \dur, 0.5 168).play; 169) 170:: 171 172Under the hood this is a consequence of how .flop prepares the given args to be passed to the Synth: 173 174code:: 175// single square brackets 176[\freq, [342, 145]].flop 177// --> [ [ freq, 342 ], [ freq, 145 ] ] 178 179// double square brackets 180[\freq, [[342, 145]]].flop 181// --> [ [ freq, [ 342, 145 ] ] ] 182:: 183 184section:: Pitfalls 185Some UGens create stereo output from mono input, and might not behave as expected regarding multichannel expansion. 186 187For example, link::Classes/Pan2:: : 188code:: 189{ Pan2.ar(SinOsc.ar([500,600]),[-0.5,0.5]) }.play; 190:: 191The expectation here might be that the two sines would get individual pan positions. And they do, but Pan2 expands into two stereo ugens nested in an outer array, resulting in a total of four output channels. code::play:: will add an link::Classes/Out:: UGen for each of them, resulting in both Pan2's writing to the same output bus: 192code:: 193Pan2.ar(SinOsc.ar([500,600]),[-0.5,0.5]) 194 195// prints: 196// [ [ an OutputProxy, an OutputProxy ], [ an OutputProxy, an OutputProxy ] ] 197:: 198 199In this case, the solution is simply to sum the nested four channels into a single stereo-channel: 200code:: 201{ Pan2.ar(SinOsc.ar([500,600]),[-0.5,0.5]).sum }.play; 202:: 203 204If we take a look at the resulting UGen graph of the code above, we can see that it is correct. The two Pan2 is mixed together to create a single stereo output: 205code:: 206{ Pan2.ar(SinOsc.ar([500,600]),[-0.5,0.5]).sum }.asSynthDef.dumpUGens 207 208// prints: 209// [ 0_Control, scalar, nil ] 210// [ 1_SinOsc, audio, [ 500, 0 ] ] 211// [ 2_Pan2, audio, [ 1_SinOsc, -0.5, 1 ] ] 212// [ 3_SinOsc, audio, [ 600, 0 ] ] 213// [ 4_Pan2, audio, [ 3_SinOsc, 0.5, 1 ] ] 214// [ 5_+, audio, [ 2_Pan2[0], 4_Pan2[0] ] ] 215// [ 6_+, audio, [ 2_Pan2[1], 4_Pan2[1] ] ] 216// [ 7_Out, audio, [ 0_Control[0], 5_+, 6_+ ] ] 217:: 218 219section:: Protecting arrays against expansion 220Some unit generators such as link::Classes/Klank:: require arrays of values as inputs. Since all arrays are expanded, you need to protect some arrays by a link::Classes/Ref:: object. 221A Ref instance is an object with a single slot named 'value' that serves as a holder of an object. 222code::Ref.new(object):: is one way to create a Ref, but there is a syntactic shortcut. The backquote code::`:: is a unary operator that is equivalent to calling code::Ref.new(something)::. So to protect arrays that are inputs to a Klank or similar UGens you write: 223code:: 224Klank.ar(`[[400,500,600],[1,2,1]], z) 225:: 226You can still create multiple Klanks by giving it an array of Ref'ed arrays. 227code:: 228Klank.ar([ `[[400,500,600],[1,2,1]], `[[700,800,900],[1,2,1]] ], z) 229:: 230is equivalent to: 231code:: 232[ Klank.ar(`[[400,500,600],[1,2,1]], z), Klank.ar(`[[700,800,900],[1,2,1]], z)] 233:: 234Also the Refs multichannelExpand when passed to a Klank: 235code:: 236Klank.ar(`[[[400,500,600], [700,800,900]],[1,2,1]], z) 237:: 238, which is is equivalent to: 239code:: 240[ Klank.ar(`[[400,500,600],[1,2,1]], z), Klank.ar(`[[700,800,900],[1,2,1]], z)] 241:: 242 243section:: Reducing channel expansion with Mix 244The link::Classes/Mix:: object provides the means for reducing multichannel arrays to a single channel. 245code:: 246Mix.new([a, b, c]) // array of channels 247:: 248or 249code:: 250[a, b, c].sum 251:: 252is equivalent to: 253code:: 254a + b + c // mixed to one 255:: 256Mix is more efficient than using + since it can perform multiple additions at a time. But the main advantage is that it can deal with situations where the number of channels is arbitrary or determined at runtime. 257code:: 258// three channels of Pulse are mixed to one channel 259{ Mix.new( Pulse.ar([400, 501, 600], [0.5, 0.1], 0.1) ) }.play 260:: 261Multi channel expansion works differently for Mix. Mix takes one input which is an array (one not protected by a Ref). That array does not cause copies of Mix to be made. 262All elements of the array are mixed together in a single Mix object. On the other hand if the array contains one or more arrays then multi channel expansion is performed one level down. This allows you to mix an array of stereo (two element) arrays resulting in one two channel array. For example: 263code:: 264Mix.new( [ [a, b], [c, d], [e, f] ] ) // input is an array of stereo pairs 265:: 266is equivalent to: 267code:: 268// mixed to a single stereo pair 269[ Mix.new( [a, c, e] ), Mix.new( [b, d, f] ) ] 270:: 271Currently it is not recursive. You cannot use Mix on arrays of arrays of arrays. 272 273Here's a final example illustrating multi channel expansion and Mix. By changing the variable 'n' you can change the number of voices in the patch. How many voices can your machine handle? 274code:: 275( 276{ 277 var n; 278 n = 8; // number of 'voices' 279 Mix.new( // mix all stereo pairs down. 280 Pan2.ar( // pan the voice to a stereo position 281 CombL.ar( // a comb filter used as a string resonator 282 Dust.ar( // random impulses as an excitation function 283 // an array to cause expansion of Dust to n channels 284 // 1 means one impulse per second on average 285 Array.fill(n, 1), 286 0.3 // amplitude 287 ), 288 0.01, // max delay time in seconds 289 // array of different random lengths for each 'string' 290 Array.fill(n, {0.004.rand+0.0003}), 291 4 // decay time in seconds 292 ), 293 Array.fill(n,{1.0.rand2}) // give each voice a different pan position 294 ) 295 ) 296}.play; 297) 298:: 299 300 301 302