Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / SCClassLibrary / Common / Audio / BasicOpsUGen.sc
blob91882193f8e82172b756747ef899e9b788de3ded
1 // These Unit Generators are instantiated by math operations on UGens
3 BasicOpUGen : UGen {
4         var <operator;
6 //      writeName { arg file;
7 //              var name, opname;
8 //              name = this.class.name.asString;
9 //              opname = operator.asString;
10 //              file.putInt8(name.size + opname.size + 1);
11 //              file.putString(name);
12 //              file.putInt8(0);
13 //              file.putString(opname);
14 //      }
15         operator_ { arg op;
16                 operator = op;
17                 specialIndex = operator.specialIndex;
18                 if(specialIndex < 0) {
19                         Error("Operator '%' applied to a UGen is not supported in scsynth".format(operator)).throw
20                 }
21         }
23         argNamesInputsOffset { ^2 }
24         argNameForInputAt { arg i;
25                 var method = this.class.class.findMethod('new');
26                 if(method.isNil or: {method.argNames.isNil},{ ^nil });
27                 ^method.argNames.at(i + this.argNamesInputsOffset)
28         }
29         dumpArgs {
30                 " ARGS:".postln;
31                 ("   operator:" + operator).postln;
32                 inputs.do({ arg in,ini;
33                         ("   " ++ (this.argNameForInputAt(ini) ? ini.asString)++":" + in + in.class).postln
34                 });
35         }
37         dumpName {
38                 ^synthIndex.asString ++ "_" ++ this.operator
39         }
42 UnaryOpUGen : BasicOpUGen {
44         *new { arg selector, a;
45                 ^this.multiNew('audio', selector, a)
46         }
48         init { arg theOperator, theInput;
49                 this.operator = theOperator;
50                 rate = theInput.rate;
51                 inputs = theInput.asArray;
52         }
54         optimizeGraph {
55                 super.performDeadCodeElimination
56         }
59 BinaryOpUGen : BasicOpUGen {
60         *new { arg selector, a, b;
61                 ^this.multiNew('audio', selector, a, b)
62         }
64         determineRate { arg a, b;
65                 if (a.rate == \demand, { ^\demand });
66                 if (b.rate == \demand, { ^\demand });
67                 if (a.rate == \audio, { ^\audio });
68                 if (b.rate == \audio, { ^\audio });
69                 if (a.rate == \control, { ^\control });
70                 if (b.rate == \control, { ^\control });
71                 ^\scalar
72         }
73         *new1 { arg rate, selector, a, b;
75                 // eliminate degenerate cases
76                 if (selector == '*', {
77                         if (a == 0.0, { ^0.0 });
78                         if (b == 0.0, { ^0.0 });
79                         if (a == 1.0, { ^b });
80                         if (a == -1.0, { ^b.neg });
81                         if (b == 1.0, { ^a });
82                         if (b == -1.0, { ^a.neg });
83                 },{
84                 if (selector == '+', {
85                         if (a == 0.0, { ^b });
86                         if (b == 0.0, { ^a });
87                 },{
88                 if (selector == '-', {
89                         if (a == 0.0, { ^b.neg });
90                         if (b == 0.0, { ^a });
91                 },{
92                 if (selector == '/', {
93                         if (b == 1.0, { ^a });
94                         if (b == -1.0, { ^a.neg });
95                 })})})});
97                 ^super.new1(rate, selector, a, b)
98         }
100         init { arg theOperator, a, b;
101                 this.operator = theOperator;
102                 rate = this.determineRate(a, b);
103                 inputs = [a, b];
104         }
106         optimizeGraph {
107                 //this.constantFolding;
110                 if (super.performDeadCodeElimination) {
111                         ^this
112                 };
114                 if (operator == '+') {
115                         this.optimizeAdd;
116                         ^this;
117                 };
119                 if (operator == '-') {
120                         this.optimizeSub
121                 };
122         }
124         optimizeAdd {
125                 var optimizedUGen;
127                 // create a Sum3 if possible.
128                 optimizedUGen = this.optimizeToSum3;
130                 // create a Sum4 if possible.
131                 if (optimizedUGen.isNil) {
132                         optimizedUGen = this.optimizeToSum4
133                 };
135                 // create a MulAdd if possible.
136                 if (optimizedUGen.isNil) {
137                         optimizedUGen = this.optimizeToMulAdd
138                 };
140                 // optimize negative additions
141                 if (optimizedUGen.isNil) {
142                         optimizedUGen = this.optimizeAddNeg
143                 };
145                 if (optimizedUGen.notNil) {
146                         synthDef.replaceUGen(this, optimizedUGen);
147                         optimizedUGen.optimizeGraph
148                 };
149         }
151         optimizeAddNeg {
152                 var a, b;
153                 #a, b = inputs;
155                 if (b.isKindOf(UnaryOpUGen) and: { b.operator == 'neg' }) {
156                         // a + b.neg -> a - b
157                         if (b.descendants.size == 1) {
158                                 buildSynthDef.removeUGen(b);
159                         } {
160                                 b.descendants.remove(this);
161                         };
162                         ^(a - b.inputs[0])
163                 };
165                 if (a.isKindOf(UnaryOpUGen) and: { a.operator == 'neg' }) {
166                         // a.neg + b -> b - a
167                         if (a.descendants.size == 1) {
168                                 buildSynthDef.removeUGen(a);
169                         } {
170                                 a.descendants.remove(this);
171                         };
172                         ^(b - a.inputs[0])
173                 };
174                 ^nil
175         }
177         optimizeToMulAdd {
178                 var a, b;
179                 #a, b = inputs;
181                 if (a.isKindOf(BinaryOpUGen) and: { a.operator == '*'
182                         and: { a.descendants.size == 1 }})
183                 {
184                         if (MulAdd.canBeMulAdd(a.inputs[0], a.inputs[1], b)) {
185                                 buildSynthDef.removeUGen(a);
186                                 ^MulAdd.new(a.inputs[0], a.inputs[1], b);
187                         };
189                         if (MulAdd.canBeMulAdd(a.inputs[1], a.inputs[0], b)) {
190                                 buildSynthDef.removeUGen(a);
191                                 ^MulAdd.new(a.inputs[1], a.inputs[0], b)
192                         };
193                 };
195                 if (b.isKindOf(BinaryOpUGen) and: { b.operator == '*'
196                         and: { b.descendants.size == 1 }})
197                 {
198                         if (MulAdd.canBeMulAdd(b.inputs[0], b.inputs[1], a)) {
199                                 buildSynthDef.removeUGen(b);
200                                 ^MulAdd.new(b.inputs[0], b.inputs[1], a)
201                         };
203                         if (MulAdd.canBeMulAdd(b.inputs[1], b.inputs[0], a)) {
204                                 buildSynthDef.removeUGen(b);
205                                 ^MulAdd.new(b.inputs[1], b.inputs[0], a)
206                         };
207                 };
208                 ^nil
209         }
211         optimizeToSum3 {
212                 var a, b;
213                 #a, b = inputs;
215                 if (a.isKindOf(BinaryOpUGen) and: { a.operator == '+'
216                         and: { a.descendants.size == 1 }}) {
217                         buildSynthDef.removeUGen(a);
218                         ^Sum3(a.inputs[0], a.inputs[1], b);
219                 };
221                 if (b.isKindOf(BinaryOpUGen) and: { b.operator == '+'
222                         and: { b.descendants.size == 1 }}) {
223                         buildSynthDef.removeUGen(b);
224                         ^Sum3(b.inputs[0], b.inputs[1], a);
225                 };
226                 ^nil
227         }
229         optimizeToSum4 {
230                 var a, b;
231                 #a, b = inputs;
233                 if (a.isKindOf(Sum3) and: { a.descendants.size == 1 }) {
234                         buildSynthDef.removeUGen(a);
235                         ^Sum4(a.inputs[0], a.inputs[1], a.inputs[2], b);
236                 };
238                 if (b.isKindOf(Sum3) and: { b.descendants.size == 1 }) {
239                         buildSynthDef.removeUGen(b);
240                         ^Sum4(b.inputs[0], b.inputs[1], b.inputs[2], a);
241                 };
242                 ^nil
243         }
245         optimizeSub {
246                 var a, b, replacement;
247                 #a, b = inputs;
249                 if (b.isKindOf(UnaryOpUGen) and: { b.operator == 'neg' }) {
250                         // a - b.neg -> a + b
251                         if (b.descendants.size == 1) {
252                                 buildSynthDef.removeUGen(b);
253                         } {
254                                 b.descendants.remove(this);
255                         };
256                         replacement = BinaryOpUGen('+', a, b.inputs[0]);
258                         synthDef.replaceUGen(this, replacement);
259                         replacement.optimizeGraph
260                 };
261                 ^nil
262         }
264         constantFolding {
265                 var a, b, aa, bb, cc, dd, temp, ac_ops, value;
267                 // associative & commutative operators
268                 ac_ops = #['+','*','min','max','&&','||'];
270                 if (ac_ops.includes(operator).not) { ^this };
272                 #a, b = inputs;
273                 if (a.isKindOf(BinaryOpUGen) and: { operator == a.operator
274                         and: { b.isKindOf(BinaryOpUGen) and: { operator == b.operator } }}) {
275                         #aa, bb = a.inputs;
276                         #cc, dd = b.inputs;
277                         if (aa.isKindOf(SimpleNumber)) {
278                                 if (cc.isKindOf(SimpleNumber)) {
279                                         b.inputs[0] = bb;
280                                         this.inputs[0] = aa.perform(operator, cc);
281                                         synthDef.removeUGen(a);
282                                 }{
283                                 if (dd.isKindOf(SimpleNumber)) {
284                                         b.inputs[1] = bb;
285                                         this.inputs[0] = aa.perform(operator, dd);
286                                         synthDef.removeUGen(a);
287                                 }}
288                         }{
289                         if (bb.isKindOf(SimpleNumber)) {
290                                 if (cc.isKindOf(SimpleNumber)) {
291                                         b.inputs[0] = aa;
292                                         this.inputs[0] = bb.perform(operator, cc);
293                                         synthDef.removeUGen(a);
294                                 }{
295                                 if (dd.isKindOf(SimpleNumber)) {
296                                         b.inputs[1] = aa;
297                                         this.inputs[0] = bb.perform(operator, dd);
298                                         synthDef.removeUGen(a);
299                                 }}
300                         }};
302                 };
303                 #a, b = inputs;
304                 if (a.isKindOf(BinaryOpUGen) and: { operator == a.operator }) {
305                         #aa, bb = a.inputs;
306                         if (b.isKindOf(SimpleNumber)) {
307                                 if (aa.isKindOf(SimpleNumber)) {
308                                         buildSynthDef.removeUGen(a);
309                                         this.inputs[0] = aa.perform(operator, b);
310                                         this.inputs[1] = bb;
311                                         ^this
312                                 };
313                                 if (bb.isKindOf(SimpleNumber)) {
314                                         buildSynthDef.removeUGen(a);
315                                         this.inputs[0] = bb.perform(operator, b);
316                                         this.inputs[1] = aa;
317                                         ^this
318                                 };
319                         };
320                         // percolate constants upward so that a subsequent folding may occur
321                         if (aa.isKindOf(SimpleNumber)) {
322                                 this.inputs[1] = aa;
323                                 a.inputs[0] = b;
324                         }{
325                         if (bb.isKindOf(SimpleNumber)) {
326                                 this.inputs[1] = bb;
327                                 a.inputs[1] = b;
328                         }};
329                 };
330                 #a, b = inputs;
331                 if (b.isKindOf(BinaryOpUGen) and: { operator == b.operator }) {
332                         #cc, dd = b.inputs;
333                         if (a.isKindOf(SimpleNumber)) {
334                                 if (cc.isKindOf(SimpleNumber)) {
335                                         buildSynthDef.removeUGen(b);
336                                         this.inputs[0] = a.perform(operator, cc);
337                                         this.inputs[1] = dd;
338                                         ^this
339                                 };
340                                 if (dd.isKindOf(SimpleNumber)) {
341                                         buildSynthDef.removeUGen(b);
342                                         this.inputs[0] = a.perform(operator, dd);
343                                         this.inputs[1] = cc;
344                                         ^this
345                                 };
346                         };
347                         // percolate constants upward so that a subsequent folding may occur
348                         if (cc.isKindOf(SimpleNumber)) {
349                                 this.inputs[0] = cc;
350                                 b.inputs[0] = a;
351                         }{
352                         if (dd.isKindOf(SimpleNumber)) {
353                                 this.inputs[0] = dd;
354                                 b.inputs[1] = a;
355                         }};
356                 };
357                 #a, b = inputs;
358                 if (a.isKindOf(SimpleNumber) and: { b.isKindOf(SimpleNumber) }) {
359                         synthDef.replaceUGen(this, a.perform(operator, b));
360                         synthDef.removeUGen(this);
361                 };
362         }
365 MulAdd : UGen {
366         *new { arg in, mul = 1.0, add = 0.0;
367                 var rate = [in, mul, add].rate;
368                 ^this.multiNew(rate, in, mul, add)
369         }
370         *new1 { arg rate, in, mul, add;
371                 var minus, nomul, noadd;
373                 // eliminate degenerate cases
374                 if (mul == 0.0, { ^add });
375                 minus = mul == -1.0;
376                 nomul = mul == 1.0;
377                 noadd = add == 0.0;
378                 if (nomul && noadd, { ^in });
379                 if (minus && noadd, { ^in.neg });
380                 if (noadd, { ^in * mul });
381                 if (minus, { ^add - in });
382                 if (nomul, { ^in + add });
384                 if (this.canBeMulAdd(in, mul, add)) {
385                         ^super.new1(rate, in, mul, add)
386                 };
387                 if (this.canBeMulAdd(mul, in, add)) {
388                         ^super.new1(rate, mul, in, add)
389                 };
390                 ^( (in * mul) + add)
391         }
392         init { arg in, mul, add;
393                 inputs = [in, mul, add];
394                 rate = inputs.rate;
395         }
397         *canBeMulAdd { arg in, mul, add;
398                 // see if these inputs satisfy the constraints of a MulAdd ugen.
399                 if (in.rate == \audio, { ^true });
400                 if (in.rate == \control
401                         and: { mul.rate == \control || { mul.rate == \scalar }}
402                         and: { add.rate == \control || { add.rate == \scalar }},
403                 {
404                         ^true
405                 });
406                 ^false
407         }
410 Sum3 : UGen {
411         *new { arg in0, in1, in2;
412                 ^this.multiNew(nil, in0, in1, in2)
413         }
415         *new1 { arg dummyRate, in0, in1, in2;
416                 var argArray, rate, sortedArgs;
417                 if (in2 == 0.0) { ^(in0 + in1) };
418                 if (in1 == 0.0) { ^(in0 + in2) };
419                 if (in0 == 0.0) { ^(in1 + in2) };
421                 argArray = [in0, in1, in2];
422                 rate = argArray.rate;
423                 sortedArgs = argArray.sort {|a b| a.rate < b.rate};
425                 ^super.new1(rate, *sortedArgs)
426         }
429 Sum4 : UGen {
430         *new { arg in0, in1, in2, in3;
431                 ^this.multiNew(nil, in0, in1, in2, in3)
432         }
434         *new1 { arg dummyRate, in0, in1, in2, in3;
435                 var argArray, rate, sortedArgs;
437                 if (in0 == 0.0) { ^Sum3.new1(nil, in1, in2, in3) };
438                 if (in1 == 0.0) { ^Sum3.new1(nil, in0, in2, in3) };
439                 if (in2 == 0.0) { ^Sum3.new1(nil, in0, in1, in3) };
440                 if (in3 == 0.0) { ^Sum3.new1(nil, in0, in1, in2) };
442                 argArray = [in0, in1, in2, in3];
443                 rate = argArray.rate;
444                 sortedArgs = argArray.sort {|a b| a.rate < b.rate};
446                 ^super.new1(rate, *sortedArgs)
447         }