Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / SCClassLibrary / Common / GUI / osx / scide_scapp / SCStethoscope.sc
blob0dbc81bb455579618831cb3d197559c4b4964b5d
1 SCStethoscope {
2         classvar ugenScopes;
3         var <server, <numChannels, <rate,  <index;
4         var <bufsize, buffer, <window, synth;
5         var scopeView, indexView, nChanView, xZoomSlider, yZoomSlider;
6         var style=0, sizeToggle=0, zx, zy, ai=0, ki=0, audiospec, controlspec, zoomspec;
9         *new { arg server, numChannels = 2, index, bufsize = 4096, zoom, rate, view, bufnum;
10                 if(server.inProcess.not, { "scope works only with internal server".error; ^nil });              ^super.newCopyArgs(server, numChannels, rate ? \audio).makeWindow(view)
11                 .index_(index ? 0).zoom_(zoom).allocBuffer(bufsize, bufnum).run;
12         }
14         *tileBounds {
15                 var screenBounds = SCWindow.screenBounds;
16                 var x = 520 + (ugenScopes.size * (223 + 5)) + 5;
17                 var right = x + 223;
18                 var y = floor(right / screenBounds.width) * 297 + 10;
19                 if(right > screenBounds.right) { x = floor(right % screenBounds.width / 223) * (223 + 5) };
20                 ^Rect(x, y, 223, 223)
21         }
23         makeBounds { arg size = 223; ^Rect(297, 5, size, size) }
25         makeWindow { arg view;
26                 if(view.isNil)
27                 {
28                         window = SCWindow("stethoscope", this.makeBounds);
29                         view = window.view;
30                         view.decorator = FlowLayout(window.view.bounds);
31                         window.front;
32                         window.onClose = { this.free };
34                 } {
35                         if(view.decorator.isNil, {
36                                 "SCStethoscope: makeWindow added a decorator to view".warn;
37                                 view.decorator = FlowLayout(view.bounds);
38                         });
39                 };
40                 scopeView = SCScope(view,
41                         Rect(0,0, view.bounds.width - 10 - 20 - 4, view.bounds.height - 40)
42                 );
43                 scopeView.background = Color.black;
44                 scopeView.resize = 5;
45                 view.keyDownAction = { arg view, char; this.keyDown(char) };
46                 view.background = Color.grey(0.6);
47                 zx = scopeView.xZoom.log2;
48                 zy = scopeView.yZoom.log2;
50                 audiospec = ControlSpec(0, server.options.numAudioBusChannels, step:1);
51                 controlspec = ControlSpec(0, server.options.numControlBusChannels, step:1);
52                 zoomspec = ControlSpec(0.125, 16, \exp);
54                 xZoomSlider = SCSlider(view, Rect(10, 10, view.bounds.width - 80, 20));
55                 xZoomSlider.action = { arg x;
56                                 /*var i;
57                                 i = this.spec.map(x.value);
58                                 this.index = i;*/
59                                 this.xZoom = zoomspec.map(x.value)
60                         };
61                 xZoomSlider.resize = 8;
62                 xZoomSlider.value = zoomspec.unmap(this.xZoom);
63                 xZoomSlider.background = Color.grey(0.6);
64                 xZoomSlider.focusColor = Color.clear;
66                 indexView = SCNumberBox(view, Rect(10, 10, 30, 20)).value_(0);
67                 indexView.action = { this.index = indexView.value;  };
68                 indexView.resize = 9;
69                 indexView.font = Font("Monaco", 9);
70                 nChanView = SCNumberBox(view, Rect(10, 10, 25, 20)).value_(numChannels);
71                 nChanView.action = { this.numChannels = nChanView.value.asInteger  };
72                 nChanView.resize = 9;
73                 nChanView.font = Font("Monaco", 9);
74                 SCStaticText(view, Rect(10, 10, 20, 20)).visible_(false);
75                 this.updateColors;
78                 view.decorator.reset;
79                 view.decorator.shift(scopeView.bounds.right, 0);
81                 yZoomSlider = SCSlider(view, Rect(scopeView.bounds.right, 0, 20, scopeView.bounds.height));
82                 yZoomSlider.action = { arg x;
83                                 this.yZoom = zoomspec.map(x.value)
84                         };
85                 yZoomSlider.resize = 6;
86                 yZoomSlider.value = zoomspec.unmap(this.yZoom);
87                 yZoomSlider.background = Color.grey(0.6);
88                 yZoomSlider.focusColor = Color.clear;
89         }
91         keyDown { arg char;
92                                 if(char === $i) { this.toInputBus; ^this };
93                                 if(char === $o) { this.toOutputBus;  ^this  };
94                                 if(char === $ ) { this.run;  ^this  };
95                                 if(char === $s) { this.style = (style + 1) % 2; ^this  };
96                                 if(char === $S) { this.style = 2;  ^this  };
97                                 if(char === $j or: { char.ascii === 0 }) { this.index = index - 1; ^this  };
98                                 if(char === $k) { this.switchRate; ^this  };
99                                 if(char === $l or: { char.ascii === 1 }) { this.index = index + 1 };
100                                 if(char === $-) {  zx = zx + 0.25; this.xZoom = 2 ** zx; ^this  };
101                                 if(char === $+) {  zx = zx - 0.25; this.xZoom = 2 ** zx; ^this  };                              if(char === $*) {  zy = zy + 0.25; this.yZoom = 2 ** zy; ^this  };
102                                 if(char === $_) {  zy = zy - 0.25; this.yZoom = 2 ** zy; ^this  };
103                                 if(char === $A) {  this.adjustBufferSize; ^this  };
104                                 if(char === $m) { this.toggleSize; ^this  };
105                                 if(char === $.) { if(synth.isPlaying) { synth.free } };
107         }
109         spec { ^if(rate === \audio) { audiospec } {  controlspec } }
111         setProperties { arg numChannels, index, bufsize=4096, zoom, rate;
113                                 if(rate.notNil) { this.rate = rate };
114                                 if(index.notNil) { this.index = index };
115                                 if(numChannels.notNil) { this.numChannels = numChannels };
116                                 if(this.bufsize != bufsize) { this.allocBuffer(bufsize) };
117                                 if(zoom.notNil) { this.zoom = zoom };
118         }
120         allocBuffer { arg argbufsize, argbufnum;
121                 bufsize = argbufsize ? bufsize;
122                 if(buffer.notNil) { buffer.free };
123                 buffer = Buffer.alloc(server, bufsize, numChannels, nil, argbufnum);
124                 scopeView.bufnum = buffer.bufnum;
125                 if(synth.isPlaying) { synth.set(\bufnum, buffer.bufnum) };
126         }
128         run {
129                 if(synth.isPlaying.not) {
130                         synth = SynthDef("stethoscope", { arg in, switch, bufnum;
131                                 var z;
132                                 z = Select.ar(switch, [In.ar(in, numChannels), K2A.ar(In.kr(in, numChannels))]);
133                                 ScopeOut.ar(z, bufnum);
134                         }).play(RootNode(server), [\bufnum, buffer.bufnum, \in, index, \switch]
135                                 ++ if('audio' === rate) { 0 } { 1 },
136                                 \addToTail
137                         );
138                         synth.isPlaying = true;
139                         NodeWatcher.register(synth);
140                 }
141         }
143         free {
144                 buffer.free;
146                 if(synth.isPlaying) {  synth.free };
147                 synth = nil;
148                 if(server.scopeWindow === this) { server.scopeWindow = nil }
149         }
151         quit {
152                 window.close;
153                 this.free;
154         }
156         numChannels_ { arg n;
158                 var isPlaying;
159                 if(n > 128) { "cannot display more than 128 channels at once".inform; n = 128 };
160                 if(n != numChannels and: { n > 0 }) {
161                         isPlaying = synth.isPlaying;
162                         if(isPlaying) { synth.free; synth.isPlaying = false; synth = nil }; // immediate
163                         numChannels = n;
165                         nChanView.value = n;
166                         this.allocBuffer;
167                         if(isPlaying) {  this.run };
168                         this.updateColors;
169                 };
170         }
172         index_ { arg val=0;
173                 var spec;
174                 spec = this.spec;
175                 index = spec.constrain(val);
176                 if(synth.isPlaying) { synth.set(\in, index) };
177                 if(rate === \audio) { ai = index } { ki = index };
178                 indexView.value = index;
179                 // xZoomSlider.value = spec.unmap(index)
180         }
182         rate_ { arg argRate=\audio;
183                 if(rate === argRate) {  ^this };
184                 if(argRate === \audio)
185                 {
186                                 if(synth.isPlaying) { synth.set(\switch, 0) };
187                                 rate = \audio;
188                                 this.updateColors;
189                                 ki = index;
190                                 this.index = ai;
191                 }
192                 {
193                                 if(synth.isPlaying) { synth.set(\switch, 1) };
194                                 rate = \control;
195                                 this.updateColors;
196                                 ai = index;
197                                 this.index = ki;
198                 }
199         }
201         size_ { arg val; if(window.notNil) { window.bounds = this.makeBounds(val) } }
202         toggleSize {  if(sizeToggle == 0)
203                                         { sizeToggle = 1; this.size_(500) }
204                                         { sizeToggle = 0; this.size_(212) }
205         }
207         xZoom_ { arg val;
208                 scopeView.xZoom = val;
209                 zx = val.log2;
210                 xZoomSlider.value = zoomspec.unmap(val);
211         }
212         yZoom_ { arg val;
213                 scopeView.yZoom = val;
214                 zy = val.log2;
215                 yZoomSlider.value = zoomspec.unmap(val);
216         }
217         xZoom { ^2.0 ** zx }
218         yZoom { ^2.0 ** zy }
220         zoom_ { arg val; this.xZoom_(val ? 1) }
222         style_ { arg val;
223                 if(numChannels < 2 and: { val == 2 }) {
224                         "SCStethoscope: can't do x/y scoping with one channel".warn;
225                         ^this;
226                 };
227                 scopeView.style = style = val
228         }
232         updateColors {
233                 scopeView.waveColors = if(\audio === rate) {
234                         Array.fill(numChannels, { Color.new255(255, 218, 000) });
235                 } {
236                         Array.fill(numChannels, { Color.new255(125, 255, 205) });
237                 }
238         }
240         switchRate { if(rate === \control) { this.rate = \audio } {  this.rate = \control } }
242         toInputBus {
243                 this.index = server.options.numOutputBusChannels;
244                 this.numChannels = server.options.numInputBusChannels;
245         }
246         toOutputBus {
247                 this.index = 0;
248                 this.numChannels = server.options.numOutputBusChannels;
249         }
250         adjustBufferSize {
251                 this.allocBuffer(max(256,nextPowerOfTwo(
252                         asInteger(scopeView.bounds.width * scopeView.xZoom))))
253         }
255         // ugenScopes
256         *ugenScopes {
257                 if(ugenScopes.isNil, { ugenScopes = Set.new; });
258                 ^ugenScopes
259         }
261         /**
262          *      @return (Server) the default server to scope on
263          */
264         *defaultServer {
265                 ^Server.internal;
266         }
268         /**
269          *      @param  aServer (Server) a server to test for scoping
270          *      @return                 (Boolean) indication whether the server can be scope'ed
271          */
272         *isValidServer { arg aServer;
273                 ^aServer.inProcess;
274         }