Adapting Faust UI architecture to PGM

I’m putting this thread here to post all questions I will eventually have about this effort. I’ve recently undertaken a medium complexity Faust project and trying to see how that builds into a VST using JUCE. I also am trying PGM.

The first things I notice is that (perhaps not surprisingly) some of the classes are different. Generic sliders and knobs work OK. In Faust there are also “bargraph” and “menu” UI items, which attach to signals in the DSP code to measure and/or control. These don’t correspond directly to the “meter” or “listbox” objects in PGM. Those are just the two I’m aware of.

The next thing to consider will be the workflow. The UI tree structure and some level of detail (knobs, sliders, scales, etc.) are contained in metadata per control in the Faust code. Again, most of this maps to corresponding items in PGM so my thoughts about the process are:

Prototype DSP and UI structure in Faust

  • export to PGM includes new UI classes in the C++ and an XML file for the “default” layout/look and feel.

Work on UI details in PGM

  • add features such as plots, which Faust is completely ignorant of - this actually seems like a bad idea; Faust should at least acknowledge or provide hooks, yes possibly based on PGM specific metadata. Otherwise the C++ code will constantly need to be hand edited to add that stuff.
  • add/override design details (bitmaps, etc.)
  • save XML

Update DSP/UI (list of elements add/delete, layout changes) in Faust

  • export C++ and XML again
  • merge with PGM modified XML (??)

Have to figure out where all the details go, and develop some discipline since it’s unlikely that back-annotation of layout info from the PGM XML to Faust is going to happen. Faust’s big attractiveness (to me anyway) is the speed at which you can prototype things to a very competent level. Combine that with the libraries and multiple targets, it’s very compelling. Even still though, you have to use its language structure rather than WYSIWYG to design the UI layout. Whereas PGM lets you drag and drop a layout.

Here are “widgets” from Faust’s point of view (from any Faust generated C++ file).

            if (type == "hgroup") {
                REAL_UI(ui_interface)->openHorizontalBox(it.label.c_str());
            } else if (type == "vgroup") {
                REAL_UI(ui_interface)->openVerticalBox(it.label.c_str());
            } else if (type == "tgroup") {
                REAL_UI(ui_interface)->openTabBox(it.label.c_str());
            } else if (type == "vslider") {
                REAL_UI(ui_interface)->addVerticalSlider(it.label.c_str(), REAL_ADR(index), init, min, max, step);
            } else if (type == "hslider") {
                REAL_UI(ui_interface)->addHorizontalSlider(it.label.c_str(), REAL_ADR(index), init, min, max, step);
            } else if (type == "checkbox") {
                REAL_UI(ui_interface)->addCheckButton(it.label.c_str(), REAL_ADR(index));
            } else if (type == "soundfile") {
                REAL_UI(ui_interface)->addSoundfile(it.label.c_str(), it.url.c_str(), SOUNDFILE_ADR(index));
            } else if (type == "hbargraph") {
                REAL_UI(ui_interface)->addHorizontalBargraph(it.label.c_str(), REAL_ADR(index), min, max);
            } else if (type == "vbargraph") {
                REAL_UI(ui_interface)->addVerticalBargraph(it.label.c_str(), REAL_ADR(index), min, max);
            } else if (type == "nentry") {
                REAL_UI(ui_interface)->addNumEntry(it.label.c_str(), REAL_ADR(index), init, min, max, step);
            } else if (type == "button") {
                REAL_UI(ui_interface)->addButton(it.label.c_str(), REAL_ADR(index));
            } else if (type == "close") {
                REAL_UI(ui_interface)->closeBox();
            }

and here are the ones PGM supports:

[foleys_AutoOrientationSlider.h]
[foleys_FileBrowserDialog.cpp]
[foleys_FileBrowserDialog.h]
[foleys_MagicLevelMeter.cpp]
[foleys_MagicLevelMeter.h]
[foleys_MagicPlotComponent.cpp]
[foleys_MagicPlotComponent.h]
[foleys_MidiLearnComponent.cpp]
[foleys_MidiLearnComponent.h]
[foleys_XYDragComponent.cpp]
[foleys_XYDragComponent.h]

Also containers are described here:

We’re going to need to come up with a new class that is similar to the Faust JUCE “uiVUMeter” class implementation but compatible with PGM as well. It can act like an input-only slider as far as connecting to a signal.

