/* ============================================================================== 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 (apvts, ParamIDs::soundboardEnable, soundboardEnable); postRoomEnableAttachment = std::make_unique (apvts, ParamIDs::postRoomEnable, postRoomEnable); soundboardMixAttachment = std::make_unique (apvts, ParamIDs::soundboardMix, soundboardMix); soundboardT60Attachment = std::make_unique (apvts, ParamIDs::soundboardT60, soundboardT60); soundboardDampAttachment = std::make_unique (apvts, ParamIDs::soundboardDamp, soundboardDamp); postRoomMixAttachment = std::make_unique (apvts, ParamIDs::postRoomMix, postRoomMix); stringSustainAttachment = std::make_unique (apvts, ParamIDs::decay, stringSustain); hammerHardnessAttachment = std::make_unique (apvts, ParamIDs::feltStiffness, hammerHardness); pm2GainAttachment = std::make_unique (apvts, ParamIDs::pm2GainDb, pm2GainDb); outputLpfAttachment = std::make_unique (apvts, ParamIDs::outputLpfCutoff, outputLpfCutoff); masterVolumeAttachment = std::make_unique (apvts, ParamIDs::masterVolume, masterVolume); temperamentAttachment = std::make_unique (apvts, ParamIDs::temperament, temperamentSelector); velocityCurveAttachment = std::make_unique (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 (1, 16, getWidth(), 40), juce::Justification::centredTop); // Title main g.setColour (juce::Colour::fromRGB (230, 225, 210)); g.drawText ("Samedi Dimanche MusPiano", juce::Rectangle (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); }