Mapping and Visualization with SuperCollider
上QQ阅读APP看书,第一时间看更新

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 scopemethod 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 waveforms

Note

Lissajous curves, named after the 19th century French mathematician Jules Antoine Lissajous, represent the ratio between two different signals and are typically used as phase scopes to visualize the phase differences between the left and right channels of a stereo signal.

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).