diff options
-rw-r--r-- | Bowelyzer.sc | 92 | ||||
-rw-r--r-- | BowelyzerGUI.sc | 353 |
2 files changed, 444 insertions, 1 deletions
diff --git a/Bowelyzer.sc b/Bowelyzer.sc index 3d01b1a..3071588 100644 --- a/Bowelyzer.sc +++ b/Bowelyzer.sc @@ -22,10 +22,100 @@ Bowelyzer{ server.waitForBoot({ hub = BowelyzerOSCHub.new(config.config); analyzer = BowelyzerAnalyzer.new(config.config); - //gui = BowelyzerGUI.new(config.config); + gui = BowelyzerGUI.new(config.config); + this.addGUIListeners; }, 5, {"scsynth failed to start!".postln}); } + addGUIListeners{ + OSCdef.newMatching( + key: \controls, + func: {|msg, time, addr, recvPort| + postln("Received: "++msg); + // if the control exists, change it + if(msg[1].notNil && msg[2].notNil && msg[3].notNil,{ + if(config.config.at(\controls).includesKey(msg[1]),{ + if(config.config.at(\controls).at(msg[1]).includesKey(msg[2]),{ + config.config.at(\controls).at(msg[1]).put(msg[2], config.getControlValue(msg[2], msg[3])); + }); + }); + }); + }, + path: "/controls", + srcID: NetAddr.new("127.0.0.1", NetAddr.langPort) + ); + OSCdef.newMatching( + key: \inputs, + func: {|msg, time, addr, recvPort| + postln("Received: "++msg); + if(msg[1].notNil && msg[2].notNil && msg[3].notNil,{ + if(config.config.at(\inputs).includesKey(msg[1]) && config.config.at(\controls).includesKey(msg[1]),{ + switch(msg[2], + \name,{ + //if the name exists and the new name is not empty, change it + if(msg[3]!= "",{ + //move the controls + config.config.at(\controls).put(msg[3].asSymbol, config.config.at(\controls).at(msg[1])); + config.config.at(\controls).removeAt(msg[1].asSymbol); + // rename the input + config.config.at(\inputs).put(msg[3].asSymbol, config.config.at(\inputs).at(msg[1])); + config.config.at(\inputs).removeAt(msg[1]); + // rename the GUI element in channelView + Routine{ + gui.channels.keysValuesDo({|name, channelView| + postln(name++"->"++channelView.name); + if(channelView.name.asSymbol == msg[1].asSymbol, { + channelView.name = msg[3].asSymbol; + }); + }); + //TODO: rename the LevelListener + }.play(AppClock); + // free the synth on the server and start a new one according to the config + analyzer.freeAnalysisSynth(msg[1].asSymbol); + analyzer.addSynthWithName(msg[3]); + Routine{ + 1.wait; + analyzer.startSynthWithNameAndControls(msg[3].asSymbol, config.config.at(\controls).at(msg[3]), config.config.at(\inputs).at(msg[3])); + }.play; + hub.freeSynthListener(msg[1]); + hub.addSynthListener(msg[3]); + hub.startSynthListener(msg[3]); + }); + }, + \input,{ + // if the input name exists and the new is an Integer and greater/equal 0 + if(((msg[3].isInteger) && (msg[3].asInteger >= 0)),{ + // change the input in configuration + config.config.at(\inputs).put(msg[1], msg[3].asInteger); + analyzer.setSynthControl(msg[1].asSymbol, \inputChannel, msg[3].asInteger); + }); + } + ); + }); + }); + }, + path: "/inputs", + srcID: NetAddr.new("127.0.0.1", NetAddr.langPort) + ); + OSCdef.newMatching( + key: \toggle, + func: {|msg, time, addr, recvPort| + postln("Received: "++msg); + if(msg[1].notNil && msg[2].notNil && msg[3].notNil,{ + if(config.config.at(\inputs).includesKey(msg[1]),{ + switch(msg[3], + 0,{analyzer.startAnalysisSynth(msg[1])}, + 1,{analyzer.stopAnalysisSynth(msg[1])} + ); + }); + }); + }, + path: "/toggle", + srcID: NetAddr.new("127.0.0.1", NetAddr.langPort) + ); + } + //TODO: add SimpleController + //TODO: delegate changes to BowelyzerConfig.config and update analyzer and GUI with it } diff --git a/BowelyzerGUI.sc b/BowelyzerGUI.sc new file mode 100644 index 0000000..f50ac63 --- /dev/null +++ b/BowelyzerGUI.sc @@ -0,0 +1,353 @@ +BowelyzerGUI{ + + var mainView, + channelContainerView, + <channels, + <indicators, + //TODO: move to BowelyzerConfig + fadeOutTime = 1.0, + fadeOutSteps = 20, + minWidth=1280, + minHeight=720; + + *new{ + arg config; + ^super.new.init(config) + } + + init{ + arg config; + postln("Launching GUI."); + channels = Set.new(config.at(\inputs).size); + indicators = Dictionary.new(config.at(\inputs).size); + this.setupMainView(config); + this.setupChannelViews(config); + //TODO: add Button for adding new channel + //TODO: add Button to save/load setup + } + + //setup the main view, in which all other views reside + setupMainView{ + arg config; + mainView = PageLayout.new("Bowelyzer"); + mainView.asView.background = Color.fromHexString("#DBDBDB"); + channelContainerView = View(mainView.asView, Rect(0, 0, mainView.bounds.width/config.at(\inputs).size, mainView.bounds.height)); + channelContainerView.asView.background = Color.fromHexString("#FEFEFE"); + channelContainerView.layout = HLayout(); + channelContainerView.layout.spacing = 10; + channelContainerView.layout.margins = [4,0,4,0]; + //TODO: add vertical scroll + } + + //setup channel views + setupChannelViews{ + arg config; + var channelView, headView, controlMeterContainerView, meterView, controlsView, controlsFromConfig, levelIndicator; + config.at(\inputs).keysValuesDo({|name, input| + channelView = View(mainView.asView, Rect(0, 0, mainView.bounds.width/config.at(\inputs).size, mainView.bounds.height)); + channelView.asView.background = Color.fromHexString("#FEEFEF"); + channelView.name = name; + channelView.layout = VLayout(); + channelView.layout.spacing = 0; + channelView.layout.margins = [0,10,0,0]; + // adding name textfield of input + headView = View(channelView.asView, Rect(0, 0, channelView.bounds.width/config.at(\inputs).size, 40)); + headView.asView.background = Color.fromHexString("#DDEFDD"); + headView.name = \inputs; + headView.layout = HLayout(); + headView.layout.spacing = 0; + headView.layout.margins = [4,0,4,0]; + headView.maxHeight_(30); + this.setupEZText(headView, "name", name.asString); + this.setupEZNumber(headView, "input", config.at(\inputs).at(name)); + + controlMeterContainerView = View(channelView.asView, Rect(0, 0, channelView.bounds.width/config.at(\inputs).size, channelView.bounds.height)); + controlMeterContainerView.asView.background = Color.fromHexString("#DDDDEF"); + controlMeterContainerView.name = \meterAndControls; + controlMeterContainerView.layout = HLayout(); + channelView.layout.spacing = 0; + channelView.layout.margins = [4,0,4,0]; + meterView = View(controlMeterContainerView.asView, Rect(0,0, channelView.bounds.width*0.1, channelView.bounds.height)); + meterView.asView.background = Color.fromHexString("#EEEFEE"); + meterView.name = \meterView; + meterView.layout = VLayout(); + meterView.maxWidth = 60; + meterView.layout.spacing = 20; + meterView.layout.margins = [0,10,0,0]; + + //setup a toggle button for each channel + this.setupPauseButton(meterView); + + // setup a small View as indicator for incoming/outgoing OSC messages for each input + this.setupOSCIndicator(meterView); + this.addOSCIndicatorFadeOutTask(name); + indicators.at(name).play(AppClock); + + + // setup LevelIndicator for each input + this.setupLevelIndicator(meterView); + this.setupLevelListener(name, NetAddr.new(config.at(\synthServerAddress), config.at(\synthServerPort))); + + controlsView = View(controlMeterContainerView.asView, Rect(0,0, channelView.bounds.width*0.9, channelView.bounds.height)); + controlsView.asView.background = Color.fromHexString("#EEEFEE"); + controlsView.name = \controls; + controlsView.layout = VLayout(); + controlsView.layout.spacing = 2; + controlsView.layout.margins = [0,0,0,0]; + + config.at(\controls).at(name).order.do({|control, i| + var unit; + var value = config.at(\controls).at(name).at(control), controlIs = BowelyzerConfig.controlContainedIn(control); + switch( + controlIs.asSymbol, + \knob, {this.setupEZKnob(controlsView, control, value, \controls, name)}, + \slider, {this.setupEZSlider(controlsView, control, value)}, + \ranger, {this.setupEZRanger(controlsView, control, value, \controls, name)} + ); + }); + + channelContainerView.layout.add(channelView); + channels.add(channelView); + }); + } + + // setup a OSC indicator view (one on each channel) + setupOSCIndicator{ + arg parent; + ^View(parent, Rect(0,0,40,40)) + .name_(\OSCIndicator) + .background_(Color.fromHexString("#EEEFEE")) + //.background_(Color.fromHexString("#99EF99")) + .maxSize_(40@20) + .visible_(true) + ; + } + + // setup tasks for changing the OSC indicator color (on receiving a message) + addOSCIndicatorFadeOutTask{ + arg name; + indicators.put( + name.asSymbol, + Task({ + channels.do({|channel| + if(channel.name.asSymbol == name,{ + channel.children.do({|channelChild| + if(channelChild.name.asSymbol == \meterAndControls,{ + channelChild.children.do({|meterAndControls| + if(meterAndControls.name.asSymbol == \meterView, { + meterAndControls.children.do({|meterView| + if(meterView.isKindOf(View) && meterView.name.asSymbol == \OSCIndicator, { + meterView.background_(Color.fromHexString("#99FF99")); + fadeOutSteps.do({|item,i| + meterView.background_(meterView.background.blend(Color.fromHexString("#EEEFEE"), (fadeOutTime/fadeOutSteps))); + (fadeOutTime/fadeOutSteps).wait; + }); + meterView.background_(Color.fromHexString("#EEEFEE")); + }); + }); + }); + }); + }); + }); + }); + }); + }) + ); + } + + freeOSCIndicatorFadeOutTask{ + + } + + // setup a listener for a level indicator by name and source (of synth server) + setupLevelListener{ + arg name, source; + postln("Setting up LevelListener for: "++name); + OSCdef.newMatching( + key: \levels++name, + func: {|msg, time, addr, recvPort| + var msgName = msg[0].asString.replace("/levels/", "").asSymbol; + { + channels.do({|channel| + if(channel.name.asSymbol == msgName,{ + channel.children.do({|channelChild| + if(channelChild.name.asSymbol == \meterAndControls,{ + channelChild.children.do({|meterAndControls| + if(meterAndControls.name.asSymbol == \meterView, { + meterAndControls.children.do({|meterView| + if(meterView.isKindOf(LevelIndicator), { + //postln("Setting up LevelIndicator for "++msgName); + meterView.value = msg[3].ampdb.linlin(-40, 0, 0, 1); + meterView.peakLevel = msg[4].ampdb.linlin(-40, 0, 0, 1); + }); + }); + }); + }); + }); + }); + }); + }); + }.defer; + }, + path: "/levels/"++name, + srcID: source + ); + } + + startLevelListener{ + arg name; + OSCdef(\levels++name).enable; + } + + stopLevelListener{ + arg name; + OSCdef(\levels++name).disable; + } + + freeLevelListener{ + arg name; + OSCdef(\levels++name).free; + } + + setupLevelIndicator{ + arg parent; + ^LevelIndicator( + parent + ).maxSize_(50@600) + .style_(\led) + .drawsPeak_(true); + } + + setupPauseButton{ + arg parent; + ^Button(parent, Rect(0,0,40,20)) + .states_([ + ["on", Color.black, Color.fromHexString("#99FF99")], + ["off", Color.black, Color.fromHexString("#FF9999")] + ]) + .action_({ + arg controlUnit; + var address = NetAddr.new("127.0.0.1", NetAddr.langPort), + type = "/toggle", + name = controlUnit.parent.parent.parent.name.asSymbol, + controlName = \active, + controlValue = controlUnit.value; + address.sendMsg(type, name, controlName, controlValue); + }) + .maxSize_(40@20); + } + + + setupEZText{ + arg parent, control, value; + ^EZText( + parent: parent, + bounds: 140@20, + label: control, + action: { + arg controlUnit; + var address = NetAddr.new("127.0.0.1", NetAddr.langPort), + type = "/"++controlUnit.view.parent.name.asString, + name = controlUnit.view.parent.parent.name.asSymbol, + controlName = controlUnit.labelView.string.asSymbol, + controlValue = controlUnit.value; + address.sendMsg(type, name, controlName, controlValue); + }, + labelWidth: 40, + textWidth: 100, + initVal:value.asString, + layout: \horz, + margin: nil + ) + .view.maxHeight_(20); + } + + setupEZNumber{ + arg parent, control, value; + ^EZNumber( + parent: parent, + bounds: 100@20, + label: control, + controlSpec: control.asSymbol, + action: { + arg controlUnit; + var address = NetAddr.new("127.0.0.1", NetAddr.langPort), + type = "/"++controlUnit.view.parent.name.asString, + name = controlUnit.view.parent.parent.name.asSymbol, + controlName = controlUnit.labelView.string.asSymbol, + controlValue = controlUnit.value; + address.sendMsg(type, name, controlName, controlValue); + }, + initVal:value, + unitWidth:0, + layout: \horz, + margin: nil + ) + .view.maxSize_(100@20); + } + + setupEZSlider{ + arg parent, control, value; + ^EZSlider( + parent: parent, + bounds: 230@40, + label: control, + controlSpec: control.asSymbol, + action: { + arg controlUnit; + var address = NetAddr.new("127.0.0.1", NetAddr.langPort), + type = "/"++controlUnit.view.parent.name.asString, + name = controlUnit.view.parent.parent.parent.name.asSymbol, + controlName = controlUnit.labelView.string.asSymbol, + controlValue = controlUnit.value; + address.sendMsg(type, name, controlName, controlValue); + }, + numberWidth: 70, + layout: \line2, + margin: nil + ); + } + + setupEZRanger{ + arg parent, control, value; + ^EZRanger( + parent: parent, + bounds: 300@16, + label: control, + controlSpec: control, + action: { + arg controlUnit; + var address = NetAddr.new("127.0.0.1", NetAddr.langPort), + type = "/"++controlUnit.view.parent.name.asString, + name = controlUnit.view.parent.parent.parent.name.asSymbol, + controlName = controlUnit.labelView.string.asSymbol, + controlValue = controlUnit.value; + address.sendMsg(type, name, controlName, controlValue); + }, + labelWidth: 120, + unitWidth:30 + ); + } + + setupEZKnob{ + arg parent, control, value, type, name; + ^EZKnob( + parent: parent, + bounds: 300@16, + label: control, + controlSpec: \freq, + action: { + arg controlUnit; + var address = NetAddr.new("127.0.0.1", NetAddr.langPort), + type = "/"++controlUnit.view.parent.name.asString, + name = controlUnit.view.parent.parent.parent.name.asSymbol, + controlName = controlUnit.labelView.string.asSymbol, + controlValue = controlUnit.value; + address.sendMsg(type, name, controlName, controlValue); + }, + labelWidth: 120, + unitWidth:30 + ); + } +} + |