/**
 * \brief   Intern class for VU-meter
 * \details There is no JUCE widgets for VU-meter, so its fully designed in this class.
 */
class uiVUMeter : public uiComponent, public juce::SettableTooltipClient, public juce::Timer
{
    
    private:
    
        FAUSTFLOAT fLevel;               // Current level of the VU-meter.
        FAUSTFLOAT fMin, fMax;           // Linear range of the VU-meter.
        FAUSTFLOAT fScaleMin, fScaleMax; // Range in dB if needed.
        bool fDB;                        // True if it's a dB VU-meter, false otherwise.
        VUMeterType fStyle;
        juce::String fUnit;
        juce::Label fLabel;               // Name of the VU-meter.
    
        bool isNameDisplayed()
        {
            return (!(getName().startsWith("0x")) && getName().isNotEmpty());
        }
        
        /** Give the right coordinates and size to the text of Label depending on the VU-meter style */
        void setLabelPos()
        {
            if (fStyle == VVUMeter) {
                // -22 on the height because of the text box.
                fLabel.setBounds((getWidth()-50)/2, getHeight()-22, 50, 20);
            } else if (fStyle == HVUMeter) {
                isNameDisplayed() ? fLabel.setBounds(63, (getHeight()-20)/2, 50, 20)
                : fLabel.setBounds(3, (getHeight()-20)/2, 50, 20);
            } else if (fStyle == NumDisplay) {
                fLabel.setBounds((getWidth()-kNumDisplayWidth)/2,
                                 (getHeight()-kNumDisplayHeight/2)/2,
                                 kNumDisplayWidth,
                                 kNumDisplayHeight/2);
            }
        }
        
        /** Contain all the initialization need for our Label */
        void setupLabel(juce::String tooltip)
        {
            setLabelPos();
            fLabel.setEditable(false, false, false);
            fLabel.setJustificationType(juce::Justification::centred);
            fLabel.setText(juce::String((int)*fZone) + " " + fUnit, juce::dontSendNotification);
            fLabel.setTooltip(tooltip);
            addAndMakeVisible(fLabel);
        }
        
        /**
         * \brief   Generic method to draw an horizontal VU-meter.
         * \details Draw the background of the bargraph, and the TextBox box, without taking
         *          care of the actual level of the VU-meter
         * \see     drawHBargraphDB
         * \see     drawHBargraphLin
         *
         * \param   g       JUCE graphics context, used to draw components or images.
         * \param   width   Width of the VU-meter widget.
         * \param   height  Height of the VU-meter widget.
         * \param   level   Current level that needs to be displayed.
         * \param   dB      True if it's a db level, false otherwise.
         */
        void drawHBargraph(juce::Graphics& g, int width, int height)
        {
            float x;
            float y = (float)(getHeight()-height)/2;
            if (isNameDisplayed()) {
                x = 120;
                width -= x;
                // VUMeter Name
                g.setColour(juce::Colours::black);
                g.drawText(getName(), 0, y, 60, height, juce::Justification::centredRight);
            } else {
                x = 60;
                width -= x;
            }
            
            // VUMeter Background
            g.setColour(juce::Colours::lightgrey);
            g.fillRect(x, y, (float)width, (float)height);
            g.setColour(juce::Colours::black);
            g.fillRect(x+1.0f, y+1.0f, (float)width-2, (float)height-2);
            
            // Label Window
            g.setColour(juce::Colours::darkgrey);
            g.fillRect((int)x-58, (getHeight()-22)/2, 52, 22);
            g.setColour(juce::Colours::green.withAlpha(0.8f));
            g.fillRect((int)x-57, (getHeight()-20)/2, 50, 20);
            
            // Call the appropriate drawing method for the level.
            fDB ? drawHBargraphDB (g, y, height) : drawHBargraphLin(g, x, y, width, height);
        }
        
