feat(gui): add knob value popup, tooltips, tab cycling, and layout system
Add a floating value label near the cursor while dragging a knob, giving immediate feedback without altering slider behaviour. Inherit SettableTooltipClient on ToggleButton so setTooltip works consistently with knobs and combos. Introduce a shared layout constants namespace in PluginEditor so every panel, row and control position derives from one spacing system. Add tooltips to all knobs, combos, toggles, and buttons. Add Tab/Shift+Tab tab cycling via keyPressed, with a TooltipWindow for hover hints.
This commit is contained in:
@@ -21,4 +21,66 @@ juce::String Knob::getTextFromValue (double value)
|
|||||||
return formatPercent ((float) value);
|
return formatPercent ((float) value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Knob::mouseDown (const juce::MouseEvent& e)
|
||||||
|
{
|
||||||
|
juce::Slider::mouseDown (e);
|
||||||
|
showValuePopup (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Knob::mouseDrag (const juce::MouseEvent& e)
|
||||||
|
{
|
||||||
|
juce::Slider::mouseDrag (e);
|
||||||
|
showValuePopup (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Knob::mouseUp (const juce::MouseEvent& e)
|
||||||
|
{
|
||||||
|
juce::Slider::mouseUp (e);
|
||||||
|
hideValuePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Knob::mouseExit (const juce::MouseEvent& e)
|
||||||
|
{
|
||||||
|
juce::Slider::mouseExit (e);
|
||||||
|
hideValuePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// The floating readout lives as a child of the top-level component so it can
|
||||||
|
// follow the cursor outside this knob's own bounds without being clipped, and
|
||||||
|
// it never intercepts mouse clicks so it can't block interaction.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void Knob::showValuePopup (const juce::MouseEvent& e)
|
||||||
|
{
|
||||||
|
auto* topLevel = getTopLevelComponent();
|
||||||
|
if (topLevel == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (valuePopup == nullptr)
|
||||||
|
{
|
||||||
|
valuePopup = std::make_unique<juce::Label>();
|
||||||
|
valuePopup->setAlwaysOnTop (true);
|
||||||
|
valuePopup->setInterceptsMouseClicks (false, false);
|
||||||
|
valuePopup->setColour (juce::Label::backgroundColourId, juce::Colours::transparentBlack);
|
||||||
|
valuePopup->setColour (juce::Label::textColourId, theme::text);
|
||||||
|
valuePopup->setFont (juce::Font (14.0f, juce::Font::bold));
|
||||||
|
valuePopup->setJustificationType (juce::Justification::centred);
|
||||||
|
topLevel->addAndMakeVisible (valuePopup.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
valuePopup->setText (getTextFromValue (getValue()), juce::dontSendNotification);
|
||||||
|
|
||||||
|
constexpr int w = 84;
|
||||||
|
constexpr int h = 24;
|
||||||
|
constexpr int gap = 10;
|
||||||
|
|
||||||
|
const auto cursor = topLevel->getLocalPoint (nullptr, e.getScreenPosition());
|
||||||
|
valuePopup->setBounds (cursor.getX() - w / 2, cursor.getY() - h - gap, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Knob::hideValuePopup()
|
||||||
|
{
|
||||||
|
valuePopup.reset();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace serum
|
} // namespace serum
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <JuceHeader.h>
|
#include <JuceHeader.h>
|
||||||
|
#include <memory>
|
||||||
#include "../Resources.h"
|
#include "../Resources.h"
|
||||||
|
|
||||||
namespace serum
|
namespace serum
|
||||||
@@ -9,6 +10,10 @@ namespace serum
|
|||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Rotary knob — a juce::Slider styled by SerumLookAndFeel. The value readout is
|
// Rotary knob — a juce::Slider styled by SerumLookAndFeel. The value readout is
|
||||||
// formatted by a user-provided function via getTextFromValue().
|
// formatted by a user-provided function via getTextFromValue().
|
||||||
|
//
|
||||||
|
// While the knob is being dragged it also shows a small floating label near the
|
||||||
|
// mouse cursor with the live (formatted) value, giving immediate feedback
|
||||||
|
// without changing the slider's normal behaviour.
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class Knob : public juce::Slider
|
class Knob : public juce::Slider
|
||||||
{
|
{
|
||||||
@@ -20,8 +25,17 @@ public:
|
|||||||
|
|
||||||
juce::String getTextFromValue (double value) override;
|
juce::String getTextFromValue (double value) override;
|
||||||
|
|
||||||
|
void mouseDown (const juce::MouseEvent& e) override;
|
||||||
|
void mouseDrag (const juce::MouseEvent& e) override;
|
||||||
|
void mouseUp (const juce::MouseEvent& e) override;
|
||||||
|
void mouseExit (const juce::MouseEvent& e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void showValuePopup (const juce::MouseEvent& e);
|
||||||
|
void hideValuePopup();
|
||||||
|
|
||||||
std::function<juce::String (float)> formatter;
|
std::function<juce::String (float)> formatter;
|
||||||
|
std::unique_ptr<juce::Label> valuePopup;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace serum
|
} // namespace serum
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ namespace serum
|
|||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// LED-style toggle (power) button that drives a float parameter (0/1) or an
|
// LED-style toggle (power) button that drives a float parameter (0/1) or an
|
||||||
// optional callback (used by RAVE).
|
// optional callback (used by RAVE).
|
||||||
|
//
|
||||||
|
// Inherits SettableTooltipClient so setTooltip() works exactly like it does on
|
||||||
|
// juce::Button / juce::Slider / juce::ComboBox (juce::Component itself has no
|
||||||
|
// tooltip support).
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class ToggleButton : public juce::Component
|
class ToggleButton : public juce::Component,
|
||||||
|
public juce::SettableTooltipClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ToggleButton (const juce::String& label = {});
|
explicit ToggleButton (const juce::String& label = {});
|
||||||
|
|||||||
+297
-126
@@ -5,6 +5,57 @@ namespace serum
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Consistent spacing system. Every panel, row and control position below is
|
||||||
|
// derived from these constants so the tabs share identical margins, padding
|
||||||
|
// and row pitches.
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
namespace layout
|
||||||
|
{
|
||||||
|
constexpr int margin = 10; // panels inset from the tab view edge
|
||||||
|
constexpr int padding = 8; // controls inset inside panels + row rhythm
|
||||||
|
constexpr int gap = 6; // horizontal spacing between standard knobs
|
||||||
|
constexpr int knobSize = 76; // standard knob footprint (incl. label)
|
||||||
|
constexpr int knobSmall = 44; // small knob width (env / LFO rows)
|
||||||
|
constexpr int rowHeight = knobSize + padding; // vertical pitch of knob rows
|
||||||
|
constexpr int panelSpacing = 10; // gap between adjacent panels
|
||||||
|
constexpr int labelHeight = 16; // label strip height
|
||||||
|
|
||||||
|
// Derived from the visual style (panel title bar and control sizes) so the
|
||||||
|
// content grid stays aligned across every tab.
|
||||||
|
constexpr int titleHeight = 22; // panel title bar height
|
||||||
|
constexpr int comboHeight = 24; // combo box height
|
||||||
|
constexpr int buttonHeight = 26; // text button height
|
||||||
|
constexpr int toggleWidth = 40; // "On"/"Sync" toggle width
|
||||||
|
constexpr int toggleHeight = 36; // toggle height (LED + visible label)
|
||||||
|
constexpr int headerHeight = toggleHeight; // top control row height
|
||||||
|
constexpr int displayHeight = 66; // waveform / filter display height
|
||||||
|
constexpr int envDisplayHeight = 56; // envelope display height
|
||||||
|
constexpr int lfoDisplayHeight = 64; // LFO display height
|
||||||
|
constexpr int smallKnobHeight = 60; // height of 44px-wide small knobs
|
||||||
|
constexpr int smallGap = 3; // horizontal spacing between small knobs
|
||||||
|
constexpr int arrowWidth = 26; // FX up/down arrow button width
|
||||||
|
constexpr int macroKnobSize = 120; // macro knob footprint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal position of a grid column (0-based).
|
||||||
|
inline int gridX (int col, int cellW, int cellGap)
|
||||||
|
{
|
||||||
|
return layout::margin + col * (cellW + cellGap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y position of a combo box centred on a panel's header row.
|
||||||
|
inline int comboRowY()
|
||||||
|
{
|
||||||
|
return layout::titleHeight + (layout::headerHeight - layout::comboHeight) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y position of the first content row below a panel's header row.
|
||||||
|
inline int bodyTop()
|
||||||
|
{
|
||||||
|
return layout::titleHeight + layout::headerHeight + layout::padding;
|
||||||
|
}
|
||||||
|
|
||||||
juce::StringArray wavetableItems()
|
juce::StringArray wavetableItems()
|
||||||
{
|
{
|
||||||
juce::StringArray a;
|
juce::StringArray a;
|
||||||
@@ -37,7 +88,7 @@ namespace
|
|||||||
juce::String fmtSlope (float v) { const int s = (int) std::llround (v * 2.0f); return juce::String (s == 0 ? 6 : (s == 1 ? 12 : 24)) + " dB"; }
|
juce::String fmtSlope (float v) { const int s = (int) std::llround (v * 2.0f); return juce::String (s == 0 ? 6 : (s == 1 ? 12 : 24)) + " dB"; }
|
||||||
juce::String fmtDepth (float v) { return juce::String (v, 2); }
|
juce::String fmtDepth (float v) { return juce::String (v, 2); }
|
||||||
|
|
||||||
void placeKnobs (std::vector<Knob*>& knobs, int x, int y, int w, int h, int gap = 6)
|
void placeKnobs (std::vector<Knob*>& knobs, int x, int y, int w, int h, int gap = layout::gap)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < (int) knobs.size(); ++i)
|
for (int i = 0; i < (int) knobs.size(); ++i)
|
||||||
knobs[(size_t) i]->setBounds (x + i * (w + gap), y, w, h);
|
knobs[(size_t) i]->setBounds (x + i * (w + gap), y, w, h);
|
||||||
@@ -60,8 +111,13 @@ namespace
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
PluginEditor::PluginEditor (SerumAltAudioProcessor& p)
|
PluginEditor::PluginEditor (SerumAltAudioProcessor& p)
|
||||||
: AudioProcessorEditor (p), processor (p),
|
: AudioProcessorEditor (p), processor (p),
|
||||||
|
tooltipWindow (this, 500),
|
||||||
masterKnob ("Master", formatPercent)
|
masterKnob ("Master", formatPercent)
|
||||||
{
|
{
|
||||||
|
// Tab / Shift+Tab cycling is handled by keyPressed(); this lets the editor
|
||||||
|
// receive keyboard focus when the user clicks anywhere on it.
|
||||||
|
setWantsKeyboardFocus (true);
|
||||||
|
|
||||||
root.setBounds (0, 0, kBaseW, kBaseH);
|
root.setBounds (0, 0, kBaseW, kBaseH);
|
||||||
addAndMakeVisible (root);
|
addAndMakeVisible (root);
|
||||||
|
|
||||||
@@ -104,18 +160,20 @@ void PluginEditor::resized()
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
Knob* PluginEditor::makeKnob (juce::Component* parent, const juce::String& name,
|
Knob* PluginEditor::makeKnob (juce::Component* parent, const juce::String& name,
|
||||||
const juce::String& paramId, std::function<juce::String (float)> fmt)
|
const juce::String& paramId, std::function<juce::String (float)> fmt,
|
||||||
|
const juce::String& tooltip)
|
||||||
{
|
{
|
||||||
auto* k = new Knob (name, std::move (fmt));
|
auto* k = new Knob (name, std::move (fmt));
|
||||||
parent->addAndMakeVisible (k);
|
parent->addAndMakeVisible (k);
|
||||||
if (paramId.isNotEmpty())
|
if (paramId.isNotEmpty())
|
||||||
sliderAttachments.push_back (std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment> (
|
sliderAttachments.push_back (std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment> (
|
||||||
processor.parameters, paramId, *k));
|
processor.parameters, paramId, *k));
|
||||||
|
k->setTooltip (tooltip.isNotEmpty() ? tooltip : name);
|
||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
|
|
||||||
juce::ComboBox* PluginEditor::makeCombo (juce::Component* parent, const juce::String& paramId,
|
juce::ComboBox* PluginEditor::makeCombo (juce::Component* parent, const juce::String& paramId,
|
||||||
const juce::StringArray& items)
|
const juce::StringArray& items, const juce::String& tooltip)
|
||||||
{
|
{
|
||||||
auto* c = new juce::ComboBox();
|
auto* c = new juce::ComboBox();
|
||||||
c->addItemList (items, 1);
|
c->addItemList (items, 1);
|
||||||
@@ -124,16 +182,19 @@ juce::ComboBox* PluginEditor::makeCombo (juce::Component* parent, const juce::St
|
|||||||
if (paramId.isNotEmpty())
|
if (paramId.isNotEmpty())
|
||||||
comboAttachments.push_back (std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment> (
|
comboAttachments.push_back (std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment> (
|
||||||
processor.parameters, paramId, *c));
|
processor.parameters, paramId, *c));
|
||||||
|
if (tooltip.isNotEmpty())
|
||||||
|
c->setTooltip (tooltip);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
ToggleButton* PluginEditor::makeToggle (juce::Component* parent, const juce::String& label,
|
ToggleButton* PluginEditor::makeToggle (juce::Component* parent, const juce::String& label,
|
||||||
const juce::String& paramId)
|
const juce::String& paramId, const juce::String& tooltip)
|
||||||
{
|
{
|
||||||
auto* t = new ToggleButton (label);
|
auto* t = new ToggleButton (label);
|
||||||
parent->addAndMakeVisible (t);
|
parent->addAndMakeVisible (t);
|
||||||
if (paramId.isNotEmpty())
|
if (paramId.isNotEmpty())
|
||||||
t->attach (processor.parameters, paramId);
|
t->attach (processor.parameters, paramId);
|
||||||
|
t->setTooltip (tooltip.isNotEmpty() ? tooltip : label);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +210,7 @@ void PluginEditor::buildTopBar()
|
|||||||
presetCombo.onChange = [this] { processor.setCurrentProgram (presetCombo.getSelectedItemIndex()); };
|
presetCombo.onChange = [this] { processor.setCurrentProgram (presetCombo.getSelectedItemIndex()); };
|
||||||
root.addAndMakeVisible (presetCombo);
|
root.addAndMakeVisible (presetCombo);
|
||||||
presetCombo.setBounds (200, 16, 220, 24);
|
presetCombo.setBounds (200, 16, 220, 24);
|
||||||
|
presetCombo.setTooltip ("Select a factory preset");
|
||||||
|
|
||||||
// UI scale.
|
// UI scale.
|
||||||
scaleCombo.addItemList ({ "75%", "100%", "125%", "150%", "200%" }, 1);
|
scaleCombo.addItemList ({ "75%", "100%", "125%", "150%", "200%" }, 1);
|
||||||
@@ -156,18 +218,25 @@ void PluginEditor::buildTopBar()
|
|||||||
scaleCombo.onChange = [this] { processor.setUiScaleIndex (scaleCombo.getSelectedItemIndex()); };
|
scaleCombo.onChange = [this] { processor.setUiScaleIndex (scaleCombo.getSelectedItemIndex()); };
|
||||||
root.addAndMakeVisible (scaleCombo);
|
root.addAndMakeVisible (scaleCombo);
|
||||||
scaleCombo.setBounds (980, 16, 70, 24);
|
scaleCombo.setBounds (980, 16, 70, 24);
|
||||||
|
scaleCombo.setTooltip ("Interface scale (75% - 200%)");
|
||||||
|
|
||||||
// RAVE.
|
// RAVE.
|
||||||
raveButton.setLabel ("RAVE");
|
raveButton.setLabel ("RAVE");
|
||||||
raveButton.setOnColour (theme::raveGlow);
|
raveButton.setOnColour (theme::raveGlow);
|
||||||
raveButton.setBounds (1062, 2, 50, 56);
|
raveButton.setBounds (1062, 2, 50, 56);
|
||||||
root.addAndMakeVisible (raveButton);
|
root.addAndMakeVisible (raveButton);
|
||||||
|
raveButton.setTooltip ("RAVE one-click boost (unison, width, drive, OTT, reverb)");
|
||||||
// RAVE uses a callback rather than a direct parameter attachment.
|
// RAVE uses a callback rather than a direct parameter attachment.
|
||||||
raveButton.setOnClick ([this] (bool on) { processor.setRaveEnabled (on); });
|
raveButton.setOnClick ([this] (bool on) { processor.setRaveEnabled (on); });
|
||||||
|
|
||||||
// Tab bar.
|
// Tab bar.
|
||||||
juce::TextButton* tabs[5] = { &tabOsc, &tabFilter, &tabMod, &tabFx, &tabMacro };
|
juce::TextButton* tabs[5] = { &tabOsc, &tabFilter, &tabMod, &tabFx, &tabMacro };
|
||||||
const char* tabNames[5] = { "OSC", "FILTER", "MOD", "FX", "MACRO" };
|
const char* tabNames[5] = { "OSC", "FILTER", "MOD", "FX", "MACRO" };
|
||||||
|
const char* tabTips[5] = { "Oscillators (Tab cycles sections)",
|
||||||
|
"Filters (Tab cycles sections)",
|
||||||
|
"Modulation: envelopes, LFOs and matrix (Tab cycles sections)",
|
||||||
|
"Effects rack (Tab cycles sections)",
|
||||||
|
"Macros (Tab cycles sections)" };
|
||||||
for (int i = 0; i < 5; ++i)
|
for (int i = 0; i < 5; ++i)
|
||||||
{
|
{
|
||||||
tabs[i]->setButtonText (tabNames[i]);
|
tabs[i]->setButtonText (tabNames[i]);
|
||||||
@@ -175,11 +244,13 @@ void PluginEditor::buildTopBar()
|
|||||||
tabs[i]->setRadioGroupId (1001);
|
tabs[i]->setRadioGroupId (1001);
|
||||||
tabs[i]->setBounds (10 + i * 92, 36, 84, 22);
|
tabs[i]->setBounds (10 + i * 92, 36, 84, 22);
|
||||||
tabs[i]->onClick = [this, i] { setTab (i); };
|
tabs[i]->onClick = [this, i] { setTab (i); };
|
||||||
|
tabs[i]->setTooltip (tabTips[i]);
|
||||||
root.addAndMakeVisible (tabs[i]);
|
root.addAndMakeVisible (tabs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Master.
|
// Master.
|
||||||
masterKnob.setBounds (906, 0, 64, 60);
|
masterKnob.setBounds (906, 0, 64, 60);
|
||||||
|
masterKnob.setTooltip ("Master output volume (0-100%)");
|
||||||
root.addAndMakeVisible (masterKnob);
|
root.addAndMakeVisible (masterKnob);
|
||||||
sliderAttachments.push_back (std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment> (
|
sliderAttachments.push_back (std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment> (
|
||||||
processor.parameters, ids::master, masterKnob));
|
processor.parameters, ids::master, masterKnob));
|
||||||
@@ -194,79 +265,90 @@ void PluginEditor::buildOscTab()
|
|||||||
oscView.setBounds (0, 60, kBaseW, kBaseH - 60);
|
oscView.setBounds (0, 60, kBaseW, kBaseH - 60);
|
||||||
root.addChildComponent (oscView);
|
root.addChildComponent (oscView);
|
||||||
|
|
||||||
oscAPanel.setBounds (10, 8, 545, 300);
|
constexpr int colW = 545; // two equal columns filling kBaseW
|
||||||
oscBPanel.setBounds (565, 8, 545, 300);
|
constexpr int oscH = 308;
|
||||||
subPanel.setBounds (10, 318, 360, 150);
|
constexpr int subH = 150;
|
||||||
noisePanel.setBounds (380, 318, 360, 150);
|
|
||||||
|
oscAPanel.setBounds (gridX (0, colW, layout::panelSpacing), layout::margin, colW, oscH);
|
||||||
|
oscBPanel.setBounds (gridX (1, colW, layout::panelSpacing), layout::margin, colW, oscH);
|
||||||
|
subPanel.setBounds (gridX (0, colW, layout::panelSpacing), layout::margin + oscH + layout::panelSpacing, colW, subH);
|
||||||
|
noisePanel.setBounds (gridX (1, colW, layout::panelSpacing), layout::margin + oscH + layout::panelSpacing, colW, subH);
|
||||||
for (auto* panel : { &oscAPanel, &oscBPanel, &subPanel, &noisePanel })
|
for (auto* panel : { &oscAPanel, &oscBPanel, &subPanel, &noisePanel })
|
||||||
oscView.addAndMakeVisible (panel);
|
oscView.addAndMakeVisible (panel);
|
||||||
|
|
||||||
|
const int comboY = comboRowY();
|
||||||
|
const int displayY = bodyTop(); // below header row
|
||||||
|
const int row1Y = displayY + layout::displayHeight + layout::padding;
|
||||||
|
const int row2Y = row1Y + layout::rowHeight;
|
||||||
|
|
||||||
// --- Oscillator A ---
|
// --- Oscillator A ---
|
||||||
makeToggle (&oscAPanel, "On", ids::oscAOn)->setBounds (10, 8, 40, 28);
|
makeToggle (&oscAPanel, "On", ids::oscAOn, "Enable oscillator A")->setBounds (layout::padding, layout::titleHeight, layout::toggleWidth, layout::toggleHeight);
|
||||||
makeCombo (&oscAPanel, ids::oscAWave, wavetableItems())->setBounds (56, 10, 200, 24);
|
makeCombo (&oscAPanel, ids::oscAWave, wavetableItems(), "Oscillator A wavetable")->setBounds (layout::padding + layout::toggleWidth + layout::gap, comboY, 200, layout::comboHeight);
|
||||||
makeCombo (&oscAPanel, ids::oscAWarp, { "None", "Bend+", "Bend-", "Sync", "PWM", "Asym", "Mirror", "Fold" })->setBounds (262, 10, 130, 24);
|
makeCombo (&oscAPanel, ids::oscAWarp, { "None", "Bend+", "Bend-", "Sync", "PWM", "Asym", "Mirror", "Fold" }, "Oscillator A warp mode")
|
||||||
|
->setBounds (layout::padding + layout::toggleWidth + layout::gap + 200 + layout::gap, comboY, 130, layout::comboHeight);
|
||||||
|
|
||||||
oscAWave.setWavetables (&processor.engine.getWavetables());
|
oscAWave.setWavetables (&processor.engine.getWavetables());
|
||||||
oscAPanel.addAndMakeVisible (oscAWave);
|
oscAPanel.addAndMakeVisible (oscAWave);
|
||||||
oscAWave.setBounds (10, 44, 525, 66);
|
oscAWave.setBounds (layout::padding, displayY, colW - 2 * layout::padding, layout::displayHeight);
|
||||||
|
|
||||||
std::vector<Knob*> rowA1 = {
|
std::vector<Knob*> rowA1 = {
|
||||||
makeKnob (&oscAPanel, "WT Pos", ids::oscAWtPos, formatPercent),
|
makeKnob (&oscAPanel, "Wavetable Position", ids::oscAWtPos, formatPercent, "Wavetable position (0-100%)"),
|
||||||
makeKnob (&oscAPanel, "Warp Amt", ids::oscAWarpAmt, formatPercent),
|
makeKnob (&oscAPanel, "Warp Amount", ids::oscAWarpAmt, formatPercent, "Warp amount (0-100%)"),
|
||||||
makeKnob (&oscAPanel, "Coarse", ids::oscACoarse, formatSemis),
|
makeKnob (&oscAPanel, "Coarse", ids::oscACoarse, formatSemis, "Pitch coarse (-24 to +24 semitones)"),
|
||||||
makeKnob (&oscAPanel, "Fine", ids::oscAFine, formatCents),
|
makeKnob (&oscAPanel, "Fine", ids::oscAFine, formatCents, "Pitch fine (-100 to +100 cents)"),
|
||||||
makeKnob (&oscAPanel, "Level", ids::oscALevel, formatPercent),
|
makeKnob (&oscAPanel, "Level", ids::oscALevel, formatPercent, "Oscillator A level (0-100%)"),
|
||||||
makeKnob (&oscAPanel, "Pan", ids::oscAPan, formatPan)
|
makeKnob (&oscAPanel, "Pan", ids::oscAPan, formatPan, "Oscillator A pan (L100-R100)")
|
||||||
};
|
};
|
||||||
placeKnobs (rowA1, 10, 118, 76, 76, 6);
|
placeKnobs (rowA1, layout::padding, row1Y, layout::knobSize, layout::knobSize);
|
||||||
|
|
||||||
std::vector<Knob*> rowA2 = {
|
std::vector<Knob*> rowA2 = {
|
||||||
makeKnob (&oscAPanel, "Unison", ids::oscAUnison, fmtUnison),
|
makeKnob (&oscAPanel, "Unison", ids::oscAUnison, fmtUnison, "Unison voices (1-16)"),
|
||||||
makeKnob (&oscAPanel, "Detune", ids::oscADetune, formatPercent),
|
makeKnob (&oscAPanel, "Detune", ids::oscADetune, formatPercent, "Unison detune (0-100%)"),
|
||||||
makeKnob (&oscAPanel, "Spread", ids::oscASpread, formatPercent),
|
makeKnob (&oscAPanel, "Spread", ids::oscASpread, formatPercent, "Unison spread (0-100%)"),
|
||||||
makeKnob (&oscAPanel, "Phase", ids::oscAPhase, formatPercent),
|
makeKnob (&oscAPanel, "Phase", ids::oscAPhase, formatPercent, "Phase (0-100%)"),
|
||||||
makeKnob (&oscAPanel, "Rand Ph", ids::oscARandPh, formatPercent)
|
makeKnob (&oscAPanel, "Random Phase", ids::oscARandPh, formatPercent, "Random phase (0-100%)")
|
||||||
};
|
};
|
||||||
placeKnobs (rowA2, 10, 198, 76, 76, 6);
|
placeKnobs (rowA2, layout::padding, row2Y, layout::knobSize, layout::knobSize);
|
||||||
|
|
||||||
// --- Oscillator B ---
|
// --- Oscillator B ---
|
||||||
makeToggle (&oscBPanel, "On", ids::oscBOn)->setBounds (10, 8, 40, 28);
|
makeToggle (&oscBPanel, "On", ids::oscBOn, "Enable oscillator B")->setBounds (layout::padding, layout::titleHeight, layout::toggleWidth, layout::toggleHeight);
|
||||||
makeCombo (&oscBPanel, ids::oscBWave, wavetableItems())->setBounds (56, 10, 200, 24);
|
makeCombo (&oscBPanel, ids::oscBWave, wavetableItems(), "Oscillator B wavetable")->setBounds (layout::padding + layout::toggleWidth + layout::gap, comboY, 200, layout::comboHeight);
|
||||||
makeCombo (&oscBPanel, ids::oscBWarp, { "None", "Bend+", "Bend-", "Sync", "PWM", "Asym", "Mirror", "Fold" })->setBounds (262, 10, 130, 24);
|
makeCombo (&oscBPanel, ids::oscBWarp, { "None", "Bend+", "Bend-", "Sync", "PWM", "Asym", "Mirror", "Fold" }, "Oscillator B warp mode")
|
||||||
|
->setBounds (layout::padding + layout::toggleWidth + layout::gap + 200 + layout::gap, comboY, 130, layout::comboHeight);
|
||||||
|
|
||||||
oscBWave.setWavetables (&processor.engine.getWavetables());
|
oscBWave.setWavetables (&processor.engine.getWavetables());
|
||||||
oscBPanel.addAndMakeVisible (oscBWave);
|
oscBPanel.addAndMakeVisible (oscBWave);
|
||||||
oscBWave.setBounds (10, 44, 525, 66);
|
oscBWave.setBounds (layout::padding, displayY, colW - 2 * layout::padding, layout::displayHeight);
|
||||||
|
|
||||||
std::vector<Knob*> rowB1 = {
|
std::vector<Knob*> rowB1 = {
|
||||||
makeKnob (&oscBPanel, "WT Pos", ids::oscBWtPos, formatPercent),
|
makeKnob (&oscBPanel, "Wavetable Position", ids::oscBWtPos, formatPercent, "Wavetable position (0-100%)"),
|
||||||
makeKnob (&oscBPanel, "Warp Amt", ids::oscBWarpAmt, formatPercent),
|
makeKnob (&oscBPanel, "Warp Amount", ids::oscBWarpAmt, formatPercent, "Warp amount (0-100%)"),
|
||||||
makeKnob (&oscBPanel, "Coarse", ids::oscBCoarse, formatSemis),
|
makeKnob (&oscBPanel, "Coarse", ids::oscBCoarse, formatSemis, "Pitch coarse (-24 to +24 semitones)"),
|
||||||
makeKnob (&oscBPanel, "Fine", ids::oscBFine, formatCents),
|
makeKnob (&oscBPanel, "Fine", ids::oscBFine, formatCents, "Pitch fine (-100 to +100 cents)"),
|
||||||
makeKnob (&oscBPanel, "Level", ids::oscBLevel, formatPercent),
|
makeKnob (&oscBPanel, "Level", ids::oscBLevel, formatPercent, "Oscillator B level (0-100%)"),
|
||||||
makeKnob (&oscBPanel, "Pan", ids::oscBPan, formatPan)
|
makeKnob (&oscBPanel, "Pan", ids::oscBPan, formatPan, "Oscillator B pan (L100-R100)")
|
||||||
};
|
};
|
||||||
placeKnobs (rowB1, 10, 118, 76, 76, 6);
|
placeKnobs (rowB1, layout::padding, row1Y, layout::knobSize, layout::knobSize);
|
||||||
|
|
||||||
std::vector<Knob*> rowB2 = {
|
std::vector<Knob*> rowB2 = {
|
||||||
makeKnob (&oscBPanel, "Unison", ids::oscBUnison, fmtUnison),
|
makeKnob (&oscBPanel, "Unison", ids::oscBUnison, fmtUnison, "Unison voices (1-16)"),
|
||||||
makeKnob (&oscBPanel, "Detune", ids::oscBDetune, formatPercent),
|
makeKnob (&oscBPanel, "Detune", ids::oscBDetune, formatPercent, "Unison detune (0-100%)"),
|
||||||
makeKnob (&oscBPanel, "Spread", ids::oscBSpread, formatPercent),
|
makeKnob (&oscBPanel, "Spread", ids::oscBSpread, formatPercent, "Unison spread (0-100%)"),
|
||||||
makeKnob (&oscBPanel, "Phase", ids::oscBPhase, formatPercent),
|
makeKnob (&oscBPanel, "Phase", ids::oscBPhase, formatPercent, "Phase (0-100%)"),
|
||||||
makeKnob (&oscBPanel, "Rand Ph", ids::oscBRandPh, formatPercent)
|
makeKnob (&oscBPanel, "Random Phase", ids::oscBRandPh, formatPercent, "Random phase (0-100%)")
|
||||||
};
|
};
|
||||||
placeKnobs (rowB2, 10, 198, 76, 76, 6);
|
placeKnobs (rowB2, layout::padding, row2Y, layout::knobSize, layout::knobSize);
|
||||||
|
|
||||||
// --- Sub ---
|
// --- Sub ---
|
||||||
makeToggle (&subPanel, "On", ids::subOn)->setBounds (10, 10, 40, 28);
|
makeToggle (&subPanel, "On", ids::subOn, "Enable sub oscillator")->setBounds (layout::padding, layout::titleHeight, layout::toggleWidth, layout::toggleHeight);
|
||||||
makeCombo (&subPanel, ids::subShape, { "Sine", "Triangle" })->setBounds (56, 12, 130, 24);
|
makeCombo (&subPanel, ids::subShape, { "Sine", "Triangle" }, "Sub oscillator waveform")->setBounds (layout::padding + layout::toggleWidth + layout::gap, comboY, 130, layout::comboHeight);
|
||||||
makeCombo (&subPanel, ids::subOct, { "-2 oct", "-1 oct", "0 oct" })->setBounds (192, 12, 90, 24);
|
makeCombo (&subPanel, ids::subOct, { "-2 oct", "-1 oct", "0 oct" }, "Sub oscillator octave")->setBounds (layout::padding + layout::toggleWidth + layout::gap + 130 + layout::gap, comboY, 90, layout::comboHeight);
|
||||||
makeKnob (&subPanel, "Level", ids::subLevel, formatPercent)->setBounds (10, 46, 76, 76);
|
makeKnob (&subPanel, "Level", ids::subLevel, formatPercent, "Sub oscillator level (0-100%)")->setBounds (layout::padding, displayY, layout::knobSize, layout::knobSize);
|
||||||
|
|
||||||
// --- Noise ---
|
// --- Noise ---
|
||||||
makeToggle (&noisePanel, "On", ids::noiseOn)->setBounds (10, 10, 40, 28);
|
makeToggle (&noisePanel, "On", ids::noiseOn, "Enable noise")->setBounds (layout::padding, layout::titleHeight, layout::toggleWidth, layout::toggleHeight);
|
||||||
makeCombo (&noisePanel, ids::noiseType, { "White", "Pink" })->setBounds (56, 12, 130, 24);
|
makeCombo (&noisePanel, ids::noiseType, { "White", "Pink" }, "Noise type")->setBounds (layout::padding + layout::toggleWidth + layout::gap, comboY, 130, layout::comboHeight);
|
||||||
makeKnob (&noisePanel, "Level", ids::noiseLevel, formatPercent)->setBounds (10, 46, 76, 76);
|
makeKnob (&noisePanel, "Level", ids::noiseLevel, formatPercent, "Noise level (0-100%)")->setBounds (layout::padding, displayY, layout::knobSize, layout::knobSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginEditor::buildFilterTab()
|
void PluginEditor::buildFilterTab()
|
||||||
@@ -274,49 +356,56 @@ void PluginEditor::buildFilterTab()
|
|||||||
filterView.setBounds (0, 60, kBaseW, kBaseH - 60);
|
filterView.setBounds (0, 60, kBaseW, kBaseH - 60);
|
||||||
root.addChildComponent (filterView);
|
root.addChildComponent (filterView);
|
||||||
|
|
||||||
filter1Panel.setBounds (10, 8, 360, 300);
|
constexpr int colW = 360;
|
||||||
filter2Panel.setBounds (380, 8, 360, 300);
|
constexpr int panelH = 300; // retains the original panel height (content top-aligned)
|
||||||
routingPanel.setBounds (750, 8, 360, 300);
|
|
||||||
|
filter1Panel.setBounds (gridX (0, colW, layout::panelSpacing), layout::margin, colW, panelH);
|
||||||
|
filter2Panel.setBounds (gridX (1, colW, layout::panelSpacing), layout::margin, colW, panelH);
|
||||||
|
routingPanel.setBounds (gridX (2, colW, layout::panelSpacing), layout::margin, colW, panelH);
|
||||||
for (auto* panel : { &filter1Panel, &filter2Panel, &routingPanel })
|
for (auto* panel : { &filter1Panel, &filter2Panel, &routingPanel })
|
||||||
filterView.addAndMakeVisible (panel);
|
filterView.addAndMakeVisible (panel);
|
||||||
|
|
||||||
const juce::StringArray filterTypes = { "Ladder LP", "Ladder HP", "Ladder BP", "Diode", "Comb", "Formant", "Screamer" };
|
const juce::StringArray filterTypes = { "Ladder LP", "Ladder HP", "Ladder BP", "Diode", "Comb", "Formant", "Screamer" };
|
||||||
const juce::StringArray slopes = { "6 dB", "12 dB", "24 dB" };
|
const juce::StringArray slopes = { "6 dB", "12 dB", "24 dB" };
|
||||||
|
|
||||||
|
const int comboY = comboRowY();
|
||||||
|
const int displayY = bodyTop();
|
||||||
|
const int knobY = displayY + layout::displayHeight + layout::padding; // aligns with OSC row 1
|
||||||
|
|
||||||
// Filter 1
|
// Filter 1
|
||||||
makeToggle (&filter1Panel, "On", ids::f1On)->setBounds (10, 8, 40, 28);
|
makeToggle (&filter1Panel, "On", ids::f1On, "Enable filter 1")->setBounds (layout::padding, layout::titleHeight, layout::toggleWidth, layout::toggleHeight);
|
||||||
makeCombo (&filter1Panel, ids::f1Type, filterTypes)->setBounds (56, 10, 180, 24);
|
makeCombo (&filter1Panel, ids::f1Type, filterTypes, "Filter 1 type")->setBounds (layout::padding + layout::toggleWidth + layout::gap, comboY, 170, layout::comboHeight);
|
||||||
makeCombo (&filter1Panel, ids::f1Slope, slopes)->setBounds (242, 10, 100, 24);
|
makeCombo (&filter1Panel, ids::f1Slope, slopes, "Filter 1 slope")->setBounds (layout::padding + layout::toggleWidth + layout::gap + 170 + layout::gap, comboY, 100, layout::comboHeight);
|
||||||
filter1Panel.addAndMakeVisible (filter1Display);
|
filter1Panel.addAndMakeVisible (filter1Display);
|
||||||
filter1Display.setBounds (10, 44, 340, 70);
|
filter1Display.setBounds (layout::padding, displayY, colW - 2 * layout::padding, layout::displayHeight);
|
||||||
|
|
||||||
std::vector<Knob*> f1 = {
|
std::vector<Knob*> f1 = {
|
||||||
makeKnob (&filter1Panel, "Cutoff", ids::f1Cutoff, formatHz),
|
makeKnob (&filter1Panel, "Cutoff", ids::f1Cutoff, formatHz, "Filter 1 cutoff (20 Hz - 20 kHz)"),
|
||||||
makeKnob (&filter1Panel, "Res", ids::f1Res, formatPercent),
|
makeKnob (&filter1Panel, "Resonance", ids::f1Res, formatPercent, "Filter 1 resonance (0-100%)"),
|
||||||
makeKnob (&filter1Panel, "Drive", ids::f1Drive, formatPercent),
|
makeKnob (&filter1Panel, "Drive", ids::f1Drive, formatPercent, "Filter 1 drive (0-100%)"),
|
||||||
makeKnob (&filter1Panel, "Keytrack", ids::f1Key, formatPercent)
|
makeKnob (&filter1Panel, "Keytrack", ids::f1Key, formatPercent, "Filter 1 keytrack (0-100%)")
|
||||||
};
|
};
|
||||||
placeKnobs (f1, 10, 124, 76, 76, 6);
|
placeKnobs (f1, layout::padding, knobY, layout::knobSize, layout::knobSize);
|
||||||
|
|
||||||
// Filter 2
|
// Filter 2
|
||||||
makeToggle (&filter2Panel, "On", ids::f2On)->setBounds (10, 8, 40, 28);
|
makeToggle (&filter2Panel, "On", ids::f2On, "Enable filter 2")->setBounds (layout::padding, layout::titleHeight, layout::toggleWidth, layout::toggleHeight);
|
||||||
makeCombo (&filter2Panel, ids::f2Type, filterTypes)->setBounds (56, 10, 180, 24);
|
makeCombo (&filter2Panel, ids::f2Type, filterTypes, "Filter 2 type")->setBounds (layout::padding + layout::toggleWidth + layout::gap, comboY, 170, layout::comboHeight);
|
||||||
makeCombo (&filter2Panel, ids::f2Slope, slopes)->setBounds (242, 10, 100, 24);
|
makeCombo (&filter2Panel, ids::f2Slope, slopes, "Filter 2 slope")->setBounds (layout::padding + layout::toggleWidth + layout::gap + 170 + layout::gap, comboY, 100, layout::comboHeight);
|
||||||
filter2Panel.addAndMakeVisible (filter2Display);
|
filter2Panel.addAndMakeVisible (filter2Display);
|
||||||
filter2Display.setBounds (10, 44, 340, 70);
|
filter2Display.setBounds (layout::padding, displayY, colW - 2 * layout::padding, layout::displayHeight);
|
||||||
|
|
||||||
std::vector<Knob*> f2 = {
|
std::vector<Knob*> f2 = {
|
||||||
makeKnob (&filter2Panel, "Cutoff", ids::f2Cutoff, formatHz),
|
makeKnob (&filter2Panel, "Cutoff", ids::f2Cutoff, formatHz, "Filter 2 cutoff (20 Hz - 20 kHz)"),
|
||||||
makeKnob (&filter2Panel, "Res", ids::f2Res, formatPercent),
|
makeKnob (&filter2Panel, "Resonance", ids::f2Res, formatPercent, "Filter 2 resonance (0-100%)"),
|
||||||
makeKnob (&filter2Panel, "Drive", ids::f2Drive, formatPercent),
|
makeKnob (&filter2Panel, "Drive", ids::f2Drive, formatPercent, "Filter 2 drive (0-100%)"),
|
||||||
makeKnob (&filter2Panel, "Keytrack", ids::f2Key, formatPercent)
|
makeKnob (&filter2Panel, "Keytrack", ids::f2Key, formatPercent, "Filter 2 keytrack (0-100%)")
|
||||||
};
|
};
|
||||||
placeKnobs (f2, 10, 124, 76, 76, 6);
|
placeKnobs (f2, layout::padding, knobY, layout::knobSize, layout::knobSize);
|
||||||
|
|
||||||
// Routing
|
// Routing
|
||||||
makeCombo (&routingPanel, ids::fRoute, { "Serial", "Parallel", "Split" })->setBounds (10, 10, 150, 24);
|
makeCombo (&routingPanel, ids::fRoute, { "Serial", "Parallel", "Split" }, "Filter routing (serial / parallel / split)")->setBounds (layout::padding, comboY, 160, layout::comboHeight);
|
||||||
makeKnob (&routingPanel, "Mix", ids::fMix, formatPercent)->setBounds (10, 50, 76, 76);
|
makeKnob (&routingPanel, "Mix", ids::fMix, formatPercent, "Filter mix (0-100%)")->setBounds (layout::padding, displayY, layout::knobSize, layout::knobSize);
|
||||||
makeKnob (&routingPanel, "Output", ids::fOut, formatPercent)->setBounds (100, 50, 76, 76);
|
makeKnob (&routingPanel, "Output", ids::fOut, formatPercent, "Filter output (0-100%)")->setBounds (layout::padding + layout::knobSize + layout::gap, displayY, layout::knobSize, layout::knobSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginEditor::buildModTab()
|
void PluginEditor::buildModTab()
|
||||||
@@ -326,15 +415,20 @@ void PluginEditor::buildModTab()
|
|||||||
|
|
||||||
const juce::StringArray lfoShapes = { "Sine", "Triangle", "Saw", "Square", "S&H", "Step", "Freehand" };
|
const juce::StringArray lfoShapes = { "Sine", "Triangle", "Saw", "Square", "S&H", "Step", "Freehand" };
|
||||||
|
|
||||||
|
constexpr int colW = 269; // four equal columns: 4*269 + 3*8 = 1100
|
||||||
|
constexpr int colGap = 8;
|
||||||
|
constexpr int envH = 154;
|
||||||
|
constexpr int lfoH = 206;
|
||||||
|
|
||||||
// Envelopes (4 across).
|
// Envelopes (4 across).
|
||||||
for (int i = 0; i < kNumEnvelopes; ++i)
|
for (int i = 0; i < kNumEnvelopes; ++i)
|
||||||
{
|
{
|
||||||
const int x = 10 + i * 275;
|
envPanels[(size_t) i].setBounds (gridX (i, colW, colGap), layout::margin, colW, envH);
|
||||||
envPanels[(size_t) i].setBounds (x, 8, 265, 150);
|
|
||||||
modView.addAndMakeVisible (envPanels[(size_t) i]);
|
modView.addAndMakeVisible (envPanels[(size_t) i]);
|
||||||
|
|
||||||
envPanels[(size_t) i].addAndMakeVisible (envDisplays[(size_t) i]);
|
envPanels[(size_t) i].addAndMakeVisible (envDisplays[(size_t) i]);
|
||||||
envDisplays[(size_t) i].setBounds (8, 8, 249, 56);
|
envDisplays[(size_t) i].setBounds (layout::padding, layout::titleHeight,
|
||||||
|
colW - 2 * layout::padding, layout::envDisplayHeight);
|
||||||
|
|
||||||
const char* idsA[4] = { ids::env1A, ids::env2A, ids::env3A, ids::env4A };
|
const char* idsA[4] = { ids::env1A, ids::env2A, ids::env3A, ids::env4A };
|
||||||
const char* idsD[4] = { ids::env1D, ids::env2D, ids::env3D, ids::env4D };
|
const char* idsD[4] = { ids::env1D, ids::env2D, ids::env3D, ids::env4D };
|
||||||
@@ -343,20 +437,22 @@ void PluginEditor::buildModTab()
|
|||||||
const char* idsC[4] = { ids::env1Curve, ids::env2Curve, ids::env3Curve, ids::env4Curve };
|
const char* idsC[4] = { ids::env1Curve, ids::env2Curve, ids::env3Curve, ids::env4Curve };
|
||||||
|
|
||||||
std::vector<Knob*> knobs = {
|
std::vector<Knob*> knobs = {
|
||||||
makeKnob (&envPanels[(size_t) i], "A", idsA[i], formatSeconds),
|
makeKnob (&envPanels[(size_t) i], "Attack", idsA[i], formatSeconds, "Attack time (0.5 ms - 12 s)"),
|
||||||
makeKnob (&envPanels[(size_t) i], "D", idsD[i], formatSeconds),
|
makeKnob (&envPanels[(size_t) i], "Decay", idsD[i], formatSeconds, "Decay time (0.5 ms - 12 s)"),
|
||||||
makeKnob (&envPanels[(size_t) i], "S", idsS[i], formatPercent),
|
makeKnob (&envPanels[(size_t) i], "Sustain", idsS[i], formatPercent, "Sustain level (0-100%)"),
|
||||||
makeKnob (&envPanels[(size_t) i], "R", idsR[i], formatSeconds),
|
makeKnob (&envPanels[(size_t) i], "Release", idsR[i], formatSeconds, "Release time (0.5 ms - 12 s)"),
|
||||||
makeKnob (&envPanels[(size_t) i], "Curve", idsC[i], formatPercent)
|
makeKnob (&envPanels[(size_t) i], "Curve", idsC[i], formatPercent, "Envelope curve (0-100%)")
|
||||||
};
|
};
|
||||||
placeKnobs (knobs, 8, 72, 44, 60, 3);
|
placeKnobs (knobs, layout::padding,
|
||||||
|
layout::titleHeight + layout::envDisplayHeight + layout::padding,
|
||||||
|
layout::knobSmall, layout::smallKnobHeight, layout::smallGap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LFOs (4 across).
|
// LFOs (4 across).
|
||||||
|
const int lfoTop = layout::margin + envH + layout::panelSpacing;
|
||||||
for (int i = 0; i < kNumLfos; ++i)
|
for (int i = 0; i < kNumLfos; ++i)
|
||||||
{
|
{
|
||||||
const int x = 10 + i * 275;
|
lfoPanels[(size_t) i].setBounds (gridX (i, colW, colGap), lfoTop, colW, lfoH);
|
||||||
lfoPanels[(size_t) i].setBounds (x, 168, 265, 180);
|
|
||||||
modView.addAndMakeVisible (lfoPanels[(size_t) i]);
|
modView.addAndMakeVisible (lfoPanels[(size_t) i]);
|
||||||
|
|
||||||
const char* rate[4] = { ids::lfo1Rate, ids::lfo2Rate, ids::lfo3Rate, ids::lfo4Rate };
|
const char* rate[4] = { ids::lfo1Rate, ids::lfo2Rate, ids::lfo3Rate, ids::lfo4Rate };
|
||||||
@@ -367,20 +463,22 @@ void PluginEditor::buildModTab()
|
|||||||
const char* fade[4] = { ids::lfo1Fade, ids::lfo2Fade, ids::lfo3Fade, ids::lfo4Fade };
|
const char* fade[4] = { ids::lfo1Fade, ids::lfo2Fade, ids::lfo3Fade, ids::lfo4Fade };
|
||||||
const char* delay[4] = { ids::lfo1Delay, ids::lfo2Delay, ids::lfo3Delay, ids::lfo4Delay };
|
const char* delay[4] = { ids::lfo1Delay, ids::lfo2Delay, ids::lfo3Delay, ids::lfo4Delay };
|
||||||
|
|
||||||
makeCombo (&lfoPanels[(size_t) i], shape[i], lfoShapes)->setBounds (8, 8, 110, 22);
|
makeCombo (&lfoPanels[(size_t) i], shape[i], lfoShapes, "LFO shape")->setBounds (layout::padding, comboRowY(), 110, layout::comboHeight);
|
||||||
makeToggle (&lfoPanels[(size_t) i], "Sync", sync[i])->setBounds (126, 4, 40, 28);
|
makeToggle (&lfoPanels[(size_t) i], "Sync", sync[i], "Tempo-sync LFO")->setBounds (layout::padding + 110 + layout::gap, layout::titleHeight, layout::toggleWidth, layout::toggleHeight);
|
||||||
|
|
||||||
lfoPanels[(size_t) i].addAndMakeVisible (lfoDisplays[(size_t) i]);
|
lfoPanels[(size_t) i].addAndMakeVisible (lfoDisplays[(size_t) i]);
|
||||||
lfoDisplays[(size_t) i].setBounds (8, 38, 249, 64);
|
lfoDisplays[(size_t) i].setBounds (layout::padding, bodyTop(), colW - 2 * layout::padding, layout::lfoDisplayHeight);
|
||||||
|
|
||||||
std::vector<Knob*> knobs = {
|
std::vector<Knob*> knobs = {
|
||||||
makeKnob (&lfoPanels[(size_t) i], "Rate", rate[i], formatPercent),
|
makeKnob (&lfoPanels[(size_t) i], "Rate", rate[i], formatPercent, "LFO rate (0.02 Hz - 30 Hz)"),
|
||||||
makeKnob (&lfoPanels[(size_t) i], "Beat", beat[i], formatPercent),
|
makeKnob (&lfoPanels[(size_t) i], "Beat", beat[i], formatPercent, "Beat division (1/32 - 4 bars)"),
|
||||||
makeKnob (&lfoPanels[(size_t) i], "Phase", phase[i], formatPercent),
|
makeKnob (&lfoPanels[(size_t) i], "Phase", phase[i], formatPercent, "Phase (0-100%)"),
|
||||||
makeKnob (&lfoPanels[(size_t) i], "Fade", fade[i], formatPercent),
|
makeKnob (&lfoPanels[(size_t) i], "Fade", fade[i], formatPercent, "Fade in (0-100%)"),
|
||||||
makeKnob (&lfoPanels[(size_t) i], "Delay", delay[i], formatPercent)
|
makeKnob (&lfoPanels[(size_t) i], "Delay", delay[i], formatPercent, "Start delay (0-100%)")
|
||||||
};
|
};
|
||||||
placeKnobs (knobs, 8, 106, 44, 60, 3);
|
placeKnobs (knobs, layout::padding,
|
||||||
|
bodyTop() + layout::lfoDisplayHeight + layout::padding,
|
||||||
|
layout::knobSmall, layout::smallKnobHeight, layout::smallGap);
|
||||||
|
|
||||||
lfoDisplays[(size_t) i].setOnShapeEdited ([this, i] (const std::vector<float>& data, int steps)
|
lfoDisplays[(size_t) i].setOnShapeEdited ([this, i] (const std::vector<float>& data, int steps)
|
||||||
{
|
{
|
||||||
@@ -389,34 +487,46 @@ void PluginEditor::buildModTab()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Modulation matrix.
|
// Modulation matrix.
|
||||||
matrixPanel.setBounds (10, 356, 1100, 268);
|
const int matrixTop = lfoTop + lfoH + layout::panelSpacing;
|
||||||
|
const int matrixH = kBaseH - 60 - matrixTop - layout::margin;
|
||||||
|
matrixPanel.setBounds (layout::margin, matrixTop, kBaseW - 2 * layout::margin, matrixH);
|
||||||
modView.addAndMakeVisible (matrixPanel);
|
modView.addAndMakeVisible (matrixPanel);
|
||||||
|
|
||||||
std::vector<ModTarget> targetEnums;
|
std::vector<ModTarget> targetEnums;
|
||||||
const juce::StringArray targetItems = modTargetItems (targetEnums);
|
const juce::StringArray targetItems = modTargetItems (targetEnums);
|
||||||
|
|
||||||
|
const int mCenter = layout::titleHeight + 30; // vertical centre of the (60px) depth knob
|
||||||
|
|
||||||
modSourceCombo.addItemList (modSourceItems(), 1);
|
modSourceCombo.addItemList (modSourceItems(), 1);
|
||||||
modSourceCombo.setSelectedItemIndex (0, juce::dontSendNotification);
|
modSourceCombo.setSelectedItemIndex (0, juce::dontSendNotification);
|
||||||
modSourceCombo.setBounds (10, 22, 170, 24);
|
modSourceCombo.setBounds (layout::padding, mCenter - layout::comboHeight / 2, 160, layout::comboHeight);
|
||||||
|
modSourceCombo.setTooltip ("Modulation source");
|
||||||
matrixPanel.addAndMakeVisible (modSourceCombo);
|
matrixPanel.addAndMakeVisible (modSourceCombo);
|
||||||
|
|
||||||
modTargetCombo.addItemList (targetItems, 1);
|
modTargetCombo.addItemList (targetItems, 1);
|
||||||
modTargetCombo.setSelectedItemIndex (0, juce::dontSendNotification);
|
modTargetCombo.setSelectedItemIndex (0, juce::dontSendNotification);
|
||||||
modTargetCombo.setBounds (190, 22, 200, 24);
|
modTargetCombo.setBounds (layout::padding + 160 + layout::gap, mCenter - layout::comboHeight / 2, 200, layout::comboHeight);
|
||||||
|
modTargetCombo.setTooltip ("Modulation destination");
|
||||||
matrixPanel.addAndMakeVisible (modTargetCombo);
|
matrixPanel.addAndMakeVisible (modTargetCombo);
|
||||||
|
|
||||||
|
const int depthX = layout::padding + 160 + layout::gap + 200 + layout::gap;
|
||||||
modDepthKnob.setRange (-1.0, 1.0);
|
modDepthKnob.setRange (-1.0, 1.0);
|
||||||
modDepthKnob.setValue (0.5, juce::dontSendNotification);
|
modDepthKnob.setValue (0.5, juce::dontSendNotification);
|
||||||
modDepthKnob.setFormatter (fmtDepth);
|
modDepthKnob.setFormatter (fmtDepth);
|
||||||
modDepthKnob.setBounds (400, 4, 76, 60);
|
modDepthKnob.setBounds (depthX, layout::titleHeight, layout::knobSize, 60);
|
||||||
|
modDepthKnob.setTooltip ("Modulation depth (-1 to +1)");
|
||||||
matrixPanel.addAndMakeVisible (modDepthKnob);
|
matrixPanel.addAndMakeVisible (modDepthKnob);
|
||||||
|
|
||||||
modBipolarToggle.setBounds (486, 10, 56, 48);
|
modBipolarToggle.setLabel ("Bipolar");
|
||||||
|
modBipolarToggle.setBounds (depthX + layout::knobSize + layout::gap, mCenter - layout::toggleHeight / 2, 60, layout::toggleHeight);
|
||||||
|
modBipolarToggle.setTooltip ("Bipolar modulation depth");
|
||||||
matrixPanel.addAndMakeVisible (modBipolarToggle);
|
matrixPanel.addAndMakeVisible (modBipolarToggle);
|
||||||
modBipolarToggle.setToggleState (true);
|
modBipolarToggle.setToggleState (true);
|
||||||
|
|
||||||
|
const int addX = depthX + layout::knobSize + layout::gap + 60 + layout::gap;
|
||||||
modAddButton.setButtonText ("Add");
|
modAddButton.setButtonText ("Add");
|
||||||
modAddButton.setBounds (550, 22, 70, 26);
|
modAddButton.setBounds (addX, mCenter - layout::buttonHeight / 2, 70, layout::buttonHeight);
|
||||||
|
modAddButton.setTooltip ("Add modulation connection");
|
||||||
modAddButton.onClick = [this, targetEnums]
|
modAddButton.onClick = [this, targetEnums]
|
||||||
{
|
{
|
||||||
const ModSource src = (ModSource) modSourceCombo.getSelectedItemIndex();
|
const ModSource src = (ModSource) modSourceCombo.getSelectedItemIndex();
|
||||||
@@ -432,7 +542,8 @@ void PluginEditor::buildModTab()
|
|||||||
matrixPanel.addAndMakeVisible (modAddButton);
|
matrixPanel.addAndMakeVisible (modAddButton);
|
||||||
|
|
||||||
modRemoveButton.setButtonText ("Remove Last");
|
modRemoveButton.setButtonText ("Remove Last");
|
||||||
modRemoveButton.setBounds (626, 22, 110, 26);
|
modRemoveButton.setBounds (addX + 70 + layout::gap, mCenter - layout::buttonHeight / 2, 110, layout::buttonHeight);
|
||||||
|
modRemoveButton.setTooltip ("Remove last modulation connection");
|
||||||
modRemoveButton.onClick = [this]
|
modRemoveButton.onClick = [this]
|
||||||
{
|
{
|
||||||
processor.engine.getMatrix().removeConnection (processor.engine.getMatrix().size() - 1);
|
processor.engine.getMatrix().removeConnection (processor.engine.getMatrix().size() - 1);
|
||||||
@@ -441,7 +552,8 @@ void PluginEditor::buildModTab()
|
|||||||
matrixPanel.addAndMakeVisible (modRemoveButton);
|
matrixPanel.addAndMakeVisible (modRemoveButton);
|
||||||
|
|
||||||
modClearButton.setButtonText ("Clear");
|
modClearButton.setButtonText ("Clear");
|
||||||
modClearButton.setBounds (742, 22, 70, 26);
|
modClearButton.setBounds (addX + 70 + layout::gap + 110 + layout::gap, mCenter - layout::buttonHeight / 2, 70, layout::buttonHeight);
|
||||||
|
modClearButton.setTooltip ("Clear all modulation connections");
|
||||||
modClearButton.onClick = [this]
|
modClearButton.onClick = [this]
|
||||||
{
|
{
|
||||||
processor.engine.getMatrix().clear();
|
processor.engine.getMatrix().clear();
|
||||||
@@ -449,7 +561,10 @@ void PluginEditor::buildModTab()
|
|||||||
};
|
};
|
||||||
matrixPanel.addAndMakeVisible (modClearButton);
|
matrixPanel.addAndMakeVisible (modClearButton);
|
||||||
|
|
||||||
modList.setBounds (10, 56, 1080, 200);
|
const int listTop = layout::titleHeight + 60 + layout::padding;
|
||||||
|
modList.setBounds (layout::padding, listTop,
|
||||||
|
kBaseW - 2 * layout::margin - 2 * layout::padding,
|
||||||
|
matrixH - listTop - layout::padding);
|
||||||
modList.setReadOnly (true);
|
modList.setReadOnly (true);
|
||||||
modList.setMultiLine (true, false);
|
modList.setMultiLine (true, false);
|
||||||
modList.setColour (juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack);
|
modList.setColour (juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack);
|
||||||
@@ -466,37 +581,44 @@ void PluginEditor::buildFxTab()
|
|||||||
const juce::StringArray fxTypes = { "Off", "Hyper", "Chorus", "Flanger", "Phaser",
|
const juce::StringArray fxTypes = { "Off", "Hyper", "Chorus", "Flanger", "Phaser",
|
||||||
"Distortion", "EQ", "Compressor", "Delay", "Reverb" };
|
"Distortion", "EQ", "Compressor", "Delay", "Reverb" };
|
||||||
|
|
||||||
|
constexpr int colW = 545;
|
||||||
|
constexpr int slotH = 150;
|
||||||
|
|
||||||
for (int i = 0; i < kNumFxSlots; ++i)
|
for (int i = 0; i < kNumFxSlots; ++i)
|
||||||
{
|
{
|
||||||
const int col = i % 2;
|
const int col = i % 2;
|
||||||
const int row = i / 2;
|
const int row = i / 2;
|
||||||
const int x = 10 + col * 555;
|
const int x = gridX (col, colW, layout::panelSpacing);
|
||||||
const int y = 8 + row * 156;
|
const int y = layout::margin + row * (slotH + layout::panelSpacing);
|
||||||
|
|
||||||
fxPanels[(size_t) i].setBounds (x, y, 545, 148);
|
fxPanels[(size_t) i].setBounds (x, y, colW, slotH);
|
||||||
fxView.addAndMakeVisible (fxPanels[(size_t) i]);
|
fxView.addAndMakeVisible (fxPanels[(size_t) i]);
|
||||||
|
|
||||||
fxTypeCombos[(size_t) i] = makeCombo (&fxPanels[(size_t) i], kFxType[i], fxTypes);
|
fxTypeCombos[(size_t) i] = makeCombo (&fxPanels[(size_t) i], kFxType[i], fxTypes,
|
||||||
fxTypeCombos[(size_t) i]->setBounds (8, 8, 160, 24);
|
"FX slot " + juce::String (i + 1) + " effect type");
|
||||||
|
fxTypeCombos[(size_t) i]->setBounds (layout::padding, comboRowY(), 160, layout::comboHeight);
|
||||||
|
|
||||||
|
const int upX = layout::padding + 160 + layout::gap;
|
||||||
fxUp[(size_t) i] = new juce::TextButton ("\xe2\x86\x91");
|
fxUp[(size_t) i] = new juce::TextButton ("\xe2\x86\x91");
|
||||||
fxUp[(size_t) i]->setBounds (176, 8, 26, 24);
|
fxUp[(size_t) i]->setBounds (upX, comboRowY(), layout::arrowWidth, layout::comboHeight);
|
||||||
|
fxUp[(size_t) i]->setTooltip ("Move effect up");
|
||||||
fxPanels[(size_t) i].addAndMakeVisible (fxUp[(size_t) i]);
|
fxPanels[(size_t) i].addAndMakeVisible (fxUp[(size_t) i]);
|
||||||
fxDown[(size_t) i] = new juce::TextButton ("\xe2\x86\x93");
|
fxDown[(size_t) i] = new juce::TextButton ("\xe2\x86\x93");
|
||||||
fxDown[(size_t) i]->setBounds (204, 8, 26, 24);
|
fxDown[(size_t) i]->setBounds (upX + layout::arrowWidth + layout::gap, comboRowY(), layout::arrowWidth, layout::comboHeight);
|
||||||
|
fxDown[(size_t) i]->setTooltip ("Move effect down");
|
||||||
fxPanels[(size_t) i].addAndMakeVisible (fxDown[(size_t) i]);
|
fxPanels[(size_t) i].addAndMakeVisible (fxDown[(size_t) i]);
|
||||||
|
|
||||||
fxUp[(size_t) i]->onClick = [this, i] { if (i > 0) swapFxSlots (i, i - 1); };
|
fxUp[(size_t) i]->onClick = [this, i] { if (i > 0) swapFxSlots (i, i - 1); };
|
||||||
fxDown[(size_t) i]->onClick = [this, i] { if (i < kNumFxSlots - 1) swapFxSlots (i, i + 1); };
|
fxDown[(size_t) i]->onClick = [this, i] { if (i < kNumFxSlots - 1) swapFxSlots (i, i + 1); };
|
||||||
|
|
||||||
std::vector<Knob*> knobs = {
|
std::vector<Knob*> knobs = {
|
||||||
makeKnob (&fxPanels[(size_t) i], "Mix", kFxMix[i], formatPercent),
|
makeKnob (&fxPanels[(size_t) i], "Mix", kFxMix[i], formatPercent, "Effect mix (0-100%)"),
|
||||||
makeKnob (&fxPanels[(size_t) i], "P1", kFxP1[i], formatPercent),
|
makeKnob (&fxPanels[(size_t) i], "P1", kFxP1[i], formatPercent, "Effect parameter 1 (0-100%)"),
|
||||||
makeKnob (&fxPanels[(size_t) i], "P2", kFxP2[i], formatPercent),
|
makeKnob (&fxPanels[(size_t) i], "P2", kFxP2[i], formatPercent, "Effect parameter 2 (0-100%)"),
|
||||||
makeKnob (&fxPanels[(size_t) i], "P3", kFxP3[i], formatPercent),
|
makeKnob (&fxPanels[(size_t) i], "P3", kFxP3[i], formatPercent, "Effect parameter 3 (0-100%)"),
|
||||||
makeKnob (&fxPanels[(size_t) i], "P4", kFxP4[i], formatPercent)
|
makeKnob (&fxPanels[(size_t) i], "P4", kFxP4[i], formatPercent, "Effect parameter 4 (0-100%)")
|
||||||
};
|
};
|
||||||
placeKnobs (knobs, 8, 40, 76, 76, 6);
|
placeKnobs (knobs, layout::padding, bodyTop(), layout::knobSize, layout::knobSize);
|
||||||
fxKnobs[(size_t) i] = knobs;
|
fxKnobs[(size_t) i] = knobs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,40 +628,56 @@ void PluginEditor::buildMacroTab()
|
|||||||
macroView.setBounds (0, 60, kBaseW, kBaseH - 60);
|
macroView.setBounds (0, 60, kBaseW, kBaseH - 60);
|
||||||
root.addChildComponent (macroView);
|
root.addChildComponent (macroView);
|
||||||
|
|
||||||
|
constexpr int colW = 269; // four equal columns: 4*269 + 3*8 = 1100
|
||||||
|
constexpr int colGap = 8;
|
||||||
|
constexpr int macroH = 158;
|
||||||
|
|
||||||
for (int i = 0; i < kNumMacros; ++i)
|
for (int i = 0; i < kNumMacros; ++i)
|
||||||
{
|
{
|
||||||
const int x = 10 + i * 275;
|
macroPanels[(size_t) i].setBounds (gridX (i, colW, colGap), layout::margin, colW, macroH);
|
||||||
macroPanels[(size_t) i].setBounds (x, 8, 265, 240);
|
|
||||||
macroView.addAndMakeVisible (macroPanels[(size_t) i]);
|
macroView.addAndMakeVisible (macroPanels[(size_t) i]);
|
||||||
|
|
||||||
const char* ids[4] = { ids::macro1, ids::macro2, ids::macro3, ids::macro4 };
|
const char* ids[4] = { ids::macro1, ids::macro2, ids::macro3, ids::macro4 };
|
||||||
macroKnobs[(size_t) i] = makeKnob (¯oPanels[(size_t) i], MacroControls::macroName (i), ids[i], formatPercent);
|
macroKnobs[(size_t) i] = makeKnob (¯oPanels[(size_t) i], MacroControls::macroName (i), ids[i], formatPercent,
|
||||||
macroKnobs[(size_t) i]->setBounds (70, 20, 120, 120);
|
MacroControls::macroName (i) + " macro (0-100%)");
|
||||||
|
macroKnobs[(size_t) i]->setBounds ((colW - layout::macroKnobSize) / 2,
|
||||||
|
layout::titleHeight + layout::padding,
|
||||||
|
layout::macroKnobSize, layout::macroKnobSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Macro assignment editor.
|
// Macro assignment editor.
|
||||||
macroAssignPanel.setBounds (10, 256, 1100, 380);
|
const int assignTop = layout::margin + macroH + layout::panelSpacing;
|
||||||
|
const int assignH = kBaseH - 60 - assignTop - layout::margin;
|
||||||
|
macroAssignPanel.setBounds (layout::margin, assignTop, kBaseW - 2 * layout::margin, assignH);
|
||||||
macroView.addAndMakeVisible (macroAssignPanel);
|
macroView.addAndMakeVisible (macroAssignPanel);
|
||||||
|
|
||||||
std::vector<ModTarget> targetEnums;
|
std::vector<ModTarget> targetEnums;
|
||||||
const juce::StringArray targetItems = modTargetItems (targetEnums);
|
const juce::StringArray targetItems = modTargetItems (targetEnums);
|
||||||
|
|
||||||
|
const int mCenter = layout::titleHeight + 30; // vertical centre of the (60px) depth knob
|
||||||
|
|
||||||
macroAssignIndex.addItemList ({ "Macro 1", "Macro 2", "Macro 3", "Macro 4" }, 1);
|
macroAssignIndex.addItemList ({ "Macro 1", "Macro 2", "Macro 3", "Macro 4" }, 1);
|
||||||
macroAssignIndex.setSelectedItemIndex (0, juce::dontSendNotification);
|
macroAssignIndex.setSelectedItemIndex (0, juce::dontSendNotification);
|
||||||
macroAssignIndex.setBounds (10, 24, 140, 24);
|
macroAssignIndex.setBounds (layout::padding, mCenter - layout::comboHeight / 2, 140, layout::comboHeight);
|
||||||
|
macroAssignIndex.setTooltip ("Macro to assign");
|
||||||
macroAssignPanel.addAndMakeVisible (macroAssignIndex);
|
macroAssignPanel.addAndMakeVisible (macroAssignIndex);
|
||||||
|
|
||||||
macroAssignTarget.addItemList (targetItems, 1);
|
macroAssignTarget.addItemList (targetItems, 1);
|
||||||
macroAssignTarget.setSelectedItemIndex (0, juce::dontSendNotification);
|
macroAssignTarget.setSelectedItemIndex (0, juce::dontSendNotification);
|
||||||
macroAssignTarget.setBounds (160, 24, 220, 24);
|
macroAssignTarget.setBounds (layout::padding + 140 + layout::gap, mCenter - layout::comboHeight / 2, 220, layout::comboHeight);
|
||||||
|
macroAssignTarget.setTooltip ("Destination parameter");
|
||||||
macroAssignPanel.addAndMakeVisible (macroAssignTarget);
|
macroAssignPanel.addAndMakeVisible (macroAssignTarget);
|
||||||
|
|
||||||
|
const int depthX = layout::padding + 140 + layout::gap + 220 + layout::gap;
|
||||||
macroDepthKnob.setFormatter (fmtDepth);
|
macroDepthKnob.setFormatter (fmtDepth);
|
||||||
macroDepthKnob.setBounds (390, 6, 76, 60);
|
macroDepthKnob.setBounds (depthX, layout::titleHeight, layout::knobSize, 60);
|
||||||
|
macroDepthKnob.setTooltip ("Macro assignment depth (0 to 1)");
|
||||||
macroAssignPanel.addAndMakeVisible (macroDepthKnob);
|
macroAssignPanel.addAndMakeVisible (macroDepthKnob);
|
||||||
|
|
||||||
|
const int assignBtnX = depthX + layout::knobSize + layout::gap;
|
||||||
macroAssignButton.setButtonText ("Assign");
|
macroAssignButton.setButtonText ("Assign");
|
||||||
macroAssignButton.setBounds (476, 24, 80, 26);
|
macroAssignButton.setBounds (assignBtnX, mCenter - layout::buttonHeight / 2, 80, layout::buttonHeight);
|
||||||
|
macroAssignButton.setTooltip ("Assign destination to the selected macro");
|
||||||
macroAssignButton.onClick = [this, targetEnums]
|
macroAssignButton.onClick = [this, targetEnums]
|
||||||
{
|
{
|
||||||
const int macro = macroAssignIndex.getSelectedItemIndex();
|
const int macro = macroAssignIndex.getSelectedItemIndex();
|
||||||
@@ -554,7 +692,8 @@ void PluginEditor::buildMacroTab()
|
|||||||
macroAssignPanel.addAndMakeVisible (macroAssignButton);
|
macroAssignPanel.addAndMakeVisible (macroAssignButton);
|
||||||
|
|
||||||
macroClearButton.setButtonText ("Clear All");
|
macroClearButton.setButtonText ("Clear All");
|
||||||
macroClearButton.setBounds (562, 24, 80, 26);
|
macroClearButton.setBounds (assignBtnX + 80 + layout::gap, mCenter - layout::buttonHeight / 2, 80, layout::buttonHeight);
|
||||||
|
macroClearButton.setTooltip ("Clear all macro assignments");
|
||||||
macroClearButton.onClick = [this]
|
macroClearButton.onClick = [this]
|
||||||
{
|
{
|
||||||
processor.engine.getMacros().clear();
|
processor.engine.getMacros().clear();
|
||||||
@@ -562,7 +701,10 @@ void PluginEditor::buildMacroTab()
|
|||||||
};
|
};
|
||||||
macroAssignPanel.addAndMakeVisible (macroClearButton);
|
macroAssignPanel.addAndMakeVisible (macroClearButton);
|
||||||
|
|
||||||
macroList.setBounds (10, 58, 1080, 310);
|
const int listTop = layout::titleHeight + 60 + layout::padding;
|
||||||
|
macroList.setBounds (layout::padding, listTop,
|
||||||
|
kBaseW - 2 * layout::margin - 2 * layout::padding,
|
||||||
|
assignH - listTop - layout::padding);
|
||||||
macroList.setReadOnly (true);
|
macroList.setReadOnly (true);
|
||||||
macroList.setMultiLine (true, false);
|
macroList.setMultiLine (true, false);
|
||||||
macroList.setColour (juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack);
|
macroList.setColour (juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack);
|
||||||
@@ -608,6 +750,35 @@ void PluginEditor::setTab (int index)
|
|||||||
tabMacro.setToggleState (currentTab == 4, juce::dontSendNotification);
|
tabMacro.setToggleState (currentTab == 4, juce::dontSendNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PluginEditor::cycleTab (int delta)
|
||||||
|
{
|
||||||
|
setTab ((currentTab + delta + 5) % 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PluginEditor::keyPressed (const juce::KeyPress& key)
|
||||||
|
{
|
||||||
|
// Only handle plain Tab / Shift+Tab (never Ctrl/Cmd/Alt+Tab).
|
||||||
|
if (key.getKeyCode() == juce::KeyPress::tabKey
|
||||||
|
&& ! key.getModifiers().isCtrlDown()
|
||||||
|
&& ! key.getModifiers().isAltDown()
|
||||||
|
&& ! key.getModifiers().isCommandDown())
|
||||||
|
{
|
||||||
|
// Leave Tab alone while a text editor or combo box is being edited so we
|
||||||
|
// don't break typing / focus traversal.
|
||||||
|
for (auto* c = juce::Component::getCurrentlyFocusedComponent(); c != nullptr; c = c->getParentComponent())
|
||||||
|
{
|
||||||
|
if (dynamic_cast<juce::TextEditor*> (c) != nullptr
|
||||||
|
|| dynamic_cast<juce::ComboBox*> (c) != nullptr)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cycleTab (key.getModifiers().isShiftDown() ? -1 : +1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void PluginEditor::applyUiScale()
|
void PluginEditor::applyUiScale()
|
||||||
{
|
{
|
||||||
const float scale = processor.getUiScale();
|
const float scale = processor.getUiScale();
|
||||||
|
|||||||
+10
-3
@@ -30,6 +30,7 @@ public:
|
|||||||
void paint (juce::Graphics&) override;
|
void paint (juce::Graphics&) override;
|
||||||
void resized() override;
|
void resized() override;
|
||||||
void timerCallback() override;
|
void timerCallback() override;
|
||||||
|
bool keyPressed (const juce::KeyPress& key) override;
|
||||||
|
|
||||||
void setTab (int index);
|
void setTab (int index);
|
||||||
|
|
||||||
@@ -42,6 +43,9 @@ private:
|
|||||||
|
|
||||||
juce::Component root;
|
juce::Component root;
|
||||||
|
|
||||||
|
// --- tooltips ---
|
||||||
|
juce::TooltipWindow tooltipWindow;
|
||||||
|
|
||||||
// --- top bar ---
|
// --- top bar ---
|
||||||
std::unique_ptr<juce::Drawable> logo;
|
std::unique_ptr<juce::Drawable> logo;
|
||||||
juce::ComboBox presetCombo;
|
juce::ComboBox presetCombo;
|
||||||
@@ -103,9 +107,12 @@ private:
|
|||||||
|
|
||||||
// --- helpers ---
|
// --- helpers ---
|
||||||
Knob* makeKnob (juce::Component* parent, const juce::String& name, const juce::String& paramId,
|
Knob* makeKnob (juce::Component* parent, const juce::String& name, const juce::String& paramId,
|
||||||
std::function<juce::String (float)> fmt = {});
|
std::function<juce::String (float)> fmt = {}, const juce::String& tooltip = {});
|
||||||
juce::ComboBox* makeCombo (juce::Component* parent, const juce::String& paramId, const juce::StringArray& items);
|
juce::ComboBox* makeCombo (juce::Component* parent, const juce::String& paramId, const juce::StringArray& items,
|
||||||
ToggleButton* makeToggle (juce::Component* parent, const juce::String& label, const juce::String& paramId);
|
const juce::String& tooltip = {});
|
||||||
|
ToggleButton* makeToggle (juce::Component* parent, const juce::String& label, const juce::String& paramId,
|
||||||
|
const juce::String& tooltip = {});
|
||||||
|
void cycleTab (int delta);
|
||||||
|
|
||||||
void buildTopBar();
|
void buildTopBar();
|
||||||
void buildOscTab();
|
void buildOscTab();
|
||||||
|
|||||||
Reference in New Issue
Block a user