That suggestion to use dynamic memory and the “owned array” worked well daniel. I got it working like that. The stretching thing is less important of an issue anyway.
For anyone’s reference, here’s the working code:
class LabeledSlider : public GroupComponent
{
public:
LabeledSlider (const String& name)
{
setText (name);
setTextLabelPosition (Justification::centredTop);
addAndMakeVisible (slider);
}
void resized() override
{
slider.setBounds (getLocalBounds().reduced (10));
}
Slider slider
{
Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow
};
};
class MainContentComponent : public AudioAppComponent
{
public:
MainContentComponent()
{
LabeledSlider* control = new LabeledSlider("Frequency");
control->slider.setRange(20.0, 20000.0);
control->slider.setSkewFactorFromMidPoint(500.0);
control->slider.setNumDecimalPlacesToDisplay(1);
control->slider.setValue(currentFrequency, dontSendNotification);
control->slider.onValueChange = [this] { targetFrequency = frequency.slider.getValue(); };
control->slider.setTextBoxStyle(Slider::TextBoxBelow, false, 100, 20);
control->slider.setRange(50.0, 5000.0);
control->slider.setSkewFactorFromMidPoint(500.0);
control->slider.setNumDecimalPlacesToDisplay(1);
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Level");
control->slider.setRange(0.0, 1.0);
control->slider.onValueChange = [this] { targetLevel = (float)level.slider.getValue(); };
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy1");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy2");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy3");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy4");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy5");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy6");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy7");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy8");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy9");
addAndMakeVisible(knobs.add(control));
setSize (600, 600);
setAudioChannels (0, 2); // no inputs, two outputs
}
~MainContentComponent()
{
shutdownAudio();
}
void resized() override
{
//==============================================================================
FlexBox knobBox;
knobBox.flexWrap = FlexBox::Wrap::wrap;
knobBox.justifyContent = FlexBox::JustifyContent::flexStart;
knobBox.alignContent = FlexBox::AlignContent::flexStart;
for (auto* k : knobs)
knobBox.items.add(FlexItem(*k).withMinHeight(80.0f).withMinWidth(80.0f).withFlex(1));
//==============================================================================
FlexBox fb;
fb.flexDirection = FlexBox::Direction::column;
fb.items.add(FlexItem(knobBox).withFlex(2.5));
fb.performLayout(getLocalBounds().toFloat());
}
inline void updateAngleDelta()
{
auto cyclesPerSample = currentFrequency / currentSampleRate;
angleDelta = cyclesPerSample * 2.0 * MathConstants<double>::pi;
}
void prepareToPlay (int, double sampleRate) override
{
currentSampleRate = sampleRate;
updateAngleDelta();
}
void releaseResources() override {}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
auto* leftBuffer = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample);
auto* rightBuffer = bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample);
auto localTargetFrequency = targetFrequency;
if (targetFrequency != currentFrequency)
{
auto frequencyIncrement = (targetFrequency - currentFrequency) / bufferToFill.numSamples;
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
auto currentSample = (float) std::sin (currentAngle);
currentFrequency += frequencyIncrement;
updateAngleDelta();
currentAngle += angleDelta;
leftBuffer[sample] = currentSample;
rightBuffer[sample] = currentSample;
}
currentFrequency = localTargetFrequency;
}
else
{
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
auto currentSample = (float) std::sin (currentAngle);
currentAngle += angleDelta;
leftBuffer[sample] = currentSample;
rightBuffer[sample] = currentSample;
}
}
auto localTargetLevel = targetLevel;
bufferToFill.buffer->applyGainRamp (bufferToFill.startSample, bufferToFill.numSamples, currentLevel, localTargetLevel);
currentLevel = localTargetLevel;
}
private:
double currentSampleRate = 0.0, currentAngle = 0.0, angleDelta = 0.0;
double currentFrequency = 500.0, targetFrequency = 500.0;
float currentLevel = 0.1f, targetLevel = 0.1f;
int rotaryDiam = 100;
LabeledSlider frequency{ "Frequency" };
LabeledSlider level{ "Level" };
LabeledSlider dummy1{ "Dummy 1" };
LabeledSlider dummy2{ "Dummy 2" };
LabeledSlider dummy3{ "Dummy 3" };
LabeledSlider dummy4{ "Dummy 4" };
LabeledSlider dummy5{ "Dummy 5" };
LabeledSlider dummy6{ "Dummy 6" };
LabeledSlider dummy7{ "Dummy 7" };
LabeledSlider dummy8{ "Dummy 8" };
LabeledSlider dummy9{ "Dummy 9" };
OwnedArray<LabeledSlider> knobs;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
I’m now trying to figure out how to nest the classes.
In a standard panel layout you need things organized like this:
Ie. You need labeled knobs (which we have done) inside of labeled groups.
I think the best way to do this is using FlexBox in the manner we have, but extending it to an extra layer so the knobs within each Group are auto-organized, and the Groups are also auto-organized on the panel.
Here is what I’m thinking about how to do this:
- Define the LabeledSlider class as part of a larger LabeledGroup class.
- All sliders will then be assigned a label by LabeledSlider (eg. Attack) and also belong to a bigger LabeledGroup (eg. ADSR).
- A FlexBox design within the LabeledGroup class definition will capture all LabeledSliders from that LabeledGroup into an array and perform Flex arrangement of them.
- Using the standard method we’ve established, an ownedarray in MainContentComponent can then capture all the LabeledGroups and organize them with FlexBox across the screen.
Does this make sense? I’m fairly certain it should work as intended, no?
The goal again is to have an entire panel of flexible groups of knobs that are sectioned out by groupings like “ADSR”, “LFO”, etc. with no need for specific locations of anything to be set.
I have been reading C++ tutorials about nested Classes for the past few hours. But I can’t quite figure it out.
Here’s the my crude attempt at what I’m thinking, although it doesn’t work in this current form:
class LabeledGroup : public GroupComponent
{
public:
LabeledGroup(const String& name)
{
setText(name);
setTextLabelPosition(Justification::centredTop);
class LabeledSlider : public GroupComponent
{
public:
LabeledSlider(const String& name)
{
setText(name);
setTextLabelPosition(Justification::centredTop);
addAndMakeVisible(slider);
}
void resized() override
{
slider.setBounds(getLocalBounds().reduced(10));
}
Slider slider
{
Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow
};
};
}
private:
void resized() override
{
OwnedArray<LabeledSlider> knobs;
//==============================================================================
FlexBox knobBox;
knobBox.flexWrap = FlexBox::Wrap::wrap;
knobBox.justifyContent = FlexBox::JustifyContent::flexStart;
knobBox.alignContent = FlexBox::AlignContent::flexStart;
for (auto* k : knobs)
knobBox.items.add(FlexItem(*k).withMinHeight(80.0f).withMinWidth(80.0f).withFlex(1));
//==============================================================================
FlexBox fb;
fb.flexDirection = FlexBox::Direction::column;
fb.items.add(FlexItem(knobBox).withFlex(2.5));
fb.performLayout(getLocalBounds().toFloat());
}
};
class MainContentComponent : public AudioAppComponent
{
public:
MainContentComponent()
{
addAndMakeVisible(group1);
LabeledSlider* control = new LabeledSlider("Frequency");
control->slider.setRange(20.0, 20000.0);
control->slider.setSkewFactorFromMidPoint(500.0);
control->slider.setNumDecimalPlacesToDisplay(1);
control->slider.setValue(currentFrequency, dontSendNotification);
control->slider.onValueChange = [this] { targetFrequency = frequency.slider.getValue(); };
control->slider.setTextBoxStyle(Slider::TextBoxBelow, false, 100, 20);
control->slider.setRange(50.0, 5000.0);
control->slider.setSkewFactorFromMidPoint(500.0);
control->slider.setNumDecimalPlacesToDisplay(1);
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Level");
control->slider.setRange(0.0, 1.0);
control->slider.onValueChange = [this] { targetLevel = (float)level.slider.getValue(); };
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy1");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy2");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy3");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy4");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy5");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy6");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy7");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy8");
addAndMakeVisible(knobs.add(control));
control = new LabeledSlider("Dummy9");
addAndMakeVisible(knobs.add(control));
setSize (600, 600);
setAudioChannels (0, 2); // no inputs, two outputs
}
~MainContentComponent()
{
shutdownAudio();
}
void resized() override
{
//==============================================================================
FlexBox knobBox;
knobBox.flexWrap = FlexBox::Wrap::wrap;
knobBox.justifyContent = FlexBox::JustifyContent::flexStart;
knobBox.alignContent = FlexBox::AlignContent::flexStart;
for (auto* k : knobgroup)
knobBox.items.add(FlexItem(*k).withMinHeight(80.0f).withMinWidth(80.0f).withFlex(1));
//==============================================================================
FlexBox fb;
fb.flexDirection = FlexBox::Direction::column;
fb.items.add(FlexItem(knobBox).withFlex(2.5));
fb.performLayout(getLocalBounds().toFloat());
}
inline void updateAngleDelta()
{
auto cyclesPerSample = currentFrequency / currentSampleRate;
angleDelta = cyclesPerSample * 2.0 * MathConstants<double>::pi;
}
void prepareToPlay (int, double sampleRate) override
{
currentSampleRate = sampleRate;
updateAngleDelta();
}
void releaseResources() override {}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
auto* leftBuffer = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample);
auto* rightBuffer = bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample);
auto localTargetFrequency = targetFrequency;
if (targetFrequency != currentFrequency)
{
auto frequencyIncrement = (targetFrequency - currentFrequency) / bufferToFill.numSamples;
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
auto currentSample = (float) std::sin (currentAngle);
currentFrequency += frequencyIncrement;
updateAngleDelta();
currentAngle += angleDelta;
leftBuffer[sample] = currentSample;
rightBuffer[sample] = currentSample;
}
currentFrequency = localTargetFrequency;
}
else
{
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
auto currentSample = (float) std::sin (currentAngle);
currentAngle += angleDelta;
leftBuffer[sample] = currentSample;
rightBuffer[sample] = currentSample;
}
}
auto localTargetLevel = targetLevel;
bufferToFill.buffer->applyGainRamp (bufferToFill.startSample, bufferToFill.numSamples, currentLevel, localTargetLevel);
currentLevel = localTargetLevel;
}
private:
double currentSampleRate = 0.0, currentAngle = 0.0, angleDelta = 0.0;
double currentFrequency = 500.0, targetFrequency = 500.0;
float currentLevel = 0.1f, targetLevel = 0.1f;
int rotaryDiam = 100;
LabeledSlider frequency{ "Frequency" };
LabeledSlider level{ "Level" };
LabeledSlider dummy1{ "Dummy 1" };
LabeledSlider dummy2{ "Dummy 2" };
LabeledSlider dummy3{ "Dummy 3" };
LabeledSlider dummy4{ "Dummy 4" };
LabeledSlider dummy5{ "Dummy 5" };
LabeledSlider dummy6{ "Dummy 6" };
LabeledSlider dummy7{ "Dummy 7" };
LabeledSlider dummy8{ "Dummy 8" };
LabeledSlider dummy9{ "Dummy 9" };
LabeledGroup group1{ "Group 1" };
OwnedArray<LabeledGroup> knobgroup;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
Main Points:
- I don’t know how to name the ownedarray within the LabeledGroup so that it picks up any LabeledSliders of a given group (it is giving me an “unidentified” error on that at present).
- I don’t know how to define the knobs as LabeledSliders that belong to a given LabeledGroup.
If you’re still feeling at all generous and I haven’t completely worn you out yet, any help? Is this the right idea or wrong idea for how to do this?
I think it’s a valuable goal to try to achieve and I think it should be possible.
Thanks