// title: Env editor with knobs to edit individual points // author: grirgz // description: // I needed an Env editor with knobs to edit individual points. Maybe it can be useful to someone. You can zoom horizontally with shift+mouseDrag. Show also a way to write classes with events // code: ( ~windowize = { arg layout; var win; win = Window.new; win.layout = layout; win.front; }; ~env_controller = ( new: { arg self, env, spec; self = self.deepCopy; self.get_env = env; self.spec = spec ?? [3, \freq.asSpec]; spec = self.spec.value[1]; spec = spec ?? self.label.asSpec ?? \widefreq.asSpec; self.level_spec = spec; self.time_spec = ControlSpec(0.01,8,\exp,0,0.1); self.curve_spec = ControlSpec(-9,9,\lin,0,0); self; }, get_xy: { arg self; var x = List.new; var y = List.new; var env = self.get_env; env.levels.do { arg lvl, n; y.add(lvl); if(n == 0) { x.add(0); } { x.add(env.times[n-1]) } }; [x.integrate.asArray,y.asArray] }, time_scale: 1/2, get_norm_xy: { arg self, time_scale; var x = List.new; var y = List.new; var env = self.get_env; var times, levels; var res; time_scale = time_scale ?? self.time_scale; times = self.time_spec.unmap(env.times); levels = self.level_spec.unmap(env.levels); x = times; y = levels; x = x.insert(0, 0); res = [x.integrate.asArray * time_scale,y.asArray]; res }, set_norm_xy: { arg self, val, time_scale; var times, levels; time_scale = time_scale ?? self.time_scale; #times, levels = val; times = times.copy; times.removeAt(0); times = times / time_scale; times = times.differentiate; times = self.time_spec.map(times); levels = self.level_spec.map(levels); self.get_env.times = times; self.get_env.levels = levels; }, edit: { arg self; ~env_editor.new(self).make_window; }, label: \bla ); ~env_to_string = { arg env; var prec = 5; "Env(%, %, %)".format( env.levels.collect({ arg x; x.asFloat.asStringPrec(prec)}), env.times.collect({arg x; x.asFloat.asStringPrec(prec)}), env.curves ); }; ~env_editor = ( new: { arg self, controller; self = self.deepCopy; self.controller = controller; self; }, update_label: { arg self; self.env_label.string_( "%".format( ~env_to_string.(self.controller.get_env), ) ); }, update_env: { arg self; self.env_view.value_(self.controller.get_norm_xy); }, update_knobs: { arg self; var index = self.env_view.index; var levels = self.controller.get_env.levels; var times = self.controller.get_env.times; var level_spec = self.controller.level_spec; var time_spec = self.controller.time_spec; if(levels[index].notNil) { self.knob_level.value = level_spec.unmap(levels[index]); }; if(times[index].notNil) { self.knob_time.value = time_spec.unmap(times[index]); }; }, make_layout: { arg self; var node_name, name, spec; var win, val, slider, label, layout; var prec = 0.001; var knob_layout; var knob_size = 15; var level_spec, time_spec, curve_spec; name = self.controller.label; level_spec = self.controller.level_spec; time_spec = self.controller.time_spec; curve_spec = self.controller.curve_spec; label = StaticText.new; label.string = name.asString; label.minWidth = 160; val = StaticText.new; val.minWidth = 100; val.string = self.controller.get_val; slider = EnvelopeView.new(nil, Rect(0, 0, 230, 80)) .drawLines_(true) .selectionColor_(Color.red) .drawRects_(true) .step_(0) .thumbSize_(10) .keepHorizontalOrder_(true) .value_(self.controller.get_norm_xy); self.env_view = slider; self.env_view.selectIndex(1); self.env_view.action = { arg env; if(env.index == 0 and: { env.x > 0 }) { env.x = 0; }; self.controller.set_norm_xy(env.value); self.update_label; self.update_knobs; }; self.env_view.mouseDownAction = { arg view, x, y, mod; self.mouse_down_point = Point(x,y); self.mouse_down_index = view.index; self.mouse_down_time_scale = self.controller.time_scale; }; self.env_view.mouseUpAction = { arg view, x, y, mod; self.update_knobs; if(mod.isShift) { self.env_view.selectIndex(self.mouse_down_index) } }; self.env_view.mouseMoveAction = { arg view, x, y, mod; var val; if(mod.isShift) { val = x - self.mouse_down_point.x; self.controller.time_scale = self.mouse_down_time_scale + (val/500); self.update_env; } }; self.env_view.keyDownAction = { self.update_knobs; }; self.knob_level = Knob.new .mode_(\vert) .maxWidth_(knob_size); self.knob_time = Knob.new .mode_(\vert) .maxWidth_(knob_size); self.knob_curve = Knob.new .mode_(\vert) .maxWidth_(knob_size); self.knob_level.action = { arg knob; var index = self.env_view.index; var levels = self.controller.get_env.levels; if(levels[index].notNil) { levels[index] = level_spec.map(knob.value); self.controller.get_env.levels = levels; self.env_view.y = knob.value; self.update_label; }; }; self.knob_time.action = { arg knob; var index = self.env_view.index; var times = self.controller.get_env.times; if(times[index].notNil) { times[index] = time_spec.map(knob.value); self.controller.get_env.times = times; self.env_view.value_(self.controller.get_norm_xy); self.env_view.selectIndex(index); self.update_label; } }; self.knob_curve.action = { arg knob; self.controller.get_env.curves = curve_spec.map(knob.value); self.env_view.curves = curve_spec.map(knob.value); self.update_label; }; self.env_label = StaticText.new .font_(Font("Arial",10)) .string_("--"); knob_layout = HLayout.new( self.knob_level, self.knob_time, self.knob_curve, self.env_label ); self.update_label; self.update_knobs; layout = VLayout.new( label, slider, knob_layout ); self.layout = layout; layout; }, make_window: { arg self; ~windowize.(self.make_layout); }, ); ~ctrl = ~env_controller.new(Env([80,1500.1,90.51,300,400],[0.1,0.2,0.1,0.1]), [0, \freq.asSpec]); ) ( SynthDef(\ftest, { arg out=0, amp=0.1, gate=1, pan=0, spread=0.8, freq=200, doneAction=2; var sig, sig1, sig2, sig3; freq = freq + EnvGen.kr(\fenv.kr(Env([80,500.1,90.51,300,400],[0.1,0.2,0.1,0.1])), gate); sig = SinOsc.ar(freq); sig = sig * EnvGen.ar(\adsr.kr(Env.adsr(0.01,0.1,0.8,0.1)),gate,doneAction:doneAction); sig = Splay.ar(sig, spread, amp, pan); Out.ar(out, sig); }).add; ); ( Pdef(\plop, Pbind( \instrument, \ftest, \fenv, Pfunc ({ ~ctrl.get_env }), \freq, 200, \dur, 1, \amp, 0.1 )).play; ) ~ctrl.edit;