3 QStethoscope created by copying SCStethoscope and adjusting code for Qt GUI.
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;
22 var screenBounds = QWindow.screenBounds;
23 var x = 520 + (ugenScopes.size * (223 + 5)) + 5;
25 var y = floor(right / screenBounds.width) * 297 + 10;
26 if(right > screenBounds.right) { x = floor(right % screenBounds.width / 223) * (223 + 5) };
30 makeBounds { arg size = 223; ^Rect(297, 5, size, size) }
32 makeWindow { arg view;
35 window = QWindow("stethoscope", this.makeBounds);
37 view.decorator = FlowLayout(window.view.bounds);
39 window.onClose = { this.free };
42 if(view.decorator.isNil, {
43 "QStethoscope: makeWindow added a decorator to view".warn;
44 view.decorator = FlowLayout(view.bounds);
47 scopeView = QScope(view,
48 Rect(0,0, view.bounds.width - 10 - 20 - 4, view.bounds.height - 40)
50 scopeView.background = Color.black;
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;
64 i = this.spec.map(x.value);
66 this.xZoom = zoomspec.map(x.value)
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; };
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 };
82 nChanView.font = QFont("Monaco", 9);
83 QStaticText(view, Rect(10, 10, 20, 20)).visible_(false);
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)
94 yZoomSlider.resize = 6;
95 yZoomSlider.value = zoomspec.unmap(this.yZoom);
96 yZoomSlider.background = Color.grey(0.6);
97 yZoomSlider.focusColor = Color.clear;
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 } };
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 };
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) };
138 if(synth.isPlaying.not) {
139 synth = SynthDef("stethoscope", { arg in, switch, bufnum;
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 },
147 synth.isPlaying = true;
148 NodeWatcher.register(synth);
155 if(synth.isPlaying) { synth.free };
157 if(server.scopeWindow === this) { server.scopeWindow = nil }
165 numChannels_ { arg n;
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
176 if(isPlaying) { this.run };
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)
191 rate_ { arg argRate=\audio;
192 if(rate === argRate) { ^this };
193 if(argRate === \audio)
195 if(synth.isPlaying) { synth.set(\switch, 0) };
202 if(synth.isPlaying) { synth.set(\switch, 1) };
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) }
217 scopeView.xZoom = val;
219 xZoomSlider.value = zoomspec.unmap(val);
222 scopeView.yZoom = val;
224 yZoomSlider.value = zoomspec.unmap(val);
229 zoom_ { arg val; this.xZoom_(val ? 1) }
232 if(numChannels < 2 and: { val == 2 }) {
233 "QStethoscope: can't do x/y scoping with one channel".warn;
236 scopeView.style = style = val
242 scopeView.waveColors = if(\audio === rate) {
243 Array.fill(numChannels, { Color.new255(255, 218, 000) });
245 Array.fill(numChannels, { Color.new255(125, 255, 205) });
249 switchRate { if(rate === \control) { this.rate = \audio } { this.rate = \control } }
252 this.index = server.options.numOutputBusChannels;
253 this.numChannels = server.options.numInputBusChannels;
257 this.numChannels = server.options.numOutputBusChannels;
260 this.allocBuffer(max(256,nextPowerOfTwo(
261 asInteger(scopeView.bounds.width * scopeView.xZoom))))
266 if(ugenScopes.isNil, { ugenScopes = Set.new; });
271 * @return (Server) the default server to scope on
278 * @param aServer (Server) a server to test for scoping
279 * @return (Boolean) indication whether the server can be scope'ed
281 *isValidServer { arg aServer;