Scoping signals
Plotter
and SoundFileView
can be exploited in several ways, but they are not really efficient for scoping real-time audio signals. SuperCollider features dedicated built-in visualizers that we use to easily scope signals in both time and frequency domains.
Scoping waveforms
As far as signals are concerned, we can easily plot their waveforms in real time by means of simply invoking scope
on UGen graphs and instances of Bus
or Server
. The scope
method is a convenient one too, which creates an instance of Stethoscope
in the background; the latter being a fully featured virtual oscilloscope. An example of this is shown in the following code:
( // Stethoscope Example Server.default.waitForBoot({ // wait for server to boot {SinOsc.ar}.scope; // scope a UGen graph }); )
An instance of Stethoscope
features dedicated controls so that we can configure its display ranges; select which and how many instances of Bus
to plot; and switch between overlay, non-overlay, or Lissajous (that is X/Y) representational modes. We can design custom oscilloscopes through ScopeView
, which is a powerful, highly parameterized waveform visualizer on its own. However, at the time of writing, and in defiance of Stethoscope
being fully functional on both kinds of servers
, ScopeView
cooperated only with the internal one. Other than this, its use involves linking it with a manually allocated instance of Buffer
whose contents are to be constantly updated using a ScopeOut
UGen (and not with an Out
UGen). In the following code, we have implemented a custom waveform/phase scope:
( // a custom dual oscilloscope Server.default = Server.internal; // make internal the default server Server.default.waitForBoot({ var waveScope, phaseScope; // the two scopes // allocate two audio buffers var bufferA = Buffer.alloc(Server.default, 1024,2); var bufferB = Buffer.alloc(Server.default, 1024,2); // a stereo signal var sound = { var signal = Resonz.ar( [ ClipNoise.ar(1.7), ClipNoise.ar(1.8) ], SinOsc.ar(1000).range(100,500)); // a stereo signal ScopeOut.ar(signal, bufferA); // update first buffer ScopeOut.ar(signal, bufferB); // update second buffer Out.ar(0,signal); // write to output }.play; // create the main Window var window = Window("Dual Oscilloscope", 640@320).front .onClose_({ // on close stop sound and free buffers sound.free; bufferA.free; bufferB.free; }); window.addFlowLayout; // add a flowLayout to the window // create the ScopeViews and set their buffers waveScope = ScopeView(window,314@310).bufnum_(bufferA.bufnum); phaseScope = ScopeView(window,314@310).bufnum_(bufferB.bufnum); // customize waveScope waveScope.style_(1) // overlay channels .waveColors_([Color.red, Color.yellow]).background_(Color.magenta(0.4)) .xZoom_(1.7).yZoom_(1.2); // scaling factors // customize phaseScope phaseScope.style_(2) // lissajous mode .waveColors_([Color.magenta]).background_(Color.cyan(0.3)) .xZoom_(1.2).yZoom_(1.2); // scaling factors }) )
Our custom scope is shown in the following screenshot:
Scoping spectra
Frequency domain refers to the representation of signals where the frequency is mapped to the horizontal dimension and amplitude to the vertical dimension. As far as real-time plotting in the frequency domain is concerned, much like waveform scoping, we can either use FreqScope
to globally scope the default output of Server
; the scopeResponse
method to scope UGen graphs on the fly; or the more sophisticated FreqScopeView
method to design custom frequency visualizers. Yet, in spite of them being very similar in spirit, there are a couple of major differences between the latter and ScopeView
, as illustrated in the following code:
( // a custom Frequency Analyzer Server.default = Server.local; // set local as the default server Server.default.waitForBoot({ // create the parent window var window = Window("Frequency Analyzer", 640@480).front .onClose_({ // on close sound.free; // stop sound scope.kill; // kill the analyzer }); // the bus to scope var bus = Bus.audio(Server.default,2); // a stereo signal var sound = { var signal = Resonz.ar( [ ClipNoise.ar(1.7), ClipNoise.ar(1.8) ], SinOsc.ar(1000).range(100,500)); // a stereo signal Out.ar(bus,signal); // update bus for scoping Out.ar(0,signal); // write to output }.play; // the frequency scope var scope = FreqScopeView(window,640@480).active_(true); // activate it scope.background_(Color.red).waveColors_([Color.yellow]); // set colors scope.dbRange_(120); // set amplitude range (in decibels) scope.inBus_(bus); // select Bus to scope }) )
Here, we read the signal directly from an instance of Bus
, rather than Buffer
. Moreover, we have to explicitly set the active
variable of FreqScope
to true
, else no scoping will occur. Ironically enough, as of this writing, FreqScopeView
will only collaborate with instances of the localhost Server
, thereby making it impossible to have both ScopeView
and FreqScopeView
based visualizers scoping the very same signal (although we can do so using Stethoscope
instead).