Open falkTX opened 2 years ago
I have no experience with this. But I guess, the way is to embed the FAUST-generated class mydsp
into something, that properly manages the GUI. (To look at this class, just run faust soundsgood.dsp
.)
Instead of patching the generated code to the GUI needs, I assume, one can make FAUST generate what one needs by using "architecture files". But I am just guessing.
This kind of control outputs is typically coded in Faust usinghbargraph/vbargraph
primitives (often along with attach
) which can deliver one value per block. Then the GUI C++ code can possibly interpret those values the way it needs.
@jkbd BTW I guess https://github.com/trummerschlunk/soundsgood/blob/master/lib/ebur128.dsp would be a nice contribution to the Faust libraries... ((-;
@sletz I take this as compliment! I have some details in mind that will need a bit of my work first. I'll get back to you within two weeks, I think.
For LV2 plugins I use an Atom port. As soon as the GUI opens, it ask the plugin to send values via Atom messages. Usually limited at 25Hz intervals (via a sample-counter in the DSP code), and when the UI is unmapped (closed, hidden or obscured). An Atom message asks the DSP to stop sending those. Peak hold etc is all on the DSP side.
Though this is likely not easily done in FAUST.
Though this is likely not easily done in FAUST.
this is my fear too. faust seems to be doing exactly what I was hoping it would not, calculating these values on an audio block basis, which for one leads to inconsistent results depending on the audio buffer/block size the user has setup.
the flow is typically:
Any simple example of C++ code that does that?
the flow is typically:
Any simple example of C++ code that does that?
Can even write something quick here.
on dsp side we have:
struct dsp {
float fOutLeft = 0.f;
float fOutRight = 0.f;
std::atomic<bool> fNeedsReset { false };
void run(const float** buffer, uint32_t frames) override
{
float tmp;
float tmpLeft = 0.f;
float tmpRight = 0.f;
for (uint32_t i=0; i<frames; ++i)
{
// left
tmp = std::abs(buffer[0][i]);
if (tmp > tmpLeft)
tmpLeft = tmp;
// right
tmp = std::abs(buffer[1][i]);
if (tmp > tmpRight)
tmpRight = tmp;
}
if (tmpLeft > 1.0f)
tmpLeft = 1.0f;
if (tmpRight > 1.0f)
tmpRight = 1.0f;
if (fNeedsReset.getAndSwap(false))
{
fOutLeft = tmpLeft;
fOutRight = tmpRight;
}
else
{
if (tmpLeft > fOutLeft)
fOutLeft = tmpLeft;
if (tmpRight > fOutRight)
fOutRight = tmpRight;
}
}
};
See how fOutLeft
and fOutRight
are kept across several audio block cycles, and only reset when fNeedsReset
was set.
This makes them accumulate the max value until told otherwise.
Also there are no log10
or similar calls to convert into dB scale, this part can be done by the UI later.
So the DSP loop computes tmpLeft
and tmpRight
on a given block size (= frames
), then the outside code use them and possibly update fOutLeft
and fOutRight
.
In Faust model you would use bargraph
to compute and produce the equivalent of tmpLeft
and tmpRight
(each block), then have some external C++ in the architecture file to use them. So I don't see real blocking issue here.
The "blocking" issue is just doing it, because we do not at the moment. My faust knowledge is very limited, close to zero, so I am unable to do this part at the moment.
Perhaps I should read this sooner than later: https://faustdoc.grame.fr/manual/architectures/.
I already have an architecture file. So custom code can be used. Seems like for this it is just a matter of doing the 2nd part of the code above on the architecture file, as @sletz describes. How would the C++ code that fetches these values from the dsp look like?
Your UI architecture would have to implement addHorizontalBargraph/addVerticalBargraph
so that to keep (= register) theFAUSTFLOAT* zone
parameter. Assuming hbargraph/vbargraph
primitives are used properly in the Faust DSP code, those zones are written at each audio block. Then the outside C++ code can read and use them.
Might be easier to handle it separately and then integrate it in soundsgood. Is there an example faust file that includes this?
Some "heavy" architectures files here:
hah i meant faust example files. the architecture stuff I can do pretty easily, not so much the faust stuff.
Some analysis DSP here: https://faustdoc.grame.fr/examples/analysis/
Unless you use shared memory between UI and DSP, you can only send new values to the UI at the end of each process cycle.
So in this case it is up to the architecture (LV2 wrapper) to read meter values from the DSP, cache them as needed, send then on demand to the GUI, and also handle peak-hold reset requests from the GUI.
this is an issue as a form of question, because I dont know faust enough to understand how this is typically done.
we want to have level meters on the gui, but in order to make these meters actually useful the dsp level-meter side needs to keep going until notified by the ui that it has read the meter values.
the amateur approach is typically to calculate RMS and other values within a process cycle, every cycle, and set the passive / output meter value to that. this is not a good approach, as the meter values would then be block-size dependent. also there could be several meter values calculated and reset before the GUI even shows a single one.. in short, level meters should run as commanded by the UI.
the flow is typically:
There are possible deviations to this, we can keep level metering active with a fixed time (in ms) if we want for example lv2 control outputs to have useful values even while UI is not open.
now my question is... how do we deal with this with faust? can we specify a time-window for the metering? can it run "forever" (not bound to time) and wait for a signal to reset its internal values?