supernova: fixes for boost-1.49 and gcc-4.7
[supercollider.git] / SCClassLibrary / QtCollider / QStethoscope2.sc
blob84ccde2f85a842e8255383e26395ec7e6f92d627
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","Overlay","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 =
105         QGridLayout()
106           .add(
107             QHLayout(
108               rateMenu,
109               idxNumBox.minWidth_(35),
110               chNumBox.minWidth_(35),
111               nil,
112               styleMenu
113             ).margins_(0).spacing_(2), 0, 0
114           )
115           .add(scopeView,1,0)
116           .add(yZoomSlider.maxWidth_(15), 1,1)
117           .add(cycleSlider.maxHeight_(15), 2,0)
118           .margins_(2).spacing_(2);
120       // ACTIONS
122       cycleSlider.action = { |me| setCycle.value(cycleSpec.map(me.value)) };
123       yZoomSlider.action = { |me| setYZoom.value(me.value) };
124       idxNumBox.action = { |me| setIndex.value(me.value) };
125       chNumBox.action = { |me| setNumChannels.value(me.value) };
126       rateMenu.action = { |me| setRate.value(me.value) };
127       styleMenu.action = { |me| setStyle.value(me.value) };
128       view.asView.keyDownAction = { |v, char| this.keyDown(char) };
129       view.onClose = { view = nil; this.quit; };
131       // LAUNCH
133       scopeView.focus;
134       if( window.notNil ) { window.front };
135     };
137     setCycle = { arg val;
138       cycle = val;
139       if( synth.notNil ) { synth.set(\frames, val) }
140     };
142     setYZoom = { arg val;
143       yZoom = val;
144       scopeView.yZoom = yZoomSpec.map(val);
145     };
147     // NOTE: assuming a single Bus
148     setIndex = { arg i;
149       bus = Bus(bus.rate, i, bus.numChannels, bus.server);
150       if(synth.notNil) { synth.set(\in, i) };
151     };
153     // NOTE: assuming a single Bus
154     setNumChannels = { arg n;
155       // we have to restart the whole thing:
156       this.stop;
157       bus = Bus(bus.rate, bus.index, n, bus.server);
158       this.run;
159     };
161     // NOTE: assuming a single Bus
162     setRate = { arg val;
163       val.switch (
164         0, {
165           bus = Bus(\audio, bus.index, bus.numChannels, bus.server);
166           busSpec = aBusSpec;
167           if(synth.notNil) { synth.set(\switch, 0) };
168         },
169         1, {
170           bus = Bus(\control, bus.index, bus.numChannels, bus.server);
171           busSpec = cBusSpec;
172           if(synth.notNil) { synth.set(\switch, 1) };
173         }
174       );
175       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index);
176       this.index = bus.index; // ensure conformance with busSpec;
177       updateColors.value;
178     };
180     setStyle = { arg val;
181       if(this.numChannels < 2 and: { val == 2 }) {
182         "QStethoscope: x/y scoping with one channel only; y will be a constant 0".warn;
183       };
184       scopeView.style = val;
185     };
187     updateColors = {
188       var colors;
189       bus.do { |b|
190         var c = if(b.rate === \audio){Color.new255(255, 218, 000)}{Color.new255(125, 255, 205)};
191         colors = colors ++ Array.fill(b.numChannels, c);
192       };
193       scopeView.waveColors = colors;
194     };
196     playSynthDef = { arg def, args;
197       if( synthWatcher.notNil ) {synthWatcher.stop};
198       synthWatcher = fork {
199         def.send(server);
200         server.sync;
201         synth = Synth.tail(RootNode(server), def.name, args);
202         CmdPeriod.add(this);
203         {if(view.notNil){updateColors.value; scopeView.start}}.defer;
204       };
205     };
207     makeGui.value(parent);
209     ServerBoot.add(this, server);
210     ServerQuit.add(this, server);
211     this.run;
212   }
214   doOnServerBoot {
215       this.run;
216   }
218   doOnServerQuit {
219       this.stop;
220       scopeBuffer.free;
221       scopeBuffer = nil;
222   }
224   cmdPeriod {
225     synth = nil;
226     this.stop;
227     CmdPeriod.remove(this);
228   }
230   run {
231     var n_chan;
233     if(running || server.serverRunning.not) {^this};
235     if(scopeBuffer.isNil){
236       scopeBuffer = ScopeBuffer.alloc(server);
237       scopeView.bufnum = scopeBuffer.index;
238       defName = "stethoscope" ++ scopeBuffer.index.asString;
239     };
241     n_chan = this.numChannels.asInteger;
243     if( bus.class === Bus ) {
244       playSynthDef.value (
245         SynthDef(defName, { arg in, switch, frames;
246           var z;
247           z = Select.ar(switch, [
248             In.ar(in, n_chan),
249             K2A.ar(In.kr(in, n_chan))]
250           );
251           ScopeOut2.ar(z, scopeBuffer.index, maxBufSize, frames );
252         }),
253         [\in, bus.index, \switch, if('audio' === bus.rate){0}{1}, \frames, cycle]
254       )
255     }{
256       playSynthDef.value (
257         SynthDef(defName, { arg frames;
258           var z = Array(n_chan);
259           bus.do { |b| z = z ++ b.ar };
260           ScopeOut2.ar(z, scopeBuffer.index, maxBufSize, frames);
261         }),
262         [\frames, cycle]
263       );
264     };
266     running = true;
267   }
269   stop {
270     if( view.notNil ) { {scopeView.stop}.defer };
272     if( synthWatcher.notNil ) { synthWatcher.stop };
274     if( synth.notNil ) {
275       synth.free;
276       synth = nil;
277     };
279     running = false;
280   }
282   quit {
283     var win;
284     this.stop;
285     ServerBoot.remove(this, server);
286     ServerQuit.remove(this, server);
287     CmdPeriod.remove(this);
288     if(scopeBuffer.notNil) {scopeBuffer.free; scopeBuffer=nil};
289     if(window.notNil) { win = window; window = nil; { win.close }.defer; };
290   }
292   setProperties { arg numChannels, index, bufsize=4096, zoom, rate;
293       var new_bus;
294       var isRunning = running;
296       if (isRunning) {this.stop};
298       // process args
300       if(index.notNil || numChannels.notNil || rate.notNil) {
301         bus = if(bus.class === Bus) {
302             Bus (
303               rate ? bus.rate,
304               index ? bus.index,
305               numChannels ? bus.numChannels,
306               server
307             )
308         }{
309             Bus (
310               rate ? \audio,
311               index ? 0,
312               numChannels ? 2,
313               server
314             )
315         };
316       };
317       if(bufsize.notNil) { maxBufSize = max(bufsize, 128) };
318       if(zoom.notNil) { yZoom = yZoomSpec.constrain(zoom) };
320       // set other vars related to args
322       busSpec = if(bus.rate === \audio) {aBusSpec} {cBusSpec};
323       cycleSpec = ControlSpec( 64, maxBufSize, \exponential );
324       cycle = cycleSpec.constrain(cycle);
326       // update GUI
328       yZoomSlider.value = yZoom;
329       cycleSlider.value = cycleSpec.unmap(cycle);
330       rateMenu.value_(if(bus.rate === \audio){0}{1}).enabled_(true);
331       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index).enabled_(true);
332       chNumBox.value_(bus.numChannels).enabled_(true);
333       scopeView.yZoom = yZoomSpec.map(yZoom);
335       if (isRunning) {this.run};
336   }
338   bufsize { ^maxBufSize }
340   bus_ { arg b;
341     var isSingle = b.class === Bus;
342     var isRunning = running;
344     if (isRunning) {this.stop};
346     bus = b;
348     if( isSingle ) {
349       busSpec = if(bus.rate === \audio) {aBusSpec} {cBusSpec};
350       rateMenu.value = if(b.rate===\audio){0}{1};
351       idxNumBox.clipLo_(busSpec.minval).clipHi_(busSpec.maxval).value_(bus.index);
352       chNumBox.value = b.numChannels;
353     }{
354       busSpec = nil;
355       rateMenu.value = nil;
356       idxNumBox.string = "-";
357       chNumBox.string = "-";
358     };
359     rateMenu.enabled = isSingle;
360     idxNumBox.enabled = isSingle;
361     chNumBox.enabled = isSingle;
363     if (isRunning) {this.run};
364   }
366   numChannels {
367     var num;
368     if( bus.class === Bus ) {
369       ^bus.numChannels;
370     }{
371       num = 0; bus.do { |b| num = num + b.numChannels };
372       ^num;
373     }
374   }
376   // will always be clipped between 0 and the amount of channels
377   // at the beginning of the current run
378   numChannels_ { arg n;
379     if( (bus.class === Bus).not ) { ^this };
380     setNumChannels.value(n);
381     chNumBox.value = n;
382   }
384   index {
385     if( bus.class === Bus ) { ^bus.index } { nil }
386   }
388   index_ { arg i;
389     if( (bus.class === Bus).not ) { ^this };
390     setIndex.value( busSpec.constrain(i) );
391     idxNumBox.value = i;
392   }
394   rate {
395     if( bus.class === Bus ) { ^bus.rate } { nil }
396   }
398   rate_ { arg argRate=\audio;
399     var val;
400     if( (bus.class === Bus).not ) { ^this };
401     val = if(argRate===\audio){0}{1};
402     setRate.value(val);
403     rateMenu.value = val;
404   }
406   switchRate {
407     if( bus.class === Bus ) {
408       this.rate = if(bus.rate === \control) {\audio} {\control};
409     }
410   }
412   // [0, 1] -> [64, 8192] frames
413   cycle_ { arg val;
414     setCycle.value( cycleSpec.constrain(val) );
415     cycleSlider.value = cycleSpec.unmap(val);
416   }
418   // [0, 1] -> [0.125, 16] y scaling factor
419   yZoom_ { arg val;
420     setYZoom.value(val);
421     yZoomSlider.value = val;
422   }
424   zoom_ { arg val; this.xZoom_(val ? 1) }
426   style_ { arg val;
427     setStyle.value(val);
428     styleMenu.value = val;
429   }
431   size_ { arg value;
432     var sz = value.asSize;
433     if( window.notNil ) { window.setInnerExtent(sz.width,sz.height) };
434   }
436   toggleSize {
437     if(window.notNil) {
438       sizeToggle = sizeToggle.not;
439       if(sizeToggle)
440         { this.size = largeSize }
441         { this.size = smallSize };
442     }
443   }
445   toInputBus {
446     var i = server.options.numOutputBusChannels;
447     var c = server.options.numInputBusChannels;
448     this.bus = Bus(\audio, i, c, server);
449   }
451   toOutputBus {
452     var c = server.options.numOutputBusChannels;
453     this.bus = Bus(\audio, 0, c, server);
454   }
456   keyDown { arg char;
457     case (
458       { char === $i }, { this.toInputBus },
459       { char === $o }, { this.toOutputBus },
460       { char === $  }, { this.run },
461       { char === $s }, { this.style = (scopeView.style + 1).wrap(0,2) },
462       { char === $S }, { this.style = 2 },
463       { char === $j }, { if(this.index.notNil) {this.index = this.index - 1} },
464       { char === $k }, { this.switchRate; },
465       { char === $l }, { if(this.index.notNil) {this.index = this.index + 1} },
466       { char === $- }, { cycleSlider.increment; cycleSlider.doAction },
467       { char === $+ }, { cycleSlider.decrement; cycleSlider.doAction },
468       { char === $* }, { yZoomSlider.increment; yZoomSlider.doAction },
469       { char === $_ }, { yZoomSlider.decrement; yZoomSlider.doAction },
470       { char === $m }, { this.toggleSize },
471       { char === $.}, { this.stop },
472       { ^false }
473     );
474     ^true;
475   }