supernova: fix boost.thread move semantics
[supercollider.git] / SCClassLibrary / QtCollider / QStethoscope2.sc
bloba65108965bbf75efa6dab1cc9bc7cee0b2b073f2
1 QStethoscope2 {
2   classvar ugenScopes;
4   // internal functions
5   var playSynthDef, makeGui, setCycle, setYZoom, setIndex, setNumChannels,
6       setRate, setStyle, updateColors;
8   // gui objects
9   var <window, <view, <scopeView, cycleSlider, yZoomSlider,
10       idxNumBox, chNumBox, styleMenu, rateMenu;
12   // static (immutable runtime environment)
13   var <server;
14   var scopeBuffer, maxBufSize;
15   var aBusSpec, cBusSpec, cycleSpec, yZoomSpec;
16   var <>smallSize, <>largeSize;
18   // runtime (mutable at runtime)
19   var <bus; // partly immutable; can't change numChannels at runtime
20   var busSpec; // either aBusSpec or cBusSpec, depending on bus rate
21   var  <cycle, <yZoom;
22   var synth, synthWatcher, defName;
23   var sizeToggle=false;
24   var running = false;
26   *implementsClass {^'Stethoscope'}
28   *defaultServer { ^if( Server.default.isLocal, Server.default, Server.local ) }
30   *isValidServer { arg aServer; ^aServer.isLocal }
32   *ugenScopes {
33     if (ugenScopes.isNil) { ugenScopes = Set.new };
34     ^ugenScopes
35   }
37   *tileBounds {
38     var screenBounds = QWindow.availableBounds;
39     var w = 250, h = 250;
40     var x = (ugenScopes.size * (w + 10)) + 10;
41     var right = x + w;
42     var y = floor(right / screenBounds.width) * (h + 20) + 20;
43     if(right > screenBounds.width)
44       { x = floor(right % screenBounds.width / (w + 10)) * (w + 10) + 10 };
45     x = x + screenBounds.left;
46     ^Rect(x, y, w, h)
47   }
49   *new {
50     arg server, numChannels = 2, index = 0, bufsize = 4096,
51         zoom = 1.0, rate = \audio, view, bufnum;
52     var bus;
54     if(server.isNil) {server = this.defaultServer};
55     if(server.isLocal.not) {Error("Can not scope on remote server.").throw};
57     bus = Bus(rate, index, numChannels, server);
59     ^super.new.initQStethoscope( server, view, bus, bufsize, 1024 * zoom.asFloat.reciprocal );
60   }
62   initQStethoscope { arg server_, parent, bus_, bufsize_, cycle_;
63     var singleBus;
65     server = server_;
67     maxBufSize = max(bufsize_, 128);
69     bus = bus_;
70     singleBus = bus.class === Bus;
72     aBusSpec = ControlSpec(0, server.options.numAudioBusChannels, step:1);
73     cBusSpec = ControlSpec(0, server.options.numControlBusChannels, step:1);
74     if( singleBus ) {
75       busSpec = if(bus.rate===\audio){aBusSpec}{cBusSpec};
76     };
78     cycleSpec = ControlSpec( 64, maxBufSize, \exponential );
79     yZoomSpec = ControlSpec( 0.125, 16, \exponential );
80     cycle = cycleSpec.constrain(cycle_);
81     yZoom = 1.0;
83     smallSize = Size(250,250);
84     largeSize = Size(500,500);
86     makeGui = { arg parent;
87       var gizmo;
89       // WINDOW, WRAPPER VIEW
91       if( window.notNil ) {window.close};
93       if( parent.isNil ) {
94         view = window = QWindow(
95           bounds: (smallSize).asRect.center_(QWindow.availableBounds.center)
96         ).name_("Stethoscope");
97       }{
98         view = QView( parent, Rect(0,0,250,250) );
99         window = nil;
100       };
102       // WIDGETS
104       scopeView = QScope2();
105       scopeView.server = server;
106       scopeView.canFocus = true;
108       cycleSlider = QSlider().orientation_(\horizontal).value_(cycleSpec.unmap(cycle));
109       yZoomSlider = QSlider().orientation_(\vertical).value_(yZoomSpec.unmap(yZoom));
111       rateMenu = QPopUpMenu().items_(["Audio","Control"]).enabled_(singleBus);
112       idxNumBox = QNumberBox().decimals_(0).step_(1).scroll_step_(1).enabled_(singleBus);
113       chNumBox = QNumberBox().decimals_(0).step_(1).scroll_step_(1)
114         .clipLo_(1).clipHi_(128).enabled_(singleBus);
116       if( singleBus ) {
117         rateMenu.value_(if(bus.rate===\audio){0}{1});
118         idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index);
119         chNumBox.value_(bus.numChannels);
120       };
122       styleMenu = QPopUpMenu().items_(["Tracks","Overlay","X/Y"]);
124       // LAYOUT
126       gizmo = idxNumBox.minSizeHint.width * 2;
127       idxNumBox.minWidth = gizmo;
128       idxNumBox.maxWidth = gizmo;
129       chNumBox.minWidth = gizmo;
130       chNumBox.maxWidth = gizmo;
132       view.layout =
133         QGridLayout()
134           .add(
135             QHLayout(
136               rateMenu,
137               idxNumBox.minWidth_(35),
138               chNumBox.minWidth_(35),
139               nil,
140               styleMenu
141             ).margins_(0).spacing_(2), 0, 0
142           )
143           .add(scopeView,1,0)
144           .add(yZoomSlider.maxWidth_(15), 1,1)
145           .add(cycleSlider.maxHeight_(15), 2,0)
146           .margins_(2).spacing_(2);
148       // ACTIONS
150       cycleSlider.action = { |me| setCycle.value(cycleSpec.map(me.value)) };
151       yZoomSlider.action = { |me| setYZoom.value(yZoomSpec.map(me.value)) };
152       idxNumBox.action = { |me| setIndex.value(me.value) };
153       chNumBox.action = { |me| setNumChannels.value(me.value) };
154       rateMenu.action = { |me| setRate.value(me.value) };
155       styleMenu.action = { |me| setStyle.value(me.value) };
156       view.asView.keyDownAction = { |v, char| this.keyDown(char) };
157       view.onClose = { view = nil; this.quit; };
159       // LAUNCH
161       scopeView.focus;
162       if( window.notNil ) { window.front };
163     };
165     setCycle = { arg val;
166       cycle = val;
167       if( synth.notNil ) { synth.set(\frames, val) }
168     };
170     setYZoom = { arg val;
171       yZoom = val;
172       scopeView.yZoom = val;
173     };
175     // NOTE: assuming a single Bus
176     setIndex = { arg i;
177       bus = Bus(bus.rate, i, bus.numChannels, bus.server);
178       if(synth.notNil) { synth.set(\in, i) };
179     };
181     // NOTE: assuming a single Bus
182     setNumChannels = { arg n;
183       // we have to restart the whole thing:
184       this.stop;
185       bus = Bus(bus.rate, bus.index, n, bus.server);
186       this.run;
187     };
189     // NOTE: assuming a single Bus
190     setRate = { arg val;
191       val.switch (
192         0, {
193           bus = Bus(\audio, bus.index, bus.numChannels, bus.server);
194           busSpec = aBusSpec;
195           if(synth.notNil) { synth.set(\switch, 0) };
196         },
197         1, {
198           bus = Bus(\control, bus.index, bus.numChannels, bus.server);
199           busSpec = cBusSpec;
200           if(synth.notNil) { synth.set(\switch, 1) };
201         }
202       );
203       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index);
204       this.index = bus.index; // ensure conformance with busSpec;
205       updateColors.value;
206     };
208     setStyle = { arg val;
209       if(this.numChannels < 2 and: { val == 2 }) {
210         "QStethoscope: x/y scoping with one channel only; y will be a constant 0".warn;
211       };
212       scopeView.style = val;
213     };
215     updateColors = {
216       var colors;
217       bus.do { |b|
218         var c = if(b.rate === \audio){Color.new255(255, 218, 000)}{Color.new255(125, 255, 205)};
219         colors = colors ++ Array.fill(b.numChannels, c);
220       };
221       scopeView.waveColors = colors;
222     };
224     playSynthDef = { arg def, args;
225       if( synthWatcher.notNil ) {synthWatcher.stop};
226       synthWatcher = fork {
227         def.send(server);
228         server.sync;
229         synth = Synth.tail(RootNode(server), def.name, args);
230         CmdPeriod.add(this);
231         {if(view.notNil){updateColors.value; scopeView.start}}.defer;
232       };
233     };
235     makeGui.value(parent);
237     ServerBoot.add(this, server);
238     ServerQuit.add(this, server);
239     this.run;
240   }
242   doOnServerBoot {
243       this.run;
244   }
246   doOnServerQuit {
247       this.stop;
248       scopeBuffer.free;
249       scopeBuffer = nil;
250   }
252   cmdPeriod {
253     synth = nil;
254     this.stop;
255     CmdPeriod.remove(this);
256   }
258   run {
259     var n_chan;
261     if(running || server.serverRunning.not) {^this};
263     if(scopeBuffer.isNil){
264       scopeBuffer = ScopeBuffer.alloc(server);
265       scopeView.bufnum = scopeBuffer.index;
266       defName = "stethoscope" ++ scopeBuffer.index.asString;
267     };
269     n_chan = this.numChannels.asInteger;
271     if( bus.class === Bus ) {
272       playSynthDef.value (
273         SynthDef(defName, { arg in, switch, frames;
274           var z;
275           z = Select.ar(switch, [
276             In.ar(in, n_chan),
277             K2A.ar(In.kr(in, n_chan))]
278           );
279           ScopeOut2.ar(z, scopeBuffer.index, maxBufSize, frames );
280         }),
281         [\in, bus.index, \switch, if('audio' === bus.rate){0}{1}, \frames, cycle]
282       )
283     }{
284       playSynthDef.value (
285         SynthDef(defName, { arg frames;
286           var z = Array(n_chan);
287           bus.do { |b| z = z ++ b.ar };
288           ScopeOut2.ar(z, scopeBuffer.index, maxBufSize, frames);
289         }),
290         [\frames, cycle]
291       );
292     };
294     running = true;
295   }
297   stop {
298     if( view.notNil ) { {scopeView.stop}.defer };
300     if( synthWatcher.notNil ) { synthWatcher.stop };
302     if( synth.notNil ) {
303       synth.free;
304       synth = nil;
305     };
307     running = false;
308   }
310   quit {
311     var win;
312     this.stop;
313     ServerBoot.remove(this, server);
314     ServerQuit.remove(this, server);
315     CmdPeriod.remove(this);
316     if(scopeBuffer.notNil) {scopeBuffer.free; scopeBuffer=nil};
317     if(window.notNil) { win = window; window = nil; { win.close }.defer; };
318   }
320   setProperties { arg numChannels, index, bufsize, zoom, rate;
321       var new_bus;
322       var isRunning = running;
324       if (isRunning) {this.stop};
326       // process args
328       if(index.notNil || numChannels.notNil || rate.notNil) {
329         bus = if(bus.class === Bus) {
330             Bus (
331               rate ? bus.rate,
332               index ? bus.index,
333               numChannels ? bus.numChannels,
334               server
335             )
336         }{
337             Bus (
338               rate ? \audio,
339               index ? 0,
340               numChannels ? 2,
341               server
342             )
343         };
344       };
345       if(bufsize.notNil) { maxBufSize = max(bufsize, 128) };
347       // set other vars related to args
349       busSpec = if(bus.rate === \audio) {aBusSpec} {cBusSpec};
350       cycleSpec = ControlSpec( 64, maxBufSize, \exponential );
351       if(zoom.notNil)
352         { cycle = cycleSpec.constrain( 1024 * zoom.asFloat.reciprocal ) };
354       // update GUI
356       cycleSlider.value = cycleSpec.unmap(cycle);
357       rateMenu.value_(if(bus.rate === \audio){0}{1}).enabled_(true);
358       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index).enabled_(true);
359       chNumBox.value_(bus.numChannels).enabled_(true);
361       if (isRunning) {this.run};
362   }
364   bufsize { ^maxBufSize }
366   bus_ { arg b;
367     var isSingle = b.class === Bus;
368     var isRunning = running;
370     if (isRunning) {this.stop};
372     bus = b;
374     if( isSingle ) {
375       busSpec = if(bus.rate === \audio) {aBusSpec} {cBusSpec};
376       rateMenu.value = if(b.rate===\audio){0}{1};
377       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index);
378       chNumBox.value = b.numChannels;
379     }{
380       busSpec = nil;
381       rateMenu.value = nil;
382       idxNumBox.string = "-";
383       chNumBox.string = "-";
384     };
385     rateMenu.enabled = isSingle;
386     idxNumBox.enabled = isSingle;
387     chNumBox.enabled = isSingle;
389     if (isRunning) {this.run};
390   }
392   numChannels {
393     var num;
394     if( bus.class === Bus ) {
395       ^bus.numChannels;
396     }{
397       num = 0; bus.do { |b| num = num + b.numChannels };
398       ^num;
399     }
400   }
402   // will always be clipped between 0 and the amount of channels
403   // at the beginning of the current run
404   numChannels_ { arg n;
405     if( (bus.class === Bus).not ) { ^this };
406     setNumChannels.value(n);
407     chNumBox.value = n;
408   }
410   index {
411     if( bus.class === Bus ) { ^bus.index } { nil }
412   }
414   index_ { arg i;
415     if( (bus.class === Bus).not ) { ^this };
416     setIndex.value( busSpec.constrain(i) );
417     idxNumBox.value = i;
418   }
420   rate {
421     if( bus.class === Bus ) { ^bus.rate } { nil }
422   }
424   rate_ { arg argRate=\audio;
425     var val;
426     if( (bus.class === Bus).not ) { ^this };
427     val = if(argRate===\audio){0}{1};
428     setRate.value(val);
429     rateMenu.value = val;
430   }
432   switchRate {
433     if( bus.class === Bus ) {
434       this.rate = if(bus.rate === \control) {\audio} {\control};
435     }
436   }
438   // [0, 1] -> [64, 8192] frames
439   cycle_ { arg val;
440     setCycle.value( cycleSpec.constrain(val) );
441     cycleSlider.value = cycleSpec.unmap(val);
442   }
444   xZoom_ { arg val; this.cycle = 1024 * val.asFloat.reciprocal }
445   xZoom { ^(1024 * cycle.reciprocal) }
447   zoom { ^this.xZoom }
448   zoom_ { arg val; this.xZoom_(val ? 1) }
450   // [0, 1] -> [0.125, 16] y scaling factor
451   yZoom_ { arg val;
452     setYZoom.value( yZoomSpec.constrain(val) );
453     yZoomSlider.value = yZoomSpec.unmap(val);
454   }
456   style_ { arg val;
457     setStyle.value(val);
458     styleMenu.value = val;
459   }
461   size_ { arg value;
462     var sz = value.asSize;
463     if( window.notNil ) { window.setInnerExtent(sz.width,sz.height) };
464   }
466   toggleSize {
467     if(window.notNil) {
468       sizeToggle = sizeToggle.not;
469       if(sizeToggle)
470         { this.size = largeSize }
471         { this.size = smallSize };
472     }
473   }
475   toInputBus {
476     var i = server.options.numOutputBusChannels;
477     var c = server.options.numInputBusChannels;
478     this.bus = Bus(\audio, i, c, server);
479   }
481   toOutputBus {
482     var c = server.options.numOutputBusChannels;
483     this.bus = Bus(\audio, 0, c, server);
484   }
486   keyDown { arg char;
487     case (
488       { char === $i }, { this.toInputBus },
489       { char === $o }, { this.toOutputBus },
490       { char === $  }, { this.run },
491       { char === $s }, { this.style = (scopeView.style + 1).wrap(0,2) },
492       { char === $S }, { this.style = 2 },
493       { char === $j }, { if(this.index.notNil) {this.index = this.index - 1} },
494       { char === $k }, { this.switchRate; },
495       { char === $l }, { if(this.index.notNil) {this.index = this.index + 1} },
496       { char === $- }, { cycleSlider.increment; cycleSlider.doAction },
497       { char === $+ }, { cycleSlider.decrement; cycleSlider.doAction },
498       { char === $* }, { yZoomSlider.increment; yZoomSlider.doAction },
499       { char === $_ }, { yZoomSlider.decrement; yZoomSlider.doAction },
500       { char === $m }, { this.toggleSize },
501       { char === $.}, { this.stop },
502       { ^false }
503     );
504     ^true;
505   }