Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / SCClassLibrary / Common / Audio / SynthDef.sc
blob66b2e39f2538ae1221df1266b43141c8ee4d1e15
1 SynthDef {
2         var <>name, <>func;
4         var <>controls, <>controlNames; // ugens add to this
5         var <>allControlNames;
6         var <>controlIndex=0;
7         var <>children;
9         var <>constants;
10         var <>constantSet;
11         var <>maxLocalBufs;
13         // topo sort
14         var <>available;
15         var <>variants;
16         var <>widthFirstUGens;
17         var rewriteInProgress;
19         var <>desc, <>metadata;
21         classvar <synthDefDir;
23         *synthDefDir_ { arg dir;
24                 if (dir.last.isPathSeparator.not )
25                         { dir = dir ++ thisProcess.platform.pathSeparator };
26                 synthDefDir = dir;
27         }
29         *initClass {
30                 synthDefDir = Platform.userAppSupportDir ++ "/synthdefs/";
31                 // Ensure exists:
32                 synthDefDir.mkdir;
33         }
35         *new { arg name, ugenGraphFunc, rates, prependArgs, variants, metadata;
36                 ^this.prNew(name).variants_(variants).metadata_(metadata).children_(Array.new(64))
37                         .build(ugenGraphFunc, rates, prependArgs)
38         }
39         *prNew { arg name;
40                 ^super.new.name_(name.asString)
41         }
43         storeArgs { ^[name.asSymbol, func] }
45         build { arg ugenGraphFunc, rates, prependArgs;
46                 protect {
47                         this.initBuild;
48                         this.buildUgenGraph(ugenGraphFunc, rates, prependArgs);
49                         this.finishBuild;
50                         func = ugenGraphFunc;
51                 } {
52                         UGen.buildSynthDef = nil;
53                 }
54         }
56         *wrap { arg func, rates, prependArgs;
57                 if (UGen.buildSynthDef.isNil) {
58                         "SynthDef.wrap should be called inside a SynthDef ugenGraphFunc.\n".error;
59                         ^0
60                 };
61                 ^UGen.buildSynthDef.buildUgenGraph(func, rates, prependArgs);
62         }
63         //only write if no file exists
64         *writeOnce { arg name, func, rates, prependArgs, variants, dir, metadata;
65                 this.new(name, func, rates, prependArgs, variants, metadata).writeOnce(dir)
66         }
67         writeOnce { arg dir;
68                 this.writeDefFile(dir, false)
69         }
71         initBuild {
72                 UGen.buildSynthDef = this;
73                 constants = Dictionary.new;
74                 constantSet = Set.new;
75                 controls = nil;
76                 controlIndex = 0;
77                 maxLocalBufs = nil;
78         }
79         buildUgenGraph { arg func, rates, prependArgs;
80                 var result;
81                 // save/restore controls in case of *wrap
82                 var saveControlNames = controlNames;
84                 controlNames = nil;
86                 prependArgs = prependArgs.asArray;
87                 this.addControlsFromArgsOfFunc(func, rates, prependArgs.size);
88                 result = func.valueArray(prependArgs ++ this.buildControls);
90                 controlNames = saveControlNames
92                 ^result
94         }
95         addControlsFromArgsOfFunc { arg func, rates, skipArgs=0;
96                 var def, names, values,argNames, specs;
98                 def = func.def;
99                 argNames = def.argNames;
100                 if(argNames.isNil) { ^nil };
101                 names = def.argNames[skipArgs..];
102                 // OK what we do here is separate the ir, tr and kr rate arguments,
103                 // create one Control ugen for all of each rate,
104                 // and then construct the argument array from combining
105                 // the OutputProxies of these two Control ugens in the original order.
106                 values = def.prototypeFrame[skipArgs..].extend( names.size );
107                 if((specs = metadata.tryPerform(\at, \specs)).notNil) {
108                         values = values.collect { |value, i|
109                                 value ?? {
110                                         (specs[names[i]] ?? { names[i] }).tryPerform(\asSpec).tryPerform(\default) ? 0
111                                 }
112                         }
113                 } {
114                         values = values.collect {|value| value ? 0.0 };
115                 };
116                 rates = rates.asArray.extend(names.size, 0).collect {|lag| lag ? 0.0 };
117                 names.do { arg name, i;
118                         var c, c2, value, lag;
119                         #c, c2 = name.asString;
120                         value = values[i];
121                         lag = rates[i];
122                         case { (lag == \ir) or: { c == $i and: { c2 == $_ }}}
123                         {
124                                 if (lag.isNumber and: { lag != 0 }) {
125                                         Post << "WARNING: lag value "<< lag <<" for i-rate arg '"
126                                                 << name <<"' will be ignored.\n";
127                                 };
128                                 this.addIr(name, value);
129                         }
130                         {(lag == \tr) or: { c == $t and: { c2 == $_ }}}
131                         {
132                                 if (lag.isNumber and: { lag != 0 }) {
133                                         Post << "WARNING: lag value "<< lag <<" for trigger arg '"
134                                                 << name <<"' will be ignored.\n";
135                                 };
136                                 this.addTr(name, value);
137                         }
138                         {(lag == \ar) or: { c == $a and: { c2 == $_ }}}
139                         {
140                                 if (lag.isNumber and: { lag != 0 }) {
141                                         Post << "WARNING: lag value "<< lag <<" for audio arg '"
142                                                 << name <<"' will be ignored.\n";
143                                 };
144                                 this.addAr(name, value);
145                         }
146                         {
147                                 if (lag == \kr) { lag = 0.0 };
148                                 this.addKr(name, value, lag);
149                         };
150                 };
151         }
153         addControlName { arg cn;
154                 controlNames = controlNames.add(cn);
155                 allControlNames = allControlNames.add(cn);
156         }
158         // allow incremental building of controls
159         addNonControl { arg name, values;
160                 this.addControlName(ControlName(name, nil, 'noncontrol',
161                         values.copy, controlNames.size));
162         }
163         addIr { arg name, values;
164                 this.addControlName(ControlName(name, controls.size, 'scalar',
165                         values.copy, controlNames.size));
166         }
167         addKr { arg name, values, lags;
168                 this.addControlName(ControlName(name, controls.size, 'control',
169                         values.copy, controlNames.size, lags.copy));
170         }
171         addTr { arg name, values;
172                 this.addControlName(ControlName(name, controls.size, 'trigger',
173                         values.copy, controlNames.size));
174         }
175         addAr { arg name, values;
176                 this.addControlName(ControlName(name, controls.size, 'audio',
177                         values.copy, controlNames.size))
178         }
180         buildControls {
181                 var controlUGens, index, values, lags, valsize;
182                 var def, argNames;
184                 var arguments = Array.newClear(controlNames.size);
186                 var nonControlNames = controlNames.select {|cn| cn.rate == 'noncontrol' };
187                 var irControlNames = controlNames.select {|cn| cn.rate == 'scalar' };
188                 var krControlNames = controlNames.select {|cn| cn.rate == 'control' };
189                 var trControlNames = controlNames.select {|cn| cn.rate == 'trigger' };
190                 var arControlNames = controlNames.select {|cn| cn.rate == 'audio' };
192                 if (nonControlNames.size > 0) {
193                         nonControlNames.do {|cn|
194                                 arguments[cn.argNum] = cn.defaultValue;
195                         };
196                 };
197                 if (irControlNames.size > 0) {
198                         values = nil;
199                         irControlNames.do {|cn|
200                                 values = values.add(cn.defaultValue);
201                         };
202                         index = controlIndex;
203                         controlUGens = Control.ir(values.flat).asArray.reshapeLike(values);
204                         irControlNames.do {|cn, i|
205                                 cn.index = index;
206                                 index = index + cn.defaultValue.asArray.size;
207                                 arguments[cn.argNum] = controlUGens[i];
208                                 this.setControlNames(controlUGens[i], cn);
209                         };
210                 };
211                 if (trControlNames.size > 0) {
212                         values = nil;
213                         trControlNames.do {|cn|
214                                 values = values.add(cn.defaultValue);
215                         };
216                         index = controlIndex;
217                         controlUGens = TrigControl.kr(values.flat).asArray.reshapeLike(values);
218                         trControlNames.do {|cn, i|
219                                 cn.index = index;
220                                 index = index + cn.defaultValue.asArray.size;
221                                 arguments[cn.argNum] = controlUGens[i];
222                                 this.setControlNames(controlUGens[i], cn);
223                         };
224                 };
225                 if (arControlNames.size > 0) {
226                         values = nil;
227                         arControlNames.do {|cn|
228                                 values = values.add(cn.defaultValue);
229                         };
230                         index = controlIndex;
231                         controlUGens = AudioControl.ar(values.flat).asArray.reshapeLike(values);
232                         arControlNames.do {|cn, i|
233                                 cn.index = index;
234                                 index = index + cn.defaultValue.asArray.size;
235                                 arguments[cn.argNum] = controlUGens[i];
236                                 this.setControlNames(controlUGens[i], cn);
237                         };
238                 };
239                 if (krControlNames.size > 0) {
240                         values = nil;
241                         lags = nil;
242                         krControlNames.do {|cn|
243                                 valsize = cn.defaultValue.asArray.size;
244                                 values = values.add(cn.defaultValue);
245                                 lags = lags.addAll(cn.lag.asArray.wrapExtend(valsize));
246                         };
247                         index = controlIndex;
248                         if (lags.any {|lag| lag != 0 }) {
249                                 controlUGens = LagControl.kr(values.flat, lags).asArray.reshapeLike(values);
250                         }{
251                                 controlUGens = Control.kr(values.flat).asArray.reshapeLike(values);
252                         };
253                         krControlNames.do {|cn, i|
254                                 cn.index = index;
255                                 index = index + cn.defaultValue.asArray.size;
256                                 arguments[cn.argNum] = controlUGens[i];
257                                 this.setControlNames(controlUGens[i], cn);
258                         };
259                 };
260                 controlNames = controlNames.reject {|cn| cn.rate == 'noncontrol' };
262                 ^arguments
263         }
265         setControlNames {arg controlUGens, cn;
266                 controlUGens.isArray.if({
267                         controlUGens.do({arg thisCtrl;
268                                 thisCtrl.name_(cn.name);
269                                 })
270                         }, {
271                         controlUGens.name_(cn.name)
272                         });
273         }
275         finishBuild {
277                 this.optimizeGraph;
278                 this.collectConstants;
279                 this.checkInputs;// will die on error
281                 // re-sort graph. reindex.
282                 this.topologicalSort;
283                 this.indexUGens;
284                 UGen.buildSynthDef = nil;
285         }
287         asBytes {
288                 var stream = CollStream.on(Int8Array.new(256));
289                 this.asArray.writeDef(stream);
290                 ^stream.collection;
291         }
292         writeDefFile { arg dir, overwrite;
293                 if((metadata.tryPerform(\at, \shouldNotSend) ? false).not) {
294                         super.writeDefFile(name, dir, overwrite);
295                 } {
296                         // actual error, not just warning as in .send and .load,
297                         // because you might try to write the file somewhere other than
298                         // the default location - could be fatal later, so crash now
299                         MethodError("This SynthDef (%) was reconstructed from a .scsyndef file. It does not contain all the required structure to write back to disk. File was not written."
300                                 .format(name), this).throw
301                 }
302         }
303         
304         writeDef { arg file;
305                 // This describes the file format for the synthdef files.
306                 var allControlNamesTemp, allControlNamesMap;
308                 file.putPascalString(name);
310                 this.writeConstants(file);
312                 //controls have been added by the Control UGens
313                 file.putInt32(controls.size);
314                 controls.do { | item |
315                         file.putFloat(item);
316                 };
318                 allControlNamesTemp = allControlNames.reject { |cn| cn.rate == \noncontrol };
319                 file.putInt32(allControlNamesTemp.size);
320                 allControlNamesTemp.do { | item |
321                         if (item.name.notNil) {
322                                 file.putPascalString(item.name.asString);
323                                 file.putInt32(item.index);
324                         };
325                 };
327                 file.putInt32(children.size);
328                 children.do { | item |
329                         item.writeDef(file);
330                 };
332                 file.putInt16(variants.size);
333                 if (variants.size > 0) {
334                         allControlNamesMap = ();
335                         allControlNamesTemp.do { |cn|
336                                 allControlNamesMap[cn.name] = cn;
337                         };
338                         variants.keysValuesDo {|varname, pairs|
339                                 var varcontrols;
341                                 varname = name ++ "." ++ varname;
342                                 if (varname.size > 32) {
343                                         Post << "variant '" << varname << "' name too long.\n";
344                                         ^nil
345                                 };
346                                 varcontrols = controls.copy;
347                                 pairs.pairsDo { |cname, values|
348                                         var cn, index;
349                                         cn = allControlNamesMap[cname];
350                                         if (cn.notNil) {
351                                                 values = values.asArray;
352                                                 if (values.size > cn.defaultValue.asArray.size) {
353                                                         postf("variant: '%' control: '%' size mismatch.\n",
354                                                                 varname, cname);
355                                                         ^nil
356                                                 }{
357                                                         index = cn.index;
358                                                         values.do {|val, i|
359                                                                 varcontrols[index + i] = val;
360                                                         }
361                                                 }
362                                         }{
363                                                 postf("variant: '%' control: '%' not found.\n",
364                                                                 varname, cname);
365                                                 ^nil
366                                         }
367                                 };
368                                 file.putPascalString(varname);
369                                 varcontrols.do { | item |
370                                         file.putFloat(item);
371                                 };
372                         };
373                 };
374         }
375         
376         writeConstants { arg file;
377                 var array = FloatArray.newClear(constants.size);
378                 constants.keysValuesDo { arg value, index;
379                         array[index] = value;
380                 };
382                 file.putInt32(constants.size);
383                 array.do { | item |
384                         file.putFloat(item)
385                 };
386         }
388         checkInputs {
389                 var firstErr;
390                 children.do { arg ugen;
391                         var err;
392                         if ((err = ugen.checkInputs).notNil) {
393                                 err = ugen.class.asString + err;
394                                 err.postln;
395                                 ugen.dumpArgs;
396                                 if(firstErr.isNil) { firstErr = err };
397                         };
398                 };
399                 if(firstErr.notNil) {
400                         ("SynthDef" + this.name + "build failed").postln;
401                         Error(firstErr).throw
402                 };
403                 ^true
404         }
408         // UGens do these
409         addUGen { arg ugen;
410                 if (rewriteInProgress.isNil) {
411                         // we don't add ugens, if a rewrite operation is in progress
412                         ugen.synthIndex = children.size;
413                         ugen.widthFirstAntecedents = widthFirstUGens.copy;
414                         children = children.add(ugen);
415                 }
416         }
417         removeUGen { arg ugen;
418                 // lazy removal: clear entry and later remove all nil enties
419                 children[ugen.synthIndex] = nil;
420         }
421         replaceUGen { arg a, b;
422                 if (b.isKindOf(UGen).not) {
423                         Error("replaceUGen assumes a UGen").throw;
424                 };
426                 b.widthFirstAntecedents = a.widthFirstAntecedents;
427                 b.descendants = a.descendants;
428                 b.synthIndex = a.synthIndex;
430                 children[a.synthIndex] = b;
432                 children.do { arg item, i;
433                         if (item.notNil) {
434                                 item.inputs.do { arg input, j;
435                                         if (input === a) { item.inputs[j] = b };
436                                 };
437                         };
438                 };
439         }
440         addConstant { arg value;
441                 if (constantSet.includes(value).not) {
442                         constantSet.add(value);
443                         constants[value] = constants.size;
444                 };
445         }
448         // multi channel expansion causes a non optimal breadth-wise ordering of the graph.
449         // the topological sort below follows branches in a depth first order,
450         // so that cache performance of connection buffers is optimized.
452         optimizeGraph {
453                 var oldSize;
454                 this.initTopoSort;
456                 rewriteInProgress = true;
457                 children.copy.do { arg ugen;
458                         ugen.optimizeGraph;
459                 };
460                 rewriteInProgress = nil;
462                 // fixup removed ugens
463                 oldSize = children.size;
464                 children.removeEvery(#[nil]);
465                 if (oldSize != children.size) {
466                         this.indexUGens
467                 };
468         }
469         collectConstants {
470                 children.do { arg ugen;
471                         ugen.collectConstants;
472                 };
473         }
475         initTopoSort {
476                 available = nil;
477                 children.do { arg ugen;
478                         ugen.antecedents = Set.new;
479                         ugen.descendants = Set.new;
480                 };
481                 children.do { arg ugen;
482                         // this populates the descendants and antecedents
483                         ugen.initTopoSort;
484                 };
485                 children.reverseDo { arg ugen;
486                         ugen.descendants = ugen.descendants.asArray.sort(
487                                                                 { arg a, b; a.synthIndex < b.synthIndex }
488                                                         );
489                         ugen.makeAvailable; // all ugens with no antecedents are made available
490                 };
491         }
492         cleanupTopoSort {
493                 children.do { arg ugen;
494                         ugen.antecedents = nil;
495                         ugen.descendants = nil;
496                         ugen.widthFirstAntecedents = nil;
497                 };
498         }
499         topologicalSort {
500                 var outStack;
501                 this.initTopoSort;
502                 while { available.size > 0 }
503                 {
504                         outStack = available.pop.schedule(outStack);
505                 };
506                 children = outStack;
507                 this.cleanupTopoSort;
508         }
509         indexUGens {
510                 children.do { arg ugen, i;
511                         ugen.synthIndex = i;
512                 };
513         }
515         dumpUGens {
516                 name.postln;
517                 children.do { arg ugen, i;
518                         var inputs, ugenName;
519                         if (ugen.inputs.notNil) {
520                                 inputs = ugen.inputs.collect {|in|
521                                         if (in.respondsTo(\dumpName)) { in.dumpName }{ in };
522                                 };
523                         };
524                         [ugen.dumpName, ugen.rate, inputs].postln;
525                 };
526         }
528         // make SynthDef available to all servers
529         add { arg libname, completionMsg, keepDef = true;
530                 var     servers, desc = this.asSynthDesc(libname ? \global, keepDef);
531                 if(libname.isNil) {
532                         servers = Server.allRunningServers
533                 } {
534                         servers = SynthDescLib.getLib(libname).servers
535                 };
536                 servers.do { |each|
537                         this.doSend(each.value, completionMsg.value(each))
538                 }
539         }
541         *removeAt { arg name, libname = \global;
542                 var lib = SynthDescLib.getLib(libname);
543                 lib.removeAt(name);
544                 lib.servers.do { |each|
545                         each.value.sendMsg("/d_free", name)
546                 };
547         }
550         // methods for special optimizations
552         // only send to servers
553         send { arg server, completionMsg;
554                 var servers = (server ?? { Server.allRunningServers }).asArray;
555                 servers.do { |each|
556                         if(each.serverRunning.not) {
557                                 "Server % not running, could not send SynthDef.".format(server.name).warn
558                         };
559                         if(metadata.trueAt(\shouldNotSend)) {
560                                 this.loadReconstructed(each, completionMsg);
561                         } {
562                                 this.doSend(each, completionMsg);
563                         }
564                 }
565         }
567         doSend { |server, completionMsg|
568                 var bytes = this.asBytes;
569                 if (bytes.size < (65535 div: 4)) {
570                         server.sendMsg("/d_recv", bytes, completionMsg)
571                 } {
572                         if (server.isLocal) {
573                                 "SynthDef % too big for sending. Retrying via synthdef file".format(name).warn;
574                                 this.writeDefFile(synthDefDir);
575                                 server.sendMsg("/d_load", synthDefDir ++ name ++ ".scsyndef", completionMsg)
576                         } {
577                                 "SynthDef % too big for sending.".format(name).warn;
578                         }
579                 }
580         }
582         // send to server and write file
583         load { arg server, completionMsg, dir(synthDefDir);
584                 server = server ? Server.default;
585                 if(metadata.trueAt(\shouldNotSend)) {
586                         this.loadReconstructed(server, completionMsg);
587                 } {
588                         // should remember what dir synthDef was written to
589                         dir = dir ? synthDefDir;
590                         this.writeDefFile(dir);
591                         server.sendMsg("/d_load", dir ++ name ++ ".scsyndef", completionMsg)
592                 };
593         }
595         // write to file and make synth description
596         store { arg libname=\global, dir(synthDefDir), completionMsg, mdPlugin;
597                 var lib = SynthDescLib.getLib(libname);
598                 var file, path = dir ++ name ++ ".scsyndef";
599                 if(metadata.falseAt(\shouldNotSend)) {
600                         protect {
601                                 var bytes, desc;
602                                 file = File(path, "w");
603                                 bytes = this.asBytes;
604                                 file.putAll(bytes);
605                                 file.close;
606                                 lib.read(path);
607                                 lib.servers.do { arg server;
608                                         this.doSend(server.value, completionMsg)
609                                 };
610                                 desc = lib[this.name.asSymbol];
611                                 desc.metadata = metadata;
612                                 SynthDesc.populateMetadataFunc.value(desc);
613                                 desc.writeMetadata(path);
614                         } {
615                                 file.close
616                         }
617                 } {
618                         lib.read(path);
619                         lib.servers.do { arg server;
620                                 this.loadReconstructed(server, completionMsg);
621                         };
622                 };
623         }
625         asSynthDesc { |libname=\global, keepDef = true|
626                 var     lib, stream = CollStream(this.asBytes);
627                 libname ?? { libname = \global };
628                 lib = SynthDescLib.getLib(libname);
629                 SynthDesc.readFile(stream, keepDef, lib.synthDescs);
630                 desc = lib[name.asSymbol];
631                 if(keepDef) { desc.def = this };
632                 if(metadata.notNil) { desc.metadata = metadata };
633                 ^desc
634         }
636         // this method warns and does not halt
637         // because loading existing def from disk is a viable alternative
638         // to get the synthdef to the server
640         loadReconstructed { arg server, completionMsg;
641                         "SynthDef (%) was reconstructed from a .scsyndef file, "
642                         "it does not contain all the required structure to send back to the server."
643                                 .format(name).warn;
644                         if(server.isLocal) {
645                                 "Loading from disk instead.".postln;
646                                 server.sendBundle(nil, ["/d_load", metadata[\loadPath], completionMsg]);
647                         } {
648                                 MethodError("Server is remote, cannot load from disk.", this).throw;
649                         };
651         }
653         // this method needs a reconsideration
654         storeOnce { arg libname=\global, dir(synthDefDir), completionMsg, mdPlugin;
655                 var     path = dir ++ name ++ ".scsyndef", lib;
656                 if(File.exists(path).not) {
657                         this.store(libname, dir, completionMsg, mdPlugin);
658                 } {
659                                 // load synthdesc from disk
660                                 // because SynthDescLib still needs to have the info
661                         lib = SynthDescLib.getLib(libname);
662                         lib.read(path);
663                 };
664         }
666         play { arg target,args,addAction=\addToHead;
667                 var synth, msg;
668 //              this.deprecated(thisMethod, Function.findRespondingMethodFor(\play));
669                 target = target.asTarget;
670                 synth = Synth.basicNew(name,target.server);
671                 msg = synth.newMsg(target, args, addAction);
672                 this.send(target.server, msg);
673                 ^synth
674         }