Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync level meters output with GUI drawing #16

Open
falkTX opened this issue Aug 14, 2022 · 18 comments
Open

sync level meters output with GUI drawing #16

falkTX opened this issue Aug 14, 2022 · 18 comments

Comments

@falkTX
Copy link
Collaborator

falkTX commented Aug 14, 2022

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:

  1. plugin is running, no UI visible yet. plugin has internal values for level metering but we skip them to save cpu load
  2. UI is open, and tells dsp to start level meter stuff
  3. dsp now has level metering active, and keep setting the level-meter value(s) across several audio cycles (that is, the maximum or average value keeps being calculated until told otherwise)
  4. UI repaint happens, it reads the last level-meter value(s) and tells the dsp to start again
  5. dsp sets its internal maximum/average to 0 and continues with the level-metering, same as step 3
  6. another UI repaint, same as step 4
  7. steps 5 and 6 keep going while UI is open
  8. UI is closed, it tells the dsp side it can stop the level metering

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?

@jkbd
Copy link

jkbd commented Aug 15, 2022

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.

@sletz
Copy link

sletz commented Aug 15, 2022

This kind of control outputs is typically coded in Faust using hbargraph/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.

@sletz
Copy link

sletz commented Aug 15, 2022

@jkbd BTW I guess https://github.com/trummerschlunk/soundsgood/blob/master/lib/ebur128.dsp would be a nice contribution to the Faust libraries... ((-;

@jkbd
Copy link

jkbd commented Aug 15, 2022

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

@x42
Copy link
Contributor

x42 commented Aug 17, 2022

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.

@falkTX
Copy link
Collaborator Author

falkTX commented Aug 17, 2022

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.

@sletz
Copy link

sletz commented Aug 18, 2022

the flow is typically:

Any simple example of C++ code that does that?

@falkTX
Copy link
Collaborator Author

falkTX commented Aug 18, 2022

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.

@sletz
Copy link

sletz commented Aug 18, 2022

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.

@falkTX
Copy link
Collaborator Author

falkTX commented Aug 18, 2022

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.

@jkbd
Copy link

jkbd commented Aug 18, 2022

Perhaps I should read this sooner than later: https://faustdoc.grame.fr/manual/architectures/.

@falkTX
Copy link
Collaborator Author

falkTX commented Aug 18, 2022

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?

@sletz
Copy link

sletz commented Aug 18, 2022

Your UI architecture would have to implement addHorizontalBargraph/addVerticalBargraph so that to keep (= register) the FAUSTFLOAT* 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.

@falkTX
Copy link
Collaborator Author

falkTX commented Aug 18, 2022

Might be easier to handle it separately and then integrate it in soundsgood.
Is there an example faust file that includes this?

@sletz
Copy link

sletz commented Aug 18, 2022

@falkTX
Copy link
Collaborator Author

falkTX commented Aug 18, 2022

hah i meant faust example files. the architecture stuff I can do pretty easily, not so much the faust stuff.

@sletz
Copy link

sletz commented Aug 18, 2022

Some analysis DSP here: https://faustdoc.grame.fr/examples/analysis/

@x42
Copy link
Contributor

x42 commented Aug 18, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants