gelyk/Source/PluginEditor.h
Armin 585f8d43dd Fix live waveform scaling, drop GTK deps, and document the project
- Tune the live spectrum display to a fixed range matched to real per-FFT-bin
  levels (-40..0 dB) so typical content sits mid-screen instead of flying to
  the top or hugging the bottom; widen the SENS offset control to -48..+24 dB
- Disable JUCE_WEB_BROWSER so the WebKitGTK WebBrowserComponent (unused) is
  compiled out, letting the plugin build on Artix/Arch without GTK/WebKit dev
  headers
- Add a verbose README covering the bands, parameters, controls, structure,
  DSP, build steps, and the Linux/Artix note
2026-08-29 23:17:32 +02:00

215 lines
8 KiB
C++

#pragma once
#include <JuceHeader.h>
#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, vertical offset added to the live waveform trace
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<juce::AudioParameterFloat*>(apvts.getParameter(gainId))),
freqP(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(freqId))),
qP(dynamic_cast<juce::AudioParameterFloat*>(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<void()> 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<std::pair<float, float>()> 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<BandFader> gain;
std::unique_ptr<juce::Slider> level;
std::unique_ptr<juce::Slider> q;
std::unique_ptr<juce::Slider> freq;
std::unique_ptr<juce::Label> freqLabel;
std::unique_ptr<juce::Label> gainCaption;
std::unique_ptr<juce::Label> gainValue;
std::unique_ptr<juce::Label> levelCaption;
std::unique_ptr<juce::Label> levelValue;
std::unique_ptr<juce::Label> qCaption;
std::unique_ptr<juce::Label> qValue;
std::unique_ptr<juce::Label> freqCaption;
std::unique_ptr<juce::Label> freqValue;
};
BandWidget bands[numBands];
std::unique_ptr<juce::Label> masterLabel;
std::unique_ptr<juce::Label> masterPercent;
std::unique_ptr<juce::Slider> master;
std::unique_ptr<juce::ComboBox> scaleBox;
std::unique_ptr<juce::Label> titleLabel;
std::unique_ptr<juce::Label> statusBar;
std::unique_ptr<juce::Label> statusGit;
std::unique_ptr<juce::Label> colorHueLabel;
std::unique_ptr<juce::Label> colorSatLabel;
std::unique_ptr<juce::Label> colorBriLabel;
std::unique_ptr<juce::Label> waveLabel;
std::unique_ptr<juce::Label> waveSensLabel;
std::unique_ptr<juce::Slider> colorHueSlider;
std::unique_ptr<juce::Slider> colorSatSlider;
std::unique_ptr<juce::Slider> colorBriSlider;
std::unique_ptr<juce::Slider> waveSlider;
std::unique_ptr<juce::Slider> waveSensSlider;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> levelAttachments[numBands];
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> qAttachments[numBands];
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> freqAttachments[numBands];
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> masterAttachment;
juce::Array<float> spectrum;
juce::Array<float> freqBins;
juce::Array<float> eqCurve;
juce::Rectangle<float> bandSections[numBands]; // knob-group outlines
std::atomic<float> scaling { 1.0f }; // for resized()
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GelykEQAudioProcessorEditor)
};
} // namespace gelyk