Tabbed editor
This commit is contained in:
@@ -367,6 +367,138 @@ private:
|
|||||||
float morphDisplayValue { 0.0f };
|
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,
|
class ExampleUIPanel : public juce::Component,
|
||||||
private juce::Timer
|
private juce::Timer
|
||||||
{
|
{
|
||||||
@@ -435,25 +567,31 @@ public:
|
|||||||
{
|
{
|
||||||
slotIndices[(size_t) activeSlot] = slotIndex;
|
slotIndices[(size_t) activeSlot] = slotIndex;
|
||||||
updateSlotThumbnails();
|
updateSlotThumbnails();
|
||||||
|
waveBrowser.refresh();
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
clearDraw.onClick = [this] { userDraw.clear(); };
|
clearDraw.onClick = [this] { userDraw.clear(); };
|
||||||
presetButton.onClick = [this] { showPresetMenu(); };
|
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.setText("DRAW WAVE", juce::dontSendNotification);
|
||||||
lblDrawWave.setColour(juce::Label::textColourId, juce::Colours::white);
|
lblDrawWave.setColour(juce::Label::textColourId, juce::Colours::white);
|
||||||
lblDrawWave.setJustificationType(juce::Justification::left);
|
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 })
|
for (auto* box : { &slotABox, &slotBBox, &slotCBox })
|
||||||
libraryTab.addAndMakeVisible(*box);
|
addAndMakeVisible(*box);
|
||||||
|
|
||||||
slotABox.setName("A");
|
slotABox.setName("A");
|
||||||
slotBBox.setName("B");
|
slotBBox.setName("B");
|
||||||
@@ -524,56 +662,6 @@ public:
|
|||||||
const int leftWidth = getWidth() / 2 - 20;
|
const int leftWidth = getWidth() / 2 - 20;
|
||||||
const int rightX = leftWidth + 30;
|
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.setColour(juce::Colours::darkred);
|
||||||
g.fillRect(juce::Rectangle<int>(rightX, blackTop + 6, getWidth() - rightX - 30, 6));
|
g.fillRect(juce::Rectangle<int>(rightX, blackTop + 6, getWidth() - rightX - 30, 6));
|
||||||
g.setColour(juce::Colours::white);
|
g.setColour(juce::Colours::white);
|
||||||
@@ -591,7 +679,12 @@ public:
|
|||||||
const int leftWidth = getWidth() / 2 - 20;
|
const int leftWidth = getWidth() / 2 - 20;
|
||||||
const int rightX = leftWidth + 30;
|
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);
|
morphLoopToggle.setBounds(rightX, morphSlider.getBottom() + 10, 120, 22);
|
||||||
morphLoopMode.setBounds(morphLoopToggle.getRight() + 8, morphSlider.getBottom() + 6, 150, 26);
|
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);
|
auto tabBounds = juce::Rectangle<int>(rightX, padTop, getWidth() - rightX - 16, blackBottom - padTop - 70);
|
||||||
wavetableTabs.setBounds(tabBounds);
|
wavetableTabs.setBounds(tabBounds);
|
||||||
|
|
||||||
auto editorBounds = editorTab.getLocalBounds().reduced(12);
|
const int buttonRowY = tabBounds.getBottom() + 8;
|
||||||
auto labelBounds = editorBounds.removeFromTop(20);
|
addToBrowser.setBounds(tabBounds.getX() + 220, buttonRowY, 150, 28);
|
||||||
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);
|
|
||||||
clearDraw.setBounds(addToBrowser.getRight() + 12, buttonRowY, 150, 28);
|
clearDraw.setBounds(addToBrowser.getRight() + 12, buttonRowY, 150, 28);
|
||||||
|
|
||||||
const int togglesY = bottom.getY() - 36;
|
const int togglesY = bottom.getY() - 36;
|
||||||
reverbOn.setBounds(getWidth() - 280, togglesY, 120, 24);
|
reverbOn.setBounds(getWidth() - 280, togglesY, 120, 24);
|
||||||
osc2Mute .setBounds(getWidth() - 140, togglesY, 140, 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;
|
const int masterW = 82;
|
||||||
lblMaster.setBounds(getWidth() - masterW - 150, top.getY() + 4, 80, 16);
|
lblMaster.setBounds(getWidth() - masterW - 150, top.getY() + 4, 80, 16);
|
||||||
@@ -688,11 +746,6 @@ public:
|
|||||||
place (10,1,lblRvWet, rvWet, "Rev Wet");
|
place (10,1,lblRvWet, rvWet, "Rev Wet");
|
||||||
}
|
}
|
||||||
|
|
||||||
void mouseDown(const juce::MouseEvent& e) override
|
|
||||||
{
|
|
||||||
handleBrowserClick(e.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
juce::Rectangle<int> getTopPanelBounds() const { return { 0, 0, getWidth(), 80 }; }
|
juce::Rectangle<int> getTopPanelBounds() const { return { 0, 0, getWidth(), 80 }; }
|
||||||
juce::Rectangle<int> getBottomPanelBounds() const
|
juce::Rectangle<int> getBottomPanelBounds() const
|
||||||
@@ -718,24 +771,6 @@ private:
|
|||||||
label.setColour(juce::Label::textColourId, juce::Colours::black);
|
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)
|
void setActiveSlot(int slot)
|
||||||
{
|
{
|
||||||
activeSlot = juce::jlimit(0, 2, slot);
|
activeSlot = juce::jlimit(0, 2, slot);
|
||||||
@@ -861,20 +896,20 @@ private:
|
|||||||
|
|
||||||
juce::ToggleButton chorusOn, reverbOn, osc2Mute;
|
juce::ToggleButton chorusOn, reverbOn, osc2Mute;
|
||||||
|
|
||||||
juce::TabbedComponent wavetableTabs { juce::TabbedButtonBar::TabsAtTop };
|
|
||||||
juce::Component editorTab, libraryTab;
|
|
||||||
|
|
||||||
WaveThumbnail slotABox, slotBBox, slotCBox;
|
WaveThumbnail slotABox, slotBBox, slotCBox;
|
||||||
DrawWaveComponent userDraw;
|
DrawWaveComponent userDraw;
|
||||||
juce::TextButton addToBrowser, clearDraw, presetButton;
|
|
||||||
juce::Label lblDrawWave;
|
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 lblCutoff, lblAttack, lblDecay, lblSustain, lblRelease;
|
||||||
juce::Label lblLfoRate, lblLfoDepth, lblFenvA, lblFenvD, lblFenvS, lblFenvR, lblFenvAmt;
|
juce::Label lblLfoRate, lblLfoDepth, lblFenvA, lblFenvD, lblFenvS, lblFenvR, lblFenvAmt;
|
||||||
juce::Label lblChRate, lblChDepth, lblChDelay, lblChFb, lblChMix;
|
juce::Label lblChRate, lblChDepth, lblChDelay, lblChFb, lblChMix;
|
||||||
juce::Label lblRvRoom, lblRvDamp, lblRvWidth, lblRvWet;
|
juce::Label lblRvRoom, lblRvDamp, lblRvWidth, lblRvWet;
|
||||||
|
|
||||||
std::vector<juce::Rectangle<int>> browserCells;
|
|
||||||
std::array<int, 3> slotIndices { 0, 1, 2 };
|
std::array<int, 3> slotIndices { 0, 1, 2 };
|
||||||
int activeSlot { 0 };
|
int activeSlot { 0 };
|
||||||
juce::String selectedPresetLabel { "Presets" };
|
juce::String selectedPresetLabel { "Presets" };
|
||||||
|
|||||||
Reference in New Issue
Block a user