mirror of
https://codeberg.org/armin/saftpumpe.git
synced 2026-09-01 04:10:46 +02:00
add bypass toggle switch UI to top-left
This commit is contained in:
parent
bd9aa2fa67
commit
9d0da19f48
2 changed files with 294 additions and 50 deletions
|
|
@ -35,9 +35,9 @@ void SaftpumpeEditor::applyPreset(int index) {
|
||||||
SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
|
SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
|
||||||
: AudioProcessorEditor(&p), processorRef(p)
|
: AudioProcessorEditor(&p), processorRef(p)
|
||||||
{
|
{
|
||||||
setSize(700, 500);
|
setSize(700, 560);
|
||||||
setResizable(true, this);
|
setResizable(true, this);
|
||||||
setResizeLimits(650, 480, 1100, 800);
|
setResizeLimits(650, 480, 1100, 900);
|
||||||
startTimerHz(30);
|
startTimerHz(30);
|
||||||
|
|
||||||
auto setupKnob = [this](juce::Slider& s, juce::Label& l, const juce::String& text,
|
auto setupKnob = [this](juce::Slider& s, juce::Label& l, const juce::String& text,
|
||||||
|
|
@ -99,7 +99,7 @@ SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
|
||||||
setupLCD(autoReleaseLCD);
|
setupLCD(autoReleaseLCD);
|
||||||
|
|
||||||
// Zoom dropdown
|
// Zoom dropdown
|
||||||
zoomLabel.setText("ZOOM", juce::dontSendNotification);
|
zoomLabel.setText("UI SCALE", juce::dontSendNotification);
|
||||||
zoomLabel.setJustificationType(juce::Justification::centredRight);
|
zoomLabel.setJustificationType(juce::Justification::centredRight);
|
||||||
zoomLabel.setColour(juce::Label::textColourId, textDim);
|
zoomLabel.setColour(juce::Label::textColourId, textDim);
|
||||||
zoomLabel.setFont(juce::Font(juce::FontOptions(10.0f)));
|
zoomLabel.setFont(juce::Font(juce::FontOptions(10.0f)));
|
||||||
|
|
@ -110,7 +110,7 @@ SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
|
||||||
zoomBox.addItem("2.0x", 3);
|
zoomBox.addItem("2.0x", 3);
|
||||||
zoomBox.addItem("2.5x", 4);
|
zoomBox.addItem("2.5x", 4);
|
||||||
zoomBox.addItem("3.0x", 5);
|
zoomBox.addItem("3.0x", 5);
|
||||||
zoomBox.setSelectedId(2, juce::dontSendNotification);
|
zoomBox.setSelectedId(1, juce::dontSendNotification);
|
||||||
zoomBox.setColour(juce::ComboBox::backgroundColourId, grooveDark);
|
zoomBox.setColour(juce::ComboBox::backgroundColourId, grooveDark);
|
||||||
zoomBox.setColour(juce::ComboBox::textColourId, textColour);
|
zoomBox.setColour(juce::ComboBox::textColourId, textColour);
|
||||||
zoomBox.setColour(juce::ComboBox::arrowColourId, textDim);
|
zoomBox.setColour(juce::ComboBox::arrowColourId, textDim);
|
||||||
|
|
@ -135,6 +135,18 @@ SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
|
||||||
presetBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff333338));
|
presetBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff333338));
|
||||||
presetBox.addListener(this);
|
presetBox.addListener(this);
|
||||||
addAndMakeVisible(presetBox);
|
addAndMakeVisible(presetBox);
|
||||||
|
|
||||||
|
// Bypass toggle
|
||||||
|
bypassLabel.setText("BYPASS", juce::dontSendNotification);
|
||||||
|
bypassLabel.setJustificationType(juce::Justification::centredRight);
|
||||||
|
bypassLabel.setColour(juce::Label::textColourId, textDim);
|
||||||
|
bypassLabel.setFont(juce::Font(juce::FontOptions(10.0f)));
|
||||||
|
addAndMakeVisible(bypassLabel);
|
||||||
|
|
||||||
|
bypassButton.setLookAndFeel(&toggleLnf);
|
||||||
|
bypassButton.setToggleState(false, juce::dontSendNotification);
|
||||||
|
addAndMakeVisible(bypassButton);
|
||||||
|
bypassAttachment = std::make_unique<ButtonAttachment>(processorRef.parameters, "bypass", bypassButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
SaftpumpeEditor::~SaftpumpeEditor() {
|
SaftpumpeEditor::~SaftpumpeEditor() {
|
||||||
|
|
@ -146,6 +158,7 @@ SaftpumpeEditor::~SaftpumpeEditor() {
|
||||||
makeupSlider.setLookAndFeel(nullptr);
|
makeupSlider.setLookAndFeel(nullptr);
|
||||||
mixSlider.setLookAndFeel(nullptr);
|
mixSlider.setLookAndFeel(nullptr);
|
||||||
autoReleaseSlider.setLookAndFeel(nullptr);
|
autoReleaseSlider.setLookAndFeel(nullptr);
|
||||||
|
bypassButton.setLookAndFeel(nullptr);
|
||||||
zoomBox.removeListener(this);
|
zoomBox.removeListener(this);
|
||||||
presetBox.removeListener(this);
|
presetBox.removeListener(this);
|
||||||
stopTimer();
|
stopTimer();
|
||||||
|
|
@ -157,7 +170,7 @@ void SaftpumpeEditor::comboBoxChanged(juce::ComboBox* box) {
|
||||||
int id = box->getSelectedId();
|
int id = box->getSelectedId();
|
||||||
if (id >= 1 && id <= 5) {
|
if (id >= 1 && id <= 5) {
|
||||||
uiScale = scales[id - 1];
|
uiScale = scales[id - 1];
|
||||||
setSize(static_cast<int>(700 * uiScale), static_cast<int>(500 * uiScale));
|
setSize(static_cast<int>(700 * uiScale), static_cast<int>(560 * uiScale));
|
||||||
}
|
}
|
||||||
} else if (box == &presetBox) {
|
} else if (box == &presetBox) {
|
||||||
int id = box->getSelectedId() - 1;
|
int id = box->getSelectedId() - 1;
|
||||||
|
|
@ -165,6 +178,14 @@ void SaftpumpeEditor::comboBoxChanged(juce::ComboBox* box) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SaftpumpeEditor::mouseUp(const juce::MouseEvent& event) {
|
||||||
|
if (scopeArea.contains(event.position.toFloat())) {
|
||||||
|
bool wasSpectrum = processorRef.isSpectrumMode();
|
||||||
|
processorRef.setSpectrumMode(!wasSpectrum);
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
juce::String SaftpumpeEditor::formatValue(int idx, float val) {
|
juce::String SaftpumpeEditor::formatValue(int idx, float val) {
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
case 0: return juce::String(val, 1) + " dB";
|
case 0: return juce::String(val, 1) + " dB";
|
||||||
|
|
@ -184,17 +205,22 @@ void SaftpumpeEditor::computeLayout() {
|
||||||
auto bounds = getLocalBounds().toFloat();
|
auto bounds = getLocalBounds().toFloat();
|
||||||
auto faceBounds = bounds.reduced(8.0f * s);
|
auto faceBounds = bounds.reduced(8.0f * s);
|
||||||
|
|
||||||
// Header
|
// Content starts below accent line (accent line at faceY + 80*s)
|
||||||
float headerH = 28.0f * s + 16.0f * s + 8.0f * s;
|
float accentY = faceBounds.getY() + 80.0f * s;
|
||||||
float headerY = faceBounds.getY() + 6.0f * s;
|
float contentTop = accentY + 22.0f * s;
|
||||||
float contentTop = headerY + headerH + 4.0f * s;
|
|
||||||
|
|
||||||
// Meters — right column
|
// Bottom controls — side-by-side preset + zoom
|
||||||
|
float ctrlW = 140.0f * s;
|
||||||
|
float ctrlH = 52.0f * s;
|
||||||
|
float bottomY = faceBounds.getBottom() - ctrlH - 10.0f * s;
|
||||||
|
|
||||||
|
// Meters — right column (stops above bottom controls)
|
||||||
float meterW = 56.0f * s;
|
float meterW = 56.0f * s;
|
||||||
meterArea = juce::Rectangle<float>(faceBounds.getRight() - meterW - 14.0f * s,
|
float meterLegW = 18.0f * s;
|
||||||
|
meterArea = juce::Rectangle<float>(faceBounds.getRight() - meterW - meterLegW - 14.0f * s,
|
||||||
contentTop,
|
contentTop,
|
||||||
meterW,
|
meterW,
|
||||||
faceBounds.getBottom() - contentTop - 48.0f * s);
|
bottomY - contentTop - 8.0f * s);
|
||||||
|
|
||||||
// Knob grid area — 2 rows x 4 cols
|
// Knob grid area — 2 rows x 4 cols
|
||||||
float gridLeft = faceBounds.getX() + 14.0f * s;
|
float gridLeft = faceBounds.getX() + 14.0f * s;
|
||||||
|
|
@ -214,23 +240,26 @@ void SaftpumpeEditor::computeLayout() {
|
||||||
for (int col = 0; col < knobsPerRow; ++col) {
|
for (int col = 0; col < knobsPerRow; ++col) {
|
||||||
int idx = row * knobsPerRow + col;
|
int idx = row * knobsPerRow + col;
|
||||||
float cx = rowBounds[row].getX() + colW * static_cast<float>(col) + colW / 2.0f;
|
float cx = rowBounds[row].getX() + colW * static_cast<float>(col) + colW / 2.0f;
|
||||||
float cy = rowBounds[row].getY() + rowH * 0.40f;
|
float cy = rowBounds[row].getY() + rowH * 0.45f;
|
||||||
knobCentre[idx] = {cx, cy};
|
knobCentre[idx] = {cx, cy};
|
||||||
knobBounds[idx] = juce::Rectangle<float>(cx - knobRadius, cy - knobRadius,
|
knobBounds[idx] = juce::Rectangle<float>(cx - knobRadius, cy - knobRadius,
|
||||||
knobRadius * 2.0f, knobRadius * 2.0f);
|
knobRadius * 2.0f, knobRadius * 2.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom: scope + zoom + preset
|
// Bottom: scope + side-by-side preset & zoom
|
||||||
float bottomY = meterArea.getBottom() + 8.0f * s;
|
|
||||||
float bottomH = faceBounds.getBottom() - bottomY - 10.0f * s;
|
float bottomH = faceBounds.getBottom() - bottomY - 10.0f * s;
|
||||||
float ctrlW = 110.0f * s;
|
float rightX = faceBounds.getRight() - ctrlW * 2.0f - 14.0f * s;
|
||||||
float ctrlGap = 6.0f * s;
|
|
||||||
float rightX = faceBounds.getRight() - ctrlW - 14.0f * s;
|
|
||||||
presetArea = juce::Rectangle<float>(rightX, bottomY, ctrlW, bottomH);
|
presetArea = juce::Rectangle<float>(rightX, bottomY, ctrlW, bottomH);
|
||||||
zoomArea = juce::Rectangle<float>(rightX, bottomY + bottomH * 0.48f, ctrlW, bottomH * 0.48f);
|
zoomArea = juce::Rectangle<float>(rightX + ctrlW + 4.0f * s, bottomY, ctrlW, bottomH);
|
||||||
scopeArea = juce::Rectangle<float>(faceBounds.getX() + 14.0f * s, bottomY,
|
scopeArea = juce::Rectangle<float>(faceBounds.getX() + 14.0f * s, bottomY,
|
||||||
presetArea.getX() - faceBounds.getX() - 24.0f * s, bottomH);
|
presetArea.getX() - faceBounds.getX() - 14.0f * s, bottomH);
|
||||||
|
|
||||||
|
// Bypass switch — top-left, below the screw
|
||||||
|
bypassArea = juce::Rectangle<float>(faceBounds.getX() + 20.0f * s,
|
||||||
|
faceBounds.getY() + 30.0f * s,
|
||||||
|
80.0f * s, 20.0f * s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaftpumpeEditor::resized() {
|
void SaftpumpeEditor::resized() {
|
||||||
|
|
@ -275,15 +304,32 @@ void SaftpumpeEditor::resized() {
|
||||||
setKnobBounds(mixSlider, mixLabel, mixLCD, 6);
|
setKnobBounds(mixSlider, mixLabel, mixLCD, 6);
|
||||||
setKnobBounds(autoReleaseSlider, autoReleaseLabel, autoReleaseLCD, 7);
|
setKnobBounds(autoReleaseSlider, autoReleaseLabel, autoReleaseLCD, 7);
|
||||||
|
|
||||||
presetLabel.setBounds(static_cast<int>(presetArea.getX()), static_cast<int>(presetArea.getY()),
|
float ctrlLabelH = 14.0f * s;
|
||||||
static_cast<int>(presetArea.getWidth()), static_cast<int>(14.0f * s));
|
float ctrlBoxH = 24.0f * s;
|
||||||
presetBox.setBounds(static_cast<int>(presetArea.getX()), static_cast<int>(presetArea.getY() + 14.0f * s),
|
float ctrlGap = 2.0f * s;
|
||||||
static_cast<int>(presetArea.getWidth()), static_cast<int>(22.0f * s));
|
float ctrlItemH = ctrlLabelH + ctrlGap + ctrlBoxH;
|
||||||
|
float ctrlPadY = (presetArea.getHeight() - ctrlItemH) / 2.0f;
|
||||||
|
|
||||||
zoomLabel.setBounds(static_cast<int>(zoomArea.getX()), static_cast<int>(zoomArea.getY()),
|
presetLabel.setBounds(presetArea.getX(), presetArea.getY() + ctrlPadY,
|
||||||
static_cast<int>(zoomArea.getWidth()), static_cast<int>(14.0f * s));
|
presetArea.getWidth(), ctrlLabelH);
|
||||||
zoomBox.setBounds(static_cast<int>(zoomArea.getX()), static_cast<int>(zoomArea.getY() + 14.0f * s),
|
presetBox.setBounds(presetArea.getX(), presetArea.getY() + ctrlPadY + ctrlLabelH + ctrlGap,
|
||||||
static_cast<int>(zoomArea.getWidth()), static_cast<int>(22.0f * s));
|
presetArea.getWidth(), ctrlBoxH);
|
||||||
|
|
||||||
|
zoomLabel.setBounds(zoomArea.getX(), zoomArea.getY() + ctrlPadY,
|
||||||
|
zoomArea.getWidth(), ctrlLabelH);
|
||||||
|
zoomBox.setBounds(zoomArea.getX(), zoomArea.getY() + ctrlPadY + ctrlLabelH + ctrlGap,
|
||||||
|
zoomArea.getWidth(), ctrlBoxH);
|
||||||
|
|
||||||
|
// Bypass switch
|
||||||
|
float bypassLabelW = 48.0f * s;
|
||||||
|
float bypassToggleW = 36.0f * s;
|
||||||
|
float bypassH = 16.0f * s;
|
||||||
|
bypassLabel.setBounds(static_cast<int>(bypassArea.getX()),
|
||||||
|
static_cast<int>(bypassArea.getY() + (bypassArea.getHeight() - bypassH) / 2.0f),
|
||||||
|
static_cast<int>(bypassLabelW), static_cast<int>(bypassH));
|
||||||
|
bypassButton.setBounds(static_cast<int>(bypassArea.getX() + bypassLabelW + 4.0f * s),
|
||||||
|
static_cast<int>(bypassArea.getY() + (bypassArea.getHeight() - bypassH) / 2.0f),
|
||||||
|
static_cast<int>(bypassToggleW), static_cast<int>(bypassH));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaftpumpeEditor::paint(juce::Graphics& g) {
|
void SaftpumpeEditor::paint(juce::Graphics& g) {
|
||||||
|
|
@ -367,26 +413,61 @@ void SaftpumpeEditor::paint(juce::Graphics& g) {
|
||||||
|
|
||||||
// Meters
|
// Meters
|
||||||
float grH = meterArea.getHeight() * 0.42f;
|
float grH = meterArea.getHeight() * 0.42f;
|
||||||
auto grBounds = juce::Rectangle<float>(meterArea.getX(), meterArea.getY(), meterArea.getWidth(), grH);
|
float meterLegW = 18.0f * s;
|
||||||
|
auto grBar = juce::Rectangle<float>(meterArea.getX(), meterArea.getY(),
|
||||||
|
meterArea.getWidth() - meterLegW, grH);
|
||||||
float gr = processorRef.getGainReductionMeter();
|
float gr = processorRef.getGainReductionMeter();
|
||||||
drawMeter(g, grBounds, gr, 0.0f, 24.0f, true);
|
drawMeter(g, grBar, gr, 0.0f, 24.0f, true);
|
||||||
drawPeakHold(g, grBounds, grPeak.peak, true);
|
drawPeakHold(g, grBar, grPeak.peak, true);
|
||||||
g.setColour(textDim);
|
g.setColour(textDim);
|
||||||
g.setFont(juce::Font(juce::FontOptions(10.0f * s)));
|
g.setFont(juce::Font(juce::FontOptions(10.0f * s)));
|
||||||
g.drawText("GR", grBounds.translated(0, grBounds.getHeight() + 2.0f * s), juce::Justification::centredTop);
|
g.drawText("GR", grBar.translated(0, grBar.getHeight() + 2.0f * s), juce::Justification::centredTop);
|
||||||
|
|
||||||
float ioY = grBounds.getBottom() + 16.0f * s;
|
// GR scale legends
|
||||||
|
{
|
||||||
|
float lx = grBar.getRight() + 2.0f * s;
|
||||||
|
float lw = meterLegW - 2.0f * s;
|
||||||
|
g.setFont(juce::Font(juce::FontOptions(8.0f * s)));
|
||||||
|
g.setColour(textDim.withAlpha(0.6f));
|
||||||
|
const float grTicks[] = { 0.0f, 6.0f, 12.0f, 18.0f, 24.0f };
|
||||||
|
const char* grLabels[] = { "0", "-6", "-12", "-18", "-24" };
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
float norm = grTicks[i] / 24.0f;
|
||||||
|
float ty = grBar.getY() + grBar.getHeight() * norm;
|
||||||
|
g.drawLine(grBar.getRight() - 2.0f * s, ty, grBar.getRight() + 2.0f * s, ty, 0.5f);
|
||||||
|
g.drawText(grLabels[i], lx, ty - 5.0f * s, lw, 10.0f * s, juce::Justification::centredLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float ioY = grBar.getBottom() + 16.0f * s;
|
||||||
float ioH = meterArea.getBottom() - ioY - 14.0f * s;
|
float ioH = meterArea.getBottom() - ioY - 14.0f * s;
|
||||||
float ioW = (meterArea.getWidth() - 3.0f * s) / 2.0f;
|
float ioBarW = (meterArea.getWidth() - meterLegW - 3.0f * s) / 2.0f;
|
||||||
auto inB = juce::Rectangle<float>(meterArea.getX(), ioY, ioW, ioH);
|
auto inBar = juce::Rectangle<float>(meterArea.getX(), ioY, ioBarW, ioH);
|
||||||
auto outB = juce::Rectangle<float>(meterArea.getX() + ioW + 3.0f * s, ioY, ioW, ioH);
|
auto outBar = juce::Rectangle<float>(meterArea.getX() + ioBarW + 3.0f * s, ioY, ioBarW, ioH);
|
||||||
|
|
||||||
drawMeter(g, inB, processorRef.getInputLevel(), -60.0f, 0.0f, false);
|
drawMeter(g, inBar, processorRef.getInputLevel(), -60.0f, 0.0f, false);
|
||||||
drawMeter(g, outB, processorRef.getOutputLevel(), -60.0f, 0.0f, false);
|
drawMeter(g, outBar, processorRef.getOutputLevel(), -60.0f, 0.0f, false);
|
||||||
drawPeakHold(g, inB, inPeak.peak);
|
drawPeakHold(g, inBar, inPeak.peak);
|
||||||
drawPeakHold(g, outB, outPeak.peak);
|
drawPeakHold(g, outBar, outPeak.peak);
|
||||||
g.drawText("IN", inB.translated(0, ioH + 2.0f * s), juce::Justification::centredTop);
|
g.setColour(textDim);
|
||||||
g.drawText("OUT", outB.translated(0, ioH + 2.0f * s), juce::Justification::centredTop);
|
g.setFont(juce::Font(juce::FontOptions(9.0f * s)));
|
||||||
|
g.drawText("IN", inBar.translated(0, ioH + 2.0f * s), juce::Justification::centredTop);
|
||||||
|
g.drawText("OUT", outBar.translated(0, ioH + 2.0f * s), juce::Justification::centredTop);
|
||||||
|
|
||||||
|
// VU scale legends
|
||||||
|
{
|
||||||
|
float lx = meterArea.getRight() - meterLegW + 2.0f * s;
|
||||||
|
float lw = meterLegW - 2.0f * s;
|
||||||
|
g.setFont(juce::Font(juce::FontOptions(7.5f * s)));
|
||||||
|
g.setColour(textDim.withAlpha(0.5f));
|
||||||
|
const float vuTicks[] = { 0.0f, 0.333f, 0.667f, 1.0f };
|
||||||
|
const char* vuLabels[] = { "0", "-20", "-40", "-60" };
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
float ty = inBar.getBottom() - inBar.getHeight() * vuTicks[i];
|
||||||
|
g.drawLine(outBar.getRight() - 1.0f * s, ty, meterArea.getRight() - 1.0f * s, ty, 0.4f);
|
||||||
|
g.drawText(vuLabels[i], lx, ty - 4.0f * s, lw, 8.0f * s, juce::Justification::centredLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Scope
|
// Scope
|
||||||
drawScope(g, scopeArea);
|
drawScope(g, scopeArea);
|
||||||
|
|
@ -395,6 +476,9 @@ void SaftpumpeEditor::paint(juce::Graphics& g) {
|
||||||
g.setColour(juce::Colour(0xff333338));
|
g.setColour(juce::Colour(0xff333338));
|
||||||
g.drawRoundedRectangle(zoomArea.reduced(2.0f * s), 2.0f * s, 0.5f);
|
g.drawRoundedRectangle(zoomArea.reduced(2.0f * s), 2.0f * s, 0.5f);
|
||||||
g.drawRoundedRectangle(presetArea.reduced(2.0f * s), 2.0f * s, 0.5f);
|
g.drawRoundedRectangle(presetArea.reduced(2.0f * s), 2.0f * s, 0.5f);
|
||||||
|
|
||||||
|
// Bypass area border
|
||||||
|
g.drawRoundedRectangle(bypassArea.reduced(1.0f * s), 2.0f * s, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaftpumpeEditor::drawKnob(juce::Graphics& g, juce::Rectangle<float> bounds,
|
void SaftpumpeEditor::drawKnob(juce::Graphics& g, juce::Rectangle<float> bounds,
|
||||||
|
|
@ -562,6 +646,11 @@ void SaftpumpeEditor::drawPeakHold(juce::Graphics& g, juce::Rectangle<float> bou
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaftpumpeEditor::drawScope(juce::Graphics& g, juce::Rectangle<float> bounds) {
|
void SaftpumpeEditor::drawScope(juce::Graphics& g, juce::Rectangle<float> bounds) {
|
||||||
|
if (processorRef.isSpectrumMode()) {
|
||||||
|
drawSpectrum(g, bounds);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float s = uiScale;
|
float s = uiScale;
|
||||||
g.setColour(grooveDark);
|
g.setColour(grooveDark);
|
||||||
g.fillRoundedRectangle(bounds, 3.0f * s);
|
g.fillRoundedRectangle(bounds, 3.0f * s);
|
||||||
|
|
@ -579,6 +668,20 @@ void SaftpumpeEditor::drawScope(juce::Graphics& g, juce::Rectangle<float> bounds
|
||||||
auto& inData = processorRef.inputScope;
|
auto& inData = processorRef.inputScope;
|
||||||
auto& outData = processorRef.outputScope;
|
auto& outData = processorRef.outputScope;
|
||||||
int numPoints = processorRef.scopeSize;
|
int numPoints = processorRef.scopeSize;
|
||||||
|
|
||||||
|
float legendY = bounds.getBottom() - 10.0f * s;
|
||||||
|
float legendX = bounds.getX() + 6.0f * s;
|
||||||
|
g.setFont(juce::Font(juce::FontOptions(8.0f * s)));
|
||||||
|
g.setColour(redLed.withAlpha(0.5f));
|
||||||
|
g.drawText("IN", legendX, legendY, 16.0f * s, 8.0f * s, juce::Justification::centredLeft);
|
||||||
|
g.setColour(greenLed.withAlpha(0.4f));
|
||||||
|
g.drawText("OUT", legendX + 18.0f * s, legendY, 22.0f * s, 8.0f * s, juce::Justification::centredLeft);
|
||||||
|
|
||||||
|
g.setColour(textDim.withAlpha(0.3f));
|
||||||
|
g.setFont(juce::Font(juce::FontOptions(7.0f * s)));
|
||||||
|
g.drawText("WAVEFORM", bounds.getRight() - 48.0f * s, legendY, 42.0f * s, 8.0f * s,
|
||||||
|
juce::Justification::centredRight);
|
||||||
|
|
||||||
if (numPoints < 2) return;
|
if (numPoints < 2) return;
|
||||||
|
|
||||||
float width = bounds.getWidth() - 4.0f;
|
float width = bounds.getWidth() - 4.0f;
|
||||||
|
|
@ -606,14 +709,101 @@ void SaftpumpeEditor::drawScope(juce::Graphics& g, juce::Rectangle<float> bounds
|
||||||
g.setColour(greenLed.withAlpha(0.65f));
|
g.setColour(greenLed.withAlpha(0.65f));
|
||||||
g.strokePath(path, juce::PathStrokeType(1.0f));
|
g.strokePath(path, juce::PathStrokeType(1.0f));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float legendY = bounds.getBottom() - 12.0f * s;
|
void SaftpumpeEditor::drawSpectrum(juce::Graphics& g, juce::Rectangle<float> bounds) {
|
||||||
|
float s = uiScale;
|
||||||
|
|
||||||
|
g.setColour(grooveDark);
|
||||||
|
g.fillRoundedRectangle(bounds, 3.0f * s);
|
||||||
|
g.setColour(juce::Colours::black.withAlpha(0.4f));
|
||||||
|
g.drawRoundedRectangle(bounds.expanded(0.5f), 3.0f * s, 1.0f);
|
||||||
|
|
||||||
|
auto area = bounds.reduced(4.0f * s);
|
||||||
|
float w = area.getWidth();
|
||||||
|
float h = area.getHeight();
|
||||||
|
float ox = area.getX();
|
||||||
|
float oy = area.getY();
|
||||||
|
|
||||||
|
g.setColour(accentLine.withAlpha(0.05f));
|
||||||
|
for (int i = 0; i <= 4; ++i) {
|
||||||
|
float ly = oy + h * static_cast<float>(i) / 4.0f;
|
||||||
|
g.drawLine(ox, ly, ox + w, ly, 0.3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int numBins = processorRef.fftSize / 2;
|
||||||
|
if (numBins < 2) return;
|
||||||
|
|
||||||
|
float minLog = std::log10(20.0f);
|
||||||
|
float maxLog = std::log10(22050.0f);
|
||||||
|
|
||||||
|
auto freqToX = [&](float freq) -> float {
|
||||||
|
float logF = std::log10(juce::jmax(freq, 20.0f));
|
||||||
|
return ox + w * (logF - minLog) / (maxLog - minLog);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto dbToY = [&](float db) -> float {
|
||||||
|
float norm = juce::jlimit(0.0f, 1.0f, (db + 80.0f) / 80.0f);
|
||||||
|
return oy + h * (1.0f - norm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const float freqs[] = { 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000 };
|
||||||
|
g.setFont(juce::Font(juce::FontOptions(7.0f * s)));
|
||||||
|
g.setColour(textDim.withAlpha(0.4f));
|
||||||
|
for (float freq : freqs) {
|
||||||
|
float fx = freqToX(freq);
|
||||||
|
g.drawLine(fx, oy, fx, oy + h, 0.3f);
|
||||||
|
juce::String label = freq >= 1000 ? juce::String(freq / 1000.0f, 0) + "k"
|
||||||
|
: juce::String(static_cast<int>(freq));
|
||||||
|
g.drawText(label, fx - 10.0f * s, oy + h + 1.0f * s, 20.0f * s, 8.0f * s,
|
||||||
|
juce::Justification::centredTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float dbTicks[] = { 0, -20, -40, -60 };
|
||||||
|
g.setColour(textDim.withAlpha(0.35f));
|
||||||
|
for (float db : dbTicks) {
|
||||||
|
float ly = dbToY(db);
|
||||||
|
g.drawLine(ox, ly, ox + w, ly, 0.3f);
|
||||||
|
g.drawText(juce::String(static_cast<int>(db)), ox - 22.0f * s, ly - 4.0f * s,
|
||||||
|
20.0f * s, 8.0f * s, juce::Justification::centredRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
float binHz = 44100.0f / static_cast<float>(processorRef.fftSize);
|
||||||
|
|
||||||
|
auto drawSpectrumLine = [&](const std::array<float, SaftpumpeProcessor::fftSize / 2>& data,
|
||||||
|
juce::Colour col, float shadowAlpha, float lineAlpha, float strokeW) {
|
||||||
|
juce::Path path;
|
||||||
|
bool started = false;
|
||||||
|
for (int i = 1; i < numBins; ++i) {
|
||||||
|
float freq = binHz * static_cast<float>(i);
|
||||||
|
if (freq < 20.0f) continue;
|
||||||
|
float fx = freqToX(freq);
|
||||||
|
float db = data[i] * 80.0f - 80.0f;
|
||||||
|
float fy = dbToY(db);
|
||||||
|
if (!started) { path.startNewSubPath(fx, fy); started = true; }
|
||||||
|
else path.lineTo(fx, fy);
|
||||||
|
}
|
||||||
|
g.setColour(col.withAlpha(shadowAlpha));
|
||||||
|
g.strokePath(path, juce::PathStrokeType(strokeW * 2.5f));
|
||||||
|
g.setColour(col.withAlpha(lineAlpha));
|
||||||
|
g.strokePath(path, juce::PathStrokeType(strokeW));
|
||||||
|
};
|
||||||
|
|
||||||
|
drawSpectrumLine(processorRef.inputSpectrum, redLed, 0.12f, 0.75f, 1.2f);
|
||||||
|
drawSpectrumLine(processorRef.outputSpectrum, greenLed, 0.10f, 0.60f, 1.0f);
|
||||||
|
|
||||||
|
float legendY = bounds.getBottom() - 10.0f * s;
|
||||||
float legendX = bounds.getX() + 6.0f * s;
|
float legendX = bounds.getX() + 6.0f * s;
|
||||||
g.setFont(juce::Font(juce::FontOptions(9.0f * s)));
|
g.setFont(juce::Font(juce::FontOptions(8.0f * s)));
|
||||||
g.setColour(redLed.withAlpha(0.8f));
|
g.setColour(redLed.withAlpha(0.5f));
|
||||||
g.drawText("IN", legendX, legendY, 20.0f * s, 10.0f * s, juce::Justification::centredLeft);
|
g.drawText("IN", legendX, legendY, 16.0f * s, 8.0f * s, juce::Justification::centredLeft);
|
||||||
g.setColour(greenLed.withAlpha(0.65f));
|
g.setColour(greenLed.withAlpha(0.4f));
|
||||||
g.drawText("OUT", legendX + 24.0f * s, legendY, 26.0f * s, 10.0f * s, juce::Justification::centredLeft);
|
g.drawText("OUT", legendX + 18.0f * s, legendY, 22.0f * s, 8.0f * s, juce::Justification::centredLeft);
|
||||||
|
|
||||||
|
g.setColour(textDim.withAlpha(0.3f));
|
||||||
|
g.setFont(juce::Font(juce::FontOptions(7.0f * s)));
|
||||||
|
g.drawText("SPECTRUM", bounds.getRight() - 52.0f * s, legendY, 46.0f * s, 8.0f * s,
|
||||||
|
juce::Justification::centredRight);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaftpumpeEditor::drawScrew(juce::Graphics& g, float cx, float cy, float radius) {
|
void SaftpumpeEditor::drawScrew(juce::Graphics& g, float cx, float cy, float radius) {
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,12 @@ public:
|
||||||
void resized() override;
|
void resized() override;
|
||||||
void timerCallback() override;
|
void timerCallback() override;
|
||||||
void comboBoxChanged(juce::ComboBox* box) override;
|
void comboBoxChanged(juce::ComboBox* box) override;
|
||||||
|
void mouseUp(const juce::MouseEvent& event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SaftpumpeProcessor& processorRef;
|
SaftpumpeProcessor& processorRef;
|
||||||
|
|
||||||
float uiScale = 1.5f;
|
float uiScale = 1.0f;
|
||||||
|
|
||||||
// Dark industrial palette
|
// Dark industrial palette
|
||||||
juce::Colour bezelColour{0xff0a0a0c};
|
juce::Colour bezelColour{0xff0a0a0c};
|
||||||
|
|
@ -51,11 +52,16 @@ private:
|
||||||
juce::ComboBox presetBox;
|
juce::ComboBox presetBox;
|
||||||
juce::Label presetLabel;
|
juce::Label presetLabel;
|
||||||
|
|
||||||
|
juce::ToggleButton bypassButton;
|
||||||
|
juce::Label bypassLabel;
|
||||||
|
|
||||||
using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;
|
using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;
|
||||||
|
using ButtonAttachment = juce::AudioProcessorValueTreeState::ButtonAttachment;
|
||||||
|
|
||||||
std::unique_ptr<SliderAttachment> thresholdAttachment, ratioAttachment, attackAttachment;
|
std::unique_ptr<SliderAttachment> thresholdAttachment, ratioAttachment, attackAttachment;
|
||||||
std::unique_ptr<SliderAttachment> releaseAttachment, kneeAttachment, makeupAttachment;
|
std::unique_ptr<SliderAttachment> releaseAttachment, kneeAttachment, makeupAttachment;
|
||||||
std::unique_ptr<SliderAttachment> mixAttachment, autoReleaseAttachment;
|
std::unique_ptr<SliderAttachment> mixAttachment, autoReleaseAttachment;
|
||||||
|
std::unique_ptr<ButtonAttachment> bypassAttachment;
|
||||||
|
|
||||||
juce::Rectangle<float> meterArea;
|
juce::Rectangle<float> meterArea;
|
||||||
juce::Rectangle<float> scopeArea;
|
juce::Rectangle<float> scopeArea;
|
||||||
|
|
@ -80,6 +86,7 @@ private:
|
||||||
void drawPeakHold(juce::Graphics& g, juce::Rectangle<float> bounds,
|
void drawPeakHold(juce::Graphics& g, juce::Rectangle<float> bounds,
|
||||||
float peakNorm, bool fromTop = false);
|
float peakNorm, bool fromTop = false);
|
||||||
void drawScope(juce::Graphics& g, juce::Rectangle<float> bounds);
|
void drawScope(juce::Graphics& g, juce::Rectangle<float> bounds);
|
||||||
|
void drawSpectrum(juce::Graphics& g, juce::Rectangle<float> bounds);
|
||||||
void drawScrew(juce::Graphics& g, float cx, float cy, float radius);
|
void drawScrew(juce::Graphics& g, float cx, float cy, float radius);
|
||||||
|
|
||||||
static constexpr float startAngle = juce::MathConstants<float>::pi * 0.75f;
|
static constexpr float startAngle = juce::MathConstants<float>::pi * 0.75f;
|
||||||
|
|
@ -95,11 +102,58 @@ private:
|
||||||
void applyPreset(int index);
|
void applyPreset(int index);
|
||||||
|
|
||||||
juce::Rectangle<float> presetArea;
|
juce::Rectangle<float> presetArea;
|
||||||
|
juce::Rectangle<float> bypassArea;
|
||||||
|
|
||||||
struct NoDotLookAndFeel : juce::LookAndFeel_V4 {
|
struct NoDotLookAndFeel : juce::LookAndFeel_V4 {
|
||||||
void drawRotarySlider(juce::Graphics&, int, int, int, int,
|
void drawRotarySlider(juce::Graphics&, int, int, int, int,
|
||||||
float, float, float, juce::Slider&) override {}
|
float, float, float, juce::Slider&) override {}
|
||||||
} lnf;
|
} lnf;
|
||||||
|
|
||||||
|
struct ToggleSwitchLNF : juce::LookAndFeel_V4 {
|
||||||
|
void drawToggleButton(juce::Graphics& g, juce::ToggleButton& btn,
|
||||||
|
bool hover, bool /*isDown*/) override {
|
||||||
|
auto bounds = btn.getLocalBounds().toFloat();
|
||||||
|
float s = bounds.getHeight() / 16.0f;
|
||||||
|
float w = bounds.getWidth();
|
||||||
|
float h = bounds.getHeight();
|
||||||
|
float cornerR = h * 0.3f;
|
||||||
|
|
||||||
|
// Groove
|
||||||
|
g.setColour(juce::Colour(0xff111114));
|
||||||
|
g.fillRoundedRectangle(bounds, cornerR);
|
||||||
|
g.setColour(juce::Colours::black.withAlpha(0.5f));
|
||||||
|
g.drawRoundedRectangle(bounds.expanded(0.5f), cornerR, 1.0f);
|
||||||
|
|
||||||
|
// Slider handle
|
||||||
|
bool on = btn.getToggleState();
|
||||||
|
float handleW = w * 0.42f;
|
||||||
|
float handleH = h * 0.72f;
|
||||||
|
float handleX = on ? (bounds.getRight() - handleW - 3.0f * s) : (bounds.getX() + 3.0f * s);
|
||||||
|
float handleY = bounds.getY() + (h - handleH) / 2.0f;
|
||||||
|
auto handleBounds = juce::Rectangle<float>(handleX, handleY, handleW, handleH);
|
||||||
|
|
||||||
|
// Handle shadow
|
||||||
|
g.setColour(juce::Colours::black.withAlpha(0.4f));
|
||||||
|
g.fillRoundedRectangle(handleBounds.translated(0, 1.0f), cornerR * 0.6f);
|
||||||
|
|
||||||
|
// Handle body
|
||||||
|
juce::Colour handleCol = on ? juce::Colour(0xff00e676) : juce::Colour(0xff4a4a50);
|
||||||
|
if (hover) handleCol = handleCol.brighter(0.15f);
|
||||||
|
g.setColour(handleCol);
|
||||||
|
g.fillRoundedRectangle(handleBounds, cornerR * 0.6f);
|
||||||
|
|
||||||
|
// Handle highlight
|
||||||
|
g.setColour(juce::Colours::white.withAlpha(on ? 0.2f : 0.08f));
|
||||||
|
g.fillRoundedRectangle(handleBounds.withHeight(handleH * 0.4f).translated(0, 1.0f), cornerR * 0.4f);
|
||||||
|
|
||||||
|
// LED glow when bypass active
|
||||||
|
if (on) {
|
||||||
|
g.setColour(juce::Colour(0xff00e676).withAlpha(0.15f));
|
||||||
|
g.fillEllipse(bounds.getCentreX() - w * 0.5f, bounds.getCentreY() - h * 0.6f,
|
||||||
|
w * 1.0f, h * 1.2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} toggleLnf;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SaftpumpeEditor)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SaftpumpeEditor)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue