====== Avoid new/delete, prefer references to pointers, use member variables for sub-Components ======
The JUCE library includes a nice collection of //managed pointer// template classes, such as //juce::ScopedPointer//, which can and should be used to avoid most uses of the ''delete'' keyword. Note that managed pointers are still assigned using ''new''.
I checked my code carefully, and was unable to find any uses of ''delete'', and found none. I did find a number of uses of ''new'', however, which fell into two categories:
- In my //VanillaJuceAudioProcessor// class, I used ''new'' to create object-instances where there was effectively no choice; the JUCE library itself required it. I also used ''new'' when when creating //juce::XmlElement// objects, following the example of the JUCE static function //juce::AudioProcessor::getXmlFromBinary()//, which returns a non-managed pointer.
- In all of my GUI-related classes, I used ''new'' to create various //juce::Component// objects, assigning the result to //juce::ScopedPointer// member variables of the //Gui...// class. I used the Projucer to generate this code, and that's what it produced, so I assumed this was the recommended approach. I have since learned, through discussion on the JUCE Forum, that this is definitely not the case.
Regarding the first category, I can only assume that Jules's admonition to "ditch all your new/delete..." was something of a generic complaint. Regarding the GUI-related classes, I now understand that the Projucer's lavish use of //ScopedPointer// is not at all recommended, and that child //Component// objects should ideally be declared as member variables.
===== Cleaning up the GUI classes =====
JUCE Forum user //Holy_City// provided the following code-example showing a variety of tricks for reducing the size of constructor functions for //juce::Component//-derived classes:
lass EnvelopeComponent : public Component
{
public:
EnvelopeComponent() /* user the initializer list */
: attack (Slider::Rotary, Slider::noTextBox), //very helpful constructor for Sliders
decay (Slider::Rotary, Slider::noTextBox),
sustain (Slider::Rotary, Slider::noTextBox),
release (Slider::Rotary, Slider::noTextBox)
{
auto initSlider = [this] (Slider& s) // lambda expression declaration
{
addAndMakeVisible (s):
s.setRange (1.0, 500.0);
s.setValue (25.0);
};
initSlider (attack);
initSlider (decay);
initSlider (release);
addAndMakeVisible (sustain);
sustain.setRange (0.0, 1.0);
sustain.setValue (0.707);
}
//.... yada yada yada
private:
Slider attack, sustain, decay, release; /* don't need to put them on different lines */
};
Following //Holy_City//'s example I was able to reduce the //GuiEgTab// class from this:
class GuiEgTab : public Component,
public SliderListener
{
public:
GuiEgTab (SynthSound* pSynthSound);
~GuiEgTab();
void paint (Graphics& g) override;
void resized() override;
void sliderValueChanged (Slider* sliderThatWasMoved) override;
void notify();
private:
SynthSound* pSound;
ScopedPointer
to this:
class GuiEgTab : public Component,
public SliderListener
{
public:
GuiEgTab (SynthSound* pSynthSound);
void paint (Graphics& g) override;
void resized() override;
void sliderValueChanged (Slider* sliderThatWasMoved) override;
void notify();
private:
SynthSound* pSound;
Label attackLabel, decayLabel, sustainLabel, releaseLabel;
Slider attackSlider, decaySlider, sustainSlider, releaseSlider;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GuiEgTab)
};
GuiEgTab::GuiEgTab (SynthSound* pSynthSound)
: pSound(pSynthSound)
, attackLabel("attack", TRANS("Attack Time (sec)"))
, decayLabel("decay", TRANS("Decay Time (sec)"))
, sustainLabel("sustain", TRANS("Sustain Level (%)"))
, releaseLabel("release", TRANS("Release Time (sec)"))
{
auto initLabel = [this](Label& label)
{
addAndMakeVisible(label);
label.setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular"));
label.setJustificationType (Justification::centredRight);
label.setEditable (false, false, false);
label.setColour (TextEditor::textColourId, Colours::black);
label.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
};
initLabel(attackLabel);
initLabel(decayLabel);
initLabel(sustainLabel);
initLabel(releaseLabel);
auto initSlider = [this](Slider& slider)
{
addAndMakeVisible(slider);
slider.setSliderStyle (Slider::LinearHorizontal);
slider.setTextBoxStyle (Slider::TextBoxRight, false, 80, 20);
slider.addListener (this);
};
initSlider(attackSlider); attackSlider.setRange (0, 10, 0);
initSlider(decaySlider); decaySlider.setRange (0, 10, 0);
initSlider(sustainSlider); sustainSlider.setRange (0, 100, 1);
initSlider(releaseSlider); releaseSlider.setRange (0, 10, 0);
notify();
}
void GuiEgTab::paint (Graphics& g)
{
g.fillAll (Colour (0xff323e44));
}
void GuiEgTab::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;
attackLabel.setBounds (labelLeft, top, labelWidth, controlHeight);
attackSlider.setBounds (controlLeft, top, sliderWidth, controlHeight);
top += controlHeight + gapHeight;
decayLabel.setBounds (labelLeft, top, labelWidth, controlHeight);
decaySlider.setBounds (controlLeft, top, sliderWidth, controlHeight);
top += controlHeight + gapHeight;
sustainLabel.setBounds (labelLeft, top, labelWidth, controlHeight);
sustainSlider.setBounds (controlLeft, top, sliderWidth, controlHeight);
top += controlHeight + gapHeight;
releaseLabel.setBounds (labelLeft, top, labelWidth, controlHeight);
releaseSlider.setBounds (controlLeft, top, sliderWidth, controlHeight);
}
void GuiEgTab::sliderValueChanged (Slider* sliderThatWasMoved)
{
double value = sliderThatWasMoved->getValue();
SynthParameters* pParams = pSound->pParams;
if (sliderThatWasMoved == &attackSlider) pParams->ampEgAttackTimeSeconds = value;
else if (sliderThatWasMoved == &decaySlider) pParams->ampEgDecayTimeSeconds = value;
else if (sliderThatWasMoved == &sustainSlider) pParams->ampEgSustainLevel = 0.01 * value;
else if (sliderThatWasMoved == &releaseSlider) pParams->ampEgReleaseTimeSeconds = value;
pSound->parameterChanged();
}
void GuiEgTab::notify()
{
SynthParameters* pParams = pSound->pParams;
attackSlider.setValue(pParams->ampEgAttackTimeSeconds);
decaySlider.setValue(pParams->ampEgDecayTimeSeconds);
sustainSlider.setValue(100.0 * pParams->ampEgSustainLevel);
releaseSlider.setValue(pParams->ampEgReleaseTimeSeconds);
}