SCDoc: Use proper static string constants instead of comparing string literals.
[supercollider.git] / SCClassLibrary / QtCollider / QStethoscope.sc
blobe393c013dd096383f198d2d152db6e1525935329
1 /*
2 [22.sep.2010]
3   QStethoscope created by copying SCStethoscope and adjusting code for Qt GUI.
4 */
6 QStethoscope {
7   classvar ugenScopes;
8   var <server, <numChannels, <rate,  <index;
9   var <bufsize, buffer, <window, synth;
10   var scopeView, indexView, nChanView, xZoomSlider, yZoomSlider;
11   var style=0, sizeToggle=0, zx, zy, ai=0, ki=0, audiospec, controlspec, zoomspec;
13   *implementsClass {^'Stethoscope'}
15   *new { arg server, numChannels = 2, index, bufsize = 4096, zoom, rate, view, bufnum;
16     if(server.inProcess.not, { "scope works only with internal server".error; ^nil });
17     ^super.newCopyArgs(server, numChannels, rate ? \audio).makeWindow(view)
18     .index_(index ? 0).zoom_(zoom).allocBuffer(bufsize, bufnum).run;
19   }
21   *tileBounds {
22     var screenBounds = QWindow.screenBounds;
23     var x = 520 + (ugenScopes.size * (223 + 5)) + 5;
24     var right = x + 223;
25     var y = floor(right / screenBounds.width) * 297 + 10;
26     if(right > screenBounds.right) { x = floor(right % screenBounds.width / 223) * (223 + 5) };
27     ^Rect(x, y, 223, 223)
28   }
30   makeBounds { arg size = 223; ^Rect(297, 5, size, size) }
32   makeWindow { arg view;
33     if(view.isNil)
34     {
35       window = QWindow("stethoscope", this.makeBounds);
36       view = window.view;
37       view.decorator = FlowLayout(window.view.bounds);
38       window.front;
39       window.onClose = { this.free };
41     } {
42       if(view.decorator.isNil, {
43         "QStethoscope: makeWindow added a decorator to view".warn;
44         view.decorator = FlowLayout(view.bounds);
45       });
46     };
47     scopeView = QScope(view,
48       Rect(0,0, view.bounds.width - 10 - 20 - 4, view.bounds.height - 40)
49     );
50     scopeView.background = Color.black;
51     scopeView.resize = 5;
52     view.keyDownAction = { arg view, char; this.keyDown(char) };
53     view.background = Color.grey(0.6);
54     zx = scopeView.xZoom.log2;
55     zy = scopeView.yZoom.log2;
57     audiospec = ControlSpec(0, server.options.numAudioBusChannels, step:1);
58     controlspec = ControlSpec(0, server.options.numControlBusChannels, step:1);
59     zoomspec = ControlSpec(0.125, 16, \exp);
61     xZoomSlider = QSlider(view, Rect(10, 10, view.bounds.width - 80, 20));
62     xZoomSlider.action = { arg x;
63         /*var i;
64         i = this.spec.map(x.value);
65         this.index = i;*/
66         this.xZoom = zoomspec.map(x.value)
67       };
68     xZoomSlider.resize = 8;
69     xZoomSlider.value = zoomspec.unmap(this.xZoom);
70     xZoomSlider.background = Color.grey(0.6);
71     xZoomSlider.focusColor = Color.clear;
73     indexView = QNumberBox(view, Rect(10, 10, 30, 20))
74                 .value_(0).decimals_(0).step_(1).scroll_step_(1);
75     indexView.action = { this.index = indexView.value;  };
76     indexView.resize = 9;
77     indexView.font = QFont("Monaco", 9);
78     nChanView = QNumberBox(view, Rect(10, 10, 25, 20))
79                 .value_(numChannels).decimals_(0).step_(1).scroll_step_(1);
80     nChanView.action = { this.numChannels = nChanView.value.asInteger  };
81     nChanView.resize = 9;
82     nChanView.font = QFont("Monaco", 9);
83     QStaticText(view, Rect(10, 10, 20, 20)).visible_(false);
84     this.updateColors;
87     view.decorator.reset;
88     view.decorator.shift(scopeView.bounds.right, 0);
90     yZoomSlider = QSlider(view, Rect(scopeView.bounds.right, 0, 20, scopeView.bounds.height));
91     yZoomSlider.action = { arg x;
92         this.yZoom = zoomspec.map(x.value)
93       };
94     yZoomSlider.resize = 6;
95     yZoomSlider.value = zoomspec.unmap(this.yZoom);
96     yZoomSlider.background = Color.grey(0.6);
97     yZoomSlider.focusColor = Color.clear;
98   }
100   keyDown { arg char;
101         if(char === $i) { this.toInputBus; ^this };
102         if(char === $o) { this.toOutputBus;  ^this  };
103         if(char === $ ) { this.run;  ^this  };
104         if(char === $s) { this.style = (style + 1) % 2; ^this  };
105         if(char === $S) { this.style = 2;  ^this  };
106         if(char === $j or: { char.ascii === 0 }) { this.index = index - 1; ^this  };
107         if(char === $k) { this.switchRate; ^this  };
108         if(char === $l or: { char.ascii === 1 }) { this.index = index + 1 };
109         if(char === $-) {  zx = zx + 0.25; this.xZoom = 2 ** zx; ^this  };
110         if(char === $+) {  zx = zx - 0.25; this.xZoom = 2 ** zx; ^this  };        if(char === $*) {  zy = zy + 0.25; this.yZoom = 2 ** zy; ^this  };
111         if(char === $_) {  zy = zy - 0.25; this.yZoom = 2 ** zy; ^this  };
112         if(char === $A) {  this.adjustBufferSize; ^this  };
113         if(char === $m) { this.toggleSize; ^this  };
114         if(char === $.) { if(synth.isPlaying) { synth.free } };
116   }
118   spec { ^if(rate === \audio) { audiospec } {  controlspec } }
120   setProperties { arg numChannels, index, bufsize=4096, zoom, rate;
122         if(rate.notNil) { this.rate = rate };
123         if(index.notNil) { this.index = index };
124         if(numChannels.notNil) { this.numChannels = numChannels };
125         if(this.bufsize != bufsize) { this.allocBuffer(bufsize) };
126         if(zoom.notNil) { this.zoom = zoom };
127   }
129   allocBuffer { arg argbufsize, argbufnum;
130     bufsize = argbufsize ? bufsize;
131     if(buffer.notNil) { buffer.free };
132     buffer = Buffer.alloc(server, bufsize, numChannels, nil, argbufnum);
133     scopeView.bufnum = buffer.bufnum;
134     if(synth.isPlaying) { synth.set(\bufnum, buffer.bufnum) };
135   }
137   run {
138     if(synth.isPlaying.not) {
139       synth = SynthDef("stethoscope", { arg in, switch, bufnum;
140         var z;
141         z = Select.ar(switch, [In.ar(in, numChannels), K2A.ar(In.kr(in, numChannels))]);
142         ScopeOut.ar(z, bufnum);
143       }).play(RootNode(server), [\bufnum, buffer.bufnum, \in, index, \switch]
144         ++ if('audio' === rate) { 0 } { 1 },
145         \addToTail
146       );
147       synth.isPlaying = true;
148       NodeWatcher.register(synth);
149     }
150   }
152   free {
153     buffer.free;
155     if(synth.isPlaying) {  synth.free };
156     synth = nil;
157     if(server.scopeWindow === this) { server.scopeWindow = nil }
158   }
160   quit {
161     window.close;
162     this.free;
163   }
165   numChannels_ { arg n;
167     var isPlaying;
168     if(n > 128) { "cannot display more than 128 channels at once".inform; n = 128 };
169     if(n != numChannels and: { n > 0 }) {
170       isPlaying = synth.isPlaying;
171       if(isPlaying) { synth.free; synth.isPlaying = false; synth = nil }; // immediate
172       numChannels = n;
174       nChanView.value = n;
175       this.allocBuffer;
176       if(isPlaying) {  this.run };
177       this.updateColors;
178     };
179   }
181   index_ { arg val=0;
182     var spec;
183     spec = this.spec;
184     index = spec.constrain(val);
185     if(synth.isPlaying) { synth.set(\in, index) };
186     if(rate === \audio) { ai = index } { ki = index };
187     indexView.value = index;
188     // xZoomSlider.value = spec.unmap(index)
189   }
191   rate_ { arg argRate=\audio;
192     if(rate === argRate) {  ^this };
193     if(argRate === \audio)
194     {
195         if(synth.isPlaying) { synth.set(\switch, 0) };
196         rate = \audio;
197         this.updateColors;
198         ki = index;
199         this.index = ai;
200     }
201     {
202         if(synth.isPlaying) { synth.set(\switch, 1) };
203         rate = \control;
204         this.updateColors;
205         ai = index;
206         this.index = ki;
207     }
208   }
210   size_ { arg val; if(window.notNil) { window.bounds = this.makeBounds(val) } }
211   toggleSize {  if(sizeToggle == 0)
212           { sizeToggle = 1; this.size_(500) }
213           { sizeToggle = 0; this.size_(212) }
214   }
216   xZoom_ { arg val;
217     scopeView.xZoom = val;
218     zx = val.log2;
219     xZoomSlider.value = zoomspec.unmap(val);
220   }
221   yZoom_ { arg val;
222     scopeView.yZoom = val;
223     zy = val.log2;
224     yZoomSlider.value = zoomspec.unmap(val);
225   }
226   xZoom { ^2.0 ** zx }
227   yZoom { ^2.0 ** zy }
229   zoom_ { arg val; this.xZoom_(val ? 1) }
231   style_ { arg val;
232     if(numChannels < 2 and: { val == 2 }) {
233       "QStethoscope: can't do x/y scoping with one channel".warn;
234       ^this;
235     };
236     scopeView.style = style = val
237   }
241   updateColors {
242     scopeView.waveColors = if(\audio === rate) {
243       Array.fill(numChannels, { Color.new255(255, 218, 000) });
244     } {
245       Array.fill(numChannels, { Color.new255(125, 255, 205) });
246     }
247   }
249   switchRate { if(rate === \control) { this.rate = \audio } {  this.rate = \control } }
251   toInputBus {
252     this.index = server.options.numOutputBusChannels;
253     this.numChannels = server.options.numInputBusChannels;
254   }
255   toOutputBus {
256     this.index = 0;
257     this.numChannels = server.options.numOutputBusChannels;
258   }
259   adjustBufferSize {
260     this.allocBuffer(max(256,nextPowerOfTwo(
261       asInteger(scopeView.bounds.width * scopeView.xZoom))))
262   }
264   // ugenScopes
265   *ugenScopes {
266     if(ugenScopes.isNil, { ugenScopes = Set.new; });
267     ^ugenScopes
268   }
270   /**
271    *  @return (Server) the default server to scope on
272    */
273   *defaultServer {
274     ^Server.internal;
275   }
277   /**
278    *  @param  aServer (Server) a server to test for scoping
279    *  @return     (Boolean) indication whether the server can be scope'ed
280    */
281   *isValidServer { arg aServer;
282     ^aServer.inProcess;
283   }