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 stream.getInt32; // version
64 numDefs = stream.getInt16;
67 desc = SynthDesc.new.readSynthDef(stream, keepDefs);
68 dict.put(desc.name.asSymbol, desc);
69 // AbstractMDPlugin dynamically determines the md archive type
70 // from the file extension
72 desc.metadata = AbstractMDPlugin.readMetadata(path);
74 this.populateMetadataFunc.value(desc);
75 if(desc.def.notNil and: { stream.isKindOf(CollStream).not }) {
76 desc.def.metadata ?? { desc.def.metadata = () };
77 desc.def.metadata.put(\shouldNotSend, true)
78 .put(\loadPath, path);
83 readSynthDef { arg stream, keepDef=false;
84 var numControls, numConstants, numControlNames, numUGens, numVariants;
90 controlDict = IdentityDictionary.new;
92 name = stream.getPascalString;
94 def = SynthDef.prNew(name);
95 UGen.buildSynthDef = def;
97 numConstants = stream.getInt16;
98 constants = FloatArray.newClear(numConstants);
99 stream.read(constants);
101 numControls = stream.getInt16;
102 def.controls = FloatArray.newClear(numControls);
103 stream.read(def.controls);
105 controls = Array.fill(numControls)
107 ControlName('?', i, '?', def.controls[i]);
110 numControlNames = stream.getInt16;
113 var controlName, controlIndex;
114 controlName = stream.getPascalString.asSymbol;
115 controlIndex = stream.getInt16;
116 controls[controlIndex].name = controlName;
117 controlNames = controlNames.add(controlName);
118 controlDict[controlName] = controls[controlIndex];
121 numUGens = stream.getInt16;
123 this.readUGenSpec(stream);
126 controls.inject(nil) {|z,y|
127 if(y.name=='?') { z.defaultValue = z.defaultValue.asArray.add(y.defaultValue); z } { y }
130 def.controlNames = controls.select {|x| x.name.notNil };
131 hasArrayArgs = controls.any { |cn| cn.name == '?' };
133 numVariants = stream.getInt16;
134 hasVariants = numVariants > 0;
135 // maybe later, read in variant names and values
136 // this is harder than it might seem at first
138 def.constants = Dictionary.new;
139 constants.do {|k,i| def.constants.put(k,i) };
141 // throw away unneeded stuff
148 UGen.buildSynthDef = nil;
153 readUGenSpec { arg stream;
154 var ugenClass, rateIndex, rate, numInputs, numOutputs, specialIndex;
155 var inputSpecs, outputSpecs;
157 var ugenInputs, ugen;
160 ugenClass = stream.getPascalString.asSymbol;
161 if(ugenClass.asClass.isNil,{
162 Error("No UGen class found for" + ugenClass + "which was specified in synth def file: " + this.name ++ ".scsyndef").throw;
164 ugenClass = ugenClass.asClass;
166 rateIndex = stream.getInt8;
167 numInputs = stream.getInt16;
168 numOutputs = stream.getInt16;
169 specialIndex = stream.getInt16;
171 inputSpecs = Int16Array.newClear(numInputs * 2);
172 outputSpecs = Int8Array.newClear(numOutputs);
174 stream.read(inputSpecs);
175 stream.read(outputSpecs);
178 forBy (0,inputSpecs.size-1,2) {|i|
179 var ugenIndex, outputIndex, input, ugen;
180 ugenIndex = inputSpecs[i];
181 outputIndex = inputSpecs[i+1];
182 input = if (ugenIndex < 0)
184 constants[outputIndex]
186 ugen = def.children[ugenIndex];
187 if (ugen.isKindOf(MultiOutUGen)) {
188 ugen.channels[outputIndex]
193 ugenInputs = ugenInputs.add(input);
196 rate = #[\scalar,\control,\audio][rateIndex];
197 ugen = ugenClass.newFromDesc(rate, numOutputs, ugenInputs, specialIndex).source;
198 ugen.addToSynth(ugen);
200 addIO = {|list, nchan|
201 var b = ugen.inputs[0];
202 if (b.class == OutputProxy and: {b.source.isKindOf(Control)}) {
203 control = controls.detect {|item| item.index == (b.outputIndex+b.source.specialIndex) };
204 if (control.notNil) { b = control.name };
206 list.add( IODesc(rate, nchan, b, ugenClass))
209 if (ugenClass.isControlUGen) {
210 // Control.newFromDesc does not set the specialIndex, since it doesn't call Control-init.
211 // Therefore we fill it in here:
212 ugen.specialIndex = specialIndex;
214 controls[i+specialIndex].rate = rate;
218 {ugenClass.isInputUGen} {inputs = addIO.value(inputs, ugen.channels.size)}
219 {ugenClass.isOutputUGen} {outputs = addIO.value(outputs, ugen.numAudioChannels)}
221 canFreeSynth = canFreeSynth or: { ugen.canFreeSynth };
227 var string, comma=false;
228 var names = IdentitySet.new,
229 suffix = this.hash.asHexString(8);
230 // if a control name is duplicated, the msgFunc will be invalid
231 // that "shouldn't" happen but it might; better to check for it
232 // and throw a proper error
233 controls.do({ |controlName|
235 if(controlName.name.asString.first.isAlpha) {
236 name = controlName.name.asSymbol;
237 if(names.includes(name)) {
238 "Could not build msgFunc for this SynthDesc: duplicate control name %"
246 // reusing variable to know if I should continue or not
248 "\nYour synthdef has been saved in the library and loaded on the server, if running.
249 Use of this synth in Patterns will not detect argument names automatically because of the duplicate name(s).".postln;
254 names = 0; // now, count the args actually added to the func
256 string = String.streamContents {|stream|
258 if (controlNames.size > 0) {
261 controls.do {|controlName, i|
263 name = controlName.name.asString;
265 if (name == "gate") {
267 if(msgFuncKeepGate) {
268 if (comma) { stream << ", " } { comma = true };
273 if (name[1] == $_) { name2 = name.drop(2) } { name2 = name };
274 if (comma) { stream << ", " } { comma = true };
280 if (controlNames.size > 0) {
283 stream << "\tvar\tx" << suffix << " = Array.new(" << (names*2) << ");\n";
285 controls.do {|controlName, i|
287 name = controlName.name.asString;
289 if (msgFuncKeepGate or: { name != "gate" }) {
290 if (name[1] == $_) { name2 = name.drop(2) } { name2 = name };
291 stream << "\t" << name2 << " !? { x" << suffix
292 << ".add('" << name << "').add(" << name2 << ") };\n";
297 stream << "\tx" << suffix << "\n}"
300 // do not compile the string if no argnames were added
301 if(names > 0) { msgFunc = string.compile.value };
303 msgFuncKeepGate_ { |bool = false|
304 if(bool != msgFuncKeepGate) {
305 msgFuncKeepGate = bool;
310 writeMetadata { arg path;
311 if(metadata.isNil) { AbstractMDPlugin.clearMetadata(path); ^this };
312 this.class.mdPlugin.writeMetadata(metadata, def, path);
315 // parse the def name out of the bytes array sent with /d_recv
316 *defNameFromBytes { arg int8Array;
317 var s,n,numDefs,size;
318 s = CollStream(int8Array);
322 numDefs = s.getInt16;
324 n = String.newClear(size);
331 var ugens = def.children;
332 var outs = ugens.select(_.writesToBus);
333 ^outs.collect { |outUgen|
334 (rate: outUgen.rate, numChannels: outUgen.numAudioChannels)
342 classvar <>all, <>global;
343 var <>name, <>synthDescs, <>servers;
345 *new { arg name, servers;
346 if (name.isNil) { "SynthDescLib must have a name".error; ^nil }
347 ^super.new.name_(name).init(servers);
350 all.put(name.asSymbol, this);
351 synthDescs = IdentityDictionary.new;
352 servers = IdentitySet.with(*inServers ? { Server.default })
355 Class.initClassTree(Server);
356 all = IdentityDictionary.new;
357 global = this.new(\global);
359 ServerBoot.add {|server|
364 *getLib { arg libname;
366 Error("library % not found".format(libname)).throw
382 at { arg i; ^synthDescs.at(i) }
384 *at { arg i; ^global.at(i) }
387 synthDescs.put(synthdesc.name.asSymbol, synthdesc);
391 ^synthDescs.removeAt(name.asSymbol);
395 servers = servers.add(server); // IdentitySet = one server only once.
398 removeServer { |server|
399 servers.remove(server);
403 var keyString = key.asString, dotIndex = keyString.indexOf($.), desc;
404 if(dotIndex.isNil) { ^synthDescs.at(key.asSymbol) };
405 if((desc = synthDescs[keyString[..dotIndex-1].asSymbol]).notNil
406 and: { desc.hasVariants })
408 { ^synthDescs.at(key.asSymbol) }
410 *match { |key| ^global.match(key) }
415 targetServers = servers
417 targetServers = #[aServer]
422 synthDescs.do {|desc| desc.send(server.value) };
428 path = SynthDef.synthDefDir ++ "*.scsyndef";
430 synthDescs = SynthDesc.read(path, true, synthDescs);
431 // postf("SynthDescLib '%' read of '%' done.\n", name, path);
437 // Basic metadata plugins
439 // to disable metadata read/write
441 *clearMetadata { |path|
442 ^thisProcess.platform.clearMetadata(path)
445 *writeMetadata { |metadata, synthdef, path|
447 this.clearMetadata(path);
448 path = this.applyExtension(path);
449 this.writeMetadataFile(metadata, synthdef, path);
451 *writeMetadataFile {}
453 // clearMetadata should ensure that only one MD file ever exists
454 // therefore we can check the subclasses in turn
455 // and return the first MD found
456 // every subclass should have a unique extension
457 *readMetadata { |path|
458 var pathTmp, classList, i;
459 path = path.splitext[0] ++ ".";
460 classList = this.allSubclasses;
461 // ensure that SynthDescLib.mdPlugin is preferred for reading,
462 // with other plugins as a fallback
463 // it will also be possible to use Events or Protos as plugins this way
464 if((i = classList.indexOf(SynthDesc.mdPlugin)).notNil and: { i > 0 }) {
465 classList = classList.copy.swap(0, i);
467 classList = [SynthDesc.mdPlugin] ++ classList;
469 classList.do({ |class|
470 if(class.notNil and: { File.exists(pathTmp = path ++ class.mdExtension) }) {
471 ^class.readMetadataFile(pathTmp)
476 *readMetadataFile { ^nil }
478 *applyExtension { |path|
479 ^path.splitext[0] ++ "." ++ this.mdExtension
481 *mdExtension { ^"" } // nothing is written anyway
484 // simple archiving of the dictionary
485 TextArchiveMDPlugin : AbstractMDPlugin {
486 *writeMetadataFile { |metadata, synthdef, path|
487 metadata.writeArchive(path)
490 *readMetadataFile { |path|
491 ^Object.readArchive(path)
493 *mdExtension { ^"txarcmeta" }