        /**
         * Method in charge of drawing the level of a horizontal dB VU-meter.
         *
         * \param   g       JUCE graphics context, used to draw components or images.
         * \param   y       y coordinate of the VU-meter.
         * \param   height  Height of the VU-meter.
         * \param   level   Current level of the VU-meter, in dB.
         */
        void drawHBargraphDB(juce::Graphics& g, int y, int height)
        {
            // Drawing Scale
            g.setFont(9.0f);
            g.setColour(juce::Colours::green);
            for (int i = -10; i > fMin; i -= 10) {
                paintScale(g, i);
            }
            for (int i = -6; i < fMax; i += 3)  {
                paintScale(g, i);
            }
            
            int alpha = 200;
            FAUSTFLOAT dblevel = dB2Scale(fLevel);
            
            // We need to test here every color changing levels, to avoid to mix colors because of the alpha,
            // and so to start the new color rectangle at the end of the previous one.
            
            // Drawing from the minimal range to the current level, or -10dB.
            g.setColour(juce::Colour((juce::uint8)40, (juce::uint8)160, (juce::uint8)40, (juce::uint8)alpha));
            g.fillRect(dB2x(fMin), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(fMin), dB2x(-10)-dB2x(fMin)), (float)height-2);
            
            // Drawing from -10dB to the current level, or -6dB.
            if (dblevel > dB2Scale(-10)) {
                g.setColour(juce::Colour((juce::uint8)160, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
                g.fillRect(dB2x(-10), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(-10), dB2x(-6)-dB2x(-10)), (float)height-2);
            }
            // Drawing from -6dB to the current level, or -3dB.
            if (dblevel > dB2Scale(-6)) {
                g.setColour(juce::Colour((juce::uint8)220, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
                g.fillRect(dB2x(-6), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(-6), dB2x(-3)-dB2x(-6)), (float)height-2);
            }
            // Drawing from -3dB to the current level, or 0dB.
            if (dblevel > dB2Scale(-3)) {
                g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)160, (juce::uint8)20, (juce::uint8)alpha));
                g.fillRect(dB2x(-3), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(-3), dB2x(0)-dB2x(-3)), (float)height-2);
            }
            // Drawing from 0dB to the current level, or the max range.
            if (dblevel > dB2Scale(0)) {
                g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)0, (juce::uint8)20, (juce::uint8)alpha));
                g.fillRect(dB2x(0), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(0), dB2x(fMax)-dB2x(0)), (float)height-2);
            }
        }
        
        /**
         * Method in charge of drawing the level of a horizontal linear VU-meter.
         *
         * \param   g       JUCE graphics context, used to draw components or images.
         * \param   x       x coordinate of the VU-meter.
         * \param   y       y coordinate of the VU-meter.
         * \param   height  Height of the VU-meter.
         * \param   width   Width of the VU-meter.
         * \param   level   Current level of the VU-meter, in linear logic.
         */
        void drawHBargraphLin(juce::Graphics& g, int x, int y, int width, int height)
        {
            int alpha = 200;
            juce::Colour c = juce::Colour((juce::uint8)255, (juce::uint8)165, (juce::uint8)0, (juce::uint8)alpha);
            
            // Drawing from the minimal range to the current level, or 20% of the VU-meter
            g.setColour(c.brighter());
            g.fillRect(x+1.0f, y+1.0f, juce::jmin<float>(fLevel*(width-2), 0.2f*(width-2)), (float)height-2);
            // Drawing from 20% of the VU-meter to the current level, or 90% of the VU-meter
            if (fLevel > 0.2f) {
                g.setColour(c);
                g.fillRect(x+1.0f + 0.2f*(width-2), y+1.0f, juce::jmin<float>((fLevel-0.2f) * (width-2), (0.9f-0.2f) * (width-2)), (float)height-2);
            }
            // Drawing from 90% of the VU-meter to the current level, or the maximal range of the VU-meter
            if (fLevel > 0.9f) {
                g.setColour(c.darker());
                g.fillRect(x+1.0f + 0.9f*(width-2), y+1.0f, juce::jmin<float>((fLevel-0.9f) * (width-2), (1.0f-0.9f) * (width-2)), (float)height-2);
            }
        }
        /**
         * \brief   Generic method to draw a vertical VU-meter.
         * \details Draw the background of the bargraph, and the TextBox box, without taking
         *          care of the actual level of the VU-meter
         * \see     drawHBargraphDB
         * \see     drawHBargraphLin
         *
         * \param   g       JUCE graphics context, used to draw components or images.
         * \param   width   Width of the VU-meter widget.
         * \param   height  Height of the VU-meter widget.
         * \param   level   Current level that needs to be displayed.
         * \param   dB      True if it's a db level, false otherwise.
         */
        void drawVBargraph(juce::Graphics& g, int width, int height)
        {
            float x = (float)(getWidth()-width)/2;
            float y;
            if (isNameDisplayed()) {
                y = (float)getHeight()-height+15;
                height -= 40;
                // VUMeter Name
                g.setColour(juce::Colours::black);
                g.drawText(getName(), getLocalBounds(), juce::Justification::centredTop);
            } else {
                y = (float)getHeight()-height;
                height -= 25;
            }
            
            // VUMeter Background
            g.setColour(juce::Colours::lightgrey);
            g.fillRect(x, y, (float)width, (float)height);
            g.setColour(juce::Colours::black);
            g.fillRect(x+1.0f, y+1.0f, (float)width-2, (float)height-2);
            
            // Label window
            g.setColour(juce::Colours::darkgrey);
            g.fillRect(juce::jmax((getWidth()-50)/2, 0), getHeight()-23, juce::jmin(getWidth(), 50), 22);
            g.setColour(juce::Colours::green.withAlpha(0.8f));
            g.fillRect(juce::jmax((getWidth()-48)/2, 1), getHeight()-22, juce::jmin(getWidth()-2, 48), 20);
            
            fDB ? drawVBargraphDB (g, x, width) : drawVBargraphLin(g, x, width);
        }
        
        /**
         * Method in charge of drawing the level of a vertical dB VU-meter.
         *
         * \param   g       JUCE graphics context, used to draw components or images.
         * \param   x       x coordinate of the VU-meter.
         * \param   width   Width of the VU-meter.
         * \param   level   Current level of the VU-meter, in dB.
         */
        void drawVBargraphDB(juce::Graphics& g, int x, int width)
        {
            // Drawing Scale
            g.setFont(9.0f);
            g.setColour(juce::Colours::green);
            for (int i = -10; i > fMin; i -= 10) {
                paintScale(g, i);
            }
            for (int i = -6; i < fMax; i += 3)  {
                paintScale(g, i);
            }
            
            int alpha = 200;
            FAUSTFLOAT dblevel = dB2Scale(fLevel);
            
            // We need to test here every color changing levels, to avoid to mix colors because of the alpha,
            // and so to start the new color rectangle at the end of the previous one.
            
            // Drawing from the minimal range to the current level, or -10dB.
            g.setColour(juce::Colour((juce::uint8)40, (juce::uint8)160, (juce::uint8)40, (juce::uint8)alpha));
            g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(-10)), (float)width-2, dB2y(fMin)-juce::jmax(dB2y(fLevel), dB2y(-10)));
            
            // Drawing from -10dB to the current level, or -6dB.
            if (dblevel > dB2Scale(-10)) {
                g.setColour(juce::Colour((juce::uint8)160, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
                g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(-6)), (float)width-2, dB2y(-10)-juce::jmax(dB2y(fLevel), dB2y(-6)));
            }
            // Drawing from -6dB to the current level, or -3dB.
            if (dblevel > dB2Scale(-6)) {
                g.setColour(juce::Colour((juce::uint8)220, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
                g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(-3)), (float)width-2, dB2y(-6)-juce::jmax(dB2y(fLevel), dB2y(-3)));
            }
            // Drawing from -3dB to the current level, or 0dB.
            if (dblevel > dB2Scale(-3)) {
                g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)160, (juce::uint8)20, (juce::uint8)alpha));
                g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(0)), (float)width-2, dB2y(-3)-juce::jmax(dB2y(fLevel), dB2y(0)));
            }
            // Drawing from 0dB to the current level, or the maximum range.
            if (dblevel > dB2Scale(0)) {
                g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)0, (juce::uint8)20, (juce::uint8)alpha));
                g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(fMax)), (float)width-2, dB2y(0)-juce::jmax(dB2y(fLevel), dB2y(fMax)));
            }
        }
        
        /**
         * Method in charge of drawing the level of a vertical linear VU-meter.
         *
         * \param   g       JUCE graphics context, used to draw components or images.
         * \param   x       x coordinate of the VU-meter.
         * \param   width   Width of the VU-meter.
         * \param   level   Current level of the VU-meter, in linear logic.
         */
        void drawVBargraphLin(juce::Graphics& g, int x, int width)
        {
            int alpha = 200;
            juce::Colour c = juce::Colour((juce::uint8)255, (juce::uint8)165, (juce::uint8)0, (juce::uint8)alpha);
            
            // Drawing from the minimal range to the current level, or 20% of the VU-meter.
            g.setColour(c.brighter());
            g.fillRect(x+1.0f, juce::jmax(lin2y(fLevel), lin2y(0.2)), (float)width-2, lin2y(fMin)-juce::jmax(lin2y(fLevel), lin2y(0.2)));
            
            // Drawing from 20% of the VU-meter to the current level, or 90% of the VU-meter.
            if (fLevel > 0.2f) {
                g.setColour(c);
                g.fillRect(x+1.0f, juce::jmax(lin2y(fLevel), lin2y(0.9)), (float)width-2, lin2y(0.2)-juce::jmax(lin2y(fLevel), lin2y(0.9)));
            }
            
            // Drawing from 90% of the VU-meter to the current level, or the maximum range.
            if (fLevel > 0.9f) {
                g.setColour(c.darker());
                g.fillRect(x+1.0f, juce::jmax(lin2y(fLevel), lin2y(fMax)), (float)width-2, lin2y(0.9)-juce::jmax(lin2y(fLevel), lin2y(fMax)));
            }
        }
        
        /**
         * Method in charge of drawing the LED VU-meter, dB or not.
         *
         * \param   g       JUCE graphics context, used to draw components or images.
         * \param   width   Width of the LED.
         * \param   height  Height of the LED.
         * \param   level   Current level of the VU-meter, dB or not.
         */
        void drawLed(juce::Graphics& g, int width, int height)
        {
            float x = (float)(getWidth() - width)/2;
            float y = (float)(getHeight() - height)/2;
            g.setColour(juce::Colours::black);
            g.fillEllipse(x, y, width, height);
            
            if (fDB) {
                int alpha = 200;
                FAUSTFLOAT dblevel = dB2Scale(fLevel);
                
                // Adjust the color depending on the current level
                g.setColour(juce::Colour((juce::uint8)40, (juce::uint8)160, (juce::uint8)40, (juce::uint8)alpha));
                if (dblevel > dB2Scale(-10)) {
                    g.setColour(juce::Colour((juce::uint8)160, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
                }
                if (dblevel > dB2Scale(-6)) {
                    g.setColour(juce::Colour((juce::uint8)220, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
                }
                if (dblevel > dB2Scale(-3)) {
                    g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)160, (juce::uint8)20, (juce::uint8)alpha));
                }
                if (dblevel > dB2Scale(0))  {
                    g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)0, (juce::uint8)20, (juce::uint8)alpha));
                }
                
                g.fillEllipse(x+1, y+1, width-2, height-2);
            } else {
                // The alpha depend on the level, from 0 to 1
                g.setColour(juce::Colours::red.withAlpha((float)fLevel));
                g.fillEllipse(x+1, y+1, width-2, height-2);
            }
        }
        
        /**
         * Method in charge of drawing the Numerical Display VU-meter, dB or not.
         *
         * \param   g       JUCE graphics context, used to draw components or images.
         * \param   width   Width of the Numerical Display.
         * \param   height  Height of the Numerical Display.
         * \param   level   Current level of the VU-meter.
         */
        void drawNumDisplay(juce::Graphics& g, int width, int height)
        {
            // Centering it
            int x = (getWidth()-width) / 2;
            int y = (getHeight()-height) / 2;
            
            // Draw box.
            g.setColour(juce::Colours::darkgrey);
            g.fillRect(x, y, width, height);
            g.setColour(juce::Colours::green.withAlpha(0.8f));
            g.fillRect(x+1, y+1, width-2, height-2);
            
            // Text is handled by the setLabelPos() function
        }

