我正在JUCE内工作,试图为音频合成器创建一个灵活的面板结构。通常,目标是创建看起来像这样的东西:
有标记的旋钮组的地方。
我正在使用JUCE和C ++,它具有FlexBox功能,该功能允许自动组织屏幕上的元素,这是理想的选择。我目前有这个结果:
所有这些标记的旋钮都可以灵活地布置在屏幕上。
它的工作方式是每个旋钮都是JUCE类中内置的“滑块”,并由名为“ LabeledSlider”的GroupComponent类标记。然后,ownedarray会拾取LabeledSlider的所有对象,内置的FlexBox函数会在屏幕上自动排列此数组。
这是该工作代码,其中仅包含一个基于频率和幅度旋钮的简单正弦波发生器:
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)
};
我现在想通过将旋钮分组到更大的类“ LabeledGroup”中来添加第二层FlexBox,以便每个“ LabeledSlider”在定义时都应属于LabeledGroup。
然后,可以使用相同的FlexBox功能自动组织LabeledGroups中的旋钮,还可以组织LabeledGroups本身。
尽管我对这一切还是陌生的,所以尽管我现在已经花了数小时来阅读有关嵌套类的信息,但我仍然不知道该怎么做。
这是我重组上述代码的粗略尝试,尽管显然不起作用:
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)
我是C ++和JUCE的新手,所以这对我来说很棘手。
我目前的主要问题(如果原则是合理的)是我不知道如何在LabeledGroup类中运行“ ownedarray”,因此给定LabeledGroup中定义的所有LabeledSliders都会自动被拾取因此由内置的FlexBox进行组织。
我也不知道如何将旋钮定义为属于给定LabeledGroup的LabeledSliders。即。如何重新编写滑块声明,以便指定组。
感谢您阅读所有这些内容。任何帮助将不胜感激。当您是新手并且以前从未见过该应用程序的真实示例时,很难弄清这些事情。