// Mixer Definition Examples // dewdrop_world, http://www.dewdrop-world.net // code is released under the LGPL, http://creativecommons.org/licenses/LGPL/2.1/ ( // the mixer definition MixerChannelDef(\mix1x4, 1, 4, SynthDef(\mix1x4, { |busin, busout, xpos, ypos, level| var sig = In.ar(busin, 1); sig = Pan4.ar(sig, xpos, ypos, level); Out.ar(busout, sig); ReplaceOut.ar(busin, sig); }), ( xpos: \bipolar, ypos: \bipolar, level: (value: 0.75, spec: \amp))); // gui definition for that mixer d = MixerGUIDef(Point(50, 330), [MixerMuteWidget, MixerRecordWidget, MixerPresendWidget, Mixer2DPanWidget, MixerLevelSlider, MixerLevelNumber, MixerPostsendWidget, MixerNameWidget, MixerOutbusWidget], [ Rect(0, 0, 20, 20), Rect(30, 0, 20, 20), Rect(0, 25, 50, 30), Rect(0, 65, 50, 50), Rect(10, 125, 30, 100), Rect(0, 230, 50, 15), Rect(0, 250, 50, 30), Rect(0, 285, 50, 20), Rect(0, 310, 50, 20) ]); // specify that the 4-channel mixer should use this def by default MixerChannelDef(\mix1x4).guidef = d; ) // now create the mixer and the gui m = MixerChannel(\test, s, 1, 4); MixingBoard(\test, nil, m); // play some sound a = m.play({ SinOsc.ar(Lag.kr(LFNoise0.kr(8).range(200, 800), 0.07), 0) }); // use the mouse to move the panner around in the box // automate and watch m.automate(\xpos, { LFNoise1.kr(0.2) }); m.automate(\ypos, { LFNoise1.kr(0.3) }); m.watch(\xpos); m.watch(\ypos); // alternate guidef for a horizontal layout e = MixerGUIDef(Point(460, 50), [MixerMuteWidget, MixerRecordWidget, MixerNameWidget, MixerPresendWidget, Mixer2DPanWidget, MixerLevelSlider, MixerLevelNumber, MixerPostsendWidget, MixerOutbusWidget], [Rect(0, 15, 15, 15), Rect(20, 15, 15, 15), Rect(40, 15, 50, 15), Rect(95, 5, 50, 20), Rect(150, 0, 50, 50), Rect(205, 15, 80, 15), Rect(290, 15, 40, 15), Rect(335, 5, 50, 15), Rect(390, 15, 50, 15) ]); // use the horizontal layout m.mcgui.guidef = e; // go back to the vertical layout m.mcgui.guidef = d; // when done playing m.free; // Mixer Widget classes, expressed as Protoes Library.put(\mixergui, \base, Proto({ ~initialize = { |layout, bounds, mixer, gui| ~mixer = mixer; ~gui = gui; // really needed? ~makeView.(layout, bounds); ~view.action_(e { |view| ~checkDoAction.(view) }); currentEnvironment }; // define ~makeView, ~doAction, ~updateView ~checkDoAction = { |view| ~mixer.notNil.if({ ~doAction.(view) }); }; ~refresh = { |bounds| ~view.bounds = bounds; ~mixer.notNil.if({ ~update.(); ~restoreView.(); }, { ~view.isActive.if({ ~clearView.(); }); }); }; ~update = { |value| ~view.isActive.if({ ~mixer.notNil.if({ ~updateView.(value); }); }); }; ~free = { ~view.remove; }; // also set updateKeys ~font = Font("Helvetica", 9); }, nil, #[\font])); Library.put(\mixergui, \mute, Library.at(\mixergui, \base).clone({ ~makeView = { |layout, bounds| ~view = GUI.button.new(layout, bounds) .states_([["M", Color.black, Color.green], ["X", Color.black, Color.red]]) }; ~doAction = { |view| ~mixer.mute(view.value > 0, false); }; ~updateView = { ~view.value_(~mixer.muted.binaryValue); }; ~clearView = { ~view.value_(0); }; ~updateKeys = [\mute]; })); Library.put(\mixergui, \record, Library.at(\mixergui, \base).clone({ ~makeView = { |layout, bounds| ~view = GUI.button.new(layout, bounds) .states_([["o", Color.red, Color.white], ["||", Color.black, Color.red]]) }; ~doAction = { |view| (view.value > 0).if({ ~mixer.unpauseRecord; }, { ~mixer.pauseRecord; }); }; ~updateView = { ~view.value_(~mixer.isRecording.binaryValue); }; ~clearView = { ~view.value_(0); }; ~updateKeys = [\record]; })); Library.put(\mixergui, \presend, Proto({ ~sendType = \pre; ~initialize = { |layout, bounds, mixer, gui, def, sendIndex| ~mixer = mixer; ~gui = gui; // really needed? ~index = sendIndex; ~spec = \amp.asSpec; ~updateKeys = [(~sendType ++ "send" ++ ~index).asSymbol]; ~makeView.(layout, bounds); currentEnvironment }; ~sliderBounds = Rect(0, 0, 50, 5); ~menuBounds = Rect(0, 10, 50, 20); ~makeView = { |layout, bounds| ~slider = GUI.slider.new(layout, ~getSliderBounds.(bounds)) .action_(e { |view| ~doSliderAction.(view) }); ~menu = GUI.popUpMenu.new(layout, ~getSliderBounds.(bounds)) .action_(e { |view| ~doMenuAction.(view) }) .items_(~gui.menuItems) .value_(~gui.menuItems.size-1) .font_(~font); ~oldValue = ~menu.value; }; ~getSliderBounds = { |guiBounds| ~sliderBounds.moveBy(guiBounds.left, guiBounds.top) }; ~getMenuBounds = { |guiBounds| ~menuBounds.moveBy(guiBounds.left, guiBounds.top) }; ~doSliderAction = { |view| ~mixer !? { ~mixer.preSends[~index].tryPerform('level_', ~spec.map(view.value), false) } }; ~doMenuAction = { |view| ~mixer !? { (~menu.value != ~oldValue).if({ // if old value is "none," create a presend (~oldValue == (~menu.items.size-1)).if({ // default level is 0 "Making presend".postln; MixerPreSend.new(~mixer, ~menu.value, 0); ~slider.setProperty(\value, 0); }, { // if new value is "none," free the presend (~menu.value == (~menu.items.size-1)).if({ ("Freeing presend " ++ ~index).postln; (~mixer.preSends[~index]).free; }, { ("Repatching send from bus " ++ (~mixer.preSends[~index]).outbus.index ++ " to " ++ ~menu.value).postln; // otherwise, just change the destination (~mixer.preSends[~index]).outbus = ~menu.value; }); }); }); // save for next call ~oldValue = ~menu.value; } }; ~update = { ~slider.isActive.if({ (~mixer.notNil and: { ~mixer.preSends[~index].notNil }).if({ ~slider.value_(~spec.unmap(~mixer.preSends[~index].level)); }, { ~clearView.(); }); }); }; ~updateMenu = { ~menu.items_(~gui.menuItems); (~mixer.notNil and: { ~mixer.preSends[~index].notNil }).if({ ~menu.value_(~oldValue = ~mixer.preSends[~index].outbus.index) }, { ~menu.value_(~oldValue = ~gui.menuItems.size - 1); }); }; ~clearView = { ~updateMenu.(); ~slider.value_(0); }; ~refresh = { |bounds| ~slider.bounds = ~getSliderBounds.(bounds); ~menu.bounds = ~getMenuBounds.(bounds); ~updateMenu.(); ~update.(); }; ~free = { ~menu.remove; ~slider.remove; }; ~font = Font("Helvetica", 9); })); Library.put(\mixergui, \postsend, Library.at(\mixergui, \presend).clone({ ~sendType = \post; ~doSliderAction = { |view| ~mixer !? { ~mixer.postSends[~index].tryPerform('level_', ~spec.map(view.value), false) } }; ~doMenuAction = { |view| ~mixer !? { (~menu.value != ~oldValue).if({ // if old value is "none," create a postsend (~oldValue == (~menu.items.size-1)).if({ // default level is 0 "Making postsend".postln; MixerPostSend.new(~mixer, ~menu.value, 0); ~slider.setProperty(\value, 0); }, { // if new value is "none," free the postsend (~menu.value == (~menu.items.size-1)).if({ ("Freeing postsend " ++ ~index).postln; (~mixer.postSends[~index]).free; }, { ("Repatching send from bus " ++ (~mixer.postSends[~index]).outbus.index ++ " to " ++ ~menu.value).postln; // otherwise, just change the destination (~mixer.postSends[~index]).outbus = ~menu.value; }); }); }); // save for next call ~oldValue = ~menu.value; } }; ~update = { ~slider.isActive.if({ (~mixer.notNil and: { ~mixer.postSends[~index].notNil }).if({ ~slider.value_(~spec.unmap(~mixer.postSends[~index].level)); }, { ~clearView.(); }); }); }; ~updateMenu = { ~menu.items_(~gui.menuItems); (~mixer.notNil and: { ~mixer.postSends[~index].notNil }).if({ ~menu.value_(~oldValue = ~mixer.postSends[~index].outbus.index) }, { ~menu.value_(~oldValue = ~gui.menuItems.size - 1); }); }; })); Library.put(\mixergui, \pan, Library.at(\mixergui, \base).clone({ ~makeView = { |layout, bounds| ~view = GUI.slider.new(layout, bounds); ~restoreView.(); ~spec = \bipolar.asSpec; }; ~doAction = { |view| ~mixer.setControl(\pan, ~spec.map(view.value), false); }; ~updateView = { ~view.value_(~spec.unmap(~mixer.getControl(\pan))); }; ~clearView = { ~view.background_(Color.clear); }; ~restoreView = { ~view.background_(HiliteGradient(~gui.color2, ~gui.color1, \h, 50, 0.5)); }; ~updateKeys = [\pan]; })); Library.put(\mixergui, \levelSlider, Library.at(\mixergui, \base).clone({ ~makeView = { |layout, bounds| ~view = GUI.slider.new(layout, bounds); ~restoreView.(); ~spec = \amp.asSpec; }; ~doAction = { |view| ~mixer.setControl(\level, ~spec.map(view.value)); }; ~updateView = { ~view.value_(~spec.unmap(~mixer.getControl(\level))); }; ~clearView = { ~view.background_(Color.clear); }; ~restoreView = { ~view.background_(Gradient(~gui.color2, ~gui.color1, \v, 50)); }; ~updateKeys = [\level]; })); Library.put(\mixergui, \levelNum, Library.at(\mixergui, \base).clone({ ~makeView = { |layout, bounds| ~view = GUI.numberBox.new(layout, bounds) .font_(~font) .align_(\center); ~spec = \amp.asSpec; }; ~doAction = { |view| ~mixer.setControl(\level, view.value.dbamp); }; ~updateView = { ~view.value_(~mixer.getControl(\level).ampdb.round(0.001)); }; ~clearView = { ~view.value_(0); }; ~updateKeys = [\level]; })); Library.put(\mixergui, \name, Library.at(\mixergui, \base).clone({ ~makeView = { |layout, bounds| ~view = GUI.dragSink.new(layout, bounds) .font_(~font) .align_(\center) }; ~checkDoAction = { |view| { view.object.draggedIntoMixerGUI(~gui); }.try({ |error| error.isKindOf(DoesNotUnderstandError).not.if({ error.throw // rethrow other errors }, { ~gui.refresh // else, reset mixer name }); }); }; ~updateView = { ~view.string_(~mixer.name); }; ~clearView = { ~view.string_("inactive"); }; ~updateKeys = [\name]; })); Library.put(\mixergui, \outbus, Library.at(\mixergui, \base).clone({ ~makeView = { |layout, bounds| ~view = GUI.popUpMenu.new(layout, bounds) .items_(~gui.menuItems) .value_(~gui.menuItems.size-1) .font_(~font); }; ~doAction = { (~mixer.notNil and: { ~view.value < (~gui.menuItems.size - 1) }).if({ ~mixer.outbus_(~view.value); }, { ~updateView.(); }); }; ~updateView = { ~view.value_(~mixer.outbus.index); }; ~clearView = { ~view.value_(~gui.menuItems.size-1); }; ~updateKeys = [\outbus]; ~font = Font("Helvetica", 9); })); Library.put(\mixergui, \pan4, Library.at(\mixergui, \base).clone({ ~makeView = { |layout, bounds| ~view = GUI.slider2D.new(layout, bounds); ~spec = \bipolar.asSpec; }; ~doAction = { |view| ~mixer.setControl(\xpos, ~spec.map(view.x), updateGUI:false); ~mixer.setControl(\ypos, ~spec.map(view.y), updateGUI:false); }; ~updateView = { ~view.x = ~spec.unmap(~mixer.getControl(\xpos)); ~view.y = ~spec.unmap(~mixer.getControl(\ypos)); }; ~clearView = { ~view.x_(0).y_(0); }; ~updateKeys = [\xpos, \ypos]; }));