4 var <>controls, <>controlNames; // ugens add to this
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 };
29 synthDefDir = Platform.userAppSupportDir ++ "/synthdefs/";
34 *new { arg name, ugenGraphFunc, rates, prependArgs, variants, metadata;
35 ^this.prNew(name).variants_(variants).metadata_(metadata)
36 .build(ugenGraphFunc, rates, prependArgs)
39 ^super.new.name_(name.asString)
42 storeArgs { ^[name.asSymbol, func] }
44 build { arg ugenGraphFunc, rates, prependArgs;
47 this.buildUgenGraph(ugenGraphFunc, rates, prependArgs);
51 UGen.buildSynthDef = nil;
55 *wrap { arg func, rates, prependArgs;
56 if (UGen.buildSynthDef.isNil) {
57 "SynthDef.wrap should be called inside a SynthDef ugenGraphFunc.\n".error;
60 ^UGen.buildSynthDef.buildUgenGraph(func, rates, prependArgs);
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)
67 this.writeDefFile(dir, false)
71 UGen.buildSynthDef = this;
72 constants = Dictionary.new;
73 constantSet = Set.new;
78 buildUgenGraph { arg func, rates, prependArgs;
80 // save/restore controls in case of *wrap
81 var saveControlNames = controlNames;
85 prependArgs = prependArgs.asArray;
86 this.addControlsFromArgsOfFunc(func, rates, prependArgs.size);
87 result = func.valueArray(prependArgs ++ this.buildControls);
89 controlNames = saveControlNames
94 addControlsFromArgsOfFunc { arg func, rates, skipArgs=0;
95 var def, names, values,argNames, specs;
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|
109 (specs[names[i]] ?? { names[i] }).tryPerform(\asSpec).tryPerform(\default) ? 0
113 values = values.collect {|value| value ? 0.0 };
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;
121 case { (lag == \ir) or: { c == $i and: { c2 == $_ }}}
123 if (lag.isNumber and: { lag != 0 }) {
124 Post << "WARNING: lag value "<< lag <<" for i-rate arg '"
125 << name <<"' will be ignored.\n";
127 this.addIr(name, value);
129 {(lag == \tr) or: { c == $t and: { c2 == $_ }}}
131 if (lag.isNumber and: { lag != 0 }) {
132 Post << "WARNING: lag value "<< lag <<" for trigger arg '"
133 << name <<"' will be ignored.\n";
135 this.addTr(name, value);
137 {(lag == \ar) or: { c == $a and: { c2 == $_ }}}
139 if (lag.isNumber and: { lag != 0 }) {
140 Post << "WARNING: lag value "<< lag <<" for audio arg '"
141 << name <<"' will be ignored.\n";
143 this.addAr(name, value);
146 if (lag == \kr) { lag = 0.0 };
147 this.addKr(name, value, lag);
152 addControlName { arg cn;
153 controlNames = controlNames.add(cn);
154 allControlNames = allControlNames.add(cn);
157 // allow incremental building of controls
158 addNonControl { arg name, values;
159 this.addControlName(ControlName(name, nil, 'noncontrol',
160 values.copy, controlNames.size));
162 addIr { arg name, values;
163 this.addControlName(ControlName(name, controls.size, 'scalar',
164 values.copy, controlNames.size));
166 addKr { arg name, values, lags;
167 this.addControlName(ControlName(name, controls.size, 'control',
168 values.copy, controlNames.size, lags.copy));
170 addTr { arg name, values;
171 this.addControlName(ControlName(name, controls.size, 'trigger',
172 values.copy, controlNames.size));
174 addAr { arg name, values;
175 this.addControlName(ControlName(name, controls.size, 'audio',
176 values.copy, controlNames.size))
180 var controlUGens, index, values, lags, valsize;
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;
196 if (irControlNames.size > 0) {
198 irControlNames.do {|cn|
199 values = values.add(cn.defaultValue);
201 index = controlIndex;
202 controlUGens = Control.ir(values.flat).asArray.reshapeLike(values);
203 irControlNames.do {|cn, i|
205 index = index + cn.defaultValue.asArray.size;
206 arguments[cn.argNum] = controlUGens[i];
207 this.setControlNames(controlUGens[i], cn);
210 if (trControlNames.size > 0) {
212 trControlNames.do {|cn|
213 values = values.add(cn.defaultValue);
215 index = controlIndex;
216 controlUGens = TrigControl.kr(values.flat).asArray.reshapeLike(values);
217 trControlNames.do {|cn, i|
219 index = index + cn.defaultValue.asArray.size;
220 arguments[cn.argNum] = controlUGens[i];
221 this.setControlNames(controlUGens[i], cn);
224 if (arControlNames.size > 0) {
226 arControlNames.do {|cn|
227 values = values.add(cn.defaultValue);
229 index = controlIndex;
230 controlUGens = AudioControl.ar(values.flat).asArray.reshapeLike(values);
231 arControlNames.do {|cn, i|
233 index = index + cn.defaultValue.asArray.size;
234 arguments[cn.argNum] = controlUGens[i];
235 this.setControlNames(controlUGens[i], cn);
238 if (krControlNames.size > 0) {
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));
246 index = controlIndex;
247 if (lags.any {|lag| lag != 0 }) {
248 controlUGens = LagControl.kr(values.flat, lags).asArray.reshapeLike(values);
250 controlUGens = Control.kr(values.flat).asArray.reshapeLike(values);
252 krControlNames.do {|cn, i|
254 index = index + cn.defaultValue.asArray.size;
255 arguments[cn.argNum] = controlUGens[i];
256 this.setControlNames(controlUGens[i], cn);
259 controlNames = controlNames.reject {|cn| cn.rate == 'noncontrol' };
264 setControlNames {arg controlUGens, cn;
265 controlUGens.isArray.if({
266 controlUGens.do({arg thisCtrl;
267 thisCtrl.name_(cn.name);
270 controlUGens.name_(cn.name)
277 this.collectConstants;
278 this.checkInputs;// will die on error
280 // re-sort graph. reindex.
281 this.topologicalSort;
283 UGen.buildSynthDef = nil;
287 var stream = CollStream.on(Int8Array.new(256));
288 this.asArray.writeDef(stream);
291 writeDefFile { arg dir, overwrite;
292 if((metadata.tryPerform(\at, \shouldNotSend) ? false).not) {
293 super.writeDefFile(name, dir, overwrite);
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
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 |
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);
325 file.putInt16(children.size);
326 children.do { | item |
330 file.putInt16(variants.size);
331 if (variants.size > 0) {
332 allControlNamesMap = ();
333 allControlNamesTemp.do { |cn|
334 allControlNamesMap[cn.name] = cn;
336 variants.keysValuesDo {|varname, pairs|
339 varname = name ++ "." ++ varname;
340 if (varname.size > 32) {
341 Post << "variant '" << varname << "' name too long.\n";
344 varcontrols = controls.copy;
345 pairs.pairsDo { |cname, values|
347 cn = allControlNamesMap[cname];
349 values = values.asArray;
350 if (values.size > cn.defaultValue.asArray.size) {
351 postf("variant: '%' control: '%' size mismatch.\n",
357 varcontrols[index + i] = val;
361 postf("variant: '%' control: '%' not found.\n",
366 file.putPascalString(varname);
367 varcontrols.do { | item |
373 writeConstants { arg file;
374 var array = FloatArray.newClear(constants.size);
375 constants.keysValuesDo { arg value, index;
376 array[index] = value;
379 file.putInt16(constants.size);
387 children.do { arg ugen;
389 if ((err = ugen.checkInputs).notNil) {
391 firstErr = if(err.indexOf($:).isNil){err}{
392 err[..err.indexOf($:)-1]
395 (ugen.class.asString + err).postln;
399 if(firstErr.notNil) {
400 ("SynthDef" + this.name + "build failed").postln;
401 Error(firstErr).throw
410 ugen.synthIndex = children.size;
411 ugen.widthFirstAntecedents = widthFirstUGens.copy;
412 children = children.add(ugen);
414 removeUGen { arg ugen;
415 // lazy removal: clear entry and later remove all nil enties
416 children[ugen.synthIndex] = nil;
418 replaceUGen { arg a, b;
419 if (b.isKindOf(UGen).not) {
420 Error("replaceUGen assumes a UGen").throw;
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;
431 item.inputs.do { arg input, j;
432 if (input === a) { item.inputs[j] = b };
437 addConstant { arg value;
438 if (constantSet.includes(value).not) {
439 constantSet.add(value);
440 constants[value] = constants.size;
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.
452 children.copy.do { arg ugen;
456 // fixup removed ugens
457 oldSize = children.size;
458 children.removeEvery(#[nil]);
459 if (oldSize != children.size) {
464 children.do { arg ugen;
465 ugen.collectConstants;
471 children.do { arg ugen;
472 ugen.antecedents = Set.new;
473 ugen.descendants = Set.new;
475 children.do { arg ugen;
476 // this populates the descendants and antecedents
479 children.reverseDo { arg ugen;
480 ugen.descendants = ugen.descendants.asArray.sort(
481 { arg a, b; a.synthIndex < b.synthIndex }
483 ugen.makeAvailable; // all ugens with no antecedents are made available
487 children.do { arg ugen;
488 ugen.antecedents = nil;
489 ugen.descendants = nil;
490 ugen.widthFirstAntecedents = nil;
496 while { available.size > 0 }
498 outStack = available.pop.schedule(outStack);
501 this.cleanupTopoSort;
504 children.do { arg ugen, i;
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 };
518 [ugen.dumpName, ugen.rate, inputs].postln;
522 // make SynthDef available to all servers
523 add { arg libname, completionMsg, keepDef = true;
524 var servers, desc = this.asSynthDesc(libname ? \global, keepDef);
526 servers = Server.allRunningServers
528 servers = SynthDescLib.getLib(libname).servers
531 this.doSend(each.value, completionMsg.value(each))
535 *removeAt { arg name, libname = \global;
536 var lib = SynthDescLib.getLib(libname);
538 lib.servers.do { |each|
539 each.value.sendMsg("/d_free", name)
544 // methods for special optimizations
546 // only send to servers
547 send { arg server, completionMsg;
548 var servers = (server ?? { Server.allRunningServers }).asArray;
550 if(each.serverRunning.not) {
551 "Server % not running, could not send SynthDef.".format(server.name).warn
553 if(metadata.trueAt(\shouldNotSend)) {
554 this.loadReconstructed(each, completionMsg);
556 this.doSend(each, completionMsg);
561 doSend { |server, completionMsg|
562 var bytes = this.asBytes;
564 server.sendMsg("/d_recv", this.asBytes, completionMsg)
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)
571 "Possible buffer overflow when sending SynthDef %.".format(name).warn;
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);
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)
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)) {
596 file = File(path, "w");
597 bytes = this.asBytes;
601 lib.servers.do { arg server;
602 this.doSend(server.value, completionMsg)
604 desc = lib[this.name.asSymbol];
605 desc.metadata = metadata;
606 SynthDesc.populateMetadataFunc.value(desc);
607 desc.writeMetadata(path);
613 lib.servers.do { arg server;
614 this.loadReconstructed(server, completionMsg);
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 };
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."
639 "Loading from disk instead.".postln;
640 server.sendBundle(nil, ["/d_load", metadata[\loadPath], completionMsg]);
642 MethodError("Server is remote, cannot load from disk.", this).throw;
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);
653 // load synthdesc from disk
654 // because SynthDescLib still needs to have the info
655 lib = SynthDescLib.getLib(libname);
660 play { arg target,args,addAction=\addToHead;
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);