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)
|
||||
: AudioProcessorEditor(&p), processorRef(p)
|
||||
{
|
||||
setSize(700, 500);
|
||||
setSize(700, 560);
|
||||
setResizable(true, this);
|
||||
setResizeLimits(650, 480, 1100, 800);
|
||||
setResizeLimits(650, 480, 1100, 900);
|
||||
startTimerHz(30);
|
||||
|
||||
auto setupKnob = [this](juce::Slider& s, juce::Label& l, const juce::String& text,
|
||||
|
|
@ -99,7 +99,7 @@ SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
|
|||
setupLCD(autoReleaseLCD);
|
||||
|
||||
// Zoom dropdown
|
||||
zoomLabel.setText("ZOOM", juce::dontSendNotification);
|
||||
zoomLabel.setText("UI SCALE", juce::dontSendNotification);
|
||||
zoomLabel.setJustificationType(juce::Justification::centredRight);
|
||||
zoomLabel.setColour(juce::Label::textColourId, textDim);
|
||||
zoomLabel.setFont(juce::Font(juce::FontOptions(10.0f)));
|
||||
|
|
@ -110,7 +110,7 @@ SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
|
|||
zoomBox.addItem("2.0x", 3);
|
||||
zoomBox.addItem("2.5x", 4);
|
||||
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::textColourId, textColour);
|
||||
zoomBox.setColour(juce::ComboBox::arrowColourId, textDim);
|
||||
|
|
@ -135,6 +135,18 @@ SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
|
|||
presetBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff333338));
|
||||
presetBox.addListener(this);
|
||||
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() {
|
||||
|
|
@ -146,6 +158,7 @@ SaftpumpeEditor::~SaftpumpeEditor() {
|
|||
makeupSlider.setLookAndFeel(nullptr);
|
||||
mixSlider.setLookAndFeel(nullptr);
|
||||
autoReleaseSlider.setLookAndFeel(nullptr);
|
||||
bypassButton.setLookAndFeel(nullptr);
|
||||
zoomBox.removeListener(this);
|
||||
presetBox.removeListener(this);
|
||||
stopTimer();
|
||||
|
|
@ -157,7 +170,7 @@ void SaftpumpeEditor::comboBoxChanged(juce::ComboBox* box) {
|
|||
int id = box->getSelectedId();
|
||||
if (id >= 1 && id <= 5) {
|
||||
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) {
|
||||
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) {
|
||||
switch (idx) {
|
||||
case 0: return juce::String(val, 1) + " dB";
|
||||
|
|
@ -184,17 +205,22 @@ void SaftpumpeEditor::computeLayout() {
|
|||
auto bounds = getLocalBounds().toFloat();
|
||||
auto faceBounds = bounds.reduced(8.0f * s);
|
||||
|
||||
// Header
|
||||
float headerH = 28.0f * s + 16.0f * s + 8.0f * s;
|
||||
float headerY = faceBounds.getY() + 6.0f * s;
|
||||
float contentTop = headerY + headerH + 4.0f * s;
|
||||
// Content starts below accent line (accent line at faceY + 80*s)
|
||||
float accentY = faceBounds.getY() + 80.0f * s;
|
||||
float contentTop = accentY + 22.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;
|
||||
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,
|
||||
meterW,
|
||||
faceBounds.getBottom() - contentTop - 48.0f * s);
|
||||
bottomY - contentTop - 8.0f * s);
|
||||
|
||||
// Knob grid area — 2 rows x 4 cols
|
||||
float gridLeft = faceBounds.getX() + 14.0f * s;
|
||||
|
|
@ -214,23 +240,26 @@ void SaftpumpeEditor::computeLayout() {
|
|||
for (int col = 0; col < knobsPerRow; ++col) {
|
||||
int idx = row * knobsPerRow + col;
|
||||
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};
|
||||
knobBounds[idx] = juce::Rectangle<float>(cx - knobRadius, cy - knobRadius,
|
||||
knobRadius * 2.0f, knobRadius * 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom: scope + zoom + preset
|
||||
float bottomY = meterArea.getBottom() + 8.0f * s;
|
||||
// Bottom: scope + side-by-side preset & zoom
|
||||
float bottomH = faceBounds.getBottom() - bottomY - 10.0f * s;
|
||||
float ctrlW = 110.0f * s;
|
||||
float ctrlGap = 6.0f * s;
|
||||
float rightX = faceBounds.getRight() - ctrlW - 14.0f * s;
|
||||
float rightX = faceBounds.getRight() - ctrlW * 2.0f - 14.0f * s;
|
||||
|
||||
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,
|
||||
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() {
|
||||
|
|
@ -275,15 +304,32 @@ void SaftpumpeEditor::resized() {
|
|||
setKnobBounds(mixSlider, mixLabel, mixLCD, 6);
|
||||
setKnobBounds(autoReleaseSlider, autoReleaseLabel, autoReleaseLCD, 7);
|
||||
|
||||
presetLabel.setBounds(static_cast<int>(presetArea.getX()), static_cast<int>(presetArea.getY()),
|
||||
static_cast<int>(presetArea.getWidth()), static_cast<int>(14.0f * s));
|
||||
presetBox.setBounds(static_cast<int>(presetArea.getX()), static_cast<int>(presetArea.getY() + 14.0f * s),
|
||||
static_cast<int>(presetArea.getWidth()), static_cast<int>(22.0f * s));
|
||||
float ctrlLabelH = 14.0f * s;
|
||||
float ctrlBoxH = 24.0f * s;
|
||||
float ctrlGap = 2.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()),
|
||||
static_cast<int>(zoomArea.getWidth()), static_cast<int>(14.0f * s));
|
||||
zoomBox.setBounds(static_cast<int>(zoomArea.getX()), static_cast<int>(zoomArea.getY() + 14.0f * s),
|
||||
static_cast<int>(zoomArea.getWidth()), static_cast<int>(22.0f * s));
|
||||
presetLabel.setBounds(presetArea.getX(), presetArea.getY() + ctrlPadY,
|
||||
presetArea.getWidth(), ctrlLabelH);
|
||||
presetBox.setBounds(presetArea.getX(), presetArea.getY() + ctrlPadY + ctrlLabelH + ctrlGap,
|
||||
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) {
|
||||
|
|
@ -367,26 +413,61 @@ void SaftpumpeEditor::paint(juce::Graphics& g) {
|
|||
|
||||
// Meters
|
||||
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();
|
||||
drawMeter(g, grBounds, gr, 0.0f, 24.0f, true);
|
||||
drawPeakHold(g, grBounds, grPeak.peak, true);
|
||||
drawMeter(g, grBar, gr, 0.0f, 24.0f, true);
|
||||
drawPeakHold(g, grBar, grPeak.peak, true);
|
||||
g.setColour(textDim);
|
||||
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 ioW = (meterArea.getWidth() - 3.0f * s) / 2.0f;
|
||||
auto inB = juce::Rectangle<float>(meterArea.getX(), ioY, ioW, ioH);
|
||||
auto outB = juce::Rectangle<float>(meterArea.getX() + ioW + 3.0f * s, ioY, ioW, ioH);
|
||||
float ioBarW = (meterArea.getWidth() - meterLegW - 3.0f * s) / 2.0f;
|
||||
auto inBar = juce::Rectangle<float>(meterArea.getX(), ioY, ioBarW, 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, outB, processorRef.getOutputLevel(), -60.0f, 0.0f, false);
|
||||
drawPeakHold(g, inB, inPeak.peak);
|
||||
drawPeakHold(g, outB, outPeak.peak);
|
||||
g.drawText("IN", inB.translated(0, ioH + 2.0f * s), juce::Justification::centredTop);
|
||||
g.drawText("OUT", outB.translated(0, ioH + 2.0f * s), juce::Justification::centredTop);
|
||||
drawMeter(g, inBar, processorRef.getInputLevel(), -60.0f, 0.0f, false);
|
||||
drawMeter(g, outBar, processorRef.getOutputLevel(), -60.0f, 0.0f, false);
|
||||
drawPeakHold(g, inBar, inPeak.peak);
|
||||
drawPeakHold(g, outBar, outPeak.peak);
|
||||
g.setColour(textDim);
|
||||
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
|
||||
drawScope(g, scopeArea);
|
||||
|
|
@ -395,6 +476,9 @@ void SaftpumpeEditor::paint(juce::Graphics& g) {
|
|||
g.setColour(juce::Colour(0xff333338));
|
||||
g.drawRoundedRectangle(zoomArea.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,
|
||||
|
|
@ -562,6 +646,11 @@ void SaftpumpeEditor::drawPeakHold(juce::Graphics& g, juce::Rectangle<float> bou
|
|||
}
|
||||
|
||||
void SaftpumpeEditor::drawScope(juce::Graphics& g, juce::Rectangle<float> bounds) {
|
||||
if (processorRef.isSpectrumMode()) {
|
||||
drawSpectrum(g, bounds);
|
||||
return;
|
||||
}
|
||||
|
||||
float s = uiScale;
|
||||
g.setColour(grooveDark);
|
||||
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& outData = processorRef.outputScope;
|
||||
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;
|
||||
|
||||
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.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;
|
||||
g.setFont(juce::Font(juce::FontOptions(9.0f * s)));
|
||||
g.setColour(redLed.withAlpha(0.8f));
|
||||
g.drawText("IN", legendX, legendY, 20.0f * s, 10.0f * s, juce::Justification::centredLeft);
|
||||
g.setColour(greenLed.withAlpha(0.65f));
|
||||
g.drawText("OUT", legendX + 24.0f * s, legendY, 26.0f * s, 10.0f * s, juce::Justification::centredLeft);
|
||||
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("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) {
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ public:
|
|||
void resized() override;
|
||||
void timerCallback() override;
|
||||
void comboBoxChanged(juce::ComboBox* box) override;
|
||||
void mouseUp(const juce::MouseEvent& event) override;
|
||||
|
||||
private:
|
||||
SaftpumpeProcessor& processorRef;
|
||||
|
||||
float uiScale = 1.5f;
|
||||
float uiScale = 1.0f;
|
||||
|
||||
// Dark industrial palette
|
||||
juce::Colour bezelColour{0xff0a0a0c};
|
||||
|
|
@ -51,11 +52,16 @@ private:
|
|||
juce::ComboBox presetBox;
|
||||
juce::Label presetLabel;
|
||||
|
||||
juce::ToggleButton bypassButton;
|
||||
juce::Label bypassLabel;
|
||||
|
||||
using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;
|
||||
using ButtonAttachment = juce::AudioProcessorValueTreeState::ButtonAttachment;
|
||||
|
||||
std::unique_ptr<SliderAttachment> thresholdAttachment, ratioAttachment, attackAttachment;
|
||||
std::unique_ptr<SliderAttachment> releaseAttachment, kneeAttachment, makeupAttachment;
|
||||
std::unique_ptr<SliderAttachment> mixAttachment, autoReleaseAttachment;
|
||||
std::unique_ptr<ButtonAttachment> bypassAttachment;
|
||||
|
||||
juce::Rectangle<float> meterArea;
|
||||
juce::Rectangle<float> scopeArea;
|
||||
|
|
@ -80,6 +86,7 @@ private:
|
|||
void drawPeakHold(juce::Graphics& g, juce::Rectangle<float> bounds,
|
||||
float peakNorm, bool fromTop = false);
|
||||
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);
|
||||
|
||||
static constexpr float startAngle = juce::MathConstants<float>::pi * 0.75f;
|
||||
|
|
@ -95,11 +102,58 @@ private:
|
|||
void applyPreset(int index);
|
||||
|
||||
juce::Rectangle<float> presetArea;
|
||||
juce::Rectangle<float> bypassArea;
|
||||
|
||||
struct NoDotLookAndFeel : juce::LookAndFeel_V4 {
|
||||
void drawRotarySlider(juce::Graphics&, int, int, int, int,
|
||||
float, float, float, juce::Slider&) override {}
|
||||
} 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)
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue