1 // document for non-current proxyspaces is not correct yet.
5 classvar <>menuHeight=18;
10 var <parent, <zone, <nameView, <typeChanView, <monitor;
11 var <nSliders, <edits, <sinks, <scrolly, skipjack;
12 var buttonFuncs, pauseBut, sendBut;
14 var <currentSettings=#[], <prevSettings=#[], <editKeys=#[], <>ignoreKeys=#[];
15 var <>replaceKeys; // a dict for slider names to be replaced
16 var <tooManyKeys = false, <keysRotation = 0;
22 Spec.add(\ampx4, [0, 4, \amp]);
23 Spec.add(\fadePx, [0, 100, \amp, 0, 0.02]);
27 *new { arg proxy, nSliders=16, parent,
28 extras=[\CLR, \reset, \scope, \doc, \end, \fade],
29 monitor=true, sinks=true, morph=false;
32 .init(parent, nSliders, extras, monitor, sinks, morph)
42 if (px.isKindOf(NodeProxy)) {
44 name = px.key ? 'anon proxy';
46 if (monitor.notNil) { monitor.proxy_(proxy) };
50 nameView.object_(proxy);
52 typeChanView.string_(type);
56 name { ^nameView.string.asSymbol }
58 nameView.string_(key.asString);
59 // if I have my own window, put proxy name there too
60 if (ownWindow) { parent.name = "edit" + key };
63 // backwards compatibility
65 pxKey_ { |key| ^this.name_(key) }
69 { nameView.object_(nil).string_("-"); }.defer;
73 init { arg inParent, numSliders, extras, monitor, sinks, morph;
76 ownWindow = inParent.isNil;
77 skin = GUI.skins[\jitSmall];
78 font = Font.new(*skin.fontSpecs);
79 nSliders = numSliders;
81 bounds = Rect(0, 0, 340, nSliders + 2 * skin.buttonHeight + 16);
82 // if building inside an existing window, w is nil.
83 // w only is notNil if it belongs to the editor.
84 parent = inParent ?? {
85 // "NodeProxyEditor: making internal win.".postln;
86 parent = Window(this.class.name, bounds.resizeBy(4, 4));
87 parent.view.background_(skin.background);
91 zone = CompositeView(parent, bounds);
93 zone.decorator = zone.decorator ?? { FlowLayout(zone.bounds).gap_(0@0) };
94 zone.background = skin.foreground;
96 replaceKeys = replaceKeys ?? { () };
100 this.makeTopLine(extras);
101 if (monitor, { this.makePxMon; zone.decorator.nextLine.shift(0, 4); });
102 if (morph, { this.makeMorph; zone.decorator.nextLine.shift(0, 4); });
104 this.makeSinksSliders(sinks);
106 // if my own window, sliders should resize
107 // - not working yet!
110 edits.do { |ez| ez.view.resize_(2) }
117 monitor = ProxyMonitorGui(proxy, zone,
118 zone.bounds.width - 4 @ (skin.buttonHeight),
119 showName: false, makeWatcher: false);
123 inform("NodeProxyEditor: preset/morph not finished yet.");
124 // zone.decorator.shift(0, 2);
125 // StaticText(zone, Rect(0,0, 330, 1)).background_(Color.gray(0.2));
126 // zone.decorator.shift(0, 2);
127 // PxPreset(proxy, zone);
130 makeTopLine { |extras|
132 nameView = DragBoth(zone, 90@menuHeight)
133 .font_(font).align_(\center)
134 .background_(Color.white);
135 nameView.setBoth_(false)
136 .mouseDownAction_ { |drag, x, y, mod|
138 try { ProxySpace.findSpace(proxy).document(proxy.key) };
143 .receiveDragHandler = { this.proxy_(View.implClass.currentDrag) };
145 typeChanView = StaticText(zone, 30@menuHeight).string_("-").align_(0).font_(font);
148 buttonFuncs[butkey].value;
151 zone.decorator.nextLine.shift(0, 4);
154 makeSinksSliders { |bigSinks|
157 sinkWidth = if (bigSinks, 40, 0); // invisibly small
158 #edits, sinks = Array.fill(nSliders, { arg i;
160 zone.decorator.nextLine;
162 sink = DragBoth(zone, Rect(0,0, sinkWidth, skin.buttonHeight))
163 .string_("-").align_(\center).visible_(false)
166 sink.action_({ arg drag;
167 var key = editKeys[i];
168 var dragged = drag.object;
169 if (dragged.isKindOf(String)) { dragged = dragged.interpret };
170 if (dragged.notNil) {
171 if(dragged.isKindOf(NodeProxy)) {
172 drag.string = "->" + dragged.key;
173 proxy.map(key, dragged);
176 if (dragged.isKindOf(SimpleNumber)) {
177 proxy.set(key, dragged);
183 ez = EZSlider(zone, (330 - sinkWidth)@(skin.buttonHeight), "", \unipolar.asSpec, labelWidth: 60, numberWidth: 42, unitWidth: 20);
185 ez.labelView.font_(font).align_(\center);
187 ez.sliderView.keyDownAction = { |view, char,modifiers,unicode,keycode|
188 if (unicode == 127) { // delete key
189 try { proxy.unset(ez.labelView.string.asSymbol) }
191 view.defaultKeyDownAction(char,modifiers,unicode,keycode);
197 lay = zone.decorator;
198 lay.left_(lay.bounds.right - 20).top_(lay.bounds.top + 48);
200 scrolly = EZScroller(zone,
201 Rect(0, 0, 14, nSliders * skin.buttonHeight),
203 { |sc| keysRotation = sc.value.asInteger; }
205 scrolly.slider.resize_(3);
206 [\scrolly, scrolly.slider.bounds];
209 highlightParams { |parOffset, num|
210 var onCol = Color(1, 0.5, 0.5);
211 var offCol = Color.clear;
212 { edits.do { |edi, i|
213 var col = if (i >= parOffset and: (i < (parOffset + num).max(0)), onCol, offCol);
214 edi.labelView.background_(col.green_([0.5, 0.7].wrapAt(i - parOffset div: 2)));
220 CLR: { Button(zone, 30@20).font_(font)
221 .states_([[\CLR, skin.fontColor, Color.clear]])
222 .action_({ arg btn, mod;
223 if (mod.isAlt) { proxy.clear } {
224 "use alt-click to clear proxy.".postln;
229 reset: { Button(zone, 30@20).font_(font)
230 .states_([[\reset, skin.fontColor, Color.clear]])
231 .action_({ proxy !? { proxy.nodeMap = ProxyNodeMap.new; this.fullUpdate; } })
234 pausR: { pauseBut = Button(zone, 30@20).font_(font)
236 ["paus", skin.fontColor, skin.onColor],
237 ["rsum", skin.fontColor, skin.offColor]
239 .action_({ arg btn; proxy !? {
240 [ { proxy.resume; }, { proxy.pause; } ].at(btn.value).value;
244 sendR: { sendBut = Button(zone, 30@20).font_(font)
246 ["send", skin.fontColor, skin.offColor],
247 ["send", skin.fontColor, skin.onColor]
249 .action_({ arg btn, mod;
250 if(proxy.notNil and: (btn.value == 0)) {
251 // alt-click osx, swingosc
252 if (mod.isAlt) { proxy.rebuild } { proxy.send }
254 btn.value_(1 - btn.value)
258 scope: { Button(zone, 36@20).font_(font)
259 .states_([[\scope, skin.fontColor, Color.clear]])
260 .action_({ proxy !? { proxy.scope } })
264 doc: { Button(zone, 30@20).font_(font)
265 .states_([[\doc, skin.fontColor, Color.clear]])
266 .action_({ |but, mod|
268 try { ProxySpace.findSpace(proxy).document(proxy.key) };
275 end: { Button(zone, 24@20).font_(font)
276 .states_([[\end, skin.fontColor, Color.clear]])
277 .action_({ proxy !? { proxy.end } })
280 fade: { var nb = EZNumber(zone, 60@20, \fade, \fadePx,
281 { |num| proxy.fadeTime_(num.value) },
282 try { proxy.fadeTime } ? 0.02,
285 nb.labelView.font_(font).background_(Color.clear);
286 nb.numberView.font_(font).background_(Color.clear);
291 rip: { Button(zone, 15@20).font_(font)
292 .states_([['^', skin.fontColor, Color.clear]])
293 .action_({ this.class.new(proxy, nSliders) })
297 wake: { Button(zone, 30@20).font_(font)
298 .states_([[\wake, skin.fontColor, Color.clear]])
299 .action_({ proxy !? { proxy.wakeUp } })
302 send: { Button(zone, 30@20).font_(font)
303 .states_([[\send, skin.fontColor, Color.clear]])
304 .action_({ proxy !? { proxy.send } })
307 rebuild: { Button(zone, 30@20).font_(font)
308 .states_([[\rbld, skin.fontColor, Color.clear]])
309 .action_({ proxy !? { proxy.rebuild } })
312 // poll: { Button(zone, 30@20).font_(font)
313 // .states_([[\poll, skin.fontColor, Color.clear]])
314 // .action_({ proxy !? { proxy.poll } }) },
316 // // show a little amp view?
317 // amp: { Button(zone, 30@20).font_(font)
318 // .states_([[\amp, skin.fontColor, Color.clear]])
319 // .action_({ "// show a little amp view?".postln }) }
326 { this.checkUpdate },
331 // w.onClose_({ skipjack.stop; });
335 getCurrentKeysValues {
336 if (proxy.isNil, {^[] });
337 currentSettings = proxy.getKeysValues(except: ignoreKeys);
338 editKeys = currentSettings.collect({ |list| list.first.asSymbol });
341 var oversize = (editKeys.size - nSliders).max(0);
342 tooManyKeys = oversize > 0;
343 keysRotation = keysRotation.clip(0, oversize);
346 // "tooManyKeys...".postln;
348 scrolly.visible_(true);
349 scrolly.numItems_(editKeys.size);
350 editKeys = editKeys.drop(keysRotation).keep(nSliders);
351 currentSettings = currentSettings.drop(keysRotation).keep(nSliders);
353 // "plenty of space.".postln;
354 scrolly.numItems_(editKeys.size);
355 scrolly.visible_(false);
357 scrolly.value_( keysRotation);
361 var oldKeys, newType;
364 this.getCurrentKeysValues;
367 // [\oldKeys, oldKeys, \editKeys, editKeys].printAll;
369 newType = if (proxy.notNil) { proxy.typeStr };
370 if (newType != oldType) {
372 typeChanView.string_(newType);
375 if (monitor.notNil) { monitor.updateAll };
377 if (pauseBut.notNil) {
378 pauseBut.value_((proxy.notNil and: { proxy.paused }).binaryValue)
380 if (sendBut.notNil) {
381 sendBut.value_((proxy.notNil and: { proxy.objects.notEmpty }).binaryValue)
384 if ( (editKeys != oldKeys), { this.updateAllEdits }, { this.updateVals });
388 this.getCurrentKeysValues;
396 // "updateVals : ".postln;
397 if (currentSettings == prevSettings) {
398 // "no change.".postln;
402 // "values have changed - updating edits.".postln;
404 editKeys.do { arg key, i;
406 // editKeys and currentSettings are in sync.
407 val = currentSettings[i][1].unbubble;
408 if (val != try { prevSettings[i][1] }) {
409 // disable for arrayed controls
410 sl.enabled_(val.size <= 1);
411 // when in doubt, use this:
412 // val = (currentSettings.detect { |set| set[0] == key } ? [])[1];
413 if(sl.numberView.hasFocus.not) {
414 if (val.isKindOf(SimpleNumber), {
415 sl.value_(val.value);
416 sl.labelView.string_(this.replaceName(key));
417 sinks[i].string_("-");
419 if (val.isKindOf(BusPlug), {
421 sinks[i].object_(val).string_(mapKey);
422 sl.labelView.string = "->" + key;
428 prevSettings = currentSettings;
432 if (replaceKeys.isNil) { ^key };
433 replaced = replaceKeys[key.asSymbol];
434 if (replaced.isNil) { ^key };
435 // "NPXE: replacing % with %.".format(key.asCompileString, replaced.asCompileString).postln;
441 // var keyPressed = false;
443 [sinks, edits].do(_.do(_.visible_(false)));
447 edits.do { arg sl, i;
448 var key, val, mappx, spec, slider, number, sink, mapKey;
449 var keyString, labelKey, isWet, isMix;
455 // editKeys and currentSettings are in sync.
456 val = currentSettings[i][1];
459 keyString = key.asString;
462 isWet = keyString.beginsWith("wet"); // a filtered slot
463 isMix = keyString.beginsWith("mix"); // an additive source
465 if (isWet or: isMix) {
466 if (isWet) { spec = \amp.asSpec };
467 if (isMix) { spec = \amp4.asSpec };
468 sl.sliderView.background_(Color.green(1.0, 0.5)).refresh;
469 sl.labelView.background_(Color.green(1.0, 0.5)).refresh;
471 sl.sliderView.background_(skin.foreground).refresh;
472 sl.labelView.background_(skin.foreground).refresh;
476 sl.labelView.string = this.replaceName(key);
477 sl.sliderView.enabled = spec.notNil;
478 sl.sliderView.visible = spec.notNil;
479 sl.numberView.enabled = true;
480 sl.numberView.visible = true;
481 sl.labelView.visible = true;
483 // sl.sliderView.keyDownAction = { keyPressed = true }; // doesn't work yet.
484 // sl.sliderView.keyUpAction = { proxy.xset(key, sl.value); keyPressed = false };
486 sl.action_({ arg nu; proxy.set(key, nu.value) });
488 if(spec.notNil) { sl.controlSpec = spec } {
489 sl.controlSpec = ControlSpec(-1e8, 1e8);
492 mappx = val.value ? 0;
494 sl.value_(mappx ? 0);
495 sink.object_(nil).string_("-");
497 // assume mappx is a proxy:
498 if (val.isKindOf(BusPlug), {
499 mapKey = currentEnvironment.findKeyForValue(mappx) ? "???";
500 sink.object_(mapKey).string_(mapKey);
501 sl.labelView.string = "->" + key;
505 // "// hide unused edits".postln;
506 sink.object_(nil).string_("-").visible_(false);
507 sl.labelView.background_(skin.foreground).string_("");
508 sl.sliderView.background_(skin.foreground);
512 // this.adjustWindowSize;
515 runUpdate { skipjack.start }
516 stopUpdate { skipjack.stop }
522 var <>proxymixer, bounds;
523 var <recorder, <recType=\mix;
524 var <>recHeaderFormat, <>recSampleFormat, <preparedForRecording=false;
525 var skipjack, <display;
527 *new { arg proxymixer, bounds;
528 ^super.newCopyArgs(proxymixer, bounds).initDefaults.makeWindow
531 recHeaderFormat = recHeaderFormat ?? { this.server.recHeaderFormat };
532 recSampleFormat = recSampleFormat ?? { this.server.recSampleFormat };
536 var numChannels=0, path, proxies, func;
537 proxies = this.selectedKeys.collect { arg key; this.at(key) };
538 if(proxies.isEmpty) { "NodeProxyEditor: no proxies.".postln; ^nil };
541 if(recType == \multichannel) {
542 proxies.do { |el| numChannels = numChannels + el.numChannels };
543 func = { proxies.collect(_.ar).flat }; // no vol for now!
545 proxies.do { |el| numChannels = max(numChannels, el.numChannels) };
547 proxies.do { |el| sum = sum +.s el.ar };
552 path = thisProcess.platform.recordingsDir +/+ "SC_PX_" ++ numChannels ++ "_" ++ Date.localtime.stamp ++ "." ++ recHeaderFormat;
553 recorder = RecNodeProxy.audio(this.server, numChannels);
554 recorder.source = func;
555 recorder.open(path, recHeaderFormat, recSampleFormat);
556 preparedForRecording = true;
560 font { ^GUI.font.new(*GUI.skins[\jitSmall].fontSpecs) }
561 skin { ^GUI.skins[\jitSmall] }
563 server { ^proxymixer.proxyspace.server }
571 preparedForRecording = false;
574 record { arg paused=false;
575 if(recorder.notNil) {recorder.record(paused) };
578 pauseRecorder { recorder !? {recorder.pause} }
580 recorder !? { if(recorder.isRecording) { recorder.unpause } { "not recording".postln } }
583 selectedKeys { ^proxymixer.selectedKeys }
584 at { arg key; ^proxymixer.proxyspace.envir.at(key) }
586 ^this.selectedKeys.collect { |key|
587 var proxy = this.at(key);
588 [ key, proxy.numChannels ]
593 var rw, recbut, pbut, numChannels, recTypeChoice;
594 var font = this.font;
596 rw = Window("recording:" + proxymixer.title, bounds);
597 rw.view.decorator = FlowLayout(rw.view.bounds.insetBy(20, 20));
598 rw.onClose = { this.removeRecorder; this.stopUpdate; };
599 rw.view.background = this.skin.background;
601 recbut = Button(rw, Rect(0, 0, 80, 20)).states_([
602 ["prepare rec", this.skin.fontcolor, Color.clear],
603 ["record >", Color.red, Color.gray(0.1)],
604 ["stop []", this.skin.fontcolor, Color.red]
609 numChannels = this.prepareForRecord;
610 if(numChannels.isNil) { b.value = 0; pbut.value = 0 }
612 2, { this.record(pbut.value == 1) },
613 0, { this.removeRecorder }
615 if(b.value == 1 and: { numChannels.notNil }) {
616 list = this.selectedKeysValues;
617 this.displayString = format("recording % channels: %", numChannels, list.join(" "));
622 pbut = Button(rw, Rect(0, 0, 80, 20)).states_([
623 ["pause", this.skin.fontcolor, Color.clear],
624 [">", Color.red, Color.gray(0.1)]
626 if(b.value == 1) { this.pauseRecorder } { this.unpauseRecorder }
630 recTypeChoice = PopUpMenu(rw, Rect(0, 0, 110, 20))
631 .items_([ \mix, \multichannel ])
633 recType = view.items[view.value];
634 if(recbut.value != 0) { recbut.valueAction = 0 }
637 recTypeChoice.value = 1;
638 Button(rw, Rect(0, 0, 60, 20))
639 .states_([["cancel", this.skin.fontcolor, Color.clear]])
640 .action_({ if(recbut.value != 0) { recbut.valueAction = 0 } })
644 rw.view.decorator.nextLine;
645 display = StaticText(rw, Rect(30, 40, 300, 20)).font_(font);
653 displayString_ { arg str;
654 display.string = str;
658 if(preparedForRecording.not) {
659 this.displayString = format("proxies: %", this.selectedKeysValues.join(" "));
663 title { ^"recorder for" + proxymixer.title }
665 runUpdate { skipjack.start; }
666 stopUpdate { skipjack.stop }
667 makeSkipJack { // stoptest should check window.
668 skipjack = SkipJack({ this.updateZones }, 0.5, name: this.title);