old quark gui: openOS is not osx only
[supercollider.git] / SCClassLibrary / QtCollider / QStethoscope2.sc
blobbd89207037b7c64af8299479780a715cf4d975ca
1 QStethoscope2 {
2   // internal functions
3   var playSynthDef, makeGui, setCycle, setYZoom, setIndex, setNumChannels,
4       setRate, setStyle, updateColors;
6   // gui objects
7   var <window, <view, <scopeView, cycleSlider, yZoomSlider,
8       idxNumBox, chNumBox, styleMenu, rateMenu;
10   // static (immutable runtime environment)
11   var <server;
12   var scopeBuffer, maxBufSize;
13   var aBusSpec, cBusSpec, cycleSpec, yZoomSpec;
14   var <>smallSize, <>largeSize;
16   // runtime (mutable at runtime)
17   var <bus; // partly immutable; can't change numChannels at runtime
18   var busSpec; // either aBusSpec or cBusSpec, depending on bus rate
19   var  <cycle, <yZoom;
20   var synth, synthWatcher, defName;
21   var sizeToggle=false;
22   var running = false;
24   *new {
25     arg server, numChannels = 2, index = 0, bufsize = 4096,
26         cycle = 1024, rate = \audio, view;
28     var bus = Bus(rate, index, numChannels, server);
29     if(server.isNil) {server = Server.default};
31     ^super.new.initQStethoscope( server, view, bus, bufsize, cycle );
32   }
34   initQStethoscope { arg server_, parent, bus_, bufsize_, cycle_;
35     var singleBus;
37     server = server_;
39     maxBufSize = max(bufsize_, 128);
41     bus = bus_;
42     singleBus = bus.class === Bus;
44     aBusSpec = ControlSpec(0, server.options.numAudioBusChannels, step:1);
45     cBusSpec = ControlSpec(0, server.options.numControlBusChannels, step:1);
46     if( singleBus ) {
47       busSpec = if(bus.rate===\audio){aBusSpec}{cBusSpec};
48     };
50     cycleSpec = ControlSpec( 64, maxBufSize, \exponential );
51     yZoomSpec = ControlSpec( 0.125, 16, \exponential );
52     cycle = cycleSpec.constrain(cycle_);
53     yZoom = yZoomSpec.unmap(1.0);
55     smallSize = Size(250,250);
56     largeSize = Size(500,500);
58     makeGui = { arg parent;
59       var gizmo;
61       // WINDOW, WRAPPER VIEW
63       if( window.notNil ) {window.close};
65       if( parent.isNil ) {
66         view = window = QWindow(
67           bounds: (smallSize).asRect.center_(QWindow.availableBounds.center)
68         ).name_("Stethoscope");
69       }{
70         view = QView( parent, Rect(0,0,250,250) );
71         window = nil;
72       };
74       // WIDGETS
76       scopeView = QScope2();
77       scopeView.server = server;
78       scopeView.canFocus = true;
80       cycleSlider = QSlider().orientation_(\horizontal).value_(cycleSpec.unmap(cycle));
81       yZoomSlider = QSlider().orientation_(\vertical).value_(yZoom);
83       rateMenu = QPopUpMenu().items_(["AUDIO","CONTROL"]).enabled_(singleBus);
84       idxNumBox = QNumberBox().decimals_(0).step_(1).scroll_step_(1).enabled_(singleBus);
85       chNumBox = QNumberBox().decimals_(0).step_(1).scroll_step_(1)
86         .clipLo_(1).clipHi_(128).enabled_(singleBus);
88       if( singleBus ) {
89         rateMenu.value_(if(bus.rate===\audio){0}{1});
90         idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index);
91         chNumBox.value_(bus.numChannels);
92       };
94       styleMenu = QPopUpMenu().items_(["TRACKS","MIX","X/Y"]);
96       // LAYOUT
98       gizmo = idxNumBox.minSizeHint.width * 2;
99       idxNumBox.minWidth = gizmo;
100       idxNumBox.maxWidth = gizmo;
101       chNumBox.minWidth = gizmo;
102       chNumBox.maxWidth = gizmo;
104       view.layout = QVLayout(
105         QGridLayout()
106           .add(
107             QHLayout(
108               rateMenu,
109               idxNumBox.minWidth_(35),
110               chNumBox.minWidth_(35),
111               nil,
112               styleMenu
113             ).margins_(0).spacing_(1), 0, 0
114           )
115           .add(scopeView,1,0)
116           .add(yZoomSlider,1,1)
117           .add(cycleSlider,2,0)
118           .margins_(0).spacing_(0)
119       ).margins_([5,0,0,0]).spacing_(1);
121       // ACTIONS
123       cycleSlider.action = { |me| setCycle.value(cycleSpec.map(me.value)) };
124       yZoomSlider.action = { |me| setYZoom.value(me.value) };
125       idxNumBox.action = { |me| setIndex.value(me.value) };
126       chNumBox.action = { |me| setNumChannels.value(me.value) };
127       rateMenu.action = { |me| setRate.value(me.value) };
128       styleMenu.action = { |me| setStyle.value(me.value) };
129       view.asView.keyDownAction = { |v, char| this.keyDown(char) };
130       view.onClose = { view = nil; this.quit; };
132       // LAUNCH
134       scopeView.focus;
135       if( window.notNil ) { window.front };
136     };
138     setCycle = { arg val;
139       cycle = val;
140       if( synth.notNil ) { synth.set(\frames, val) }
141     };
143     setYZoom = { arg val;
144       yZoom = val;
145       scopeView.yZoom = yZoomSpec.map(val);
146     };
148     // NOTE: assuming a single Bus
149     setIndex = { arg i;
150       bus = Bus(bus.rate, i, bus.numChannels, bus.server);
151       if(synth.notNil) { synth.set(\in, i) };
152     };
154     // NOTE: assuming a single Bus
155     setNumChannels = { arg n;
156       // we have to restart the whole thing:
157       this.stop;
158       bus = Bus(bus.rate, bus.index, n, bus.server);
159       this.run;
160     };
162     // NOTE: assuming a single Bus
163     setRate = { arg val;
164       val.switch (
165         0, {
166           bus = Bus(\audio, bus.index, bus.numChannels, bus.server);
167           busSpec = aBusSpec;
168           if(synth.notNil) { synth.set(\switch, 0) };
169         },
170         1, {
171           bus = Bus(\control, bus.index, bus.numChannels, bus.server);
172           busSpec = cBusSpec;
173           if(synth.notNil) { synth.set(\switch, 1) };
174         }
175       );
176       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index);
177       this.index = bus.index; // ensure conformance with busSpec;
178       updateColors.value;
179     };
181     setStyle = { arg val;
182       if(this.numChannels < 2 and: { val == 2 }) {
183         "QStethoscope: x/y scoping with one channel only; y will be a constant 0".warn;
184       };
185       scopeView.style = val;
186     };
188     updateColors = {
189       var colors;
190       bus.do { |b|
191         var c = if(b.rate === \audio){Color.new255(255, 218, 000)}{Color.new255(125, 255, 205)};
192         colors = colors ++ Array.fill(b.numChannels, c);
193       };
194       scopeView.waveColors = colors;
195     };
197     playSynthDef = { arg def, args;
198       if( synthWatcher.notNil ) {synthWatcher.stop};
199       synthWatcher = fork {
200         def.send(server);
201         server.sync;
202         synth = Synth.tail(RootNode(server), def.name, args);
203         CmdPeriod.add(this);
204         {if(view.notNil){updateColors.value; scopeView.start}}.defer;
205       };
206     };
208     makeGui.value(parent);
210     ServerBoot.add(this, server);
211     ServerQuit.add(this, server);
212     this.run;
213   }
215   doOnServerBoot {
216       this.run;
217   }
219   doOnServerQuit {
220       this.stop;
221       scopeBuffer.free;
222       scopeBuffer = nil;
223   }
225   cmdPeriod {
226     synth = nil;
227     this.stop;
228     CmdPeriod.remove(this);
229   }
231   run {
232     var n_chan;
234     if(running || server.serverRunning.not) {^this};
236     if(scopeBuffer.isNil){
237       scopeBuffer = ScopeBuffer.alloc(server);
238       scopeView.bufnum = scopeBuffer.index;
239       defName = "stethoscope" ++ scopeBuffer.index.asString;
240     };
242     n_chan = this.numChannels.asInteger;
244     if( bus.class === Bus ) {
245       playSynthDef.value (
246         SynthDef(defName, { arg in, switch, frames;
247           var z;
248           z = Select.ar(switch, [
249             In.ar(in, n_chan),
250             K2A.ar(In.kr(in, n_chan))]
251           );
252           ScopeOut2.ar(z, scopeBuffer.index, maxBufSize, frames );
253         }),
254         [\in, bus.index, \switch, if('audio' === bus.rate){0}{1}, \frames, cycle]
255       )
256     }{
257       playSynthDef.value (
258         SynthDef(defName, { arg frames;
259           var z = Array(n_chan);
260           bus.do { |b| z = z ++ b.ar };
261           ScopeOut2.ar(z, scopeBuffer.index, maxBufSize, frames);
262         }),
263         [\frames, cycle]
264       );
265     };
267     running = true;
268   }
270   stop {
271     if( view.notNil ) { {scopeView.stop}.defer };
273     if( synthWatcher.notNil ) { synthWatcher.stop };
275     if( synth.notNil ) {
276       synth.free;
277       synth = nil;
278     };
280     running = false;
281   }
283   quit {
284     var win;
285     this.stop;
286     ServerBoot.remove(this, server);
287     ServerQuit.remove(this, server);
288     CmdPeriod.remove(this);
289     if(scopeBuffer.notNil) {scopeBuffer.free; scopeBuffer=nil};
290     if(window.notNil) { win = window; window = nil; { win.close }.defer; };
291   }
293   setProperties { arg numChannels, index, bufsize=4096, zoom, rate;
294       var new_bus;
295       var isRunning = running;
297       if (isRunning) {this.stop};
299       // process args
301       if(index.notNil || numChannels.notNil || rate.notNil) {
302         bus = if(bus.class === Bus) {
303             Bus (
304               rate ? bus.rate,
305               index ? bus.index,
306               numChannels ? bus.numChannels,
307               server
308             )
309         }{
310             Bus (
311               rate ? \audio,
312               index ? 0,
313               numChannels ? 2,
314               server
315             )
316         };
317       };
318       if(bufsize.notNil) { maxBufSize = max(bufsize, 128) };
319       if(zoom.notNil) { yZoom = yZoomSpec.constrain(zoom) };
321       // set other vars related to args
323       busSpec = if(bus.rate === \audio) {aBusSpec} {cBusSpec};
324       cycleSpec = ControlSpec( 64, maxBufSize, \exponential );
325       cycle = cycleSpec.constrain(cycle);
327       // update GUI
329       yZoomSlider.value = yZoom;
330       cycleSlider.value = cycleSpec.unmap(cycle);
331       rateMenu.value_(if(bus.rate === \audio){0}{1}).enabled_(true);
332       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index).enabled_(true);
333       chNumBox.value_(bus.numChannels).enabled_(true);
334       scopeView.yZoom = yZoomSpec.map(yZoom);
336       if (isRunning) {this.run};
337   }
339   bufsize { ^maxBufSize }
341   bus_ { arg b;
342     var isSingle = b.class === Bus;
343     var isRunning = running;
345     if (isRunning) {this.stop};
347     bus = b;
349     if( isSingle ) {
350       busSpec = if(bus.rate === \audio) {aBusSpec} {cBusSpec};
351       rateMenu.value = if(b.rate===\audio){0}{1};
352       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index);
353       chNumBox.value = b.numChannels;
354     }{
355       busSpec = nil;
356       rateMenu.value = nil;
357       idxNumBox.string = "-";
358       chNumBox.string = "-";
359     };
360     rateMenu.enabled = isSingle;
361     idxNumBox.enabled = isSingle;
362     chNumBox.enabled = isSingle;
364     if (isRunning) {this.run};
365   }
367   numChannels {
368     var num;
369     if( bus.class === Bus ) {
370       ^bus.numChannels;
371     }{
372       num = 0; bus.do { |b| num = num + b.numChannels };
373       ^num;
374     }
375   }
377   // will always be clipped between 0 and the amount of channels
378   // at the beginning of the current run
379   numChannels_ { arg n;
380     if( (bus.class === Bus).not ) { ^this };
381     setNumChannels.value(n);
382     chNumBox.value = n;
383   }
385   index {
386     if( bus.class === Bus ) { ^bus.index } { nil }
387   }
389   index_ { arg i;
390     if( (bus.class === Bus).not ) { ^this };
391     setIndex.value( busSpec.constrain(i) );
392     idxNumBox.value = i;
393   }
395   rate {
396     if( bus.class === Bus ) { ^bus.rate } { nil }
397   }
399   rate_ { arg argRate=\audio;
400     var val;
401     if( (bus.class === Bus).not ) { ^this };
402     val = if(argRate===\audio){0}{1};
403     setRate.value(val);
404     rateMenu.value = val;
405   }
407   switchRate {
408     if( bus.class === Bus ) {
409       this.rate = if(bus.rate === \control) {\audio} {\control};
410     }
411   }
413   // [0, 1] -> [64, 8192] frames
414   cycle_ { arg val;
415     setCycle.value( cycleSpec.constrain(val) );
416     cycleSlider.value = cycleSpec.unmap(val);
417   }
419   // [0, 1] -> [0.125, 16] y scaling factor
420   yZoom_ { arg val;
421     setYZoom.value(val);
422     yZoomSlider.value = val;
423   }
425   zoom_ { arg val; this.xZoom_(val ? 1) }
427   style_ { arg val;
428     setStyle.value(val);
429     styleMenu.value = val;
430   }
432   size_ { arg value;
433     var sz = value.asSize;
434     if( window.notNil ) { window.setInnerExtent(sz.width,sz.height) };
435   }
437   toggleSize {
438     if(window.notNil) {
439       sizeToggle = sizeToggle.not;
440       if(sizeToggle)
441         { this.size = largeSize }
442         { this.size = smallSize };
443     }
444   }
446   toInputBus {
447     var i = server.options.numOutputBusChannels;
448     var c = server.options.numInputBusChannels;
449     this.bus = Bus(\audio, i, c, server);
450   }
452   toOutputBus {
453     var c = server.options.numOutputBusChannels;
454     this.bus = Bus(\audio, 0, c, server);
455   }
457   keyDown { arg char;
458     case (
459       { char === $i }, { this.toInputBus },
460       { char === $o }, { this.toOutputBus },
461       { char === $  }, { this.run },
462       { char === $s }, { this.style = (scopeView.style + 1).wrap(0,2) },
463       { char === $S }, { this.style = 2 },
464       { char === $j }, { if(this.index.notNil) {this.index = this.index - 1} },
465       { char === $k }, { this.switchRate; },
466       { char === $l }, { if(this.index.notNil) {this.index = this.index + 1} },
467       { char === $- }, { cycleSlider.increment; cycleSlider.doAction },
468       { char === $+ }, { cycleSlider.decrement; cycleSlider.doAction },
469       { char === $* }, { yZoomSlider.increment; yZoomSlider.doAction },
470       { char === $_ }, { yZoomSlider.decrement; yZoomSlider.doAction },
471       { char === $m }, { this.toggleSize },
472       { char === $.}, { this.stop },
473       { ^false }
474     );
475     ^true;
476   }