BowelyzerConfig{ const true, \amplitudeAttackTime -> 0.01, \amplitudeReleaseTime -> 0.1, \hfHainsworth -> 1.0, \hfFoote -> 0.0, \hfThreshold -> 0.3, \hfWaitTime -> 0.04, \pitchInitFreq -> 440, \pitchMinFreq -> 60, \pitchMaxFreq -> 4000, \pitchExecFreq -> 100, \pitchMaxBinsPerOctave -> 16, \pitchMedian -> 1, \pitchAmpThreshold -> 0.01, \pitchPeakThreshold -> 0.5, \pitchDownSample -> 1, \sendReplyFreq ->20 ]); defaultConfig = Dictionary.with(*[ \inputs -> Dictionary.with(*[ \left -> 0, \right -> 1 ]), \synthServerAddress -> "127.0.0.1", \synthServerPort -> 57110, \hubAddress -> "127.0.0.1", \hubPort -> 57120, \forwardAddress -> "127.0.0.1", \forwardPort -> 57120, \controls -> Dictionary.with(*[ \left -> defaultControls, \right -> defaultControls ]) ]); } // read configuration from file readConfigurationFile{ arg configFile; var configFromFile, reader, path; path = PathName.new(configFile.asString); if(path.isFile,{ postln("Reading configuration file: "++configFile); try{ configFromFile = (configFile.asString).parseYAMLFile; configFromFile.keysValuesDo({|key, value| switch(key.asSymbol, \controls,{ // adding names dictionary config.put(key.asSymbol, Dictionary.new(value.size)); // going through the names and their associated dictionaries value.keysValuesDo({|name, dictionary| if(dictionary.isKindOf(Dictionary),{ config.at(key.asSymbol).put(name.asSymbol, Dictionary.new(dictionary.size)); // going through each control dictionary with controls and their values dictionary.keysValuesDo({|control, controlValue| if(defaultControls.includesKey(control.asSymbol),{ config.at(key.asSymbol).at(name.asSymbol).put(control.asSymbol, this.getControlValue(control.asSymbol, controlValue)); }); }); },{ error("This element should be of type Dictionary: "++dictionary); }); }); }, \inputs,{ config.put(key.asSymbol, Dictionary.new()); value.keysValuesDo({|name, channel| config.at(\inputs).put(name.asSymbol, channel.asInteger); }); }, { if(defaultConfig.includesKey(key.asSymbol),{ config.put(key.asSymbol, this.getSettingValue(key.asSymbol, value)); },{ error("Key is not included in known configuration: "++key); }); } ); }); postln("Reading of configuration complete. Now validating..."); if(this.validateConfig,{ postln("Validation complete."); ^true; },{ postln("Validation failed."); ^false; }); }{ ("Failed parsing the file: "++configFile).error; ("Make sure its JSON syntax is valid!").error; ^false; }; }, { error("File not readable:"++configFile.asString); ^false; }); } // write a valid JSON file from configuration writeConfigurationFile{ arg fileName; var configFile, path, overwriteConfigFilePath = false, writeFinished = false; configFilePath.postln; fileName.postln; if(fileName.isNil, { if(configFilePath.notNil, { path = configFilePath; },{ "No configuration file defined!".error; }); },{ path = fileName; overwriteConfigFilePath = true; }); if(path != "",{ path.postln; try{ configFile = File.use( path.asString, "w", {|configFile| var controlNamesSize = config.at(\controls).size, currentControlName = 0, controlsSize = defaultControls.size, currentControl = 0, inputsSize = config.at(\inputs).size, currentInput = 0; configFile.write("{\n"); // global settings config.pairsDo({|key, value| if(key != \controls && key != \inputs, { if(hasToBeString.includes(key), { configFile.write(" \""++key++"\": \""++value++"\",\n"); }); if(hasToBeInteger.includes(key),{ configFile.write(" \""++key++"\": "++value++",\n"); }); }); }); // controls configFile.write(" \"controls\": {\n"); config.at(\controls).pairsDo({|key, value| currentControlName = currentControlName+1; configFile.write(" \""++key++"\": {\n"); value.pairsDo({|control, controlValue| currentControl = currentControl+1; if(currentControl == controlsSize,{ configFile.write(" \""++control++"\": "++controlValue++"\n"); },{ configFile.write(" \""++control++"\": "++controlValue++",\n"); }); }); // closing brace if(currentControlName == controlNamesSize,{ configFile.write(" }\n"); },{ configFile.write(" },\n"); }); currentControl = 0; }); // closing brace for controls configFile.write(" },\n"); // inputs configFile.write(" \"inputs\": {\n"); config.at(\inputs).pairsDo({|key, value| currentInput = currentInput+1; if(currentInput == inputsSize,{ configFile.write(" \""++key++"\": "++value++"\n"); },{ configFile.write(" \""++key++"\": "++value++",\n"); }); }); // closing brace for inputs configFile.write(" }\n"); // closing brace for notation configFile.write("}\n"); configFile.close; writeFinished = true; } ); }{ ("Failed writing the file: "++path).error; ^false; } }); if(writeFinished && overwriteConfigFilePath, { configFilePath = fileName; }); } // return a control value as correct type and within its defined ControlSpec range getControlValue{ arg control, value; if(defaultControls.includesKey(control.asSymbol),{ if(hasToBeFloat.includes(control.asSymbol),{ ^control.asSymbol.asSpec.constrain(value.asFloat).asFloat; }); if(hasToBeInteger.includes(control.asSymbol),{ ^control.asSymbol.asSpec.constrain(value.asInteger).asInteger; }); if(hasToBeBool.includes(control.asSymbol),{ switch(value, 0,{^false}, 1,{^true}, "true",{^true}, "false",{^false} ); }); },{ ^nil; }); } //get a setting value as correct type getSettingValue{ arg setting, value; if(defaultConfig.includesKey(setting.asSymbol),{ if(hasToBeString.includes(setting.asSymbol),{ ^value.asString; }); if(hasToBeInteger.includes(setting.asSymbol),{ ^value.asInteger; }); },{ ^nil; }); } // validate/extend/fix the config and return true if it's useable validateConfig{ var broken = false; //fail if there are no inputs defined if(config.includesKey(\inputs).not,{ "No inputs defined!".error; ^false; }); // if there are no controls defined at all, add at least a Dictionary for them if(config.includesKey(\controls).not,{ config.put(\controls, Dictionary.new(config.at(\inputs).size)); }); // go through the defaultConfig/defaultControl collection and compare it to the provided config defaultConfig.keysValuesDo({|key, value| if(key != \controls && key != \inputs,{ // if a standard setting is not in the configuration, add it from the default if(config.includesKey(key).not,{ config.put(key, value); }); }); }); // go through the inputs and add controls, if there are none setup for them config.at(\inputs).keysValuesDo({|name, channel| // if there are no controls defined for the input, add them from default if(config.at(\controls).includesKey(name).not,{ config.at(\controls).put(name, defaultControls); }); }); // go through each control dictionary config.at(\controls).keysValuesDo({|name, controlDictionary| if(config.at(\inputs).includesKey(name),{ // go through the defaultControl Dictionary and add missing controls to controls from config defaultControls.keysValuesDo({|defaultControl, defaultControlValue| if(controlDictionary.includesKey(defaultControl).not,{ config.at(\controls).at(name).put(defaultControl, defaultControlValue); }); }); },{ // remove controls for non-existing inputs config.at(\controls).removeAt(name); }); }); ^true; } //print the current config showConfig{ postln("Configuration is: "); config.keysValuesDo{|key, value| switch(key, \controls, { postln(key++" ("++key.class++") -> ("++value.class++")"); value.keysValuesDo({|subKey, subValue| postln(" "++subKey++" ("++subKey.class++") -> ("++subValue.class++")"); subValue.keysValuesDo({|subSubKey, subSubValue| postln(" "++subSubKey++" ("++subSubKey.class++") -> "++subSubValue++" ("++subSubValue.class++")"); }); }); }, \inputs, { postln(key++" ("++key.class++") -> ("++value.class++")"); value.keysValuesDo({|subKey, subValue| postln(" "++subKey++" ("++subKey.class++") -> "++subValue++" ("++subValue.class++")"); }); }, {postln(key++" ("++key.class++") -> "++value++" ("++value.class++")");} ); }; } getSynthControl{ arg name, control; if(config.at(\controls).includesKey(name) && config.at(\controls).at(name).includes(control),{ ^config.at(\controls).at(name).at(control); },{ ^false; }); } // sets a control to a value for a synth setSynthControl{ arg name, control; if(config.at(\controls).includesKey(name) && config.at(\controls).at(name).includes(control[0]),{ config.at(\controls).at(name).put(control[0], control[1]); ^true; },{ ^false; }); } }