For the meters both would be possible from PGMs POV:
You can add a LinearBar option to the Slider or (what I would prefer) inherit foleys::MagicMeterSource that you feed with the values from faust.
If that helps I would add an Abstract super class.

The benefit is that in future there will be LookAndFeel methods and different Meter styles to choose from.

OK, thanks for the suggestion. I am currently in way over my head as regards both PGM and Faust internals and C++ for that matter! I’ll continue studying this. I don’t like the idea of calling a bargraph a slider, yet the “wiring” to internal signals is already done. Don’t be surprised - I may ask some stupid questions.

The way I get Faust bargraphs into PGM is to replace them with output signals (i.e., bring out each bargraph input signal as an output signal from the Faust block diagram, which usually requires some added signal routing), and pushSamples(thoseSignals) to a MagicLevelSource in PGM (in processBlock).

I agree that Faust/C++/ PGM is a learning curve and a half, but it’s well worth it!

I can be pretty determined sometimes. I may have met my match though. Along the way I discovered that using the regular faust2juce script without GUI Magic, anything placed under a tabbed group collapses to a single pixel in height. Tracing the code around to try to see how box sizes are figured is pretty difficult. And obviously not suitable for discussion here. :zipper_mouth_face:

I think anyone working in Faust most likely will be using the control metadata in Faust to organize the layout of the UI. So they will tend not to rearrange things in the GUI Magic editor, just change graphics, background colors, etc. That is just one way of working of course. Another way to work is to forget that there is any link between the Faust source code and the layout, and just redo it using PGM. This is not an impossible proposition, just cumbersome.

