Tabbed editor

This commit is contained in:
Tim
2025-11-09 05:28:02 +00:00
parent 61bcef19aa
commit b5e63d3adf

View File

@@ -367,6 +367,138 @@ private:
float morphDisplayValue { 0.0f };
};
class WaveBrowserComponent : public juce::Component
{
public:
explicit WaveBrowserComponent(DummyWavetableSynthAudioProcessor& processorRef)
: audioProcessor(processorRef)
{
}
void setOnWaveSelected(std::function<void(int)> handler) { onWaveSelected = std::move(handler); }
void paint(juce::Graphics& g) override
{
g.fillAll(juce::Colours::black.withAlpha(0.35f));
g.setColour(juce::Colours::grey);
g.drawRect(getLocalBounds());
auto grid = getLocalBounds().reduced(8);
const int cellW = juce::jmax(1, grid.getWidth() / kBrowserColumns);
const int cellH = juce::jmax(1, grid.getHeight() / kBrowserRows);
browserCells.clear();
browserCells.reserve(kBrowserColumns * kBrowserRows);
const int waveCount = audioProcessor.getWaveTableCount();
for (int r = 0; r < kBrowserRows; ++r)
for (int c = 0; c < kBrowserColumns; ++c)
{
const int idx = r * kBrowserColumns + c;
auto cell = juce::Rectangle<int>(grid.getX() + c * cellW,
grid.getY() + r * cellH,
cellW, cellH);
browserCells.push_back(cell);
g.setColour(juce::Colours::darkgrey);
g.drawRect(cell);
if (idx < waveCount)
{
if (const auto* tbl = audioProcessor.getPreviewTablePtr(idx))
{
g.setColour(juce::Colours::lime);
juce::Path p;
p.startNewSubPath((float) cell.getX(), (float) cell.getCentreY());
const int n = (int) tbl->size();
for (int i = 0; i < n; i += 32)
{
const float x = juce::jmap((float) i, 0.0f, (float) n,
(float) cell.getX(), (float) cell.getRight());
const float y = juce::jmap((*tbl)[(size_t) i], -1.0f, 1.0f,
(float) cell.getBottom(), (float) cell.getY());
p.lineTo(x, y);
}
g.strokePath(p, juce::PathStrokeType(1.0f));
}
}
else
{
g.setColour(juce::Colours::darkgrey);
g.drawLine((float) cell.getX(), (float) cell.getY(), (float) cell.getRight(), (float) cell.getBottom(), 0.5f);
g.drawLine((float) cell.getRight(), (float) cell.getY(), (float) cell.getX(), (float) cell.getBottom(), 0.5f);
}
}
}
void mouseDown(const juce::MouseEvent& e) override
{
const int waveCount = audioProcessor.getWaveTableCount();
for (size_t i = 0; i < browserCells.size(); ++i)
{
if (browserCells[i].contains(e.getPosition()))
{
if ((int) i >= waveCount)
return;
if (onWaveSelected != nullptr)
onWaveSelected((int) i);
break;
}
}
}
void refresh() { repaint(); }
private:
DummyWavetableSynthAudioProcessor& audioProcessor;
std::vector<juce::Rectangle<int>> browserCells;
std::function<void(int)> onWaveSelected;
};
class EditorTabContent : public juce::Component
{
public:
EditorTabContent(juce::Label& titleLabel, DrawWaveComponent& drawComponent)
: label(titleLabel), draw(drawComponent)
{
addAndMakeVisible(label);
addAndMakeVisible(draw);
}
void resized() override
{
auto area = getLocalBounds().reduced(12);
auto labelArea = area.removeFromTop(20);
label.setBounds(labelArea);
area.removeFromTop(6);
draw.setBounds(area);
}
private:
juce::Label& label;
DrawWaveComponent& draw;
};
class LibraryTabContent : public juce::Component
{
public:
explicit LibraryTabContent(WaveBrowserComponent& browserComponent)
: browser(browserComponent)
{
addAndMakeVisible(browser);
}
void resized() override
{
browser.setBounds(getLocalBounds().reduced(8));
}
private:
WaveBrowserComponent& browser;
};
class ExampleUIPanel : public juce::Component,
private juce::Timer
{
@@ -435,25 +567,31 @@ public:
{
slotIndices[(size_t) activeSlot] = slotIndex;
updateSlotThumbnails();
waveBrowser.refresh();
repaint();
}
};
clearDraw.onClick = [this] { userDraw.clear(); };
presetButton.onClick = [this] { showPresetMenu(); };
addAndMakeVisible(wavetableTabs);
wavetableTabs.addTab("Editor", juce::Colours::transparentBlack, &editorTab, false);
wavetableTabs.addTab("Library", juce::Colours::transparentBlack, &libraryTab, false);
wavetableTabs.setColour(juce::TabbedComponent::outlineColourId, juce::Colours::darkgrey);
editorTab.addAndMakeVisible(lblDrawWave);
editorTab.addAndMakeVisible(userDraw);
lblDrawWave.setText("DRAW WAVE", juce::dontSendNotification);
lblDrawWave.setColour(juce::Label::textColourId, juce::Colours::white);
lblDrawWave.setJustificationType(juce::Justification::left);
addAndMakeVisible(wavetableTabs);
wavetableTabs.addTab("Editor", juce::Colours::transparentBlack, &editorTabContent, false);
wavetableTabs.addTab("Library", juce::Colours::transparentBlack, &libraryTabContent, false);
wavetableTabs.setColour(juce::TabbedComponent::outlineColourId, juce::Colours::darkgrey);
waveBrowser.setOnWaveSelected([this](int index)
{
slotIndices[(size_t) activeSlot] = index;
updateSlotThumbnails();
repaint();
});
for (auto* box : { &slotABox, &slotBBox, &slotCBox })
libraryTab.addAndMakeVisible(*box);
addAndMakeVisible(*box);
slotABox.setName("A");
slotBBox.setName("B");
@@ -524,56 +662,6 @@ public:
const int leftWidth = getWidth() / 2 - 20;
const int rightX = leftWidth + 30;
auto browser = juce::Rectangle<int>(10, blackTop + 8, leftWidth - 20, blackBottom - blackTop - 16);
g.setColour(juce::Colours::grey);
g.drawRect(browser);
const int cellW = browser.getWidth() / kBrowserColumns;
const int cellH = browser.getHeight() / kBrowserRows;
browserCells.clear();
browserCells.reserve(kBrowserColumns * kBrowserRows);
const int waveCount = audioProcessor.getWaveTableCount();
for (int r = 0; r < kBrowserRows; ++r)
for (int c = 0; c < kBrowserColumns; ++c)
{
const int idx = r * kBrowserColumns + c;
auto cell = juce::Rectangle<int>(browser.getX() + c * cellW,
browser.getY() + r * cellH,
cellW, cellH);
browserCells.push_back(cell);
g.setColour(juce::Colours::darkgrey);
g.drawRect(cell);
if (idx < waveCount)
{
if (const auto* tbl = audioProcessor.getPreviewTablePtr(idx))
{
g.setColour(juce::Colours::lime);
juce::Path p;
p.startNewSubPath((float) cell.getX(), (float) cell.getCentreY());
const int n = (int) tbl->size();
for (int i = 0; i < n; i += 32)
{
const float x = juce::jmap((float) i, 0.0f, (float) n,
(float) cell.getX(), (float) cell.getRight());
const float y = juce::jmap((*tbl)[(size_t) i], -1.0f, 1.0f,
(float) cell.getBottom(), (float) cell.getY());
p.lineTo(x, y);
}
g.strokePath(p, juce::PathStrokeType(1.0f));
}
}
else
{
g.setColour(juce::Colours::darkgrey);
g.drawLine((float) cell.getX(), (float) cell.getY(), (float) cell.getRight(), (float) cell.getBottom(), 0.5f);
g.drawLine((float) cell.getRight(), (float) cell.getY(), (float) cell.getX(), (float) cell.getBottom(), 0.5f);
}
}
g.setColour(juce::Colours::darkred);
g.fillRect(juce::Rectangle<int>(rightX, blackTop + 6, getWidth() - rightX - 30, 6));
g.setColour(juce::Colours::white);
@@ -591,7 +679,12 @@ public:
const int leftWidth = getWidth() / 2 - 20;
const int rightX = leftWidth + 30;
morphSlider.setBounds(rightX, blackTop + 16, getWidth() - rightX - 30, 18);
const int boxH = 28;
slotABox.setBounds(rightX, blackTop + 6, 130, boxH);
slotBBox.setBounds(rightX + 140, blackTop + 6, 130, boxH);
slotCBox.setBounds(rightX + 280, blackTop + 6, 130, boxH);
morphSlider.setBounds(rightX, blackTop + boxH + 10, getWidth() - rightX - 30, 18);
morphLoopToggle.setBounds(rightX, morphSlider.getBottom() + 10, 120, 22);
morphLoopMode.setBounds(morphLoopToggle.getRight() + 8, morphSlider.getBottom() + 6, 150, 26);
@@ -601,49 +694,14 @@ public:
auto tabBounds = juce::Rectangle<int>(rightX, padTop, getWidth() - rightX - 16, blackBottom - padTop - 70);
wavetableTabs.setBounds(tabBounds);
auto editorBounds = editorTab.getLocalBounds().reduced(12);
auto labelBounds = editorBounds.removeFromTop(20);
lblDrawWave.setBounds(labelBounds.getX(), labelBounds.getY(), 140, labelBounds.getHeight());
editorBounds.removeFromTop(6);
userDraw.setBounds(editorBounds);
auto libraryBounds = libraryTab.getLocalBounds().reduced(12);
const int slotSpacing = 12;
const int slotHeight = 32;
int slotWidth = (libraryBounds.getWidth() - slotSpacing * 2) / 3;
slotWidth = juce::jmax(90, slotWidth);
const bool fitsHorizontally = (slotWidth * 3 + slotSpacing * 2) <= libraryBounds.getWidth();
if (fitsHorizontally)
{
const int slotY = libraryBounds.getY();
int slotX = libraryBounds.getX();
slotABox.setBounds(slotX, slotY, slotWidth, slotHeight);
slotX += slotWidth + slotSpacing;
slotBBox.setBounds(slotX, slotY, slotWidth, slotHeight);
slotX += slotWidth + slotSpacing;
slotCBox.setBounds(slotX, slotY, slotWidth, slotHeight);
}
else
{
int slotY = libraryBounds.getY();
const int slotX = libraryBounds.getX();
slotWidth = libraryBounds.getWidth();
for (auto* box : { &slotABox, &slotBBox, &slotCBox })
{
box->setBounds(slotX, slotY, slotWidth, slotHeight);
slotY += slotHeight + slotSpacing;
}
}
const int buttonRowY = wavetableTabs.getBottom() + 8;
addToBrowser.setBounds(wavetableTabs.getX() + 220, buttonRowY, 150, 28);
const int buttonRowY = tabBounds.getBottom() + 8;
addToBrowser.setBounds(tabBounds.getX() + 220, buttonRowY, 150, 28);
clearDraw.setBounds(addToBrowser.getRight() + 12, buttonRowY, 150, 28);
const int togglesY = bottom.getY() - 36;
reverbOn.setBounds(getWidth() - 280, togglesY, 120, 24);
osc2Mute .setBounds(getWidth() - 140, togglesY, 140, 24);
chorusOn.setBounds(wavetableTabs.getX(), buttonRowY, 90, 20);
chorusOn.setBounds(tabBounds.getX(), buttonRowY, 90, 20);
const int masterW = 82;
lblMaster.setBounds(getWidth() - masterW - 150, top.getY() + 4, 80, 16);
@@ -688,11 +746,6 @@ public:
place (10,1,lblRvWet, rvWet, "Rev Wet");
}
void mouseDown(const juce::MouseEvent& e) override
{
handleBrowserClick(e.getPosition());
}
private:
juce::Rectangle<int> getTopPanelBounds() const { return { 0, 0, getWidth(), 80 }; }
juce::Rectangle<int> getBottomPanelBounds() const
@@ -718,24 +771,6 @@ private:
label.setColour(juce::Label::textColourId, juce::Colours::black);
}
void handleBrowserClick(juce::Point<int> pos)
{
const int waveCount = audioProcessor.getWaveTableCount();
for (size_t i = 0; i < browserCells.size(); ++i)
{
if (browserCells[i].contains(pos))
{
if ((int) i >= waveCount)
return;
slotIndices[(size_t) activeSlot] = (int) i;
updateSlotThumbnails();
repaint();
break;
}
}
}
void setActiveSlot(int slot)
{
activeSlot = juce::jlimit(0, 2, slot);
@@ -861,20 +896,20 @@ private:
juce::ToggleButton chorusOn, reverbOn, osc2Mute;
juce::TabbedComponent wavetableTabs { juce::TabbedButtonBar::TabsAtTop };
juce::Component editorTab, libraryTab;
WaveThumbnail slotABox, slotBBox, slotCBox;
DrawWaveComponent userDraw;
juce::TextButton addToBrowser, clearDraw, presetButton;
juce::Label lblDrawWave;
WaveBrowserComponent waveBrowser { audioProcessor };
EditorTabContent editorTabContent { lblDrawWave, userDraw };
LibraryTabContent libraryTabContent { waveBrowser };
juce::TabbedComponent wavetableTabs { juce::TabbedButtonBar::TabsAtTop };
juce::TextButton addToBrowser, clearDraw, presetButton;
juce::Label lblCutoff, lblAttack, lblDecay, lblSustain, lblRelease;
juce::Label lblLfoRate, lblLfoDepth, lblFenvA, lblFenvD, lblFenvS, lblFenvR, lblFenvAmt;
juce::Label lblChRate, lblChDepth, lblChDelay, lblChFb, lblChMix;
juce::Label lblRvRoom, lblRvDamp, lblRvWidth, lblRvWet;
std::vector<juce::Rectangle<int>> browserCells;
std::array<int, 3> slotIndices { 0, 1, 2 };
int activeSlot { 0 };
juce::String selectedPresetLabel { "Presets" };