GetDunne Wiki

Notes from the desk of Shane Dunne, software development consultant

User Tools

Site Tools


the_plugin_editor

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
the_plugin_editor [2017/08/30 18:35]
shane created
the_plugin_editor [2017/10/02 19:38] (current)
shane [The GuiTabs object]
Line 29: Line 29:
     , guiTabs(p.getSound())     , guiTabs(p.getSound())
 { {
-    setSize (600, 400);+    setSize (600, 300);
     addAndMakeVisible(&guiTabs);     addAndMakeVisible(&guiTabs);
     p.addChangeListener(this);     p.addChangeListener(this);
Line 46: Line 46:
 void VanillaJuceAudioProcessorEditor::resized() void VanillaJuceAudioProcessorEditor::resized()
 { {
-    guiTabs.setBounds(0, 0, proportionOfWidth(1.0000f), proportionOfHeight(1.0000f));+    guiTabs.setBounds(0, 0, getWidth(), getHeight());
 } }
  
Line 96: Line 96:
 GuiTabs::~GuiTabs() GuiTabs::~GuiTabs()
 { {
-    // tabbedComponent will take care of deleting our tab objects 
-    tabbedComponent = nullptr; 
 } }
  
Line 108: Line 106:
 void GuiTabs::resized() void GuiTabs::resized()
 { {
-    tabbedComponent->setBounds (0, 0, proportionOfWidth (1.0000f), proportionOfHeight (1.0000f));+    tabbedComponent->setBounds (0, 0, getWidth(), getHeight());
 } }
  
Line 119: Line 117:
 </code> </code>
 The constructor creates the //tabbedComponent// object and calls //addAndMakeVisible()// on it, sets the depth of the tabs bar to 32 pixels, creates the three tab objects and calls //tabbedComponent->addTab//, and sets the first (zeroth) tab to be the initially-selected one. The constructor creates the //tabbedComponent// object and calls //addAndMakeVisible()// on it, sets the depth of the tabs bar to 32 pixels, creates the three tab objects and calls //tabbedComponent->addTab//, and sets the first (zeroth) tab to be the initially-selected one.
 +
 +Note there are no ''delete''s in the destructor, despite the use of ''new'' in the constructor. Setting the last argument of //addTab()// to //true// tells //tabbedComponent// to take ownership of the three tab objects, so it will delete them. The //tabbedComponent// member variable itself is declared as a JUCE //ScopedPointer//, so the object it points to (the tabs object), will get deleted automatically when the //GuiTabs// object itself gets deleted.
 +
 +The //paint()// member function fills the window with the background color. This is necessary, despite the fact that the //resized()// sets the bounds of //tabbedComponent// to the full size of the GUI window, because when //tabbedComponent// draws itself, it draws only the three tabs at the top and their contents below---it //does not// draw anything into the space to the right of the third tab. The //fillAll()// call in //paint()// ensures this gets filled in, by filling the entire GUI window with the background color before //tabbedComponent// renders.
 +
 +===== The individual tabs =====
 +The three tab classes //GuiMainTab//, //GuiOscTab//, and //GuiEgTab// are all very similar, so let's just look at //GuiMainTab// which is the simplest of the three. Here's the class declaration:
 +<code cpp>
 +class GuiMainTab : public Component, public SliderListener
 +{
 +public:
 +    GuiMainTab (SynthSound* pSynthSound);
 +    ~GuiMainTab();
 +
 +    void paint (Graphics& g) override;
 +    void resized() override;
 +    void sliderValueChanged (Slider* sliderThatWasMoved) override;
 +
 +    void notify();
 +
 +private:
 +    SynthSound* pSound;
 +
 +    ScopedPointer<Label> masterLevelLabel;
 +    ScopedPointer<Slider> masterLevelSlider;
 +    ScopedPointer<Label> pbUpLabel;
 +    ScopedPointer<Slider> pbUpSlider;
 +    ScopedPointer<Label> pbDownLabel;
 +    ScopedPointer<Slider> pbDownSlider;
 +
 +    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GuiMainTab)
 +};
 +</code>
 +
 +This class inherits from //juce::SliderListener// as well as from //juce::Component//, to ensure that it gets notified (via a call to //sliderValueChanged()//) whenever any of its three slider controls changes.
 +
 +The constructor takes a //SynthSound*// argument, which it saves in the member variable //pSound//. The //GuiTabs// object didn't need to do this, because it only needed to pass the //SynthSound*// pointer on to the constructors of the tab-objects. The tab objects themselves do need to store a copy of the //SynthSound// pointer, so they can use it in two ways:
 +  - To read and write the parameters of the current patch, the tab object can dereference the //pSound// pointer to get at its //pParams// member, which points to the //SynthParams// structure.
 +  - Whenever any of the tab's controls changes any parameter value, it must call //pSound->parameterChanged()// to notify the //Synth// object, which in turn will notify all active synthesizer voices.
 +
 +Note the use of //ScopedPointer//s for all of the contained objects. This ensures we don't have to put a series of ''delete''s into the destructor. In fact, the destructor for //GuiMainTab// is empty.
 +
 +The bulk of the constructor code just creates and sets up the three slider controls and their labels. I developed this code by using the Projucer to create a stand-alone GUI program, using its interactive GUI editor, then I looked at the code it produced and adapted that for use here.
 +<code cpp>
 +GuiMainTab::GuiMainTab (SynthSound* pSynthSound)
 +    : pSound(pSynthSound)
 +{
 +    addAndMakeVisible (masterLevelLabel = new Label ("master level label", TRANS("Master Volume")));
 +    masterLevelLabel->setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular"));
 +    masterLevelLabel->setJustificationType (Justification::centredRight);
 +    masterLevelLabel->setEditable (false, false, false);
 +    masterLevelLabel->setColour (TextEditor::textColourId, Colours::black);
 +    masterLevelLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
 +
 +    addAndMakeVisible (masterLevelSlider = new Slider ("Master Volume"));
 +    masterLevelSlider->setRange (0, 10, 0);
 +    masterLevelSlider->setSliderStyle (Slider::LinearHorizontal);
 +    masterLevelSlider->setTextBoxStyle (Slider::TextBoxRight, false, 80, 20);
 +    masterLevelSlider->addListener (this);
 +
 +    addAndMakeVisible(pbUpLabel = new Label("PB up semis", TRANS("P.Bend up (semi)")));
 +    pbUpLabel->setFont(Font(15.00f, Font::plain).withTypefaceStyle("Regular"));
 +    pbUpLabel->setJustificationType(Justification::centredRight);
 +    pbUpLabel->setEditable(false, false, false);
 +    pbUpLabel->setColour(TextEditor::textColourId, Colours::black);
 +    pbUpLabel->setColour(TextEditor::backgroundColourId, Colour(0x00000000));
 +
 +    addAndMakeVisible(pbUpSlider = new Slider("PB up semis"));
 +    pbUpSlider->setRange(0, 12, 1);
 +    pbUpSlider->setSliderStyle(Slider::LinearHorizontal);
 +    pbUpSlider->setTextBoxStyle(Slider::TextBoxRight, false, 80, 20);
 +    pbUpSlider->addListener(this);
 +
 +    addAndMakeVisible(pbDownLabel = new Label("PB down semis", TRANS("P.Bend down (semi)")));
 +    pbDownLabel->setFont(Font(15.00f, Font::plain).withTypefaceStyle("Regular"));
 +    pbDownLabel->setJustificationType(Justification::centredRight);
 +    pbDownLabel->setEditable(false, false, false);
 +    pbDownLabel->setColour(TextEditor::textColourId, Colours::black);
 +    pbDownLabel->setColour(TextEditor::backgroundColourId, Colour(0x00000000));
 +
 +    addAndMakeVisible(pbDownSlider = new Slider("PB down semis"));
 +    pbDownSlider->setRange(0, 12, 1);
 +    pbDownSlider->setSliderStyle(Slider::LinearHorizontal);
 +    pbDownSlider->setTextBoxStyle(Slider::TextBoxRight, false, 80, 20);
 +    pbDownSlider->addListener(this);
 +
 +    notify();
 +}
 +</code>
 +The constructor ends with a call to this tab's own //notify()// function, which is more commonly called in response to parameter changes within the plugin processor. It sets the values of all three sliders, based on the values of the corresponding parameters.
 +<code cpp>
 +    SynthParameters* pParams = pSound->pParams;
 +    masterLevelSlider->setValue(10.0 * pParams->masterLevel);
 +    pbUpSlider->setValue(pParams->pitchBendUpSemitones);
 +    pbDownSlider->setValue(pParams->pitchBendDownSemitones);
 +</code>
 +
 +The //sliderValueChanged()// callback does the reverse operation, changing the value of a parameter whenever the corresponding slider is adjusted:
 +<code cpp>
 +void GuiMainTab::sliderValueChanged (Slider* sliderThatWasMoved)
 +{
 +    double value = sliderThatWasMoved->getValue();
 +    SynthParameters* pParams = pSound->pParams;
 +    if (sliderThatWasMoved == masterLevelSlider)
 +    {
 +        pParams->masterLevel = 0.1 * value;
 +    }
 +    else if (sliderThatWasMoved == pbUpSlider)
 +    {
 +        pParams->pitchBendUpSemitones = int(value);
 +    }
 +    else if (sliderThatWasMoved == pbDownSlider)
 +    {
 +        pParams->pitchBendDownSemitones = int(value);
 +    }
 +    pSound->parameterChanged();
 +}
 +</code>
 +The final //pSound->parameterChanged()// call propagates the change back to the //Synth// object and thence to any active synth voices.
 +
 +The //paint()// member function takes care of filling the entire background behind the labels and sliders:
 +<code cpp>
 +void GuiMainTab::paint (Graphics& g)
 +{
 +    g.fillAll (Colour (0xff323e44));
 +}
 +</code>
 +
 +The //resized()// function completes the layout by calling //setBounds()// on all the child //Component// objects. Unfortunately, the present version of the Projucer doesn't write code like this; it simply puts numeric constant values for all four //setBounds()// arguments. I used the Projucer-generated code to glean key dimensional values, then rewrote the code to use these somewhat dynamically:
 +<code cpp>
 +void GuiMainTab::resized()
 +{
 +    const int labelLeft = 16;
 +    const int controlLeft = 144;
 +    const int labelWidth = 120;
 +    const int sliderWidth = 420;
 +    const int controlHeight = 24;
 +    const int gapHeight = 8;
 +
 +    int top = 20;
 +    masterLevelLabel->setBounds(labelLeft, top, labelWidth, controlHeight);
 +    masterLevelSlider->setBounds(controlLeft, top, sliderWidth, controlHeight);
 +    top += controlHeight + 5 * gapHeight;
 +    pbUpLabel->setBounds(labelLeft, top, labelWidth, controlHeight);
 +    pbUpSlider->setBounds(controlLeft, top, sliderWidth, controlHeight);
 +    top += controlHeight + gapHeight;
 +    pbDownLabel->setBounds(labelLeft, top, labelWidth, controlHeight);
 +    pbDownSlider->setBounds(controlLeft, top, sliderWidth, controlHeight);
 +}
 +</code>
 +
 +The code above will lay out the labels and sliders so they are aligned nicely, with certain gaps between them. It does not make any reference to the tab object's own bounds. If you want to create a dynamic layout where, for example, the slider widths adjust automatically to fill the available horizontal space, you would need to compute the //sliderWidth// value dynamically based on the tab's own bounds (call the //getBounds()// function).
 +
 +And that's pretty much it for the VanillaJuce GUI. The other two tabs don't differ in any meaningful way.
 +
the_plugin_editor.1504118108.txt.gz · Last modified: 2017/08/30 18:35 by shane