390 lines
19 KiB
C++
390 lines
19 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file contains the basic framework code for a JUCE plugin editor.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <JuceHeader.h>
|
|
#include "PluginProcessor.h"
|
|
|
|
//==============================================================================
|
|
/**
|
|
*/
|
|
class FluteSynthAudioProcessorEditor : public juce::AudioProcessorEditor,
|
|
private juce::Timer
|
|
{
|
|
public:
|
|
FluteSynthAudioProcessorEditor (FluteSynthAudioProcessor&);
|
|
~FluteSynthAudioProcessorEditor() override;
|
|
|
|
//==============================================================================
|
|
void paint (juce::Graphics&) override;
|
|
void resized() override;
|
|
|
|
private:
|
|
FluteSynthAudioProcessor& audioProcessor;
|
|
|
|
juce::ComboBox presetSelector;
|
|
juce::Label presetLabel { {}, "Preset" };
|
|
juce::ComboBox temperamentSelector;
|
|
juce::Label temperamentLabel { {}, "Temperament" };
|
|
juce::ComboBox velocityCurveSelector;
|
|
juce::Label velocityCurveLabel { {}, "Velocity Curve" };
|
|
juce::ToggleButton soundboardEnable { "Soundboard" };
|
|
juce::ToggleButton postRoomEnable { "Post Reverb" };
|
|
juce::TextButton resetButton { "Reset to preset" };
|
|
juce::Slider soundboardMix;
|
|
juce::Slider soundboardT60;
|
|
juce::Slider soundboardDamp;
|
|
juce::Slider postRoomMix;
|
|
juce::Slider stringSustain;
|
|
juce::Slider hammerHardness;
|
|
juce::Slider pm2GainDb;
|
|
juce::Slider outputLpfCutoff;
|
|
juce::Slider masterVolume;
|
|
juce::Label soundboardMixLabel { {}, "Soundboard\nMix" };
|
|
juce::Label soundboardT60Label { {}, "Reverb Decay" };
|
|
juce::Label soundboardDampLabel{ {}, "Damp" };
|
|
juce::Label postRoomMixLabel { {}, "Post Reverb" };
|
|
juce::Label stringSustainLabel { {}, "String\nSustain" };
|
|
juce::Label hammerHardnessLabel { {}, "Hammer\nHardness" };
|
|
juce::Label pm2GainLabel { {}, "PM2 Gain" };
|
|
juce::Label outputLpfLabel { {}, "Brightness" };
|
|
juce::Label masterVolumeLabel { {}, "Master" };
|
|
|
|
using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;
|
|
using ButtonAttachment = juce::AudioProcessorValueTreeState::ButtonAttachment;
|
|
using ComboBoxAttachment = juce::AudioProcessorValueTreeState::ComboBoxAttachment;
|
|
|
|
std::unique_ptr<ButtonAttachment> soundboardEnableAttachment;
|
|
std::unique_ptr<ButtonAttachment> postRoomEnableAttachment;
|
|
std::unique_ptr<SliderAttachment> soundboardMixAttachment;
|
|
std::unique_ptr<SliderAttachment> soundboardT60Attachment;
|
|
std::unique_ptr<SliderAttachment> soundboardDampAttachment;
|
|
std::unique_ptr<SliderAttachment> postRoomMixAttachment;
|
|
std::unique_ptr<SliderAttachment> stringSustainAttachment;
|
|
std::unique_ptr<SliderAttachment> hammerHardnessAttachment;
|
|
std::unique_ptr<SliderAttachment> pm2GainAttachment;
|
|
std::unique_ptr<SliderAttachment> outputLpfAttachment;
|
|
std::unique_ptr<SliderAttachment> masterVolumeAttachment;
|
|
std::unique_ptr<ComboBoxAttachment> temperamentAttachment;
|
|
std::unique_ptr<ComboBoxAttachment> velocityCurveAttachment;
|
|
|
|
//==============================================================================
|
|
// Custom LookAndFeel for elegant dropdown menus
|
|
struct ElegantComboBoxLookAndFeel : juce::LookAndFeel_V4
|
|
{
|
|
ElegantComboBoxLookAndFeel()
|
|
{
|
|
// Set popup menu colors
|
|
setColour (juce::PopupMenu::backgroundColourId, juce::Colour::fromRGB (18, 20, 24));
|
|
setColour (juce::PopupMenu::textColourId, juce::Colour::fromRGB (220, 220, 225));
|
|
setColour (juce::PopupMenu::highlightedBackgroundColourId, juce::Colour::fromRGB (60, 70, 85));
|
|
setColour (juce::PopupMenu::highlightedTextColourId, juce::Colours::white);
|
|
}
|
|
|
|
void drawComboBox (juce::Graphics& g, int width, int height, bool isButtonDown,
|
|
int buttonX, int buttonY, int buttonW, int buttonH,
|
|
juce::ComboBox& box) override
|
|
{
|
|
juce::ignoreUnused (buttonX, buttonY, buttonW, buttonH);
|
|
|
|
auto bounds = juce::Rectangle<float> (0, 0, (float) width, (float) height);
|
|
auto cornerSize = 3.0f; // Reduced from 6.0f
|
|
|
|
// Outer glow/shadow effect
|
|
g.setColour (juce::Colours::black.withAlpha (0.4f));
|
|
g.fillRoundedRectangle (bounds.translated (0, 2).reduced (1), cornerSize);
|
|
|
|
// Main background gradient - dark metallic
|
|
juce::ColourGradient bgGradient (juce::Colour::fromRGB (38, 42, 48),
|
|
bounds.getCentreX(), bounds.getY(),
|
|
juce::Colour::fromRGB (22, 24, 28),
|
|
bounds.getCentreX(), bounds.getBottom(),
|
|
false);
|
|
g.setGradientFill (bgGradient);
|
|
g.fillRoundedRectangle (bounds.reduced (1), cornerSize);
|
|
|
|
// Subtle inner highlight at top
|
|
juce::ColourGradient innerHighlight (juce::Colours::white.withAlpha (0.08f),
|
|
bounds.getCentreX(), bounds.getY() + 1,
|
|
juce::Colours::transparentWhite,
|
|
bounds.getCentreX(), bounds.getY() + height * 0.4f,
|
|
false);
|
|
g.setGradientFill (innerHighlight);
|
|
g.fillRoundedRectangle (bounds.reduced (2), cornerSize - 1);
|
|
|
|
// Border - subtle golden/brass tint for piano aesthetic
|
|
auto borderColour = isButtonDown ? juce::Colour::fromRGB (140, 130, 100)
|
|
: juce::Colour::fromRGB (80, 75, 65);
|
|
if (box.hasKeyboardFocus (false))
|
|
borderColour = juce::Colour::fromRGB (160, 150, 120);
|
|
|
|
g.setColour (borderColour);
|
|
g.drawRoundedRectangle (bounds.reduced (1.5f), cornerSize, 1.0f);
|
|
|
|
// Arrow indicator
|
|
auto arrowZone = bounds.removeFromRight ((float) height).reduced (8);
|
|
juce::Path arrow;
|
|
auto arrowH = arrowZone.getHeight() * 0.3f;
|
|
auto arrowW = arrowZone.getWidth() * 0.5f;
|
|
arrow.addTriangle (arrowZone.getCentreX() - arrowW * 0.5f, arrowZone.getCentreY() - arrowH * 0.3f,
|
|
arrowZone.getCentreX() + arrowW * 0.5f, arrowZone.getCentreY() - arrowH * 0.3f,
|
|
arrowZone.getCentreX(), arrowZone.getCentreY() + arrowH * 0.5f);
|
|
|
|
g.setColour (juce::Colour::fromRGB (180, 175, 165));
|
|
g.fillPath (arrow);
|
|
}
|
|
|
|
void drawPopupMenuBackground (juce::Graphics& g, int width, int height) override
|
|
{
|
|
auto bounds = juce::Rectangle<float> (0, 0, (float) width, (float) height);
|
|
|
|
// Shadow
|
|
g.setColour (juce::Colours::black.withAlpha (0.5f));
|
|
g.fillRoundedRectangle (bounds.translated (2, 3), 3.0f); // Reduced from 6.0f
|
|
|
|
// Main background
|
|
juce::ColourGradient bgGradient (juce::Colour::fromRGB (28, 32, 38),
|
|
bounds.getCentreX(), bounds.getY(),
|
|
juce::Colour::fromRGB (18, 20, 24),
|
|
bounds.getCentreX(), bounds.getBottom(),
|
|
false);
|
|
g.setGradientFill (bgGradient);
|
|
g.fillRoundedRectangle (bounds, 3.0f); // Reduced from 6.0f
|
|
|
|
// Border
|
|
g.setColour (juce::Colour::fromRGB (60, 58, 52));
|
|
g.drawRoundedRectangle (bounds.reduced (0.5f), 3.0f, 1.0f); // Reduced from 6.0f
|
|
}
|
|
|
|
void drawPopupMenuItem (juce::Graphics& g, const juce::Rectangle<int>& area,
|
|
bool isSeparator, bool isActive, bool isHighlighted,
|
|
bool isTicked, bool hasSubMenu,
|
|
const juce::String& text, const juce::String& shortcutKeyText,
|
|
const juce::Drawable* icon, const juce::Colour* textColour) override
|
|
{
|
|
juce::ignoreUnused (shortcutKeyText, icon, textColour, hasSubMenu);
|
|
|
|
if (isSeparator)
|
|
{
|
|
auto r = area.reduced (5, 0).toFloat();
|
|
r.removeFromTop ((float) r.getHeight() * 0.5f - 0.5f);
|
|
g.setColour (juce::Colour::fromRGB (50, 52, 58));
|
|
g.fillRect (r.removeFromTop (1.0f));
|
|
return;
|
|
}
|
|
|
|
auto r = area.reduced (2);
|
|
|
|
if (isHighlighted && isActive)
|
|
{
|
|
// Highlighted background with subtle gradient
|
|
juce::ColourGradient hlGradient (juce::Colour::fromRGB (55, 65, 80),
|
|
(float) r.getCentreX(), (float) r.getY(),
|
|
juce::Colour::fromRGB (45, 52, 65),
|
|
(float) r.getCentreX(), (float) r.getBottom(),
|
|
false);
|
|
g.setGradientFill (hlGradient);
|
|
g.fillRoundedRectangle (r.toFloat(), 4.0f);
|
|
}
|
|
|
|
auto textColourToUse = isHighlighted ? juce::Colours::white
|
|
: juce::Colour::fromRGB (200, 200, 205);
|
|
if (! isActive)
|
|
textColourToUse = textColourToUse.withAlpha (0.4f);
|
|
|
|
g.setColour (textColourToUse);
|
|
g.setFont (getPopupMenuFont().withHeight (15.0f));
|
|
|
|
auto textArea = r.reduced (12, 0);
|
|
|
|
if (isTicked)
|
|
{
|
|
// Checkmark for selected item
|
|
auto tickArea = textArea.removeFromLeft (20);
|
|
juce::Path tick;
|
|
tick.startNewSubPath ((float) tickArea.getX() + 4, (float) tickArea.getCentreY());
|
|
tick.lineTo ((float) tickArea.getX() + 8, (float) tickArea.getCentreY() + 4);
|
|
tick.lineTo ((float) tickArea.getX() + 14, (float) tickArea.getCentreY() - 4);
|
|
g.setColour (juce::Colour::fromRGB (180, 170, 140));
|
|
g.strokePath (tick, juce::PathStrokeType (2.0f));
|
|
}
|
|
|
|
g.drawFittedText (text, textArea, juce::Justification::centredLeft, 1);
|
|
}
|
|
|
|
juce::Font getComboBoxFont (juce::ComboBox&) override
|
|
{
|
|
return juce::Font (15.0f);
|
|
}
|
|
|
|
void positionComboBoxText (juce::ComboBox& box, juce::Label& label) override
|
|
{
|
|
label.setBounds (12, 0, box.getWidth() - 30, box.getHeight());
|
|
label.setFont (getComboBoxFont (box));
|
|
label.setColour (juce::Label::textColourId, juce::Colour::fromRGB (220, 218, 212));
|
|
}
|
|
};
|
|
|
|
//==============================================================================
|
|
// Custom LookAndFeel for toggle buttons
|
|
struct ElegantToggleLookAndFeel : juce::LookAndFeel_V4
|
|
{
|
|
void drawToggleButton (juce::Graphics& g, juce::ToggleButton& button,
|
|
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override
|
|
{
|
|
juce::ignoreUnused (shouldDrawButtonAsDown);
|
|
|
|
auto bounds = button.getLocalBounds().toFloat();
|
|
auto tickBounds = bounds.removeFromLeft (bounds.getHeight()).reduced (4);
|
|
|
|
// Checkbox background
|
|
juce::ColourGradient bgGradient (juce::Colour::fromRGB (35, 38, 44),
|
|
tickBounds.getCentreX(), tickBounds.getY(),
|
|
juce::Colour::fromRGB (20, 22, 26),
|
|
tickBounds.getCentreX(), tickBounds.getBottom(),
|
|
false);
|
|
g.setGradientFill (bgGradient);
|
|
g.fillRoundedRectangle (tickBounds, 4.0f);
|
|
|
|
// Border
|
|
auto borderColour = shouldDrawButtonAsHighlighted ? juce::Colour::fromRGB (100, 95, 80)
|
|
: juce::Colour::fromRGB (70, 68, 62);
|
|
g.setColour (borderColour);
|
|
g.drawRoundedRectangle (tickBounds.reduced (0.5f), 4.0f, 1.0f);
|
|
|
|
if (button.getToggleState())
|
|
{
|
|
// Filled state with warm glow
|
|
auto innerBounds = tickBounds.reduced (3);
|
|
juce::ColourGradient fillGradient (juce::Colour::fromRGB (160, 150, 120),
|
|
innerBounds.getCentreX(), innerBounds.getY(),
|
|
juce::Colour::fromRGB (120, 110, 85),
|
|
innerBounds.getCentreX(), innerBounds.getBottom(),
|
|
false);
|
|
g.setGradientFill (fillGradient);
|
|
g.fillRoundedRectangle (innerBounds, 2.0f);
|
|
|
|
// Checkmark
|
|
juce::Path tick;
|
|
auto cx = tickBounds.getCentreX();
|
|
auto cy = tickBounds.getCentreY();
|
|
tick.startNewSubPath (cx - 4, cy);
|
|
tick.lineTo (cx - 1, cy + 3);
|
|
tick.lineTo (cx + 5, cy - 4);
|
|
g.setColour (juce::Colour::fromRGB (30, 28, 24));
|
|
g.strokePath (tick, juce::PathStrokeType (2.2f, juce::PathStrokeType::curved));
|
|
}
|
|
|
|
// Label text
|
|
g.setColour (juce::Colour::fromRGB (210, 208, 200));
|
|
g.setFont (14.5f);
|
|
g.drawText (button.getButtonText(), bounds.reduced (4, 0),
|
|
juce::Justification::centredLeft, true);
|
|
}
|
|
};
|
|
|
|
//==============================================================================
|
|
// Custom LookAndFeel for sliders - compact design
|
|
struct DarkRectSliderLookAndFeel : juce::LookAndFeel_V4
|
|
{
|
|
void drawLinearSlider (juce::Graphics& g, int x, int y, int width, int height,
|
|
float sliderPos, float min, float max, const juce::Slider::SliderStyle style,
|
|
juce::Slider& slider) override
|
|
{
|
|
juce::ignoreUnused (min, max, style, slider);
|
|
|
|
// Track - narrower
|
|
auto trackWidth = width * 0.11f; // Further reduced
|
|
auto track = juce::Rectangle<float> ((float) x + (width - trackWidth) * 0.5f,
|
|
(float) y + 4.0f,
|
|
trackWidth,
|
|
(float) height - 8.0f);
|
|
|
|
// Track shadow
|
|
g.setColour (juce::Colours::black.withAlpha (0.3f));
|
|
g.fillRoundedRectangle (track.translated (1, 1), 2.0f);
|
|
|
|
// Track gradient
|
|
juce::ColourGradient trackGradient (juce::Colour::fromRGB (15, 15, 18),
|
|
track.getCentreX(), track.getY(),
|
|
juce::Colour::fromRGB (8, 8, 10),
|
|
track.getCentreX(), track.getBottom(),
|
|
false);
|
|
g.setGradientFill (trackGradient);
|
|
g.fillRoundedRectangle (track, 2.0f);
|
|
|
|
// Track border
|
|
g.setColour (juce::Colour::fromRGB (45, 45, 50));
|
|
g.drawRoundedRectangle (track, 2.0f, 1.0f);
|
|
|
|
// Fill from bottom to thumb position
|
|
auto fillTrack = track;
|
|
fillTrack.setTop (sliderPos);
|
|
juce::ColourGradient fillGradient (juce::Colour::fromRGB (80, 75, 60).withAlpha (0.4f),
|
|
fillTrack.getCentreX(), fillTrack.getY(),
|
|
juce::Colour::fromRGB (60, 55, 45).withAlpha (0.2f),
|
|
fillTrack.getCentreX(), fillTrack.getBottom(),
|
|
false);
|
|
g.setGradientFill (fillGradient);
|
|
g.fillRoundedRectangle (fillTrack, 2.0f);
|
|
|
|
// Thumb - smaller
|
|
float thumbW = (float) width * 0.50f;
|
|
float thumbH = 9.0f; // Reduced from 11.0f
|
|
float thumbX = (float) x + (width - thumbW) * 0.5f;
|
|
float thumbY = sliderPos - thumbH * 0.5f;
|
|
juce::Rectangle<float> thumb (thumbX, thumbY, thumbW, thumbH);
|
|
|
|
// Thumb shadow
|
|
g.setColour (juce::Colours::black.withAlpha (0.4f));
|
|
g.fillRoundedRectangle (thumb.translated (0, 1.5f), 2.0f);
|
|
|
|
// Thumb gradient
|
|
juce::ColourGradient thumbGradient (juce::Colour::fromRGB (130, 125, 115),
|
|
thumb.getCentreX(), thumb.getY(),
|
|
juce::Colour::fromRGB (70, 68, 62),
|
|
thumb.getCentreX(), thumb.getBottom(),
|
|
false);
|
|
g.setGradientFill (thumbGradient);
|
|
g.fillRoundedRectangle (thumb, 2.0f);
|
|
|
|
// Thumb highlight
|
|
auto highlightRect = thumb.reduced (1.5f).withHeight (thumb.getHeight() * 0.35f);
|
|
g.setColour (juce::Colours::white.withAlpha (0.12f));
|
|
g.fillRoundedRectangle (highlightRect, 1.0f);
|
|
|
|
// Thumb border
|
|
g.setColour (juce::Colour::fromRGB (25, 25, 28));
|
|
g.drawRoundedRectangle (thumb, 2.0f, 1.0f);
|
|
|
|
// Center notch on thumb
|
|
g.setColour (juce::Colour::fromRGB (40, 40, 44));
|
|
g.fillRect (thumb.getCentreX() - 5.0f, thumb.getCentreY() - 0.5f, 10.0f, 1.0f);
|
|
}
|
|
|
|
juce::Label* createSliderTextBox (juce::Slider& slider) override
|
|
{
|
|
auto* label = LookAndFeel_V4::createSliderTextBox (slider);
|
|
label->setColour (juce::Label::textColourId, juce::Colour::fromRGB (180, 178, 170));
|
|
label->setColour (juce::Label::backgroundColourId, juce::Colour::fromRGB (20, 22, 26));
|
|
label->setColour (juce::Label::outlineColourId, juce::Colour::fromRGB (50, 50, 55));
|
|
return label;
|
|
}
|
|
};
|
|
|
|
ElegantComboBoxLookAndFeel comboLook;
|
|
ElegantToggleLookAndFeel toggleLook;
|
|
DarkRectSliderLookAndFeel sliderLook;
|
|
|
|
void syncControlsFromParams();
|
|
void timerCallback() override;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FluteSynthAudioProcessorEditor)
|
|
};
|