So my thoughts here are about that way of working, where you take layout changes from the Faust code and combine that with graphical elements described separately.

I think it makes most sense to have the faust2juce process create an XML file with as much info as possible supported by PGM currently and also expanding the functionality to pick up new features on both Faust (e.g. plot/analyzer UI element) and PGM (in-line vuMeter) sides.

[spoiler alert - I now completely change my mind]

Just to be sure, I started from scratch in PGM and recreated my Faust layout. It was pretty fast and easy. Also PGM offers flexibility that doesn’t make sense to shove into Faust (though I’d still like to support plots). So maybe now my focus is assuming that we don’t care if the Faust code creates a PGM layout since it’s so fast and easy to just do it directly. At least, for what I am doing it was not difficult. So the highest priority thing for me to think about is to make the bargraphs and menu style sliders work.

Moving right along, I now am pretty convinced that decoupling the Faust/JUCE UI code completely from PGM is the best approach.
a) Working with PGM is 10 times faster than figuring out UI addresses in Faust (even though you might want to do it anyway when prototyping in Faust IDE).
b) Eliminate any consideration of needing to back-annotate the Faust code to maintain UI sync with PGM.
c) Defer consideration of class mismatches for certain types of controls.

Right now I want to talk about (c). Added a couple checkboxes in my Faust code and it turns out that PGM checkboxes can be directly assigned the parameter address and it works fine.

