From 74bb3e3290d8f4ea82b2caf79f4b96bb7991faad Mon Sep 17 00:00:00 2001 From: David Runge Date: Sun, 29 May 2016 13:38:07 +0200 Subject: First commit of bowelyzer code. Includes Bowelyzer.sc, Bowelyzer{Analyzer,Config,OSCHub}.sc, bowelyzer.scd (for help) and darmstadt.json for a standard configuration in JSON. --- Bowelyzer.sc | 30 +++++++ BowelyzerAnalyzer.sc | 155 ++++++++++++++++++++++++++++++++++ BowelyzerConfig.sc | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++ BowelyzerOSCHub.sc | 47 +++++++++++ bowelyzer.scd | 12 +++ darmstadt.json | 54 ++++++++++++ 6 files changed, 527 insertions(+) create mode 100644 Bowelyzer.sc create mode 100644 BowelyzerAnalyzer.sc create mode 100644 BowelyzerConfig.sc create mode 100644 BowelyzerOSCHub.sc create mode 100644 bowelyzer.scd create mode 100644 darmstadt.json diff --git a/Bowelyzer.sc b/Bowelyzer.sc new file mode 100644 index 0000000..3e2be34 --- /dev/null +++ b/Bowelyzer.sc @@ -0,0 +1,30 @@ +Bowelyzer{ + + var gui, <>server, <>analyzer, <>config, <>hub; + + *new{ + arg config; + ^super.new.init(config); + } + + init{ + //initialize with configuration, if available (else use default) + arg config; + this.config = BowelyzerConfig.new(config); + server = Server.new(\bowelyzer, BowelyzerOSCHub.getNetAddr(this.config.config.at("synthServerAddress"), this.config.config.at("synthServerPort"))); + Server.default = server; + server.waitForBoot({ + analyzer = BowelyzerAnalyzer.new(this.config.config); + hub = BowelyzerOSCHub.new(this.config.config); + }, + 5, + {"scsynth failed to start!".postln}); + //meter = ServerMeter.new(server, this.config.config.at(\channels)); + //server.meter(this.config.config.at(\channels)); + //server.makeGUI; + //server.scope; + } + + + +} \ No newline at end of file diff --git a/BowelyzerAnalyzer.sc b/BowelyzerAnalyzer.sc new file mode 100644 index 0000000..f6dda05 --- /dev/null +++ b/BowelyzerAnalyzer.sc @@ -0,0 +1,155 @@ +BowelyzerAnalyzer{ + + var <>config; + var SynthDef(name, { + arg sendReplyFreq, + amplitudeAttackTime, + amplitudeReleaseTime, + hfHainsworth, + hfFoote, + hfThreshold, + hfWaitTime, + pitchInitFreq, + pitchMinFreq, + pitchMaxFreq, + pitchExecFreq, + pitchMaxBinsPerOctave, + pitchMedian, + pitchAmpThreshold, + pitchPeakThreshold, + pitchDownSample + ; + var amplitude, input, detect, pitch, hasPitch; + //TODO: Add volume for SoundIn + input = SoundIn.ar(bus: i+config.at("channelOffset")[i]); + amplitude = Amplitude.kr( + in: input, + attackTime: amplitudeAttackTime, + releaseTime: amplitudeReleaseTime + ); + detect = A2K.kr( + PV_HainsworthFoote.ar( + FFT(LocalBuf(2048), input), + proph: hfHainsworth, + propf: hfFoote, + threshold: hfThreshold, + waittime: hfWaitTime + ) + ); + # pitch, hasPitch = Pitch.kr( + in: input, + initFreq: pitchInitFreq, + minFreq: pitchMinFreq, + maxFreq: pitchMaxFreq, + execFreq: pitchExecFreq, + maxBinsPerOctave: pitchMaxBinsPerOctave, + median: pitchMedian, + ampThreshold: pitchAmpThreshold, + peakThreshold: pitchPeakThreshold, + downSample: pitchDownSample + ); + SendReply.kr( + Impulse.kr(sendReplyFreq), + '/'++config.at("names")[i].asSymbol, + [amplitude, pitch, hasPitch, detect] + ); + }).play( + target: analyzerGroup, + args: //arguments to the Synth. Order matters! + [ + controls.at("sendReplyFreq"), + controls.at("amplitudeAttackTime"), + controls.at("amplitudeReleaseTime"), + controls.at("hfHainsworth"), + controls.at("hfFoote"), + controls.at("hfThreshold"), + controls.at("hfWaitTime"), + controls.at("pitchInitFreq"), + controls.at("pitchMinFreq"), + controls.at("pitchMaxFreq"), + controls.at("pitchExecFreq"), + controls.at("pitchMaxBinsPerOctave"), + controls.at("pitchMedian"), + controls.at("pitchAmpThreshold"), + controls.at("pitchPeakThreshold"), + controls.at("pitchDownSample") + ] + ) + ); + }); + } + + setAnalysisSynthControl{ + arg name, control; + controls.at(name).put(control[0], control[1]); + synths.at(name).set(control[0], control[1]); + } +//TODO: get all this from BowelyzerConfig + getAnalysisSynthControl{ + arg name, control; + synths.at(name).get( + control, + { + arg value; + controls.at(name).put(control, value); + } + ); + //TODO: wait for the control to be set + ^controls.at(name.asSymbol).at(control.asSymbol); + } + + startAnalysisSynth{ + arg name; + synths.at(name).run(true); + } + + stopAnalysisSynth{ + arg name; + synths.at(name).run(false); + } + + startAllAnalysisSynths{ + analyzerGroup.run(true); + } + + stopAllAnalysisSynths{ + analyzerGroup.run(false); + } +} diff --git a/BowelyzerConfig.sc b/BowelyzerConfig.sc new file mode 100644 index 0000000..9686c17 --- /dev/null +++ b/BowelyzerConfig.sc @@ -0,0 +1,229 @@ +BowelyzerConfig{ + + var 0.01, + "amplitudejReleaseTime" -> 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(*[ + "channelOffset" -> [0, 0], + "names" -> ["left", "right"], + "synthServerAddress" -> "127.0.0.1", + "synthServerPort"->57110, + "hubAddress" -> "127.0.0.1", + "hubPort" -> 57120, + "forwardAddress" -> "127.0.0.1", + "forwardPort" -> 9999, + "controls" -> Dictionary.with(*[ + "left" -> defaultControls, + "right" -> defaultControls + ]) + ]); + ^defaultConfig; + } + +// 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| + if(config.includesKey(key),{ + config.put(key,value); + }); + }); + this.validateConfig; + }{ + ("Failed parsing the file: "++configFile).error; + ("Make sure its JSON syntax is valid!").error; + }; + }, { + error("File not readable:"++configFile.asString); + }); + this.validateConfig; + } + + //check common mistakes in configuration and try fixing them + validateConfig{ + var broken = false; + //check for correct types + config.keysValuesDo({|key, value| + if(key.isKindOf(String).not,{ + error("Key is no string: "++key); + broken = true; + }); + // check if the strings are associated with the correct keys + if(hasToBeString.includes(key.asSymbol) && value.isKindOf(String).not, { + config.put(key, value.asString); + }); + // check if the integers are associated with the correct keys + if(hasToBeInteger.includes(key.asSymbol) && value.isKindOf(Integer).not, { + config.put(key, value.asInteger); + }); + // check if the floats are associated with the correct keys + if(hasToBeFloat.includes(key.asSymbol) && value.isKindOf(Float).not, { + config.put(key, value.asFloat); + }); + // check if the dictionaries are associated with the correct keys + if(hasToBeDictionary.includes(key.asSymbol) && value.isKindOf(Dictionary).not, { + error("Value ("++value++") of key ("++key++") should be of type Dictionary."); + broken = true; + }); + //check if arrays are associated with the correct keys + if(hasToBeArray.includes(key.asSymbol),{ + if(value.isArray.not,{ + error("Value ("++value++") of key ("++key++") should be Array."); + broken = true; + },{ + //setting correct types in Arrays + if((key == "channelOffset") && (value.size >= 1) && value[0].isKindOf(Integer).not,{ + for(0, value.size-1, {|i| + config.at(key)[i] = value[i].asInteger; + }); + }); + if((key == "names") && (value.size >= 1) && value[0].isKindOf(String).not,{ + for(0, value.size-1, {|i| + config.at(key)[i] = value[i].asString; + }); + }); + }); + }); + // check if controls dictionaries are setup correctly + if(key == "controls",{ + value.keysValuesDo({|name, controls| + if(hasToBeDictionary.includes(name.asSymbol) && controls.isKindOf(Dictionary).not, { + error("Value ("++controls++") of key ("++name++") should be of type Dictionary."); + broken = true; + },{ + config.at("controls").at(name).keysValuesDo({|controlKey, controlValue| + // check if the integers are associated with the correct keys + if(hasToBeInteger.includes(controlKey.asSymbol) && controlValue.isKindOf(Integer).not, { + config.at(key).at(name).put(controlKey, controlValue.asInteger); + }); + // check if the floats are associated with the correct keys + if(hasToBeFloat.includes(controlKey.asSymbol) && controlValue.isKindOf(Float).not, { + config.at(key).at(name).put(controlKey, controlValue.asFloat); + }); + }); + }); + }); + }); + }); + // if not completely broken, fix stuff + if(broken,{ + error("There were errors. Reverting to default config."); + config = this.createDefaultConfig; + },{ + postln("Fixing stuff"); + // zero-pad channel offsets + if(config.at("names").size > config.at("channelOffset").size,{ + var difference = config.at("names").size - config.at("channelOffset").size; + config.at("channelOffset").asArray = config.at("channelOffset")++Array.fill(difference, {arg i; i*0}); + }); + //TODO: add defaultControls for channels that have none + //TODO: fill non-existing keys with default values (in case config file is incomplete) + }); + } + + //print the current config + showConfig{ + postln("Configuration is:"); + config.keysValuesDo{|key, value| + if(key == "controls",{ + postln(key++" ("++key.class++") ->"); + value.keysValuesDo{|key, value| + postln( key++" ("++key.class++"):"); + value.keysValuesDo{|key, value| + postln(" "++key++" ("++key.class++") -> "++value++" ("++value.class++")"); + } + } + },{ + postln(key++" ("++key.class++") -> "++value++" ("++value.class++")"); + }); + }; + } + +} diff --git a/BowelyzerOSCHub.sc b/BowelyzerOSCHub.sc new file mode 100644 index 0000000..028de55 --- /dev/null +++ b/BowelyzerOSCHub.sc @@ -0,0 +1,47 @@ +BowelyzerOSCHub{ + var <>config,