2 var <>rate, <>numberOfChannels, <>startingChannel, <>type;
4 *new { arg rate, numberOfChannels, startingChannel="?", type;
5 ^super.newCopyArgs(rate, numberOfChannels, startingChannel, type)
9 stream << rate.asString << " " << type.name << " " << startingChannel << " " << numberOfChannels
15 classvar <>mdPlugin, <>populateMetadataFunc;
17 var <>name, <>controlNames, <>controlDict;
18 var <>controls, <>inputs, <>outputs;
21 var <>constants, <>def;
22 var <>msgFunc, <>hasGate = false, <>hasArrayArgs, <>hasVariants, <>canFreeSynth = false;
23 var <msgFuncKeepGate = false;
25 *newFrom { arg synthdef;
30 mdPlugin = AbstractMDPlugin; // override in your startup file
33 send { arg server, completionMsg;
34 def.send(server, completionMsg);
38 stream << "SynthDesc '" << name << "' \nControls:\n";
39 controls.do {|control| control.printOn(stream); $\n.printOn(stream) };
40 inputs.do {|input| stream << " I "; input.printOn(stream); $\n.printOn(stream) };
41 outputs.do {|output| stream << " O "; output.printOn(stream); $\n.printOn(stream) };
44 *read { arg path, keepDefs=false, dict;
45 dict = dict ?? { IdentityDictionary.new };
46 path.pathMatch.do { |filename|
48 file = File(filename, "r");
50 dict = this.readFile(file, keepDefs, dict, filename);
57 // path is for metadata -- only this method has direct access to the new SynthDesc
58 // really this should be a private method -- use *read instead
59 *readFile { arg stream, keepDefs=false, dict, path;
61 dict = dict ?? { IdentityDictionary.new };
62 stream.getInt32; // 'SCgf'
63 version = stream.getInt32; // version
64 numDefs = stream.getInt16;
68 desc = SynthDesc.new.readSynthDef2(stream, keepDefs);
70 desc = SynthDesc.new.readSynthDef(stream, keepDefs);
72 dict.put(desc.name.asSymbol, desc);
73 // AbstractMDPlugin dynamically determines the md archive type
74 // from the file extension
76 desc.metadata = AbstractMDPlugin.readMetadata(path);
78 this.populateMetadataFunc.value(desc);
79 if(desc.def.notNil and: { stream.isKindOf(CollStream).not }) {
80 desc.def.metadata ?? { desc.def.metadata = () };
81 desc.def.metadata.put(\shouldNotSend, true)
82 .put(\loadPath, path);
87 readSynthDef { arg stream, keepDef=false;
88 var numControls, numConstants, numControlNames, numUGens, numVariants;
94 controlDict = IdentityDictionary.new;
96 name = stream.getPascalString;
98 def = SynthDef.prNew(name);
99 UGen.buildSynthDef = def;
101 numConstants = stream.getInt16;
102 constants = FloatArray.newClear(numConstants);
103 stream.read(constants);
105 numControls = stream.getInt16;
106 def.controls = FloatArray.newClear(numControls);
107 stream.read(def.controls);
109 controls = Array.fill(numControls)
111 ControlName('?', i, '?', def.controls[i]);
114 numControlNames = stream.getInt16;
117 var controlName, controlIndex;
118 controlName = stream.getPascalString.asSymbol;
119 controlIndex = stream.getInt16;
120 controls[controlIndex].name = controlName;
121 controlNames = controlNames.add(controlName);
122 controlDict[controlName] = controls[controlIndex];
125 numUGens = stream.getInt16;
127 this.readUGenSpec(stream);
130 controls.inject(nil) {|z,y|
131 if(y.name=='?') { z.defaultValue = z.defaultValue.asArray.add(y.defaultValue); z } { y }
134 def.controlNames = controls.select {|x| x.name.notNil };
135 hasArrayArgs = controls.any { |cn| cn.name == '?' };
137 numVariants = stream.getInt16;
138 hasVariants = numVariants > 0;
139 // maybe later, read in variant names and values
140 // this is harder than it might seem at first
142 def.constants = Dictionary.new;
143 constants.do {|k,i| def.constants.put(k,i) };
145 // throw away unneeded stuff
152 UGen.buildSynthDef = nil;
158 readSynthDef2 { arg stream, keepDef=false;
159 var numControls, numConstants, numControlNames, numUGens, numVariants;
165 controlDict = IdentityDictionary.new;
167 name = stream.getPascalString;
169 def = SynthDef.prNew(name);
170 UGen.buildSynthDef = def;
172 numConstants = stream.getInt32;
173 constants = FloatArray.newClear(numConstants);
174 stream.read(constants);
176 numControls = stream.getInt32;
177 def.controls = FloatArray.newClear(numControls);
178 stream.read(def.controls);
180 controls = Array.fill(numControls)
182 ControlName('?', i, '?', def.controls[i]);
185 numControlNames = stream.getInt32;
188 var controlName, controlIndex;
189 controlName = stream.getPascalString.asSymbol;
190 controlIndex = stream.getInt32;
191 controls[controlIndex].name = controlName;
192 controlNames = controlNames.add(controlName);
193 controlDict[controlName] = controls[controlIndex];
196 numUGens = stream.getInt32;
198 this.readUGenSpec2(stream);
201 controls.inject(nil) {|z,y|
202 if(y.name=='?') { z.defaultValue = z.defaultValue.asArray.add(y.defaultValue); z } { y }
205 def.controlNames = controls.select {|x| x.name.notNil };
206 hasArrayArgs = controls.any { |cn| cn.name == '?' };
208 numVariants = stream.getInt16;
209 hasVariants = numVariants > 0;
210 // maybe later, read in variant names and values
211 // this is harder than it might seem at first
213 def.constants = Dictionary.new;
214 constants.do {|k,i| def.constants.put(k,i) };
216 // throw away unneeded stuff
223 UGen.buildSynthDef = nil;
228 readUGenSpec { arg stream;
229 var ugenClass, rateIndex, rate, numInputs, numOutputs, specialIndex;
230 var inputSpecs, outputSpecs;
232 var ugenInputs, ugen;
235 ugenClass = stream.getPascalString.asSymbol;
236 if(ugenClass.asClass.isNil,{
237 Error("No UGen class found for" + ugenClass + "which was specified in synth def file: " + this.name ++ ".scsyndef").throw;
239 ugenClass = ugenClass.asClass;
241 rateIndex = stream.getInt8;
242 numInputs = stream.getInt16;
243 numOutputs = stream.getInt16;
244 specialIndex = stream.getInt16;
246 inputSpecs = Int16Array.newClear(numInputs * 2);
247 outputSpecs = Int8Array.newClear(numOutputs);
249 stream.read(inputSpecs);
250 stream.read(outputSpecs);
253 forBy (0,inputSpecs.size-1,2) {|i|
254 var ugenIndex, outputIndex, input, ugen;
255 ugenIndex = inputSpecs[i];
256 outputIndex = inputSpecs[i+1];
257 input = if (ugenIndex < 0)
259 constants[outputIndex]
261 ugen = def.children[ugenIndex];
262 if (ugen.isKindOf(MultiOutUGen)) {
263 ugen.channels[outputIndex]
268 ugenInputs = ugenInputs.add(input);
271 rate = #[\scalar,\control,\audio][rateIndex];
272 ugen = ugenClass.newFromDesc(rate, numOutputs, ugenInputs, specialIndex).source;
273 ugen.addToSynth(ugen);
275 addIO = {|list, nchan|
276 var b = ugen.inputs[0];
277 if (b.class == OutputProxy and: {b.source.isKindOf(Control)}) {
278 control = controls.detect {|item| item.index == (b.outputIndex+b.source.specialIndex) };
279 if (control.notNil) { b = control.name };
281 list.add( IODesc(rate, nchan, b, ugenClass))
284 if (ugenClass.isControlUGen) {
285 // Control.newFromDesc does not set the specialIndex, since it doesn't call Control-init.
286 // Therefore we fill it in here:
287 ugen.specialIndex = specialIndex;
289 controls[i+specialIndex].rate = rate;
293 {ugenClass.isInputUGen} {inputs = addIO.value(inputs, ugen.channels.size)}
294 {ugenClass.isOutputUGen} {outputs = addIO.value(outputs, ugen.numAudioChannels)}
296 canFreeSynth = canFreeSynth or: { ugen.canFreeSynth };
302 readUGenSpec2 { arg stream;
303 var ugenClass, rateIndex, rate, numInputs, numOutputs, specialIndex;
304 var inputSpecs, outputSpecs;
306 var ugenInputs, ugen;
309 ugenClass = stream.getPascalString.asSymbol;
310 if(ugenClass.asClass.isNil,{
311 Error("No UGen class found for" + ugenClass + "which was specified in synth def file: " + this.name ++ ".scsyndef").throw;
313 ugenClass = ugenClass.asClass;
315 rateIndex = stream.getInt8;
316 numInputs = stream.getInt32;
317 numOutputs = stream.getInt32;
318 specialIndex = stream.getInt16;
320 inputSpecs = Int32Array.newClear(numInputs * 2);
321 outputSpecs = Int8Array.newClear(numOutputs);
323 stream.read(inputSpecs);
324 stream.read(outputSpecs);
327 forBy (0,inputSpecs.size-1,2) {|i|
328 var ugenIndex, outputIndex, input, ugen;
329 ugenIndex = inputSpecs[i];
330 outputIndex = inputSpecs[i+1];
331 input = if (ugenIndex < 0)
333 constants[outputIndex]
335 ugen = def.children[ugenIndex];
336 if (ugen.isKindOf(MultiOutUGen)) {
337 ugen.channels[outputIndex]
342 ugenInputs = ugenInputs.add(input);
345 rate = #[\scalar,\control,\audio][rateIndex];
346 ugen = ugenClass.newFromDesc(rate, numOutputs, ugenInputs, specialIndex).source;
347 ugen.addToSynth(ugen);
349 addIO = {|list, nchan|
350 var b = ugen.inputs[0];
351 if (b.class == OutputProxy and: {b.source.isKindOf(Control)}) {
352 control = controls.detect {|item| item.index == (b.outputIndex+b.source.specialIndex) };
353 if (control.notNil) { b = control.name };
355 list.add( IODesc(rate, nchan, b, ugenClass))
358 if (ugenClass.isControlUGen) {
359 // Control.newFromDesc does not set the specialIndex, since it doesn't call Control-init.
360 // Therefore we fill it in here:
361 ugen.specialIndex = specialIndex;
363 controls[i+specialIndex].rate = rate;
367 {ugenClass.isInputUGen} {inputs = addIO.value(inputs, ugen.channels.size)}
368 {ugenClass.isOutputUGen} {outputs = addIO.value(outputs, ugen.numAudioChannels)}
370 canFreeSynth = canFreeSynth or: { ugen.canFreeSynth };
376 var string, comma=false;
377 var names = IdentitySet.new,
378 suffix = this.hash.asHexString(8);
379 // if a control name is duplicated, the msgFunc will be invalid
380 // that "shouldn't" happen but it might; better to check for it
381 // and throw a proper error
382 controls.do({ |controlName|
384 if(controlName.name.asString.first.isAlpha) {
385 name = controlName.name.asSymbol;
386 if(names.includes(name)) {
387 "Could not build msgFunc for this SynthDesc: duplicate control name %"
395 // reusing variable to know if I should continue or not
397 "\nYour synthdef has been saved in the library and loaded on the server, if running.
398 Use of this synth in Patterns will not detect argument names automatically because of the duplicate name(s).".postln;
403 names = 0; // now, count the args actually added to the func
405 string = String.streamContents {|stream|
407 if (controlNames.size > 0) {
410 controls.do {|controlName, i|
412 name = controlName.name.asString;
414 if (name == "gate") {
416 if(msgFuncKeepGate) {
417 if (comma) { stream << ", " } { comma = true };
422 if (name[1] == $_) { name2 = name.drop(2) } { name2 = name };
423 if (comma) { stream << ", " } { comma = true };
429 if (controlNames.size > 0) {
432 stream << "\tvar\tx" << suffix << " = Array.new(" << (names*2) << ");\n";
434 controls.do {|controlName, i|
436 name = controlName.name.asString;
438 if (msgFuncKeepGate or: { name != "gate" }) {
439 if (name[1] == $_) { name2 = name.drop(2) } { name2 = name };
440 stream << "\t" << name2 << " !? { x" << suffix
441 << ".add('" << name << "').add(" << name2 << ") };\n";
446 stream << "\tx" << suffix << "\n}"
449 // do not compile the string if no argnames were added
450 if(names > 0) { msgFunc = string.compile.value };
453 msgFuncKeepGate_ { |bool = false|
454 if(bool != msgFuncKeepGate) {
455 msgFuncKeepGate = bool;
460 writeMetadata { arg path;
461 if(metadata.isNil) { AbstractMDPlugin.clearMetadata(path); ^this };
462 this.class.mdPlugin.writeMetadata(metadata, def, path);
465 // parse the def name out of the bytes array sent with /d_recv
466 *defNameFromBytes { arg int8Array;
467 var stream, n, numDefs, size;
468 stream = CollStream(int8Array);
472 numDefs = stream.getInt16;
473 size = stream.getInt8;
474 n = String.newClear(size);
476 stream.getChar.asAscii
481 var ugens = def.children;
482 var outs = ugens.select(_.writesToBus);
483 ^outs.collect { |outUgen|
484 (rate: outUgen.rate, numChannels: outUgen.numAudioChannels)
492 classvar <>all, <>global;
493 var <>name, <>synthDescs, <>servers;
495 *new { arg name, servers;
496 if (name.isNil) { "SynthDescLib must have a name".error; ^nil }
497 ^super.new.name_(name).init(servers);
500 all.put(name.asSymbol, this);
501 synthDescs = IdentityDictionary.new;
502 servers = IdentitySet.with(*inServers ? { Server.default })
505 Class.initClassTree(Server);
506 all = IdentityDictionary.new;
507 global = this.new(\global);
509 ServerBoot.add { |server|
510 // tryToLoadReconstructedDefs = false:
511 // since this is done automatically, w/o user action,
512 // it should not try to do things that will cause warnings
513 // (or errors, if one of the servers is not local)
514 this.send(server, false)
518 *getLib { arg libname;
520 Error("library % not found".format(libname)).throw
528 *send { |server, tryToLoadReconstructedDefs = true|
529 global.send(server, tryToLoadReconstructedDefs);
536 at { arg i; ^synthDescs.at(i) }
538 *at { arg i; ^global.at(i) }
541 synthDescs.put(synthdesc.name.asSymbol, synthdesc);
545 ^synthDescs.removeAt(name.asSymbol);
549 servers = servers.add(server); // IdentitySet = one server only once.
552 removeServer { |server|
553 servers.remove(server);
557 var keyString = key.asString, dotIndex = keyString.indexOf($.), desc;
558 if(dotIndex.isNil) { ^synthDescs.at(key.asSymbol) };
559 if((desc = synthDescs[keyString[..dotIndex-1].asSymbol]).notNil
560 and: { desc.hasVariants })
562 { ^synthDescs.at(key.asSymbol) }
564 *match { |key| ^global.match(key) }
566 send { |aServer, tryToLoadReconstructedDefs = true|
568 (aServer ? servers).do { |server|
569 server = server.value;
570 synthDescs.do { |desc|
571 if(desc.def.metadata.trueAt(\shouldNotSend).not) {
572 desc.send(server.value)
574 if(tryToLoadReconstructedDefs) {
575 desc.def.loadReconstructed(server);
584 path = SynthDef.synthDefDir ++ "*.scsyndef";
586 synthDescs = SynthDesc.read(path, true, synthDescs);
592 // Basic metadata plugins
594 // to disable metadata read/write
596 *clearMetadata { |path|
597 ^thisProcess.platform.clearMetadata(path)
600 *writeMetadata { |metadata, synthdef, path|
602 this.clearMetadata(path);
603 path = this.applyExtension(path);
604 this.writeMetadataFile(metadata, synthdef, path);
606 *writeMetadataFile {}
608 // clearMetadata should ensure that only one MD file ever exists
609 // therefore we can check the subclasses in turn
610 // and return the first MD found
611 // every subclass should have a unique extension
612 *readMetadata { |path|
613 var pathTmp, classList, i;
614 path = path.splitext[0] ++ ".";
615 classList = this.allSubclasses;
616 // ensure that SynthDescLib.mdPlugin is preferred for reading,
617 // with other plugins as a fallback
618 // it will also be possible to use Events or Protos as plugins this way
619 if((i = classList.indexOf(SynthDesc.mdPlugin)).notNil and: { i > 0 }) {
620 classList = classList.copy.swap(0, i);
622 classList = [SynthDesc.mdPlugin] ++ classList;
624 classList.do({ |class|
625 if(class.notNil and: { File.exists(pathTmp = path ++ class.mdExtension) }) {
626 ^class.readMetadataFile(pathTmp)
631 *readMetadataFile { ^nil }
633 *applyExtension { |path|
634 ^path.splitext[0] ++ "." ++ this.mdExtension
636 *mdExtension { ^"" } // nothing is written anyway
639 // simple archiving of the dictionary
640 TextArchiveMDPlugin : AbstractMDPlugin {
641 *writeMetadataFile { |metadata, synthdef, path|
642 metadata.writeArchive(path)
645 *readMetadataFile { |path|
646 ^Object.readArchive(path)
648 *mdExtension { ^"txarcmeta" }