Next up, the List Box. Faust lets you select one of several text choices using a slider with [style:menu] metadata included. But I’m just trying to figure out how to use a ListBox in the full PGM world.

I see the example “SignalGenerator” code and its function

juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout()

which looks like it sets everything in the UI up initially, including the ListBoxes, e.g.

    juce::AudioProcessorValueTreeState::ParameterLayout layout;
    auto generator = std::make_unique<juce::AudioProcessorParameterGroup>("Generator", TRANS ("Generator"), "|");
    generator->addChild (std::make_unique<juce::AudioParameterChoice>(IDs::mainType, "Type", juce::StringArray ("None", "Sine", "Triangle", "Square"), 1),
                         std::make_unique<juce::AudioParameterFloat>(IDs::mainFreq, "Frequency", freqRange, 440),
                         std::make_unique<juce::AudioParameterFloat>(IDs::mainLevel, "Level", juce::NormalisableRange<float>(-100.0f, 0.0, 1.0), -6.0f));

I’ve compiled and run this example separately, but I’d like to include a PGM ListBox in my own code now. I’m starting with automatically-and-manually-PGM-modified Faust-generated C++ (I added some features to support a -magic flag to faust2juce). I am having quite a bit of difficulty figuring out what I need to do. At this point, my class inherits from MagicProcessor and after that it just (cough) magically works, so my interaction with the Magic classes etc. is non existent.

#if defined(PLUGIN_MAGIC)

class FaustPlugInAudioProcessor : public foleys::MagicProcessor, private juce::Timer
{
    
public:
    juce::AudioProcessorValueTreeState treeState{ *this, nullptr };
    FaustPlugInAudioProcessor();
    virtual ~FaustPlugInAudioProcessor() {}
    
    void prepareToPlay (double sampleRate, int samplesPerBlock) override;
    
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
    
