Files
Piano/PluginEditor.cpp
2026-01-10 10:54:35 +00:00

422 lines
19 KiB
C++

/*
==============================================================================
This file contains the basic framework code for a JUCE plugin editor.
==============================================================================
*/
#include "PluginProcessor.h"
#include "PluginEditor.h"
//==============================================================================
FluteSynthAudioProcessorEditor::FluteSynthAudioProcessorEditor (FluteSynthAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p)
{
auto& apvts = audioProcessor.getAPVTS();
// Preset selector setup
auto presetNames = audioProcessor.getEmbeddedPresetNames();
if (presetNames.isEmpty())
presetNames.add ("Preset 1");
presetSelector.addItemList (presetNames, 1);
presetSelector.onChange = [this]()
{
audioProcessor.selectEmbeddedPreset (presetSelector.getSelectedItemIndex());
};
presetSelector.setSelectedItemIndex (juce::jlimit (0, presetSelector.getNumItems() - 1,
audioProcessor.getActiveEmbeddedPresetIndex()),
juce::dontSendNotification);
presetSelector.setLookAndFeel (&comboLook);
// Preset label styling
presetLabel.setJustificationType (juce::Justification::centred);
presetLabel.setColour (juce::Label::textColourId, juce::Colour::fromRGB (160, 155, 145));
presetLabel.setFont (juce::Font (13.0f, juce::Font::plain));
// Temperament selector setup
temperamentSelector.addItemList (
{ "Preset", "12-TET", "Werckmeister", "Kirnberger", "Meantone", "Pythagorean" }, 1);
temperamentSelector.setLookAndFeel (&comboLook);
// Temperament label styling
temperamentLabel.setJustificationType (juce::Justification::centred);
temperamentLabel.setColour (juce::Label::textColourId, juce::Colour::fromRGB (160, 155, 145));
temperamentLabel.setFont (juce::Font (13.0f, juce::Font::plain));
// Velocity Curve selector setup
velocityCurveSelector.addItemList (
{ "Light", "Normal", "Heavy", "Fixed" }, 1);
velocityCurveSelector.setLookAndFeel (&comboLook);
// Velocity Curve label styling
velocityCurveLabel.setJustificationType (juce::Justification::centred);
velocityCurveLabel.setColour (juce::Label::textColourId, juce::Colour::fromRGB (160, 155, 145));
velocityCurveLabel.setFont (juce::Font (13.0f, juce::Font::plain));
// Toggle buttons
soundboardEnable.setLookAndFeel (&toggleLook);
postRoomEnable.setLookAndFeel (&toggleLook);
// Slider setup helper
auto prepSlider = [] (juce::Slider& s, juce::String suffix)
{
s.setSliderStyle (juce::Slider::LinearVertical);
s.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 120, 18);
s.setTextValueSuffix (suffix);
};
prepSlider (soundboardMix, "");
prepSlider (soundboardT60, " s");
prepSlider (soundboardDamp, "");
prepSlider (postRoomMix, "");
prepSlider (stringSustain, " s");
prepSlider (hammerHardness, "");
prepSlider (pm2GainDb, " dB");
prepSlider (outputLpfCutoff, " Hz");
prepSlider (masterVolume, "");
soundboardMix.setLookAndFeel (&sliderLook);
soundboardT60.setLookAndFeel (&sliderLook);
soundboardDamp.setLookAndFeel (&sliderLook);
postRoomMix.setLookAndFeel (&sliderLook);
stringSustain.setLookAndFeel (&sliderLook);
hammerHardness.setLookAndFeel (&sliderLook);
pm2GainDb.setLookAndFeel (&sliderLook);
outputLpfCutoff.setLookAndFeel (&sliderLook);
masterVolume.setLookAndFeel (&sliderLook);
// Parameter attachments
soundboardEnableAttachment = std::make_unique<ButtonAttachment> (apvts, ParamIDs::soundboardEnable, soundboardEnable);
postRoomEnableAttachment = std::make_unique<ButtonAttachment> (apvts, ParamIDs::postRoomEnable, postRoomEnable);
soundboardMixAttachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::soundboardMix, soundboardMix);
soundboardT60Attachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::soundboardT60, soundboardT60);
soundboardDampAttachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::soundboardDamp, soundboardDamp);
postRoomMixAttachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::postRoomMix, postRoomMix);
stringSustainAttachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::decay, stringSustain);
hammerHardnessAttachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::feltStiffness, hammerHardness);
pm2GainAttachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::pm2GainDb, pm2GainDb);
outputLpfAttachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::outputLpfCutoff, outputLpfCutoff);
masterVolumeAttachment = std::make_unique<SliderAttachment> (apvts, ParamIDs::masterVolume, masterVolume);
temperamentAttachment = std::make_unique<ComboBoxAttachment> (apvts, ParamIDs::temperament, temperamentSelector);
velocityCurveAttachment = std::make_unique<ComboBoxAttachment> (apvts, ParamIDs::velocityCurve, velocityCurveSelector);
syncControlsFromParams();
startTimerHz (30);
// Add components
addAndMakeVisible (soundboardEnable);
addAndMakeVisible (postRoomEnable);
resetButton.setVisible (false);
addAndMakeVisible (presetSelector);
addAndMakeVisible (presetLabel);
addAndMakeVisible (temperamentSelector);
addAndMakeVisible (temperamentLabel);
addAndMakeVisible (velocityCurveSelector);
addAndMakeVisible (velocityCurveLabel);
addAndMakeVisible (soundboardMix);
addAndMakeVisible (soundboardDamp);
addAndMakeVisible (postRoomMix);
addAndMakeVisible (stringSustain);
addAndMakeVisible (hammerHardness);
addAndMakeVisible (pm2GainDb);
addAndMakeVisible (outputLpfCutoff);
addAndMakeVisible (masterVolume);
// Slider label styling
auto styleLabel = [] (juce::Label& label)
{
label.setJustificationType (juce::Justification::centred);
label.setColour (juce::Label::textColourId, juce::Colour::fromRGB (160, 155, 145));
label.setFont (juce::Font (13.0f, juce::Font::plain));
};
styleLabel (soundboardMixLabel);
styleLabel (soundboardDampLabel);
styleLabel (postRoomMixLabel);
styleLabel (stringSustainLabel);
styleLabel (hammerHardnessLabel);
styleLabel (pm2GainLabel);
styleLabel (outputLpfLabel);
styleLabel (masterVolumeLabel);
addAndMakeVisible (soundboardMixLabel);
addAndMakeVisible (soundboardDampLabel);
addAndMakeVisible (postRoomMixLabel);
addAndMakeVisible (stringSustainLabel);
addAndMakeVisible (hammerHardnessLabel);
addAndMakeVisible (pm2GainLabel);
addAndMakeVisible (outputLpfLabel);
addAndMakeVisible (masterVolumeLabel);
resetButton.onClick = [this]()
{
audioProcessor.resetToEmbeddedPreset();
};
setSize (1040, 520);
}
FluteSynthAudioProcessorEditor::~FluteSynthAudioProcessorEditor()
{
soundboardMix.setLookAndFeel (nullptr);
soundboardT60.setLookAndFeel (nullptr);
soundboardDamp.setLookAndFeel (nullptr);
postRoomMix.setLookAndFeel (nullptr);
stringSustain.setLookAndFeel (nullptr);
hammerHardness.setLookAndFeel (nullptr);
pm2GainDb.setLookAndFeel (nullptr);
outputLpfCutoff.setLookAndFeel (nullptr);
masterVolume.setLookAndFeel (nullptr);
presetSelector.setLookAndFeel (nullptr);
temperamentSelector.setLookAndFeel (nullptr);
soundboardEnable.setLookAndFeel (nullptr);
postRoomEnable.setLookAndFeel (nullptr);
velocityCurveSelector.setLookAndFeel (nullptr);
}
void FluteSynthAudioProcessorEditor::syncControlsFromParams()
{
auto& apvts = audioProcessor.getAPVTS();
if (auto* v = apvts.getRawParameterValue (ParamIDs::soundboardEnable))
soundboardEnable.setToggleState (v->load() >= 0.5f, juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::postRoomEnable))
postRoomEnable.setToggleState (v->load() >= 0.5f, juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::soundboardMix))
soundboardMix.setValue (v->load(), juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::soundboardDamp))
soundboardDamp.setValue (v->load(), juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::postRoomMix))
postRoomMix.setValue (v->load(), juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::decay))
stringSustain.setValue (v->load(), juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::feltStiffness))
hammerHardness.setValue (v->load(), juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::pm2GainDb))
pm2GainDb.setValue (v->load(), juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::outputLpfCutoff))
outputLpfCutoff.setValue (v->load(), juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::masterVolume))
masterVolume.setValue (v->load(), juce::dontSendNotification);
if (auto* v = apvts.getRawParameterValue (ParamIDs::velocityCurve))
velocityCurveSelector.setSelectedItemIndex ((int) std::round (v->load()),
juce::dontSendNotification);
if (presetSelector.getNumItems() > 0)
{
const int idx = juce::jlimit (0, presetSelector.getNumItems() - 1,
audioProcessor.getActiveEmbeddedPresetIndex());
presetSelector.setSelectedItemIndex (idx, juce::dontSendNotification);
}
}
void FluteSynthAudioProcessorEditor::timerCallback()
{
if (audioProcessor.consumePendingPresetUiSync())
syncControlsFromParams();
}
//==============================================================================
void FluteSynthAudioProcessorEditor::paint (juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat();
// Deep piano-black lacquer background - darker for more contrast
auto deepBlack = juce::Colour::fromRGB (2, 2, 3);
g.fillAll (deepBlack);
// Outer frame with subtle gradient
auto frameArea = bounds.reduced (4.0f);
juce::ColourGradient frameGradient (juce::Colour::fromRGB (14, 14, 18),
frameArea.getX(), frameArea.getY(),
juce::Colour::fromRGB (4, 4, 6),
frameArea.getRight(), frameArea.getBottom(),
false);
g.setGradientFill (frameGradient);
g.fillRoundedRectangle (frameArea, 12.0f);
// Inner content area - very dark
auto contentArea = frameArea.reduced (6.0f);
juce::ColourGradient contentGradient (juce::Colour::fromRGB (6, 6, 8),
contentArea.getCentreX(), contentArea.getY(),
juce::Colour::fromRGB (2, 2, 3),
contentArea.getCentreX(), contentArea.getBottom(),
false);
g.setGradientFill (contentGradient);
g.fillRoundedRectangle (contentArea, 8.0f);
// High-contrast gloss highlight - brighter and more defined
juce::Path glossPath;
glossPath.addRoundedRectangle (contentArea, 8.0f);
juce::ColourGradient gloss;
auto glossCenterX = contentArea.getX() + contentArea.getWidth() * 0.30f;
gloss.point1 = { glossCenterX - contentArea.getWidth() * 0.22f, contentArea.getY() };
gloss.point2 = { glossCenterX + contentArea.getWidth() * 0.32f, contentArea.getBottom() };
gloss.addColour (0.0, juce::Colours::transparentWhite);
gloss.addColour (0.26, juce::Colours::transparentWhite);
gloss.addColour (0.40, juce::Colours::white.withAlpha (0.22f)); // Brighter
gloss.addColour (0.48, juce::Colours::white.withAlpha (0.28f)); // Peak brightness - increased
gloss.addColour (0.56, juce::Colours::white.withAlpha (0.18f));
gloss.addColour (0.70, juce::Colours::transparentWhite);
gloss.addColour (1.0, juce::Colours::transparentWhite);
g.setGradientFill (gloss);
g.fillPath (glossPath);
// Additional vertical sheen for lacquer depth - brighter
juce::ColourGradient sheen (juce::Colours::white.withAlpha (0.16f),
contentArea.getCentreX() - contentArea.getWidth() * 0.12f,
contentArea.getY(),
juce::Colours::transparentWhite,
contentArea.getCentreX() - contentArea.getWidth() * 0.12f,
contentArea.getY() + contentArea.getHeight() * 0.45f,
false);
g.setGradientFill (sheen);
g.fillRoundedRectangle (contentArea.withHeight (contentArea.getHeight() * 0.35f), 8.0f);
// Subtle top edge highlight
g.setColour (juce::Colour::fromRGB (50, 50, 55));
g.drawHorizontalLine ((int) (contentArea.getY() + 1), contentArea.getX() + 20, contentArea.getRight() - 20);
// Decorative brass-like accent line below title
auto accentY = 58.0f;
juce::ColourGradient accentGradient (juce::Colours::transparentBlack,
contentArea.getX(), accentY,
juce::Colours::transparentBlack,
contentArea.getRight(), accentY,
false);
accentGradient.addColour (0.15, juce::Colour::fromRGB (80, 72, 55).withAlpha (0.0f));
accentGradient.addColour (0.35, juce::Colour::fromRGB (120, 108, 80).withAlpha (0.5f));
accentGradient.addColour (0.5, juce::Colour::fromRGB (140, 128, 95).withAlpha (0.6f));
accentGradient.addColour (0.65, juce::Colour::fromRGB (120, 108, 80).withAlpha (0.5f));
accentGradient.addColour (0.85, juce::Colour::fromRGB (80, 72, 55).withAlpha (0.0f));
g.setGradientFill (accentGradient);
g.fillRect (contentArea.getX(), accentY, contentArea.getWidth(), 1.5f);
// Section divider line above sliders
auto dividerY = 175.0f;
g.setColour (juce::Colour::fromRGB (30, 30, 35));
g.drawHorizontalLine ((int) dividerY, contentArea.getX() + 30, contentArea.getRight() - 30);
g.setColour (juce::Colour::fromRGB (12, 12, 15));
g.drawHorizontalLine ((int) dividerY + 1, contentArea.getX() + 30, contentArea.getRight() - 30);
// Title with elegant styling
g.setColour (juce::Colour::fromRGB (220, 215, 200));
juce::Font titleFont (juce::Font::getDefaultSerifFontName(), 30.0f, juce::Font::italic);
g.setFont (titleFont);
// Title shadow
g.setColour (juce::Colours::black.withAlpha (0.6f));
g.drawText ("Samedi Dimanche MusPiano",
juce::Rectangle<int> (1, 16, getWidth(), 40),
juce::Justification::centredTop);
// Title main
g.setColour (juce::Colour::fromRGB (230, 225, 210));
g.drawText ("Samedi Dimanche MusPiano",
juce::Rectangle<int> (0, 15, getWidth(), 40),
juce::Justification::centredTop);
// Stronger vignette corners for depth
auto vignetteSize = 180.0f;
juce::ColourGradient vignetteTL (juce::Colours::black.withAlpha (0.4f),
contentArea.getX(), contentArea.getY(),
juce::Colours::transparentBlack,
contentArea.getX() + vignetteSize, contentArea.getY() + vignetteSize,
true);
g.setGradientFill (vignetteTL);
g.fillRect (contentArea.getX(), contentArea.getY(), vignetteSize, vignetteSize);
juce::ColourGradient vignetteBR (juce::Colours::black.withAlpha (0.45f),
contentArea.getRight(), contentArea.getBottom(),
juce::Colours::transparentBlack,
contentArea.getRight() - vignetteSize, contentArea.getBottom() - vignetteSize,
true);
g.setGradientFill (vignetteBR);
g.fillRect (contentArea.getRight() - vignetteSize, contentArea.getBottom() - vignetteSize, vignetteSize, vignetteSize);
// Inner frame border
g.setColour (juce::Colour::fromRGB (25, 25, 30));
g.drawRoundedRectangle (contentArea.reduced (0.5f), 8.0f, 1.0f);
// Outer frame border with subtle brass tint
g.setColour (juce::Colour::fromRGB (40, 38, 34));
g.drawRoundedRectangle (frameArea.reduced (0.5f), 12.0f, 1.5f);
}
void FluteSynthAudioProcessorEditor::resized()
{
auto area = getLocalBounds().reduced (20);
// Title area - increased space
area.removeFromTop (55);
// Control row - dropdowns and toggles (moved up with more spacing)
auto controlRow = area.removeFromTop (90);
controlRow.removeFromTop (8); // Add top padding
// Preset dropdown
auto presetArea = controlRow.removeFromLeft (200);
presetLabel.setBounds (presetArea.removeFromTop (22));
presetSelector.setBounds (presetArea.removeFromTop (36).reduced (4, 2));
controlRow.removeFromLeft (20); // Spacing between dropdowns
// Temperament dropdown
auto temperArea = controlRow.removeFromLeft (200);
temperamentLabel.setBounds (temperArea.removeFromTop (22));
temperamentSelector.setBounds (temperArea.removeFromTop (36).reduced (4, 2));
controlRow.removeFromLeft (20); // Spacing between dropdowns
// Velocity curve dropdown
auto velocityArea = controlRow.removeFromLeft (200);
velocityCurveLabel.setBounds (velocityArea.removeFromTop (22));
velocityCurveSelector.setBounds (velocityArea.removeFromTop (36).reduced (4, 2));
controlRow.removeFromLeft (20); // Spacing before toggles
// Toggle buttons - vertically stacked for cleaner look
auto toggleArea = controlRow.removeFromLeft (150);
toggleArea.removeFromTop (4);
const int toggleHeight = 24;
const int toggleGap = 4;
soundboardEnable.setBounds (toggleArea.removeFromTop (toggleHeight));
toggleArea.removeFromTop (toggleGap);
postRoomEnable.setBounds (toggleArea.removeFromTop (toggleHeight));
resetButton.setBounds (0, 0, 0, 0);
// Spacing before sliders
area.removeFromTop (35);
// Slider section with labels above - smaller sliders with more spacing
auto sliderSection = area.removeFromTop (210);
const int numSliders = 8;
const int sliderWidth = sliderSection.getWidth() / numSliders;
const int labelHeight = 34;
const int sliderPadding = 16;
auto layoutSlider = [&] (juce::Slider& slider, juce::Label& label, int index)
{
juce::ignoreUnused (index);
auto sliderArea = sliderSection.removeFromLeft (sliderWidth);
sliderArea = sliderArea.reduced (sliderPadding, 0);
auto labelArea = sliderArea.removeFromTop (labelHeight);
label.setBounds (labelArea);
slider.setBounds (sliderArea.reduced (10, 8)); // More padding around sliders
};
layoutSlider (soundboardMix, soundboardMixLabel, 0);
layoutSlider (soundboardDamp, soundboardDampLabel, 1);
layoutSlider (postRoomMix, postRoomMixLabel, 2);
layoutSlider (stringSustain, stringSustainLabel, 3);
layoutSlider (hammerHardness, hammerHardnessLabel, 4);
layoutSlider (pm2GainDb, pm2GainLabel, 5);
layoutSlider (outputLpfCutoff, outputLpfLabel, 6);
layoutSlider (masterVolume, masterVolumeLabel, 7);
}