Hi guys,
I’m experiencing some (minor) problem here in using ScopeOut2
and buffers and .scope
method.
I’ve found this strange behavior (at least strange to me) in working on a new project.
I was able to reproduce the error below in order for you to better understand what I’m talking about.
Here’s an image showing what I’m trying to do.
I’m using groups, quadraphonic busses, routing from a group to another one.
I’m also using a quadraphonic buffer in order to plot on GUI the scope of the signal.
The most important synth here is the outputstage one which is responsible to control the overall level of the audio passing through it. This is also the synth which is controllable via the GUI (level, solo and mute).
It makes use of the quadraphonic buffer (via ScopeOut2
) and it is sending analysis data (amplitude and peak) with SendReply
.
If you have the patience and want to follow me in my reasoning, here the first of two code snippets I’ve prepared.
This is the main code, responsible of creating the general stuff, like groups, routing, busses, buffer, defining synths, instantiate them and so on.
(
s = Server.local;
s.options.memSize = 8192 * 128;
s.options.blockSize = 64;
s.options.sampleRate = 48000;
s.options.numOutputBusChannels_(4);
s.latency = 0.20;
s.waitForBoot({
"defining network".postln;
~sc = NetAddr.new("127.0.0.1", 15300);
"defining hardware outputs".postln;
~out = 0;
"creating busses".postln;
~bus = Bus.audio(s, 4);
"defining groups".postln;
~grp_main = Group.new();
~grp_outputstages = Group.tail(~grp_main);
~grp_music = Group.new(~grp_main, \addToHead);
"defining scope buffers".postln;
// define a buffer in order for audio signal to be plotted
// on the GUI via ScopeOut2
~buffer_scopeBuf = Buffer.alloc(s, 1024, 4);
// define
SynthDef(\outputstage, {
|
in=0, out=0, amp=1.0,
level = 1.0, lagtime=5.0,
mute = 1.0,
replyID=0
|
var trigger = Impulse.kr(10);
var sig = In.ar( in, 4 );
sig = sig * amp * VarLag.kr( level, lagtime ) * VarLag.kr( mute, 0.05 );
// scope Ugens and measurements
ScopeOut2.ar(sig, ~buffer_scopeBuf);
// measure rms and Peak
4.do({
|i|
SendReply.kr(
trigger,
'/levels', [Amplitude.kr(sig[i]), Peak.ar(sig[i], trigger).lag(0, 3)],
replyID:replyID+i
);
});
// outputs
Out.ar(out, sig);
}).add;
s.sync;
"output stage creation".postln;
~synth_outputstage = Synth(\outputstage,
[
\in, ~bus,
\level, 1.0,
\out, ~out,
\replyID, ~out,
],
target:~grp_outputstages
);
"\nWARNING: setup complete!".postln;
// define some synth to make some test
SynthDef(\test_sine, {
|
atk=0.01,rls=5,amp=1,
freq=40, out=0, pan=0.0
|
var env = EnvGen.ar(Env.perc(atk, rls), doneAction:2);
var sig = SinOsc.ar(freq) * amp * env;
sig = PanAz.ar(4, sig, pan, orientation: 0.0);
Out.ar(out, sig);
}).add;
s.sync;
Pbindef(\test,
\instrument, \test_sine,
\out, ~bus,
\pan, Pwhite(-1.0, 1.0, inf),
\dur, 0.25,
\group, ~grp_music
).quant_(4).play;
});
)
This is the second part of the code, the one you use as a GUI controller for the outputstage synth.
Please don’t mind the strange names for the variables, you will also find variable I’m not using right here. That’s because I’ve taken the code directly from the development code for the project I’m working on, cleaning it up a bit just for the purpose of this post.
Inside this code you will find also the OSC functions to take care of messages coming from the GUI and from the outputstage synth to update the GUI meters.
(
// composite views
var gui_window, gui_compositeView;
var gui_quadro_cv, gui_quadro_vlayout, gui_quadro_scopeView, gui_quadro_slider2D, gui_quadro_slider_level, gui_quadro_slider_spread, gui_quadro_numberbox_level, gui_quadro_mute_button, gui_quadro_slider_lfe_xover_freq, gui_quadro_slider_lfe_sum_compensation;
var gui_quadro_solo_button, gui_room1_solo_button, gui_room2_solo_button, gui_room3_solo_button, gui_room4_solo_button, gui_room5_solo_button, gui_lfe_solo_button;
var gui_meter_critical = 0.99; // 0.99 == -0.0872dB (originariamente -3.dbamp)
var gui_meter_warning = 0.9; // 0.9 == -0.915dB (originariamente -6.dbamp)
var gui_cpu_metering_statictext;
var master_tester_window,master_tester_compositeView;
// DEBUG STUFF END ******************************************************************/
// make a window and bring it to front
gui_window=Window("Sound debugger", Rect(0, 0,510,320)).front;
// create a child of which is a useful element to manage
// all graphical stuff like margins, padding, colors and
// fluid positioning of the elements
gui_compositeView=CompositeView(gui_window,gui_window.view.bounds).background_(Color.black);
// in particular you can use a decorator handle layout management
gui_compositeView.decorator = FlowLayout(gui_compositeView.bounds, margin: 5@5, gap: 5@5);
gui_cpu_metering_statictext = StaticText.new(gui_compositeView, Rect(0,0,400,100))
.string_("empty")
.align_(\center)
.stringColor_(Color.white);
// a routine to write inside the static text box the value of
// * avarage and peak CPU load
// * number of synths and UGEN
(
Routine {
var runningTime, currentDate;
var peakCPU, avgCPU, nSynth, nUgen, running;
inf.do{
// Set the value of the StaticText to the value in the control bus.
// Setting GUI values is asynchronous, so you must use .defer in the system clock.
// Also you must check if the window is still open, since Routine will continue for at least
// one step after you close the window.
{
if(gui_window.isClosed.not) {
runningTime = TempoClock.default.seconds.asTimeString;
currentDate = Date.getDate.asString;
peakCPU = s.peakCPU.round(0.01);
avgCPU = s.avgCPU.round(0.01);
nSynth = s.numSynths;
nUgen = s.numUGens;
running = s.serverRunning;
// print on GUI
gui_cpu_metering_statictext.string = "peak CPU: " ++ peakCPU ++
"\t-\tAvg CPU: " ++ avgCPU ++
"\n# synths: " ++ nSynth ++
"\t-\t# ugens: " ++ nUgen ++
"\ncurrent Date: " ++ currentDate ++
"\nrunning time: " ++ runningTime ++
"\nserver running: " ++ running;
// print on dump file
//[currentDate, runningTime].postln;
};
}.defer;
1.wait;
};
}.play;
);
~level_indicators = [];
// GUI /////////////////////////////////////////
gui_quadro_cv = CompositeView(gui_compositeView,Rect(0,0,500,100)).background_(Color.grey);
gui_quadro_cv.decorator = FlowLayout(gui_quadro_cv.bounds, margin: 15@5, gap: 5@5);
gui_quadro_vlayout = VLayoutView(gui_quadro_cv,Rect(0,0,60,100)).background_(Color.grey);
StaticText.new(gui_quadro_vlayout, Rect(0,0,70,20)).string_("quadro").stringColor_(Color.black);
gui_quadro_numberbox_level = NumberBox(gui_quadro_vlayout, Rect(0, 50, 20, 20))
.value_(1.0)
.clipHi_(1.0)
.clipLo_(0.0);
gui_quadro_mute_button = Button(gui_quadro_vlayout, Rect(0,0,20,20))
.states_([
["M", Color.white, Color.grey],
["M", Color.black, Color.red]
]);
gui_quadro_slider_level = Slider(gui_quadro_cv, Rect(0, 0, 20, 85))
.value_(1.0);
// now that we have declared all the necessay gui interfaces, we can add actions
gui_quadro_numberbox_level.action_({|nb|
//("numberBox changed" ++ nb.value).postln;
~sc.sendMsg("/test/level", nb.value, 0.500);
gui_quadro_slider_level.value_(nb.value); //TODO: here we need a forward declaration kind of behaviour
});
gui_quadro_mute_button.action_({|bt|
("button"++bt.value).postln;
switch( bt.value,
0, {
//gui_quadro_numberbox_level.valueAction_(1.0)
~sc.sendMsg("/test/Mute", 1);
},
1, {
//gui_quadro_numberbox_level.valueAction_(0.0)
~sc.sendMsg("/test/Mute", 0);
}
);
});
gui_quadro_slider_level.action_({
|sl|
//("slider changed" ++ sl.value).postln;
gui_quadro_numberbox_level.valueAction_(sl.value);
});
gui_quadro_scopeView = ScopeView(gui_quadro_cv, Rect(0,0,90,90)); // this is SCScope
gui_quadro_scopeView.bufnum = ~buffer_scopeBuf;
gui_quadro_scopeView.server_(s);
gui_quadro_scopeView.start;
4.do({
|i|
~level_indicators = ~level_indicators ++ LevelIndicator(gui_quadro_cv, Rect(0,0,10,85)).front;
~level_indicators.last.warning = gui_meter_warning;
~level_indicators.last.critical = gui_meter_critical;
~level_indicators.last.drawsPeak = true; // optionally show peak level
~level_indicators.last.numTicks = 9;
~level_indicators.last.numMajorTicks = 3;
//d.style = \led; d.stepWidth = 9;
~level_indicators.last.style = \continuous;
});
// SOLO ////////////////////////////////////////////////
gui_quadro_solo_button = Button(gui_quadro_vlayout, Rect(0,0,20,20))
.states_([
["S", Color.white, Color.grey],
["S", Color.black, Color.yellow]
])
.action_({
|bt|
("button"++bt.value).postln;
});
// OSC /////////////////////////////////////////////////
~osc_gui_levels = OSCFunc({arg msg;
{
~level_indicators[ msg[2] ].value = msg[3].ampdb.linlin(-80, 0, 0.0, 1);
~level_indicators[ msg[2] ].peakLevel = msg[4].ampdb.linlin(-80, 0, 0.0, 1);
}.defer;
}, '/levels', s.addr);
~osc_quadro_level = OSCFunc({
| msg, time, addr, recvPort |
postln("quadro level" + msg[1]);
~grp_outputstages.do({
|item|
item.set(\level, msg[1], \lagtime, msg[2]);
});
}, "/test/level", recvPort:15300);
~osc_quadro_mute = OSCFunc({
| msg, time, addr, recvPort |
postln("quadro mute" + msg[1]);
~grp_outputstages.do({
|item|
item.set(\mute, msg[1]);
});
}, "/test/Mute", recvPort:15300);
);
now try to evaluate this line in order to plot the bus
~bus.scope;
If you are experiencing the same error I’m getting, the console will show you something like this
ScopeOut2: Requested scope buffer unavailable! (index: 0, channels: 4, size: 4096)
what does it means?
what kind of error is this and how can I prevent my code from generating it?
Maybe I’m doing something wrong in buffer allocation? Or maybe I’m using the ScopeOut2
class inappropriately?
The same error also appears if I try to use the following line
s.scope;
It seems the stethoscope windows is not showing the correct signal at all.
For example it you use the GUI to set the output stage level to -inf (drag the slider all the way down), you sould not hear anything on your headphones (which is perfectly correct) however you shoud always see something happening on the ~bus
, which is feeding the outputstage synth but the scope is showing silence on all 4 channels.
Why the scope seems to show only the hardware output?
I hope I’ve explained the point here and I will provide more details If you want to dig deeper into it.
Thank you so much for your help and support