    void processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) override
    {
        jassert (! isUsingDoublePrecision());
        process (buffer, midiMessages);
    }
    
    void processBlock (juce::AudioBuffer<double>& buffer, juce::MidiBuffer& midiMessages) override
    {
        jassert (isUsingDoublePrecision());
        process (buffer, midiMessages);
    }
    
    const juce::String getName() const override;
    
    bool acceptsMidi() const override;
    bool producesMidi() const override;
    double getTailLengthSeconds() const override;
    
    int getNumPrograms() override;
    int getCurrentProgram() override;
    void setCurrentProgram (int index) override;
    const juce::String getProgramName (int index) override;
    void changeProgramName (int index, const juce::String& newName) override;
    
    void releaseResources() override
    {}
    
    void timerCallback() override;
    
    juce::AudioProcessor::BusesProperties getBusesProperties();
    bool supportsDoublePrecisionProcessing() const override;
    
#ifdef JUCE_POLY
    std::unique_ptr<FaustSynthesiser> fSynth;
#else
#if defined(MIDICTRL)
    std::unique_ptr<juce_midi_handler> fMIDIHandler;
    std::unique_ptr<MidiUI> fMIDIUI;
#endif
    std::unique_ptr<faustdsp> fDSP;
#endif
    
#if defined(OSCCTRL)
    std::unique_ptr<JuceOSCUI> fOSCUI;
#endif
    
#if defined(SOUNDFILE)
    std::unique_ptr<SoundUI> fSoundUI;
#endif
    
    JuceStateUI fStateUI;
    JuceParameterUI fParameterUI;
  
private:
    
    template <typename FloatType>
    void process (juce::AudioBuffer<FloatType>& buffer, juce::MidiBuffer& midiMessages);
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FaustPlugInAudioProcessor)
    
};

#else

I think I would benefit from a high level overview of what is going on here.

Is the sample code still the way you would write it considering some changes in the supporting classes since the time it was first written (e.g. MagicState)? e.g. I cannot find any “ParameterLayout” references in my C++ code at the moment.

I’m going to spend a little time stepping through the example code to see what happens when.


void SignalGeneratorAudioProcessor::setOscillator (juce::dsp::Oscillator<float>& osc, WaveType type)
{
    if (type == WaveType::Sine)
        osc.initialise ([](auto in) { return std::sin (in); });
    else if (type == WaveType::Triangle)
        osc.initialise ([](auto in) { return in / juce::MathConstants<float>::pi; });
    else if (type == WaveType::Square)
        osc.initialise ([](auto in) { return in < 0 ? 1.0f : -1.0f; });
    else
        osc.initialise ([](auto) { return 0.0f; });
}

void SignalGeneratorAudioProcessor::parameterChanged (const juce::String& param, float value)
{
    if (param == IDs::mainType)
        setOscillator (mainOSC, WaveType (juce::roundToInt (value)));
    else if (param == IDs::lfoType)
        setOscillator (lfoOSC, WaveType (juce::roundToInt (value)));
    else if (param == IDs::vfoType)
        setOscillator (vfoOSC, WaveType (juce::roundToInt (value)));
}

These functions handle getting the value from the ListBox. I’d need to send that to a value being tracked in Faust, e.g.

		ui_interface->declare(&fVslider4, "03", "");
		ui_interface->declare(&fVslider4, "style", "menu{'Sine':0;'Triangle':1;'Sawtooth':2}");
		ui_interface->addVerticalSlider("LFOType", &fVslider4, 0.0f, 0.0f, 2.0f, 1.0f);

fVsliider4 apparently.

If I make the object a ComboBox instead of a ListBox (the SignalGenerator actually uses ComboBox) then I get a way to select a parameter from my parameter tree just like with a slider. I am just not sure how to set the range of the control or to make it display the correct selections.

Does this have to be done in code, or is there a way using the GUI Editor alone that I can set the text selections and return values for the ComboBox?

@daniel If I make the object a ComboBox then I get a way to select a parameter from my parameter tree just like with a slider. I am just not sure how to set the range of the control or to make it display the correct selections.

Does this have to be done in code, or is there a way using the GUI Editor alone that I can set the text selections and return values for the ComboBox?

I’m not otherwise creating or adding objects to a layout as in the SignalGenerator demo.

I’ve created a PR to add some more hooks for easier integration between Faust and PGM. Use faust2juce -magic to create a PGM compatible C++ file. The latest change also drops out a lot of the generated Juce UI code that is not used by PGM.

Thanks for the effort, I left a few lines feedback there. I didn’t do an in-depth review though:

It’s incrementally following my own attempts to bridge the gap. Thanks for commenting on the PR.