4 var <>controls, <>controlNames; // ugens add to this
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 };
30 synthDefDir = Platform.userAppSupportDir ++ "/synthdefs/";
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)
40 ^super.new.name_(name.asString)
43 storeArgs { ^[name.asSymbol, func] }
45 build { arg ugenGraphFunc, rates, prependArgs;
48 this.buildUgenGraph(ugenGraphFunc, rates, prependArgs);
52 UGen.buildSynthDef = nil;
56 *wrap { arg func, rates, prependArgs;
57 if (UGen.buildSynthDef.isNil) {
58 "SynthDef.wrap should be called inside a SynthDef ugenGraphFunc.\n".error;
61 ^UGen.buildSynthDef.buildUgenGraph(func, rates, prependArgs);
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)
68 this.writeDefFile(dir, false)
72 UGen.buildSynthDef = this;
73 constants = Dictionary.new;
74 constantSet = Set.new;
79 buildUgenGraph { arg func, rates, prependArgs;
81 // save/restore controls in case of *wrap
82 var saveControlNames = controlNames;
86 prependArgs = prependArgs.asArray;
87 this.addControlsFromArgsOfFunc(func, rates, prependArgs.size);
88 result = func.valueArray(prependArgs ++ this.buildControls);
90 controlNames = saveControlNames
95 addControlsFromArgsOfFunc { arg func, rates, skipArgs=0;
96 var def, names, values,argNames, specs;
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|
110 (specs[names[i]] ?? { names[i] }).tryPerform(\asSpec).tryPerform(\default) ? 0
114 values = values.collect {|value| value ? 0.0 };
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;
122 case { (lag == \ir) or: { c == $i and: { c2 == $_ }}}
124 if (lag.isNumber and: { lag != 0 }) {
125 Post << "WARNING: lag value "<< lag <<" for i-rate arg '"
126 << name <<"' will be ignored.\n";
128 this.addIr(name, value);
130 {(lag == \tr) or: { c == $t and: { c2 == $_ }}}
132 if (lag.isNumber and: { lag != 0 }) {
133 Post << "WARNING: lag value "<< lag <<" for trigger arg '"
134 << name <<"' will be ignored.\n";
136 this.addTr(name, value);
138 {(lag == \ar) or: { c == $a and: { c2 == $_ }}}
140 if (lag.isNumber and: { lag != 0 }) {
141 Post << "WARNING: lag value "<< lag <<" for audio arg '"
142 << name <<"' will be ignored.\n";
144 this.addAr(name, value);
147 if (lag == \kr) { lag = 0.0 };
148 this.addKr(name, value, lag);
153 addControlName { arg cn;
154 controlNames = controlNames.add(cn);
155 allControlNames = allControlNames.add(cn);
158 // allow incremental building of controls
159 addNonControl { arg name, values;
160 this.addControlName(ControlName(name, nil, 'noncontrol',
161 values.copy, controlNames.size));
163 addIr { arg name, values;
164 this.addControlName(ControlName(name, controls.size, 'scalar',
165 values.copy, controlNames.size));
167 addKr { arg name, values, lags;
168 this.addControlName(ControlName(name, controls.size, 'control',
169 values.copy, controlNames.size, lags.copy));
171 addTr { arg name, values;
172 this.addControlName(ControlName(name, controls.size, 'trigger',
173 values.copy, controlNames.size));
175 addAr { arg name, values;
176 this.addControlName(ControlName(name, controls.size, 'audio',
177 values.copy, controlNames.size))
181 var controlUGens, index, values, lags, valsize;
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;
197 if (irControlNames.size > 0) {
199 irControlNames.do {|cn|
200 values = values.add(cn.defaultValue);
202 index = controlIndex;
203 controlUGens = Control.ir(values.flat).asArray.reshapeLike(values);
204 irControlNames.do {|cn, i|
206 index = index + cn.defaultValue.asArray.size;
207 arguments[cn.argNum] = controlUGens[i];
208 this.setControlNames(controlUGens[i], cn);
211 if (trControlNames.size > 0) {
213 trControlNames.do {|cn|
214 values = values.add(cn.defaultValue);
216 index = controlIndex;
217 controlUGens = TrigControl.kr(values.flat).asArray.reshapeLike(values);
218 trControlNames.do {|cn, i|
220 index = index + cn.defaultValue.asArray.size;
221 arguments[cn.argNum] = controlUGens[i];
222 this.setControlNames(controlUGens[i], cn);
225 if (arControlNames.size > 0) {
227 arControlNames.do {|cn|
228 values = values.add(cn.defaultValue);
230 index = controlIndex;
231 controlUGens = AudioControl.ar(values.flat).asArray.reshapeLike(values);
232 arControlNames.do {|cn, i|
234 index = index + cn.defaultValue.asArray.size;
235 arguments[cn.argNum] = controlUGens[i];
236 this.setControlNames(controlUGens[i], cn);
239 if (krControlNames.size > 0) {
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));
247 index = controlIndex;
248 if (lags.any {|lag| lag != 0 }) {
249 controlUGens = LagControl.kr(values.flat, lags).asArray.reshapeLike(values);
251 controlUGens = Control.kr(values.flat).asArray.reshapeLike(values);
253 krControlNames.do {|cn, i|
255 index = index + cn.defaultValue.asArray.size;
256 arguments[cn.argNum] = controlUGens[i];
257 this.setControlNames(controlUGens[i], cn);
260 controlNames = controlNames.reject {|cn| cn.rate == 'noncontrol' };
265 setControlNames {arg controlUGens, cn;
266 controlUGens.isArray.if({
267 controlUGens.do({arg thisCtrl;
268 thisCtrl.name_(cn.name);
271 controlUGens.name_(cn.name)
278 this.collectConstants;
279 this.checkInputs;// will die on error
281 // re-sort graph. reindex.
282 this.topologicalSort;
284 UGen.buildSynthDef = nil;
288 var stream = CollStream.on(Int8Array.new(256));
289 this.asArray.writeDef(stream);
292 writeDefFile { arg dir, overwrite;
293 if((metadata.tryPerform(\at, \shouldNotSend) ? false).not) {
294 super.writeDefFile(name, dir, overwrite);
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
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 |
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);
327 file.putInt32(children.size);
328 children.do { | item |
332 file.putInt16(variants.size);
333 if (variants.size > 0) {
334 allControlNamesMap = ();
335 allControlNamesTemp.do { |cn|
336 allControlNamesMap[cn.name] = cn;
338 variants.keysValuesDo {|varname, pairs|
341 varname = name ++ "." ++ varname;
342 if (varname.size > 32) {
343 Post << "variant '" << varname << "' name too long.\n";
346 varcontrols = controls.copy;
347 pairs.pairsDo { |cname, values|
349 cn = allControlNamesMap[cname];
351 values = values.asArray;
352 if (values.size > cn.defaultValue.asArray.size) {
353 postf("variant: '%' control: '%' size mismatch.\n",
359 varcontrols[index + i] = val;
363 postf("variant: '%' control: '%' not found.\n",
368 file.putPascalString(varname);
369 varcontrols.do { | item |
376 writeConstants { arg file;
377 var array = FloatArray.newClear(constants.size);
378 constants.keysValuesDo { arg value, index;
379 array[index] = value;
382 file.putInt32(constants.size);
390 children.do { arg ugen;
392 if ((err = ugen.checkInputs).notNil) {
393 err = ugen.class.asString + err;
396 if(firstErr.isNil) { firstErr = err };
399 if(firstErr.notNil) {
400 ("SynthDef" + this.name + "build failed").postln;
401 Error(firstErr).throw
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);
417 removeUGen { arg ugen;
418 // lazy removal: clear entry and later remove all nil enties
419 children[ugen.synthIndex] = nil;
421 replaceUGen { arg a, b;
422 if (b.isKindOf(UGen).not) {
423 Error("replaceUGen assumes a UGen").throw;
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;
434 item.inputs.do { arg input, j;
435 if (input === a) { item.inputs[j] = b };
440 addConstant { arg value;
441 if (constantSet.includes(value).not) {
442 constantSet.add(value);
443 constants[value] = constants.size;
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.
456 rewriteInProgress = true;
457 children.copy.do { arg ugen;
460 rewriteInProgress = nil;
462 // fixup removed ugens
463 oldSize = children.size;
464 children.removeEvery(#[nil]);
465 if (oldSize != children.size) {
470 children.do { arg ugen;
471 ugen.collectConstants;
477 children.do { arg ugen;
478 ugen.antecedents = Set.new;
479 ugen.descendants = Set.new;
481 children.do { arg ugen;
482 // this populates the descendants and antecedents
485 children.reverseDo { arg ugen;
486 ugen.descendants = ugen.descendants.asArray.sort(
487 { arg a, b; a.synthIndex < b.synthIndex }
489 ugen.makeAvailable; // all ugens with no antecedents are made available
493 children.do { arg ugen;
494 ugen.antecedents = nil;
495 ugen.descendants = nil;
496 ugen.widthFirstAntecedents = nil;
502 while { available.size > 0 }
504 outStack = available.pop.schedule(outStack);
507 this.cleanupTopoSort;
510 children.do { arg ugen, i;
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 };
524 [ugen.dumpName, ugen.rate, inputs].postln;
528 // make SynthDef available to all servers
529 add { arg libname, completionMsg, keepDef = true;
530 var servers, desc = this.asSynthDesc(libname ? \global, keepDef);
532 servers = Server.allRunningServers
534 servers = SynthDescLib.getLib(libname).servers
537 this.doSend(each.value, completionMsg.value(each))
541 *removeAt { arg name, libname = \global;
542 var lib = SynthDescLib.getLib(libname);
544 lib.servers.do { |each|
545 each.value.sendMsg("/d_free", name)
550 // methods for special optimizations
552 // only send to servers
553 send { arg server, completionMsg;
554 var servers = (server ?? { Server.allRunningServers }).asArray;
556 if(each.serverRunning.not) {
557 "Server % not running, could not send SynthDef.".format(server.name).warn
559 if(metadata.trueAt(\shouldNotSend)) {
560 this.loadReconstructed(each, completionMsg);
562 this.doSend(each, completionMsg);
567 doSend { |server, completionMsg|
568 var bytes = this.asBytes;
569 if (bytes.size < (65535 div: 4)) {
570 server.sendMsg("/d_recv", bytes, completionMsg)
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)
577 "SynthDef % too big for sending.".format(name).warn;
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);
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)
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)) {
602 file = File(path, "w");
603 bytes = this.asBytes;
607 lib.servers.do { arg server;
608 this.doSend(server.value, completionMsg)
610 desc = lib[this.name.asSymbol];
611 desc.metadata = metadata;
612 SynthDesc.populateMetadataFunc.value(desc);
613 desc.writeMetadata(path);
619 lib.servers.do { arg server;
620 this.loadReconstructed(server, completionMsg);
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 };
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."
645 "Loading from disk instead.".postln;
646 server.sendBundle(nil, ["/d_load", metadata[\loadPath], completionMsg]);
648 MethodError("Server is remote, cannot load from disk.", this).throw;
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);
659 // load synthdesc from disk
660 // because SynthDescLib still needs to have the info
661 lib = SynthDescLib.getLib(libname);
666 play { arg target,args,addAction=\addToHead;
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);