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