mirror of
https://codeberg.org/armin/gelyk.git
synced 2026-10-11 17:01:53 +02:00
Initial Gelyk EQ plugin
This commit is contained in:
commit
13863f4522
9 changed files with 1080 additions and 0 deletions
348
Source/PluginEditor.cpp
Normal file
348
Source/PluginEditor.cpp
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
#include "PluginEditor.h"
|
||||
|
||||
namespace gelyk
|
||||
{
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// NeonLookAndFeel
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
NeonLookAndFeel::NeonLookAndFeel()
|
||||
{
|
||||
setColour(juce::Slider::textBoxTextColourId, cyan);
|
||||
setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentBlack);
|
||||
setColour(juce::Slider::textBoxBackgroundColourId, panel);
|
||||
setColour(juce::ComboBox::backgroundColourId, panel);
|
||||
setColour(juce::ComboBox::outlineColourId, line);
|
||||
setColour(juce::ComboBox::textColourId, cyan);
|
||||
setColour(juce::ComboBox::arrowColourId, cyan);
|
||||
}
|
||||
|
||||
void NeonLookAndFeel::drawLinearSlider(juce::Graphics& g, int x, int y, int width, int height,
|
||||
float sliderPos, float, float,
|
||||
const juce::Slider::SliderStyle style, juce::Slider& slider)
|
||||
{
|
||||
const bool vertical = (style == juce::Slider::LinearVertical);
|
||||
const float cx = x + width * 0.5f;
|
||||
|
||||
// Track
|
||||
juce::Path track;
|
||||
if (vertical)
|
||||
{
|
||||
const float tw = 2.0f;
|
||||
track.addRoundedRectangle(cx - tw * 0.5f, (float) y, tw, (float) height, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float th = 2.0f;
|
||||
track.addRoundedRectangle((float) x, y + height * 0.5f - th * 0.5f, (float) width, th, 1.0f);
|
||||
}
|
||||
g.setColour(line);
|
||||
g.fillPath(track);
|
||||
|
||||
// Filled region from centre (0 dB) down to the handle, glowing.
|
||||
if (vertical)
|
||||
{
|
||||
const float midY = y + height * 0.5f;
|
||||
const float top = juce::jmin(midY, sliderPos);
|
||||
const float bottom = juce::jmax(midY, sliderPos);
|
||||
juce::Path fill;
|
||||
fill.addRoundedRectangle(cx - 3.0f, top, 6.0f, juce::jmax(1.0f, bottom - top), 3.0f);
|
||||
g.setColour(cyan);
|
||||
g.fillPath(fill);
|
||||
|
||||
// Handle.
|
||||
g.setColour(slider.isEnabled() && isMouseOverOrDragging(true) ? violet : cyan);
|
||||
g.fillEllipse(cx - 5.0f, sliderPos - 5.0f, 10.0f, 10.0f);
|
||||
g.setColour(bg);
|
||||
g.fillEllipse(cx - 2.0f, sliderPos - 2.0f, 4.0f, 4.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
g.setColour(cyan);
|
||||
g.fillRect(x, y + height * 0.5f - 3.0f, (float) width, 6.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void NeonLookAndFeel::drawComboBox(juce::Graphics& g, int width, int height, bool,
|
||||
int, int, int, int, juce::ComboBox& box)
|
||||
{
|
||||
auto corner = 4.0f;
|
||||
auto bounds = box.getLocalBounds().toFloat();
|
||||
g.setColour(panel);
|
||||
g.fillRoundedRectangle(bounds, corner);
|
||||
g.setColour(cyan);
|
||||
g.drawRoundedRectangle(bounds, corner, 1.0f);
|
||||
|
||||
juce::Rectangle<int> arrowZone(width - 20, 0, 20, height);
|
||||
juce::Path p;
|
||||
p.startNewSubPath((float) arrowZone.getX() + 4.0f, (float) arrowZone.getCentreY() - 3.0f);
|
||||
p.lineTo((float) arrowZone.getCentreX(), (float) arrowZone.getCentreY() + 3.0f);
|
||||
p.lineTo((float) arrowZone.getX() + 12.0f, (float) arrowZone.getCentreY() - 3.0f);
|
||||
g.setColour(cyan);
|
||||
g.strokePath(p, juce::PathStrokeType(1.5f));
|
||||
}
|
||||
|
||||
void NeonLookAndFeel::positionComboBoxText(juce::ComboBox& box, juce::Label& label)
|
||||
{
|
||||
label.setBounds(2, 1, box.getWidth() - 22, box.getHeight() - 2);
|
||||
label.setFont(juce::FontOptions(13.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GelykEQAudioProcessorEditor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor& p)
|
||||
: AudioProcessorEditor(&p),
|
||||
processor(p),
|
||||
analyserAttachment(processor.getAPVTS().parameterState,
|
||||
"analyserEnabled",
|
||||
[this](float v) { processor.getAnalyser().setEnabled(v > 0.5f); })
|
||||
{
|
||||
setLookAndFeel(&lookAndFeel);
|
||||
setOpaque(true);
|
||||
|
||||
titleLabel = std::make_unique<juce::Label>("title", "GELYKEQ");
|
||||
titleLabel->setFont(juce::FontOptions(26.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
titleLabel->setColour(juce::Label::textColourId, cyan);
|
||||
titleLabel->setJustificationType(juce::Justification::centredLeft);
|
||||
addAndMakeVisible(*titleLabel);
|
||||
|
||||
scaleBox = std::make_unique<juce::ComboBox>("scale");
|
||||
scaleBox->addItem("100%", 100);
|
||||
scaleBox->addItem("125%", 125);
|
||||
scaleBox->addItem("150%", 150);
|
||||
scaleBox->addItem("175%", 175);
|
||||
scaleBox->addItem("200%", 200);
|
||||
scaleBox->setSelectedId(100, juce::dontSendNotification);
|
||||
scaleBox->onChange = [this] { scaleUI((float) scaleBox->getSelectedId()); };
|
||||
addAndMakeVisible(*scaleBox);
|
||||
|
||||
// Master gain (vertical fader, far right).
|
||||
masterLabel = std::make_unique<juce::Label>("mlab", "MASTER");
|
||||
masterLabel->setFont(juce::FontOptions(11.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
masterLabel->setColour(juce::Label::textColourId, magenta);
|
||||
masterLabel->setJustificationType(juce::Justification::centred);
|
||||
master = std::make_unique<juce::Slider>(juce::Slider::LinearVertical,
|
||||
juce::Slider::TextBoxBelow);
|
||||
master->setLookAndFeel(&lookAndFeel);
|
||||
master->setSliderStyle(juce::Slider::LinearVertical);
|
||||
master->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 40, 14);
|
||||
master->setRange(-24.0, 24.0, 0.01);
|
||||
addAndMakeVisible(*masterLabel);
|
||||
addAndMakeVisible(*master);
|
||||
masterAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processor.getAPVTS(), "master", *master);
|
||||
|
||||
// Band faders.
|
||||
const char* const names[numBands] = { "LOW", "LOW-MID", "MID", "HIGH-MID", "HIGH" };
|
||||
const char* const freqs[numBands] = { "60", "200", "1K", "4K", "10K" };
|
||||
for (int i = 0; i < numBands; ++i)
|
||||
{
|
||||
auto& w = bands[i];
|
||||
|
||||
w.freqLabel = std::make_unique<juce::Label>("freq", freqs[i]);
|
||||
w.freqLabel->setFont(juce::FontOptions(13.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
w.freqLabel->setColour(juce::Label::textColourId, textDim);
|
||||
w.freqLabel->setJustificationType(juce::Justification::centred);
|
||||
|
||||
w.gainLabel = std::make_unique<juce::Label>("gname", names[i]);
|
||||
w.gainLabel->setFont(juce::FontOptions(11.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
w.gainLabel->setColour(juce::Label::textColourId, i % 2 == 0 ? cyan : magenta);
|
||||
w.gainLabel->setJustificationType(juce::Justification::centred);
|
||||
|
||||
w.gain = std::make_unique<juce::Slider>(juce::Slider::LinearVertical, juce::Slider::TextBoxBelow);
|
||||
w.gain->setLookAndFeel(&lookAndFeel);
|
||||
w.gain->setSliderStyle(juce::Slider::LinearVertical);
|
||||
w.gain->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 40, 14);
|
||||
w.gain->setRange(-24.0, 24.0, 0.01);
|
||||
w.gain->setDoubleClickReturnValue(true, 0.0);
|
||||
|
||||
w.q = std::make_unique<juce::Slider>(juce::Slider::LinearHorizontal, juce::Slider::NoTextBox);
|
||||
w.q->setLookAndFeel(&lookAndFeel);
|
||||
w.q->setRange(0.1, 6.0, 0.01);
|
||||
w.q->setDoubleClickReturnValue(true, 1.0);
|
||||
|
||||
addAndMakeVisible(*w.freqLabel);
|
||||
addAndMakeVisible(*w.gainLabel);
|
||||
addAndMakeVisible(*w.gain);
|
||||
addAndMakeVisible(*w.q);
|
||||
|
||||
gainAttachments[i] = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processor.getAPVTS(), "gain" + juce::String(i), *w.gain);
|
||||
qAttachments[i] = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processor.getAPVTS(), "q" + juce::String(i), *w.q);
|
||||
}
|
||||
|
||||
// Status bar.
|
||||
statusGit = std::make_unique<juce::Label>("git", processor.getGitInfo());
|
||||
statusGit->setFont(juce::FontOptions(11.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
statusGit->setColour(juce::Label::textColourId, textDim);
|
||||
statusGit->setJustificationType(juce::Justification::centredRight);
|
||||
|
||||
statusBar = std::make_unique<juce::Label>("status",
|
||||
"GELYKEQ v" + juce::String(ProjectInfo::versionString) + " | AU / VST3 ");
|
||||
statusBar->setFont(juce::FontOptions(11.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
statusBar->setColour(juce::Label::textColourId, textDim);
|
||||
statusBar->setJustificationType(juce::Justification::centredLeft);
|
||||
addAndMakeVisible(*statusBar);
|
||||
addAndMakeVisible(*statusGit);
|
||||
|
||||
spectrum.ensureStorageAllocated(128);
|
||||
freqBins.ensureStorageAllocated(128);
|
||||
|
||||
startTimerHz(30);
|
||||
rebuildFaders();
|
||||
scaleUI(100.0f);
|
||||
}
|
||||
|
||||
GelykEQAudioProcessorEditor::~GelykEQAudioProcessorEditor()
|
||||
{
|
||||
stopTimer();
|
||||
setLookAndFeel(nullptr);
|
||||
}
|
||||
|
||||
void GelykEQAudioProcessorEditor::timerCallback()
|
||||
{
|
||||
processor.getAnalyser().copySpectrum(spectrum, freqBins, 128);
|
||||
repaint();
|
||||
}
|
||||
|
||||
void GelykEQAudioProcessorEditor::rebuildFaders()
|
||||
{
|
||||
// Nothing needed at construction; sliders are attachments.
|
||||
}
|
||||
|
||||
void GelykEQAudioProcessorEditor::scaleUI(float percent)
|
||||
{
|
||||
const float s = percent / 100.0f;
|
||||
scaling.store(s);
|
||||
setSize((int) (920 * s), (int) (540 * s));
|
||||
resized();
|
||||
}
|
||||
|
||||
void GelykEQAudioProcessorEditor::resized()
|
||||
{
|
||||
juce::Rectangle<int> area = getLocalBounds();
|
||||
const float s = scaling.load();
|
||||
|
||||
const int headerH = (int) (46 * s);
|
||||
const int footerH = (int) (26 * s);
|
||||
const int pad = (int) (12 * s);
|
||||
|
||||
// Header.
|
||||
juce::Rectangle<int> header = area.removeFromTop(headerH);
|
||||
titleLabel->setBounds(header.removeFromLeft((int) (160 * s)).reduced(pad, 0));
|
||||
scaleBox->setBounds(header.removeFromRight((int) (90 * s)).reduced((int)(6*s), (int)(9*s)));
|
||||
|
||||
// Footer.
|
||||
juce::Rectangle<int> footer = area.removeFromBottom(footerH);
|
||||
footer.reduce(pad, 0);
|
||||
statusBar->setBounds(footer.removeFromLeft(footer.getWidth() / 2));
|
||||
statusGit->setBounds(footer);
|
||||
|
||||
// Main grid.
|
||||
const int masterW = (int) (64 * s);
|
||||
juce::Rectangle<int> mainArea = area;
|
||||
juce::Rectangle<int> masterArea = mainArea.removeFromRight(masterW);
|
||||
|
||||
const int x0 = mainArea.getX();
|
||||
const int y0 = mainArea.getY();
|
||||
const int totalW = mainArea.getWidth();
|
||||
|
||||
const int gap = (int) (10 * s);
|
||||
const int colW = (totalW - gap * (numBands - 1)) / numBands;
|
||||
|
||||
for (int i = 0; i < numBands; ++i)
|
||||
{
|
||||
auto& w = bands[i];
|
||||
juce::Rectangle<int> col(x0 + i * (colW + gap), y0, colW, mainArea.getHeight());
|
||||
|
||||
const int topLabelH = (int) (18 * s);
|
||||
const int freqH = (int) (16 * s);
|
||||
const int gainH = (int) (14 * s);
|
||||
|
||||
juce::Rectangle<int> sub = col.reduced((int)(8*s), 0);
|
||||
w.gainLabel->setBounds(sub.removeFromTop(topLabelH));
|
||||
|
||||
juce::Rectangle<int> analyserArea = sub.removeFromTop(sub.getHeight() - freqH - gainH - (int)(18*s) - (int)(10*s));
|
||||
|
||||
w.freqLabel->setBounds(sub.removeFromTop(freqH));
|
||||
w.gain->setBounds(sub.removeFromTop(gainH + (int)(34*s)));
|
||||
|
||||
w.q->setBounds(col.removeFromBottom((int)(18*s)).reduced((int)(8*s), 0));
|
||||
}
|
||||
|
||||
// Master column.
|
||||
juce::Rectangle<int> msub = masterArea.reduced((int)(14*s), 0);
|
||||
masterLabel->setBounds(msub.removeFromTop((int) (18 * s)));
|
||||
master->setBounds(msub);
|
||||
}
|
||||
|
||||
void GelykEQAudioProcessorEditor::paint(juce::Graphics& g)
|
||||
{
|
||||
const float s = scaling.load();
|
||||
g.fillAll(bg);
|
||||
|
||||
// Analyser canvas area matches fader columns.
|
||||
juce::Rectangle<int> area = getLocalBounds();
|
||||
const int headerH = (int) (46 * s);
|
||||
const int footerH = (int) (26 * s);
|
||||
juce::Rectangle<int> mainArea = area;
|
||||
mainArea.removeFromTop(headerH);
|
||||
mainArea.removeFromBottom(footerH);
|
||||
mainArea.removeFromRight((int) (64 * s));
|
||||
|
||||
// Neon grid lines behind faders.
|
||||
g.setColour(line);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
float f = (i + 1) / 5.0f;
|
||||
// 0 dB line.
|
||||
if (i == 2)
|
||||
continue;
|
||||
float yy = mainArea.getY() + 18.0f * s + (mainArea.getHeight() - 18.0f * s - 60.0f * s) * f;
|
||||
g.drawHorizontalLine((int) yy + (int)(18*s), (float) mainArea.getX(), (float) mainArea.getRight());
|
||||
}
|
||||
|
||||
// The 0 dB centre line (glowing).
|
||||
const float centreLineY = mainArea.getY() + (int)(18*s) +
|
||||
(mainArea.getHeight() - (int)(18*s) - (int)(60*s)) * 0.5f;
|
||||
g.setColour(cyan.withAlpha(0.55f));
|
||||
g.drawHorizontalLine((int) centreLineY, (float) mainArea.getX(), (float) mainArea.getRight());
|
||||
|
||||
// Spectrum overlay.
|
||||
if (spectrum.size() > 2)
|
||||
{
|
||||
juce::Path path;
|
||||
const int x0 = mainArea.getX();
|
||||
const int width = mainArea.getWidth();
|
||||
const float topY = mainArea.getY() + 18.0f * s;
|
||||
const float botY = mainArea.getY() + (int)(8*s) + (mainArea.getHeight() - (int)(18*s) - (int)(60*s)) * 0.92f;
|
||||
|
||||
for (int i = 0; i < spectrum.size(); ++i)
|
||||
{
|
||||
float db = juce::jlimit(-72.0f, 12.0f, spectrum[i]);
|
||||
float norm = (db + 72.0f) / 84.0f; // 0..1 up
|
||||
float xx = (float) x0 + (float) i / (float) (spectrum.size() - 1) * width;
|
||||
float yy = botY - norm * (botY - topY);
|
||||
if (i == 0) path.startNewSubPath(xx, yy);
|
||||
else path.lineTo(xx, yy);
|
||||
}
|
||||
|
||||
juce::Path filled = path;
|
||||
filled.lineTo((float) mainArea.getRight(), botY);
|
||||
filled.lineTo((float) mainArea.getX(), botY);
|
||||
filled.closeSubPath();
|
||||
|
||||
g.setGradientFill(juce::ColourGradient(magenta.withAlpha(0.35f), (float) mainArea.getX(), topY,
|
||||
magenta.withAlpha(0.05f), (float) mainArea.getX(), botY,
|
||||
false));
|
||||
g.fillPath(filled);
|
||||
g.setColour(cyan.withAlpha(0.9f));
|
||||
g.strokePath(path, juce::PathStrokeType(1.6f * s));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gelyk
|
||||
Loading…
Add table
Add a link
Reference in a new issue