- Mapping and Visualization with SuperCollider
- Marinos Koutsomichalis
- 649字
- 2021-04-02 10:18:16
Metering levels
Besides plotting the actual signal or dataset, there are situations where we merely want to monitor changes in some magnitude. The most typical scenario is metering the amplitude of some signal, but we could meter anything really, as long as it is represented by some numerical value.
Monitoring signals
Generic metering in SuperCollider is primarily addressed by the LevelIndicator
class. To monitor some magnitude specific to a signal, we first need to track it, write the resulting values to some control-rate instance of Bus
or to some instance of Buffer
, and later use an instance of Routine
to manually update the value of LevelIndicator
as appropriate. For now, we will limit ourselves to using the Amplitude
UGen to only track the amplitude; in Chapter 6, Data Acquisition and Mapping, we will discuss how to track other kinds of magnitudes and how to extract information out of audio signals. Note also that a convenient meter
method does exist, yet it is only limited to instances of Server
and to monitoring the global I/O streams of all its default channels (for example, Server.default.meter
).
( // Simple Level Metering Server.default.waitForBoot({ // create the parent window var window = Window.new("Level Metering", Rect(200,400,60,220)).front .onClose_({ // stop routine when the window is closed updateIndicator.stop; sound.free; }); var bus = Bus.control(); // create a Bus to store amplitude data // an audio signal var sound = { var sound = WhiteNoise.ar(Demand.kr(Dust.kr(20),0,Dbrown(0,1,0.3))); var amp = Amplitude.kr(sound); // track the signal's amplitude Out.kr(bus, amp); // write amplitude data to control bus Out.ar(0,sound); // write sound to output bus }.play; // create and customize Indicator var indicator = LevelIndicator(window,Rect(10,10,40,200)) .warning_(0.5) // set warning level .critical_(0.7) // set critical level .background_(Color.cyan) // set Color .numTicks_(12) // set number of measurement lines .numMajorTicks_(3) // set number of major measurement lines .drawsPeak_(true); // draw Peak Values // update the Indicator's value with a routine var updateIndicator = fork{loop{ bus.get({ // get current value from the bus arg value; {indicator.value_(value); // set Indicator's value indicator.peakLevel_(value); // set Indicator's peak value }.defer(); // schedule in the AppClock }); 0.01.wait; // indicator will be updated every 0.01 seconds }}; }); )
Again, note that we use a defer
block to schedule anything that is GUI-related to the AppClock
subclass.
Monitoring numerical data
Apart from a signal's magnitude, LevelIndicator
can be used to monitor any kind of data we may be interested in. In the following code, we loop through an eight-channel multidimensional dataset:
( // Monitoring a complex numerical Dataset var indicators, updateIndicators; var index = 0; // a global index used to iterate through the dataset var dataset = Array.fill(8,{Array.fill(1000,{rrand(0,1.0)})}); // a multi-dimensional dataset // create window var window = Window.new("Monitoring a complex numerical dataset", 360@210).front.onClose_({ updateIndicators.stop }); window.addFlowLayout; // add flowLayout // create and customize 8 Level indicators indicators = Array.fill(8, {LevelIndicator(window,40@200)}); indicators.do { arg item; item.warning_(0.8).critical_(0.9).background_(Color.cyan).drawsPeak_(true); }; // update the indicators with a routine updateIndicators = fork{loop{ indicators.do{ arg item, i; { var value = dataset[i][index]; // read value from the dataset item.value_(value); // set each Indicator's value item.peakLevel_(value); // set each Indicator's peak value }.defer(); // schedule in the AppClock }; // increment index or set to 0 if it has exceeded dataset's size if ( index < 1000) {index = index + 1;} {index = 0; }; 0.1.wait; // indicators will be updated every 0.1 seconds }}; )
This time an array of LevelIndicator
objects is used instead of a singleton element, and of course, there is no need for some specialized tracking UGen. We merely use an instance of Routine
to access the dataset by means of a global index, which is accordingly incremented once some datum is read. This is so that it always reflects the position of the next object. We will also need an if
construct to zero out the index once it has exceeded our dataset's size in order to reiterate from the beginning.