#pragma once #include #include "PluginProcessor.h" namespace gelyk { class NeonLookAndFeel : public juce::LookAndFeel_V4 { public: NeonLookAndFeel(); void drawLinearSlider(juce::Graphics&, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const juce::Slider::SliderStyle, juce::Slider&) override; void drawRotarySlider(juce::Graphics&, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, juce::Slider&) override; void drawComboBox(juce::Graphics&, int width, int height, bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, juce::ComboBox&) override; void positionComboBoxText(juce::ComboBox&, juce::Label&) override; juce::Colour cyan() const { return transform(baseCyan); } juce::Colour magenta() const { return transform(baseMagenta); } juce::Colour violet() const { return transform(baseViolet); } juce::Colour bg() const { return transform(baseBg); } juce::Colour panel() const { return transform(basePanel); } juce::Colour line() const { return transform(baseLine); } juce::Colour textDim() const { return transform(baseTextDim); } void refreshPalette(); float hueShift = 0.0f; float saturation = 1.0f; float brightness = 1.0f; float waveformOpacity = 0.2f; float waveSensitivity = 0.0f; // in dB, added to the spectrum display level float uiScale = 1.0f; private: juce::Colour transform(juce::Colour c) const { return c.withRotatedHue(hueShift) .withMultipliedSaturation(saturation) .withMultipliedBrightness(brightness); } const juce::Colour baseCyan { 0xff00e5ff }; const juce::Colour baseMagenta{ 0xffff2fd6 }; const juce::Colour baseViolet { 0xff8a2be2 }; const juce::Colour baseBg { 0xff0b0f16 }; const juce::Colour basePanel { 0xff121a24 }; const juce::Colour baseLine { 0xff1f2a38 }; const juce::Colour baseTextDim{ 0xff7a8aa0 }; }; // --------------------------------------------------------------------------- // Log-frequency horizontal mapping shared by the band columns and the EQ curve, // so a band's position in the visualizer always lines up with the curve peak at // the same frequency. 20 Hz sits at the left edge, 20 kHz at the right. inline float bandLogNorm(float freq) { return (std::log10(freq) - std::log10(20.0f)) / (std::log10(20000.0f) - std::log10(20.0f)); } inline float bandFreqToX(float freq, float x0, float width) { return x0 + bandLogNorm(freq) * width; } inline float bandXToFreq(float x, float x0, float width) { const float norm = (width > 0.0f) ? (x - x0) / width : 0.0f; return std::pow(10.0f, std::log10(20.0f) + norm * (std::log10(20000.0f) - std::log10(20.0f))); } // The large fader of each band. Dragging vertically changes the level, dragging // horizontally changes the band's frequency, and scrolling the mouse wheel // nudges the band's Q. All three write straight to the plugin's parameters. class BandFader final : public juce::Component { public: BandFader(juce::AudioProcessorValueTreeState& apvts, const juce::String& gainId, const juce::String& freqId, const juce::String& qId, NeonLookAndFeel& lnf) : gainP(dynamic_cast(apvts.getParameter(gainId))), freqP(dynamic_cast(apvts.getParameter(freqId))), qP(dynamic_cast(apvts.getParameter(qId))), lf(lnf) { setRepaintsOnMouseActivity(false); } void paint(juce::Graphics&) override; void mouseDown(const juce::MouseEvent&) override; void mouseDrag(const juce::MouseEvent&) override; void mouseUp(const juce::MouseEvent&) override; void mouseEnter(const juce::MouseEvent&) override; void mouseExit(const juce::MouseEvent&) override; void mouseWheelMove(const juce::MouseEvent&, const juce::MouseWheelDetails&) override; float getGain() const { return gainP != nullptr ? (float) gainP->get() : 0.0f; } // The x-origin and width of the full visualizer area, used so a horizontal // drag moves the fader 1:1 with the mouse cursor. void setDragSpace(float x0, float width) { dragX0 = x0; dragWidth = width; } // Ask the containing editor to redispose the columns after a frequency drag. std::function onFreqChange; // Returns the ordering bounds [minFreq, maxFreq] this band's frequency is // allowed to take, so bands keep their left-to-right order. std::function()> onGetFreqBounds; private: juce::AudioParameterFloat* gainP; juce::AudioParameterFloat* freqP; juce::AudioParameterFloat* qP; NeonLookAndFeel& lf; bool dragging = false; bool hovered = false; float startGain = 0.0f; float startFreq = 1000.0f; int startX = 0; int startY = 0; float dragX0 = 0.0f; float dragWidth = 1.0f; }; // --------------------------------------------------------------------------- class GelykEQAudioProcessorEditor final : public juce::AudioProcessorEditor, public juce::Timer { public: explicit GelykEQAudioProcessorEditor(GelykEQAudioProcessor&); ~GelykEQAudioProcessorEditor() override; void paint(juce::Graphics&) override; void resized() override; void timerCallback() override; private: void rebuildFaders(); void scaleUI(float percent); void refreshColors(); void refreshFonts(float s); void updateGainValue(int i); void updateMasterPercent(); GelykEQAudioProcessor& processor; NeonLookAndFeel lookAndFeel; static constexpr int numBands = 7; struct BandWidget { std::unique_ptr gain; std::unique_ptr level; std::unique_ptr q; std::unique_ptr freq; std::unique_ptr freqLabel; std::unique_ptr gainCaption; std::unique_ptr gainValue; std::unique_ptr levelCaption; std::unique_ptr levelValue; std::unique_ptr qCaption; std::unique_ptr qValue; std::unique_ptr freqCaption; std::unique_ptr freqValue; }; BandWidget bands[numBands]; std::unique_ptr masterLabel; std::unique_ptr masterPercent; std::unique_ptr master; std::unique_ptr scaleBox; std::unique_ptr titleLabel; std::unique_ptr statusBar; std::unique_ptr statusGit; std::unique_ptr colorHueLabel; std::unique_ptr colorSatLabel; std::unique_ptr colorBriLabel; std::unique_ptr waveLabel; std::unique_ptr waveSensLabel; std::unique_ptr colorHueSlider; std::unique_ptr colorSatSlider; std::unique_ptr colorBriSlider; std::unique_ptr waveSlider; std::unique_ptr waveSensSlider; std::unique_ptr levelAttachments[numBands]; std::unique_ptr qAttachments[numBands]; std::unique_ptr freqAttachments[numBands]; std::unique_ptr masterAttachment; juce::Array spectrum; juce::Array freqBins; juce::Array eqCurve; juce::Rectangle bandSections[numBands]; // knob-group outlines std::atomic scaling { 1.0f }; // for resized() JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GelykEQAudioProcessorEditor) }; } // namespace gelyk