mirror of
https://codeberg.org/armin/ambivalence.git
synced 2026-09-01 12:20:48 +02:00
UI changes, layout redefinition
This commit is contained in:
parent
42cf8432bf
commit
1d9ba772fe
12 changed files with 536 additions and 528 deletions
|
|
@ -32,9 +32,7 @@ void AmbivalenceLookAndFeel::drawRotarySlider(juce::Graphics& g,
|
|||
float angle = startAngle + sliderPos * (endAngle - startAngle);
|
||||
juce::Path fill;
|
||||
fill.addCentredArc(cx, cy, r, r, 0.f, startAngle, angle, true);
|
||||
juce::ColourGradient grad(AmbivalenceColors::AccentBlue, cx - r, cy,
|
||||
AmbivalenceColors::Accent, cx + r, cy, false);
|
||||
g.setGradientFill(grad);
|
||||
g.setColour(AmbivalenceColors::ArcFill);
|
||||
g.strokePath(fill, juce::PathStrokeType(th,
|
||||
juce::PathStrokeType::curved, juce::PathStrokeType::rounded));
|
||||
|
||||
|
|
@ -83,8 +81,8 @@ void AmbivalenceLookAndFeel::positionComboBoxText(juce::ComboBox& box, juce::Lab
|
|||
label.setFont(getComboBoxFont(box));
|
||||
}
|
||||
|
||||
juce::Font AmbivalenceLookAndFeel::getLabelFont(juce::Label&) { return mainFont.withHeight(10.f); }
|
||||
juce::Font AmbivalenceLookAndFeel::getComboBoxFont(juce::ComboBox&) { return mainFont.withHeight(11.f); }
|
||||
juce::Font AmbivalenceLookAndFeel::getLabelFont(juce::Label& label) { return label.getFont(); }
|
||||
juce::Font AmbivalenceLookAndFeel::getComboBoxFont(juce::ComboBox&) { return mainFont.withHeight(11.f * zoomScale); }
|
||||
|
||||
void AmbivalenceLookAndFeel::drawGroupComponentOutline(juce::Graphics& g,
|
||||
int w, int h, const juce::String& text,
|
||||
|
|
@ -111,130 +109,6 @@ void AmbivalenceLookAndFeel::drawGroupComponentOutline(juce::Graphics& g,
|
|||
juce::Justification::centredLeft);
|
||||
}
|
||||
|
||||
// --- RT60Visualizer --------------------------------------------------
|
||||
RT60Visualizer::RT60Visualizer() {
|
||||
displayRT60.fill(1.0f);
|
||||
startTimerHz(30);
|
||||
}
|
||||
RT60Visualizer::~RT60Visualizer() { stopTimer(); }
|
||||
|
||||
void RT60Visualizer::timerCallback() {
|
||||
if (!processor) return;
|
||||
|
||||
auto live = processor->getRT60ForDisplay();
|
||||
for (int i = 0; i < FDNReverb::NUM_BANDS; ++i)
|
||||
displayRT60[i] += 0.25f * (live[i] - displayRT60[i]);
|
||||
|
||||
// * dynamic Y axis above : current maximum RT60 value x 1.3 smoothly
|
||||
float maxVal = *std::max_element(displayRT60.begin(), displayRT60.end());
|
||||
float targetMax = std::max(MAX_RT60_DISPLAY_FLOOR, maxVal * 1.3f);
|
||||
// exponential ( rise quickly , fall gentle -> frequently )
|
||||
float smoothFactor = (targetMax > dynamicMaxRT60) ? 0.15f : 0.03f;
|
||||
dynamicMaxRT60 += smoothFactor * (targetMax - dynamicMaxRT60);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void RT60Visualizer::paint(juce::Graphics& g)
|
||||
{
|
||||
auto b = getLocalBounds().toFloat().reduced(2.f);
|
||||
float W = b.getWidth(), H = b.getHeight();
|
||||
float x0 = b.getX(), y0 = b.getY();
|
||||
|
||||
g.setColour(AmbivalenceColors::Surface);
|
||||
g.fillRoundedRectangle(b, 4.f);
|
||||
g.setColour(AmbivalenceColors::Border);
|
||||
g.drawRoundedRectangle(b.reduced(0.5f), 4.f, 1.f);
|
||||
|
||||
// * dynamic Y axis
|
||||
float logMin = std::log10(MIN_RT60_DISPLAY);
|
||||
float logMax = std::log10(dynamicMaxRT60);
|
||||
|
||||
// grid value dynamic Y axis
|
||||
// fixed value dynamicMaxRT60 below drawing
|
||||
static constexpr float kAllGridVals[] = {
|
||||
0.1f, 0.3f, 0.5f, 1.0f, 2.0f, 4.0f,
|
||||
8.0f, 12.0f, 16.0f, 20.0f
|
||||
};
|
||||
|
||||
// grid
|
||||
g.setColour(AmbivalenceColors::Separator);
|
||||
for (float v : kAllGridVals) {
|
||||
if (v > dynamicMaxRT60 * 1.05f) break;
|
||||
float ny = 1.f - (std::log10(v) - logMin) / (logMax - logMin);
|
||||
g.drawHorizontalLine((int)(y0 + ny * H), x0 + 36.f, x0 + W - 4.f);
|
||||
}
|
||||
|
||||
// frequency label (X axis )
|
||||
g.setFont(8.5f);
|
||||
g.setColour(AmbivalenceColors::TextSecondary);
|
||||
static const char* fLbls[] = {
|
||||
"31","63","125","250","500","1k","2k","4k","8k","16k"
|
||||
};
|
||||
for (int i = 0; i < FDNReverb::NUM_BANDS; ++i) {
|
||||
float px = x0 + 36.f + (float)i / (FDNReverb::NUM_BANDS - 1) * (W - 40.f);
|
||||
g.drawText(fLbls[i], (int)(px - 12.f), (int)(y0 + H - 14.f),
|
||||
24, 13, juce::Justification::centred);
|
||||
}
|
||||
|
||||
// seconds label (Y axis ) - dynamic
|
||||
for (float v : kAllGridVals) {
|
||||
if (v > dynamicMaxRT60 * 1.05f) break;
|
||||
float ny = 1.f - (std::log10(v) - logMin) / (logMax - logMin);
|
||||
float py = y0 + ny * H;
|
||||
juce::String lbl = (v < 1.f)
|
||||
? juce::String(v, 1) + "s"
|
||||
: (v < 10.f ? juce::String(v, 1) : juce::String((int)v)) + "s";
|
||||
g.drawText(lbl, (int)(x0 + 2.f), (int)(py - 7.f), 32, 14,
|
||||
juce::Justification::centredLeft);
|
||||
}
|
||||
|
||||
auto plotCurve = [&](const std::array<float, FDNReverb::NUM_BANDS>& rt60,
|
||||
juce::Colour col, float thick)
|
||||
{
|
||||
juce::Path path;
|
||||
bool first = true;
|
||||
for (int i = 0; i < FDNReverb::NUM_BANDS; ++i) {
|
||||
float v = std::clamp(rt60[i], MIN_RT60_DISPLAY, dynamicMaxRT60);
|
||||
float ny = 1.f - (std::log10(v) - logMin) / (logMax - logMin);
|
||||
float px = x0 + 36.f + (float)i / (FDNReverb::NUM_BANDS - 1) * (W - 40.f);
|
||||
float py = y0 + ny * H;
|
||||
if (first) { path.startNewSubPath(px, py); first = false; }
|
||||
else path.lineTo(px, py);
|
||||
}
|
||||
g.setColour(col);
|
||||
g.strokePath(path, juce::PathStrokeType(thick,
|
||||
juce::PathStrokeType::curved, juce::PathStrokeType::rounded));
|
||||
|
||||
for (int i = 0; i < FDNReverb::NUM_BANDS; ++i) {
|
||||
float v = std::clamp(rt60[i], MIN_RT60_DISPLAY, dynamicMaxRT60);
|
||||
float ny = 1.f - (std::log10(v) - logMin) / (logMax - logMin);
|
||||
float px = x0 + 36.f + (float)i / (FDNReverb::NUM_BANDS - 1) * (W - 40.f);
|
||||
float py = y0 + ny * H;
|
||||
g.fillEllipse(px - 3.f, py - 3.f, 6.f, 6.f);
|
||||
}
|
||||
};
|
||||
|
||||
// source preset curve (from the selected algorithm)
|
||||
if (processor) {
|
||||
int algo = (int)*processor->apvts.getRawParameterValue("algorithm");
|
||||
auto& preset = *FDNReverb::ALL_PRESETS[
|
||||
juce::jlimit(0, FDNReverb::NUM_ALGORITHMS - 1, algo)];
|
||||
plotCurve(preset.acoustics.rt60,
|
||||
AmbivalenceColors::TextSecondary.withAlpha(0.5f), 1.f);
|
||||
}
|
||||
|
||||
// current RT60 curve (measured)
|
||||
plotCurve(displayRT60, AmbivalenceColors::Accent, 2.f);
|
||||
|
||||
// top right title
|
||||
g.setColour(AmbivalenceColors::TextSecondary);
|
||||
g.setFont(9.f);
|
||||
g.drawText("RT60 (s) per band",
|
||||
(int)x0 + 36, (int)y0 + 3, (int)W - 40, 12,
|
||||
juce::Justification::right);
|
||||
}
|
||||
|
||||
// --- VUMeter ---------------------------------------------------------
|
||||
VUMeter::VUMeter(const juce::String& lbl, Side s) : label(lbl), side(s) {}
|
||||
|
||||
|
|
@ -244,24 +118,33 @@ void VUMeter::paint(juce::Graphics& g)
|
|||
g.setColour(AmbivalenceColors::Surface);
|
||||
g.fillRoundedRectangle(b, 3.f);
|
||||
|
||||
float bx = b.getX() + 22.f, bw = b.getWidth() - 22.f;
|
||||
const float inset = 8.f;
|
||||
const float labelW = 20.f;
|
||||
const float bx = b.getX() + inset + labelW + 2.f;
|
||||
const float bw = b.getWidth() - (bx - b.getX()) - inset;
|
||||
|
||||
const float barH = 7.f;
|
||||
const float gap = 2.f;
|
||||
const float blockH = barH * 2.f + gap;
|
||||
const float startY = b.getY() + (b.getHeight() - blockH) * 0.5f;
|
||||
|
||||
auto bar = [&](float y, float level) {
|
||||
float n = juce::jlimit(0.f, 1.f, juce::jmap(
|
||||
juce::Decibels::gainToDecibels(level + 1e-9f), -60.f, 0.f, 0.f, 1.f));
|
||||
g.setColour(AmbivalenceColors::ArcTrack);
|
||||
g.fillRoundedRectangle(bx, y, bw, 7.f, 2.f);
|
||||
g.fillRoundedRectangle(bx, y, bw, barH, 2.f);
|
||||
juce::ColourGradient gr(AmbivalenceColors::AccentBlue, bx, y,
|
||||
AmbivalenceColors::Accent, bx + bw, y, false);
|
||||
g.setGradientFill(gr);
|
||||
g.fillRoundedRectangle(bx, y, bw * n, 7.f, 2.f);
|
||||
g.fillRoundedRectangle(bx, y, bw * n, barH, 2.f);
|
||||
};
|
||||
bar(b.getY() + 2.f, levelL);
|
||||
bar(b.getY() + 11.f, levelR);
|
||||
bar(startY, levelL);
|
||||
bar(startY + barH + gap, levelR);
|
||||
|
||||
g.setColour(AmbivalenceColors::TextSecondary);
|
||||
g.setFont(8.f);
|
||||
g.drawText(label, (int)b.getX(), (int)b.getY(), 20, (int)b.getHeight(),
|
||||
juce::Justification::centredLeft);
|
||||
g.setFont(8.f * zoomScale);
|
||||
g.drawText(label, (int)(b.getX() + inset), (int)b.getY(), (int)labelW,
|
||||
(int)b.getHeight(), juce::Justification::centredLeft);
|
||||
}
|
||||
|
||||
// --- ArcKnob ---------------------------------------------------------
|
||||
|
|
@ -290,65 +173,4 @@ void ArcKnob::build(juce::AudioProcessorValueTreeState& apvts,
|
|||
attachment.reset(
|
||||
new juce::AudioProcessorValueTreeState::SliderAttachment(
|
||||
apvts, paramID, slider));
|
||||
}
|
||||
|
||||
// --- AlgorithmSelector -----------------------------------------------
|
||||
AlgorithmSelector::AlgorithmSelector(juce::AudioProcessorValueTreeState& a)
|
||||
: apvts(a)
|
||||
{
|
||||
static const char* names[] = {
|
||||
"ROOM1","ROOM2","HALL1","HALL2","PLATE","SPRING","GOLDFOIL"
|
||||
};
|
||||
for (int i = 0; i < FDNReverb::NUM_ALGORITHMS; ++i) {
|
||||
buttons[i].setButtonText(names[i]);
|
||||
addAndMakeVisible(buttons[i]);
|
||||
int idx = i;
|
||||
buttons[i].onClick = [this, idx] {
|
||||
if (auto* param = apvts.getParameter("algorithm"))
|
||||
param->setValueNotifyingHost(
|
||||
(float)idx / (float)(FDNReverb::NUM_ALGORITHMS - 1));
|
||||
};
|
||||
}
|
||||
apvts.addParameterListener("algorithm", this);
|
||||
currentAlgo = juce::roundToInt(
|
||||
*apvts.getRawParameterValue("algorithm")
|
||||
* (FDNReverb::NUM_ALGORITHMS - 1));
|
||||
}
|
||||
|
||||
AlgorithmSelector::~AlgorithmSelector() {
|
||||
apvts.removeParameterListener("algorithm", this);
|
||||
}
|
||||
|
||||
void AlgorithmSelector::parameterChanged(const juce::String&, float newVal) {
|
||||
int newAlgo = juce::jlimit(0, FDNReverb::NUM_ALGORITHMS - 1,
|
||||
juce::roundToInt(newVal));
|
||||
juce::MessageManager::callAsync([this, newAlgo] {
|
||||
currentAlgo = newAlgo;
|
||||
updateButtonColors();
|
||||
});
|
||||
}
|
||||
|
||||
void AlgorithmSelector::updateButtonColors() {
|
||||
for (int i = 0; i < FDNReverb::NUM_ALGORITHMS; ++i) {
|
||||
bool on = (i == currentAlgo);
|
||||
buttons[i].setColour(juce::TextButton::buttonColourId,
|
||||
on ? AmbivalenceColors::Accent : AmbivalenceColors::Surface);
|
||||
buttons[i].setColour(juce::TextButton::textColourOffId,
|
||||
on ? AmbivalenceColors::Background : AmbivalenceColors::TextSecondary);
|
||||
buttons[i].repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void AlgorithmSelector::paint(juce::Graphics& g) {
|
||||
g.setColour(AmbivalenceColors::Surface);
|
||||
g.fillRoundedRectangle(getLocalBounds().toFloat(), 4.f);
|
||||
}
|
||||
|
||||
void AlgorithmSelector::resized() {
|
||||
auto area = getLocalBounds().reduced(2);
|
||||
int btnW = area.getWidth() / FDNReverb::NUM_ALGORITHMS;
|
||||
for (int i = 0; i < FDNReverb::NUM_ALGORITHMS; ++i)
|
||||
buttons[i].setBounds(area.getX() + i * btnW, area.getY(),
|
||||
btnW - 1, area.getHeight());
|
||||
updateButtonColors();
|
||||
}
|
||||
|
|
@ -1,22 +1,21 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../AlgorithmPresets.h"
|
||||
|
||||
class FDNReverbAudioProcessor;
|
||||
|
||||
// --- Ambivalence Design System ------------------------------------------
|
||||
namespace AmbivalenceColors {
|
||||
const juce::Colour Background{ 0xFF1A1A1A };
|
||||
const juce::Colour Surface{ 0xFF242424 };
|
||||
const juce::Colour Panel{ 0xFF2C2C2C };
|
||||
const juce::Colour Border{ 0xFF3C3C3C };
|
||||
const juce::Colour Accent{ 0xFFFF6B00 };
|
||||
const juce::Colour AccentBlue{ 0xFF4090FF };
|
||||
const juce::Colour TextPrimary{ 0xFFE8E8E8 };
|
||||
const juce::Colour TextSecondary{ 0xFF888888 };
|
||||
const juce::Colour ArcTrack{ 0xFF3A3A3A };
|
||||
const juce::Colour ArcFill{ 0xFFFF6B00 };
|
||||
const juce::Colour Separator{ 0xFF383838 };
|
||||
const juce::Colour Background{ 0xFF181819 }; // palette 0
|
||||
const juce::Colour Surface{ 0xFF2C2E34 }; // theme background
|
||||
const juce::Colour Panel{ 0xFF2C2E34 }; // theme background
|
||||
const juce::Colour Border{ 0xFF3B3E48 }; // selection background
|
||||
const juce::Colour Accent{ 0xFFF39660 }; // palette 6/14
|
||||
const juce::Colour AccentBlue{ 0xFF76CCE0 }; // palette 4/12
|
||||
const juce::Colour TextPrimary{ 0xFFE2E2E3 }; // foreground
|
||||
const juce::Colour TextSecondary{ 0xFF7F8490 }; // palette 8
|
||||
const juce::Colour ArcTrack{ 0xFF3B3E48 }; // selection background
|
||||
const juce::Colour ArcFill{ 0xFF76CCE0 }; // plain blue arc
|
||||
const juce::Colour Separator{ 0xFF2C2E34 }; // theme background
|
||||
}
|
||||
|
||||
// --- Ambivalence LookAndFeel --------------------------------------------
|
||||
|
|
@ -38,32 +37,12 @@ public:
|
|||
void drawGroupComponentOutline(juce::Graphics&, int w, int h,
|
||||
const juce::String&, const juce::Justification&,
|
||||
juce::GroupComponent&) override;
|
||||
|
||||
float zoomScale{ 1.0f }; // pushed by the editor so fonts scale with zoom
|
||||
private:
|
||||
juce::Font mainFont;
|
||||
};
|
||||
|
||||
// --- RT60 Visualizer -------------------------------------------------
|
||||
class RT60Visualizer : public juce::Component, private juce::Timer
|
||||
{
|
||||
public:
|
||||
RT60Visualizer();
|
||||
~RT60Visualizer() override;
|
||||
void setProcessor(FDNReverbAudioProcessor* p) { processor = p; }
|
||||
void paint(juce::Graphics&) override;
|
||||
private:
|
||||
void timerCallback() override;
|
||||
FDNReverbAudioProcessor* processor{ nullptr };
|
||||
std::array<float, FDNReverb::NUM_BANDS> displayRT60;
|
||||
|
||||
static constexpr float MIN_RT60_DISPLAY = 0.05f;
|
||||
static constexpr float MAX_RT60_DISPLAY_FLOOR = 4.0f; // Y axis above value
|
||||
|
||||
// * dynamic Y axis above : effectiveRT60 maximum value smoothly
|
||||
float dynamicMaxRT60{ MAX_RT60_DISPLAY_FLOOR };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RT60Visualizer)
|
||||
};
|
||||
|
||||
// --- VU Meter --------------------------------------------------------
|
||||
class VUMeter : public juce::Component
|
||||
{
|
||||
|
|
@ -72,10 +51,12 @@ public:
|
|||
VUMeter(const juce::String& label, Side side);
|
||||
void paint(juce::Graphics&) override;
|
||||
void setLevels(float l, float r) noexcept { levelL = l; levelR = r; }
|
||||
void setZoomScale(float s) noexcept { zoomScale = s; repaint(); }
|
||||
private:
|
||||
juce::String label;
|
||||
Side side;
|
||||
float levelL{ 0.f }, levelR{ 0.f };
|
||||
float zoomScale{ 1.0f };
|
||||
};
|
||||
|
||||
// --- Labelled Arc Knob -----------------------------------------------
|
||||
|
|
@ -89,22 +70,4 @@ struct ArcKnob {
|
|||
const juce::String& labelText,
|
||||
juce::Component* parent,
|
||||
AmbivalenceLookAndFeel& laf);
|
||||
};
|
||||
|
||||
// --- Algorithm Selector ----------------------------------------------
|
||||
class AlgorithmSelector : public juce::Component,
|
||||
private juce::AudioProcessorValueTreeState::Listener
|
||||
{
|
||||
public:
|
||||
AlgorithmSelector(juce::AudioProcessorValueTreeState& apvts);
|
||||
~AlgorithmSelector() override;
|
||||
void paint(juce::Graphics&) override;
|
||||
void resized() override;
|
||||
private:
|
||||
void parameterChanged(const juce::String&, float) override;
|
||||
void updateButtonColors();
|
||||
std::array<juce::TextButton, FDNReverb::NUM_ALGORITHMS> buttons;
|
||||
juce::AudioProcessorValueTreeState& apvts;
|
||||
int currentAlgo{ 0 };
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AlgorithmSelector)
|
||||
};
|
||||
|
|
@ -84,7 +84,7 @@ void DecayCurveViz::paint(juce::Graphics& g)
|
|||
// --- ER zone background ---
|
||||
{
|
||||
const float erZoneW = plotW * splitRatio;
|
||||
g.setColour(juce::Colour(0xFF1A2535));
|
||||
g.setColour(AmbivalenceColors::Surface);
|
||||
g.fillRect(plotX, plotY, erZoneW, plotH);
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ void DecayCurveViz::paint(juce::Graphics& g)
|
|||
}
|
||||
|
||||
// --- axis label ---
|
||||
g.setFont(juce::Font(juce::FontOptions(8.5f)));
|
||||
g.setFont(juce::Font(juce::FontOptions(8.5f * zoomScale)));
|
||||
g.setColour(AmbivalenceColors::TextSecondary.withAlpha(0.6f));
|
||||
|
||||
for (float db = 0.0f; db >= -60.0f; db -= 20.0f) {
|
||||
|
|
@ -197,7 +197,7 @@ void DecayCurveViz::paint(juce::Graphics& g)
|
|||
// : wide 5px + marker + envelope fill
|
||||
// -------------------------------------------------------------------------
|
||||
if (!cachedERBypassed && cachedERTapCount > 0) {
|
||||
const juce::Colour blueColor = juce::Colour::fromRGB(80, 160, 230);
|
||||
const juce::Colour blueColor = AmbivalenceColors::AccentBlue;
|
||||
|
||||
// -- ER envelope fill region --
|
||||
if (cachedERTapCount >= 2) {
|
||||
|
|
@ -300,9 +300,9 @@ void DecayCurveViz::paint(juce::Graphics& g)
|
|||
|
||||
// --- zone label ---
|
||||
g.setFont(juce::Font(juce::FontOptions(
|
||||
"Helvetica Neue", 8.0f, juce::Font::bold)));
|
||||
"Helvetica Neue", 8.0f * zoomScale, juce::Font::bold)));
|
||||
|
||||
g.setColour(juce::Colour::fromRGB(80, 160, 230).withAlpha(0.9f));
|
||||
g.setColour(AmbivalenceColors::AccentBlue.withAlpha(0.9f));
|
||||
g.drawText("ER",
|
||||
(int)(plotX + 4), (int)(plotY + 2),
|
||||
30, 12, juce::Justification::centredLeft);
|
||||
|
|
@ -315,7 +315,7 @@ void DecayCurveViz::paint(juce::Graphics& g)
|
|||
40, 12, juce::Justification::centredLeft);
|
||||
}
|
||||
|
||||
g.setFont(juce::Font(juce::FontOptions(7.5f)));
|
||||
g.setFont(juce::Font(juce::FontOptions(7.5f * zoomScale)));
|
||||
g.setColour(AmbivalenceColors::TextSecondary.withAlpha(0.4f));
|
||||
g.drawText("0-200ms (x2)",
|
||||
(int)(plotX + 2), (int)(plotY + plotH - 14),
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ public:
|
|||
~DecayCurveViz() override;
|
||||
|
||||
void setProcessor(FDNReverbAudioProcessor* p) noexcept { processor = p; }
|
||||
void setZoomScale(float s) noexcept { zoomScale = s; repaint(); }
|
||||
void paint(juce::Graphics& g) override;
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
void timerCallback() override;
|
||||
float zoomScale{ 1.0f };
|
||||
|
||||
// --- time axis -----------------------------------------------
|
||||
// time axis :
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@
|
|||
#include "BuildInfo.h"
|
||||
|
||||
static constexpr int Y_HEADER = 8;
|
||||
static constexpr int Y_ALGO = 48;
|
||||
static constexpr int Y_SLABEL1 = 86;
|
||||
static constexpr int Y_ROW1 = 104;
|
||||
static constexpr int Y_SLABEL2 = 204;
|
||||
static constexpr int Y_ROW2 = 222;
|
||||
static constexpr int Y_SEP = 322;
|
||||
static constexpr int Y_VIZ = 326;
|
||||
static constexpr int Y_SLABEL1 = 46;
|
||||
static constexpr int Y_ROW1 = 64;
|
||||
static constexpr int Y_SLABEL2 = 164;
|
||||
static constexpr int Y_ROW2 = 182;
|
||||
static constexpr int Y_SEP = 282;
|
||||
static constexpr int Y_VIZ = 286;
|
||||
|
||||
static constexpr int SEC_TIME = 8;
|
||||
static constexpr int SEC_FREQUENCY = 254;
|
||||
|
|
@ -21,28 +20,26 @@ static constexpr int SEP_FD = 409;
|
|||
static constexpr int SEP_DS = 655;
|
||||
static constexpr int SEP_SC = 737;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// EditorContent
|
||||
// -----------------------------------------------------------------------------
|
||||
// constructor
|
||||
// -----------------------------------------------------------------------------
|
||||
FDNReverbEditor::FDNReverbEditor(FDNReverbAudioProcessor& p)
|
||||
: AudioProcessorEditor(&p),
|
||||
audioProcessor(p),
|
||||
algoSelector(p.apvts),
|
||||
vuIn("IN", VUMeter::Side::Input),
|
||||
EditorContent::EditorContent(FDNReverbAudioProcessor& p, AmbivalenceLookAndFeel& laf)
|
||||
: vuIn("IN", VUMeter::Side::Input),
|
||||
vuOut("OUT", VUMeter::Side::Output)
|
||||
{
|
||||
setLookAndFeel(&laf);
|
||||
setSize(W, H);
|
||||
setOpaque(true);
|
||||
lookAndFeel = &laf;
|
||||
|
||||
// -- Title --
|
||||
titleLabel.setText("AMBIVALENCE 1.1", juce::dontSendNotification);
|
||||
titleLabel.setText("AMBIVALENCE", juce::dontSendNotification);
|
||||
titleLabel.setFont(juce::Font(juce::FontOptions(
|
||||
"Helvetica Neue", 14.f, juce::Font::bold)));
|
||||
"Helvetica Neue", 24.f, juce::Font::bold)));
|
||||
titleLabel.setColour(juce::Label::textColourId, AmbivalenceColors::TextPrimary);
|
||||
titleLabel.setJustificationType(juce::Justification::centredLeft);
|
||||
addAndMakeVisible(titleLabel);
|
||||
|
||||
addAndMakeVisible(algoSelector);
|
||||
|
||||
auto BK = [&](ArcKnob& k, const char* id, const char* lbl) {
|
||||
k.build(p.apvts, id, lbl, this, laf);
|
||||
};
|
||||
|
|
@ -68,7 +65,7 @@ FDNReverbEditor::FDNReverbEditor(FDNReverbAudioProcessor& p)
|
|||
BK(kHiCutNorm, "hicut", "HI CUT");
|
||||
|
||||
// -- ProMode button --
|
||||
proModeButton.setButtonText("PRO");
|
||||
proModeButton.setButtonText("EQ");
|
||||
proModeButton.setClickingTogglesState(true);
|
||||
proModeButton.setColour(juce::TextButton::buttonOnColourId, AmbivalenceColors::Accent);
|
||||
proModeButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
|
|
@ -79,8 +76,16 @@ FDNReverbEditor::FDNReverbEditor(FDNReverbAudioProcessor& p)
|
|||
new juce::AudioProcessorValueTreeState::ButtonAttachment(
|
||||
p.apvts, "promode", proModeButton));
|
||||
|
||||
// -- Zoom menu button --
|
||||
zoomButton.setButtonText(juce::String(
|
||||
juce::roundToInt(EditorContent::kDefaultZoom * 100.f)) + "%");
|
||||
zoomButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
zoomButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::TextPrimary);
|
||||
zoomButton.setTooltip("UI zoom");
|
||||
addAndMakeVisible(zoomButton);
|
||||
|
||||
// -- ER SOLO button --
|
||||
erSoloButton.setButtonText("ER SOLO");
|
||||
erSoloButton.setButtonText("ER ONLY");
|
||||
erSoloButton.setClickingTogglesState(true);
|
||||
erSoloButton.setColour(juce::TextButton::buttonOnColourId, AmbivalenceColors::AccentBlue);
|
||||
erSoloButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
|
|
@ -124,82 +129,8 @@ FDNReverbEditor::FDNReverbEditor(FDNReverbAudioProcessor& p)
|
|||
BK(kLoCutPro, "locut", "LO CUT");
|
||||
BK(kHiCutPro, "hicut", "HI CUT");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// preset UI
|
||||
// -------------------------------------------------------------------------
|
||||
presetManager = std::make_unique<PresetManager>(p);
|
||||
|
||||
// < PREV
|
||||
presetPrevButton.setButtonText("<");
|
||||
presetPrevButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
presetPrevButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::TextPrimary);
|
||||
addAndMakeVisible(presetPrevButton);
|
||||
presetPrevButton.onClick = [this] {
|
||||
presetManager->loadPrevPreset();
|
||||
};
|
||||
|
||||
// preset name combo
|
||||
presetCombo.setLookAndFeel(&laf);
|
||||
addAndMakeVisible(presetCombo);
|
||||
presetCombo.onChange = [this] {
|
||||
int idx = presetCombo.getSelectedItemIndex();
|
||||
auto names = presetManager->getPresetNames();
|
||||
if (idx >= 0 && idx < names.size())
|
||||
presetManager->loadPreset(names[idx]);
|
||||
};
|
||||
|
||||
// > NEXT
|
||||
presetNextButton.setButtonText(">");
|
||||
presetNextButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
presetNextButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::TextPrimary);
|
||||
addAndMakeVisible(presetNextButton);
|
||||
presetNextButton.onClick = [this] {
|
||||
presetManager->loadNextPreset();
|
||||
};
|
||||
|
||||
// SAVE
|
||||
presetSaveButton.setButtonText("SAVE");
|
||||
presetSaveButton.setColour(juce::TextButton::buttonColourId,
|
||||
AmbivalenceColors::Accent.withAlpha(0.75f));
|
||||
presetSaveButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::Background);
|
||||
addAndMakeVisible(presetSaveButton);
|
||||
presetSaveButton.onClick = [this] { savePresetWithDialog(); };
|
||||
|
||||
// --- after changes ---
|
||||
// LOAD
|
||||
presetLoadButton.setButtonText("LOAD");
|
||||
presetLoadButton.setColour(juce::TextButton::buttonColourId,
|
||||
AmbivalenceColors::AccentBlue.withAlpha(0.75f));
|
||||
presetLoadButton.setColour(juce::TextButton::textColourOffId,
|
||||
AmbivalenceColors::Background);
|
||||
addAndMakeVisible(presetLoadButton);
|
||||
presetLoadButton.onClick = [this] {
|
||||
auto names = presetManager->getPresetNames();
|
||||
int idx = presetCombo.getSelectedItemIndex();
|
||||
if (idx >= 0 && idx < names.size())
|
||||
presetManager->loadPreset(names[idx]);
|
||||
};
|
||||
|
||||
// DELETE
|
||||
presetDeleteButton.setButtonText("DELETE");
|
||||
presetDeleteButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
presetDeleteButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::TextSecondary);
|
||||
addAndMakeVisible(presetDeleteButton);
|
||||
presetDeleteButton.onClick = [this] { deleteCurrentPreset(); };
|
||||
|
||||
// callback setup
|
||||
// in the constructor callback setup
|
||||
presetManager->onPresetListChanged = [this] { refreshPresetCombo(); };
|
||||
|
||||
// * fix : whenever the preset name changes Processor notify
|
||||
presetManager->onPresetLoaded = [this](const juce::String& name) {
|
||||
audioProcessor.setLastSavedPresetName(name);
|
||||
refreshPresetCombo();
|
||||
};
|
||||
// -- Visualizers --
|
||||
rt60Viz.setProcessor(&p);
|
||||
decayCurveViz.setProcessor(&p);
|
||||
addAndMakeVisible(rt60Viz);
|
||||
addAndMakeVisible(decayCurveViz);
|
||||
|
||||
addAndMakeVisible(vuIn);
|
||||
|
|
@ -254,75 +185,32 @@ FDNReverbEditor::FDNReverbEditor(FDNReverbAudioProcessor& p)
|
|||
"Helvetica Neue", 8.5f, juce::Font::plain)));
|
||||
statusLabel.setColour(juce::Label::textColourId,
|
||||
AmbivalenceColors::TextSecondary.withAlpha(0.6f));
|
||||
statusLabel.setJustificationType(juce::Justification::centred);
|
||||
statusLabel.setJustificationType(juce::Justification::centredRight);
|
||||
addAndMakeVisible(statusLabel);
|
||||
|
||||
// --- after changes ---
|
||||
refreshPresetCombo(); // added: initialize the combo at startup
|
||||
// -- Preset UI (wiring is done by the editor) --
|
||||
addAndMakeVisible(presetPrevButton);
|
||||
addAndMakeVisible(presetCombo);
|
||||
addAndMakeVisible(presetNextButton);
|
||||
addAndMakeVisible(presetSaveButton);
|
||||
addAndMakeVisible(presetLoadButton);
|
||||
addAndMakeVisible(presetDeleteButton);
|
||||
|
||||
updatePanelVisibility();
|
||||
startTimerHz(60);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// destructor
|
||||
// EditorContent::updatePanelVisibility
|
||||
// -----------------------------------------------------------------------------
|
||||
FDNReverbEditor::~FDNReverbEditor() {
|
||||
stopTimer();
|
||||
setLookAndFeel(nullptr);
|
||||
satTypeCombo.setLookAndFeel(nullptr);
|
||||
presetCombo.setLookAndFeel(nullptr);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// timerCallback
|
||||
// -----------------------------------------------------------------------------
|
||||
void FDNReverbEditor::timerCallback()
|
||||
{
|
||||
vuIn.setLevels(audioProcessor.getInputRMSL(),
|
||||
audioProcessor.getInputRMSR());
|
||||
vuOut.setLevels(audioProcessor.getOutputRMSL(),
|
||||
audioProcessor.getOutputRMSR());
|
||||
vuIn.repaint();
|
||||
vuOut.repaint();
|
||||
|
||||
static int metricsCounter = 0;
|
||||
if (++metricsCounter >= 2) {
|
||||
metricsCounter = 0;
|
||||
labelD50Value.setText(
|
||||
juce::String(audioProcessor.getD50() * 100.0f, 1) + "%",
|
||||
juce::dontSendNotification);
|
||||
labelC50Value.setText(
|
||||
juce::String(audioProcessor.getC50(), 1) + "dB",
|
||||
juce::dontSendNotification);
|
||||
labelC80Value.setText(
|
||||
juce::String(audioProcessor.getC80(), 1) + "dB",
|
||||
juce::dontSendNotification);
|
||||
labelEDTValue.setText(
|
||||
juce::String(audioProcessor.getEDT(), 2) + "s",
|
||||
juce::dontSendNotification);
|
||||
}
|
||||
|
||||
bool newProMode = (*audioProcessor.apvts.getRawParameterValue("promode") > 0.5f);
|
||||
if (newProMode != isProMode) {
|
||||
isProMode = newProMode;
|
||||
updatePanelVisibility();
|
||||
resized();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// updatePanelVisibility
|
||||
// -----------------------------------------------------------------------------
|
||||
void FDNReverbEditor::updatePanelVisibility()
|
||||
void EditorContent::updatePanelVisibility()
|
||||
{
|
||||
auto setKnob = [](ArcKnob& k, bool vis) {
|
||||
k.slider.setVisible(vis);
|
||||
k.label.setVisible(vis);
|
||||
};
|
||||
|
||||
const bool showNormal = !isProMode;
|
||||
const bool showPro = isProMode;
|
||||
const bool showNormal = !proMode;
|
||||
const bool showPro = proMode;
|
||||
|
||||
setKnob(kPreDelay, showNormal);
|
||||
setKnob(kRoomSize, showNormal);
|
||||
|
|
@ -357,37 +245,100 @@ void FDNReverbEditor::updatePanelVisibility()
|
|||
presetPrevButton.setVisible(true);
|
||||
presetCombo.setVisible(true);
|
||||
presetNextButton.setVisible(true);
|
||||
// --- after changes ---
|
||||
presetSaveButton.setVisible(true);
|
||||
presetLoadButton.setVisible(true); // * added
|
||||
presetLoadButton.setVisible(true);
|
||||
presetDeleteButton.setVisible(true);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// resized
|
||||
// EditorContent::setProMode
|
||||
// -----------------------------------------------------------------------------
|
||||
void FDNReverbEditor::resized()
|
||||
void EditorContent::setProMode(bool pro)
|
||||
{
|
||||
titleLabel.setBounds(PAD, Y_HEADER, 180, 32);
|
||||
proModeButton.setBounds(196, Y_HEADER + 5, 52, 22);
|
||||
erSoloButton.setBounds(256, Y_HEADER + 5, 72, 22);
|
||||
vuIn.setBounds(W - 220, Y_HEADER + 2, 96, 28);
|
||||
vuOut.setBounds(W - 120, Y_HEADER + 2, 96, 28);
|
||||
if (proMode == pro)
|
||||
return;
|
||||
|
||||
algoSelector.setBounds(PAD, Y_ALGO, W - PAD * 2, 30);
|
||||
proMode = pro;
|
||||
updatePanelVisibility();
|
||||
resized();
|
||||
repaint();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// EditorContent::resized
|
||||
// -----------------------------------------------------------------------------
|
||||
void EditorContent::resized()
|
||||
{
|
||||
uiScale = getWidth() > 0 ? (float)getWidth() / (float)W : 1.0f;
|
||||
|
||||
// scale fonts that are set explicitly (knob/button/combo fonts handled
|
||||
// via the LookAndFeel which already has the same uiScale pushed to it).
|
||||
auto labelFont = [this](float h, int style = juce::Font::plain) {
|
||||
return juce::Font(juce::FontOptions("Helvetica Neue", h * uiScale, style));
|
||||
};
|
||||
|
||||
titleLabel.setFont(labelFont(24.f, juce::Font::bold));
|
||||
satTypeLabel.setFont(labelFont(9.f));
|
||||
labelMetricsTitle.setFont(labelFont(8.5f, juce::Font::bold));
|
||||
labelD50Caption.setFont(labelFont(8.f));
|
||||
labelC50Caption.setFont(labelFont(8.f));
|
||||
labelC80Caption.setFont(labelFont(8.f));
|
||||
labelEDTCaption.setFont(labelFont(8.f));
|
||||
labelD50Value.setFont(labelFont(9.5f, juce::Font::bold));
|
||||
labelC50Value.setFont(labelFont(9.5f, juce::Font::bold));
|
||||
labelC80Value.setFont(labelFont(9.5f, juce::Font::bold));
|
||||
labelEDTValue.setFont(labelFont(9.5f, juce::Font::bold));
|
||||
statusLabel.setFont(labelFont(8.5f));
|
||||
|
||||
ArcKnob* knobs[] = {
|
||||
&kPreDelay, &kRoomSize, &kDecay,
|
||||
&kHFDamp, &kLFAbsorb,
|
||||
&kDiffusion, &kModAmt, &kModRate,
|
||||
&kStereoW,
|
||||
&kERLevel, &kSaturation,
|
||||
&kWet, &kDry,
|
||||
&kDuckAmt, &kDuckThr, &kDuckAtt, &kDuckRel,
|
||||
&kLoCutNorm, &kHiCutNorm,
|
||||
&kRTBands[0], &kRTBands[1], &kRTBands[2], &kRTBands[3], &kRTBands[4],
|
||||
&kRTBands[5], &kRTBands[6], &kRTBands[7], &kRTBands[8], &kRTBands[9],
|
||||
&kTiltLow, &kTiltMid, &kTiltHigh,
|
||||
&kLoCutPro, &kHiCutPro
|
||||
};
|
||||
for (auto* k : knobs)
|
||||
k->label.setFont(labelFont(9.f));
|
||||
|
||||
if (lookAndFeel != nullptr)
|
||||
lookAndFeel->zoomScale = uiScale;
|
||||
decayCurveViz.setZoomScale(uiScale);
|
||||
|
||||
// layout is written in fixed design coords (W x H); S() maps to the
|
||||
// actual component size so no host-hostile setTransform is needed.
|
||||
auto S = [this](int x, int y, int w, int h) {
|
||||
return juce::Rectangle<int>(juce::roundToInt(x * uiScale),
|
||||
juce::roundToInt(y * uiScale),
|
||||
juce::roundToInt(w * uiScale),
|
||||
juce::roundToInt(h * uiScale));
|
||||
};
|
||||
|
||||
titleLabel.setBounds(S(PAD, Y_HEADER - 6, 300, 44));
|
||||
proModeButton.setBounds(S(316, Y_HEADER + 5, 52, 22));
|
||||
erSoloButton.setBounds(S(376, Y_HEADER + 5, 72, 22));
|
||||
vuIn.setBounds(S(W - 220, Y_HEADER + 2, 96, 28));
|
||||
vuOut.setBounds(S(W - 120, Y_HEADER + 2, 96, 28));
|
||||
zoomButton.setBounds(S(W - 272, Y_HEADER + 5, 48, 22));
|
||||
|
||||
auto place1 = [&](ArcKnob& k, int& x, int y) {
|
||||
k.label.setBounds(x, y, KNOB_W, KNOB_LBL_H);
|
||||
k.slider.setBounds(x, y + KNOB_LBL_H, KNOB_W, KNOB_H);
|
||||
k.label.setBounds(S(x, y, KNOB_W, KNOB_LBL_H));
|
||||
k.slider.setBounds(S(x, y + KNOB_LBL_H, KNOB_W, KNOB_H));
|
||||
x += KNOB_W + ROW1_GAP;
|
||||
};
|
||||
auto place2 = [&](ArcKnob& k, int& x, int y) {
|
||||
k.label.setBounds(x, y, KNOB_W, KNOB_LBL_H);
|
||||
k.slider.setBounds(x, y + KNOB_LBL_H, KNOB_W, KNOB_H);
|
||||
k.label.setBounds(S(x, y, KNOB_W, KNOB_LBL_H));
|
||||
k.slider.setBounds(S(x, y + KNOB_LBL_H, KNOB_W, KNOB_H));
|
||||
x += KNOB_W + PAD;
|
||||
};
|
||||
|
||||
if (!isProMode) {
|
||||
if (!proMode) {
|
||||
// -- Row 1 --
|
||||
int kx = PAD;
|
||||
place1(kPreDelay, kx, Y_ROW1);
|
||||
|
|
@ -424,8 +375,8 @@ void FDNReverbEditor::resized()
|
|||
|
||||
// -- ProMode row 2 --
|
||||
int kx2 = PAD;
|
||||
satTypeLabel.setBounds(kx2, Y_SLABEL2, KNOB_W, KNOB_LBL_H);
|
||||
satTypeCombo.setBounds(kx2, Y_SLABEL2 + KNOB_LBL_H + 2, KNOB_W + PAD, 24);
|
||||
satTypeLabel.setBounds(S(kx2, Y_SLABEL2, KNOB_W, KNOB_LBL_H));
|
||||
satTypeCombo.setBounds(S(kx2, Y_SLABEL2 + KNOB_LBL_H + 2, KNOB_W + PAD, 24));
|
||||
kx2 += KNOB_W + PAD + PAD + 8;
|
||||
place2(kTiltLow, kx2, Y_ROW2);
|
||||
place2(kTiltMid, kx2, Y_ROW2);
|
||||
|
|
@ -439,10 +390,10 @@ void FDNReverbEditor::resized()
|
|||
// preset UI (always, mode-independent)
|
||||
// -------------------------------------------------------------------------
|
||||
// layout (PRESET_PANEL_X=632 starting from ):
|
||||
//
|
||||
//
|
||||
// top row Y=Y_ROW2: [<(26)] gap4 [combo(154)] gap4 [>(26)] -> right edge 848
|
||||
// bottom row Y=Y_ROW2+34:[SAVE(104)] gap8 [DELETE(104)] -> right edge 848
|
||||
//
|
||||
//
|
||||
// separator vertical line : PRESET_PANEL_X - 9 = 623
|
||||
// -------------------------------------------------------------------------
|
||||
{
|
||||
|
|
@ -450,31 +401,26 @@ void FDNReverbEditor::resized()
|
|||
const int btnH = 26;
|
||||
|
||||
// top row
|
||||
presetPrevButton.setBounds(px, Y_ROW2, 26, btnH);
|
||||
presetCombo.setBounds(px + 30, Y_ROW2, 154, btnH);
|
||||
presetNextButton.setBounds(px + 188, Y_ROW2, 26, btnH);
|
||||
presetPrevButton.setBounds(S(px, Y_ROW2, 26, btnH));
|
||||
presetCombo.setBounds(S(px + 30, Y_ROW2, 154, btnH));
|
||||
presetNextButton.setBounds(S(px + 188, Y_ROW2, 26, btnH));
|
||||
|
||||
// --- after changes ---
|
||||
// bottom row : [SAVE(68)] [LOAD(68)] [DELETE(68)]
|
||||
presetSaveButton.setBounds(px, Y_ROW2 + 34, 68, btnH);
|
||||
presetLoadButton.setBounds(px + 72, Y_ROW2 + 34, 68, btnH);
|
||||
presetDeleteButton.setBounds(px + 144, Y_ROW2 + 34, 68, btnH);
|
||||
// bottom row : [SAVE(68)] [LOAD(68)] [DELETE(68)]
|
||||
presetSaveButton.setBounds(S(px, Y_ROW2 + 34, 68, btnH));
|
||||
presetLoadButton.setBounds(S(px + 72, Y_ROW2 + 34, 68, btnH));
|
||||
presetDeleteButton.setBounds(S(px + 144, Y_ROW2 + 34, 68, btnH));
|
||||
}
|
||||
|
||||
// -- Visualizers --
|
||||
const int vizTotalH = H - Y_VIZ - STATUS_H - PAD;
|
||||
const int rt60Height = vizTotalH / 2 - 2;
|
||||
const int decayHeight = vizTotalH / 2 - 2;
|
||||
const int decayY = Y_VIZ + rt60Height + 4;
|
||||
// -- Visualizer --
|
||||
const int decayHeight = H - Y_VIZ - STATUS_H - PAD;
|
||||
|
||||
rt60Viz.setBounds(PAD, Y_VIZ, W - PAD * 2, rt60Height);
|
||||
decayCurveViz.setBounds(PAD, decayY, W - PAD * 2, decayHeight);
|
||||
decayCurveViz.setBounds(S(PAD, Y_VIZ, W - PAD * 2, decayHeight));
|
||||
|
||||
// -- AcousticMetrics --
|
||||
const int metricsRight = W - PAD - 8;
|
||||
const int metricsW = 280;
|
||||
const int metricsLeft = metricsRight - metricsW;
|
||||
const int metricsTop = decayY + 6;
|
||||
const int metricsTop = Y_VIZ + 6;
|
||||
const int metricsRowH = 14;
|
||||
const int metricsRow1Y = metricsTop + 14;
|
||||
const int metricsRow2Y = metricsRow1Y + metricsRowH;
|
||||
|
|
@ -483,37 +429,33 @@ void FDNReverbEditor::resized()
|
|||
const int colSpacing = 8;
|
||||
const int colW = captionW + valueW;
|
||||
|
||||
labelMetricsTitle.setBounds(metricsLeft, metricsTop, 80, 12);
|
||||
labelD50Caption.setBounds(metricsLeft, metricsRow1Y, captionW, metricsRowH);
|
||||
labelD50Value.setBounds(metricsLeft + captionW, metricsRow1Y, valueW, metricsRowH);
|
||||
labelC50Caption.setBounds(metricsLeft + colW + colSpacing, metricsRow1Y, captionW, metricsRowH);
|
||||
labelC50Value.setBounds(metricsLeft + colW + colSpacing + captionW, metricsRow1Y, valueW, metricsRowH);
|
||||
labelC80Caption.setBounds(metricsLeft, metricsRow2Y, captionW, metricsRowH);
|
||||
labelC80Value.setBounds(metricsLeft + captionW, metricsRow2Y, valueW, metricsRowH);
|
||||
labelEDTCaption.setBounds(metricsLeft + colW + colSpacing, metricsRow2Y, captionW, metricsRowH);
|
||||
labelEDTValue.setBounds(metricsLeft + colW + colSpacing + captionW, metricsRow2Y, valueW, metricsRowH);
|
||||
labelMetricsTitle.setBounds(S(metricsLeft, metricsTop, 80, 12));
|
||||
labelD50Caption.setBounds(S(metricsLeft, metricsRow1Y, captionW, metricsRowH));
|
||||
labelD50Value.setBounds(S(metricsLeft + captionW, metricsRow1Y, valueW, metricsRowH));
|
||||
labelC50Caption.setBounds(S(metricsLeft + colW + colSpacing, metricsRow1Y, captionW, metricsRowH));
|
||||
labelC50Value.setBounds(S(metricsLeft + colW + colSpacing + captionW, metricsRow1Y, valueW, metricsRowH));
|
||||
labelC80Caption.setBounds(S(metricsLeft, metricsRow2Y, captionW, metricsRowH));
|
||||
labelC80Value.setBounds(S(metricsLeft + captionW, metricsRow2Y, valueW, metricsRowH));
|
||||
labelEDTCaption.setBounds(S(metricsLeft + colW + colSpacing, metricsRow2Y, captionW, metricsRowH));
|
||||
labelEDTValue.setBounds(S(metricsLeft + colW + colSpacing + captionW, metricsRow2Y, valueW, metricsRowH));
|
||||
|
||||
// -- Git build-info status bar --
|
||||
statusLabel.setBounds(PAD, H - STATUS_H, W - PAD * 2, STATUS_H);
|
||||
statusLabel.setBounds(S(PAD, H - STATUS_H, W - PAD - 16, STATUS_H));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// paint
|
||||
// EditorContent::paint
|
||||
// -----------------------------------------------------------------------------
|
||||
void FDNReverbEditor::paint(juce::Graphics& g)
|
||||
void EditorContent::paint(juce::Graphics& g)
|
||||
{
|
||||
g.fillAll(AmbivalenceColors::Background);
|
||||
g.addTransform(juce::AffineTransform::scale(uiScale));
|
||||
|
||||
juce::ColourGradient grad(
|
||||
AmbivalenceColors::Surface.withAlpha(0.12f), 0.f, 0.f,
|
||||
AmbivalenceColors::Background, 0.f, (float)H, false);
|
||||
g.setGradientFill(grad);
|
||||
g.fillAll();
|
||||
|
||||
g.setFont(juce::Font(juce::FontOptions(8.f)));
|
||||
g.setColour(AmbivalenceColors::TextSecondary.withAlpha(0.35f));
|
||||
g.drawText("16ch FDN | SAPF | ISM-ER | 44.1-192kHz",
|
||||
PAD + 336, Y_HEADER + 10, W / 2, 12,
|
||||
juce::Justification::centredLeft);
|
||||
g.fillRect(0.f, 0.f, (float)W, (float)H);
|
||||
|
||||
g.setColour(AmbivalenceColors::Separator);
|
||||
g.drawHorizontalLine(Y_SEP, (float)PAD, (float)(W - PAD));
|
||||
|
|
@ -525,7 +467,7 @@ void FDNReverbEditor::paint(juce::Graphics& g)
|
|||
g.drawText(t, x, y, 200, 14, juce::Justification::centredLeft);
|
||||
};
|
||||
|
||||
if (!isProMode) {
|
||||
if (!proMode) {
|
||||
// -- Row 1 separator --
|
||||
g.setColour(AmbivalenceColors::Separator);
|
||||
g.drawVerticalLine(SEP_TF, (float)Y_SLABEL1, (float)(Y_ROW1 + UNIT_H));
|
||||
|
|
@ -583,12 +525,197 @@ void FDNReverbEditor::paint(juce::Graphics& g)
|
|||
g.drawHorizontalLine(H - STATUS_H - 1, (float)PAD, (float)(W - PAD));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// EditorContent::Zoom control
|
||||
// -----------------------------------------------------------------------------
|
||||
void EditorContent::showZoomMenu()
|
||||
{
|
||||
static constexpr float zoomLevels[] = { 0.75f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f };
|
||||
juce::PopupMenu menu;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
const int pct = juce::roundToInt(zoomLevels[i] * 100.f);
|
||||
menu.addItem(juce::String(pct) + "%", true,
|
||||
juce::approximatelyEqual(zoomLevels[i], currentZoom),
|
||||
[this, level = zoomLevels[i]] { setZoomLevel(level); });
|
||||
}
|
||||
menu.showMenuAsync(juce::PopupMenu::Options()
|
||||
.withTargetComponent(&zoomButton)
|
||||
.withStandardItemHeight(22));
|
||||
}
|
||||
|
||||
void EditorContent::setZoomLevel(float scale)
|
||||
{
|
||||
currentZoom = juce::jlimit(0.75f, 3.0f, scale);
|
||||
zoomButton.setButtonText(
|
||||
juce::String(juce::roundToInt(currentZoom * 100.f)) + "%");
|
||||
if (onZoomChange)
|
||||
onZoomChange(currentZoom);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// constructor
|
||||
// -----------------------------------------------------------------------------
|
||||
FDNReverbEditor::FDNReverbEditor(FDNReverbAudioProcessor& p)
|
||||
: AudioProcessorEditor(&p),
|
||||
audioProcessor(p)
|
||||
{
|
||||
setLookAndFeel(&laf);
|
||||
setSize(juce::roundToInt((float)EditorContent::W * EditorContent::kDefaultZoom),
|
||||
juce::roundToInt((float)EditorContent::H * EditorContent::kDefaultZoom));
|
||||
|
||||
content = std::make_unique<EditorContent>(p, laf);
|
||||
addAndMakeVisible(content.get());
|
||||
|
||||
// setSize() in the constructor runs resized() while content is still
|
||||
// null, and hosts open the editor at exactly this size, so resized()
|
||||
// would otherwise never run again - leaving content at 0x0 (black
|
||||
// canvas). Lay it out explicitly here.
|
||||
resized();
|
||||
|
||||
// when the user picks a zoom level, resize the (untransformed) editor so
|
||||
// it exactly matches the scaled content. resized() derives the content
|
||||
// scale from the actual editor width, so hosts that clamp the window
|
||||
// still end up with a correct, fully-covered UI.
|
||||
content->onZoomChange = [this](float z) {
|
||||
setSize(juce::roundToInt((float)EditorContent::W * z),
|
||||
juce::roundToInt((float)EditorContent::H * z));
|
||||
};
|
||||
|
||||
// -- Preset UI wiring --
|
||||
presetManager = std::make_unique<PresetManager>(p);
|
||||
|
||||
content->zoomButton.onClick = [this] { content->showZoomMenu(); };
|
||||
|
||||
content->presetPrevButton.setButtonText("<");
|
||||
content->presetPrevButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
content->presetPrevButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::TextPrimary);
|
||||
content->presetPrevButton.onClick = [this] {
|
||||
presetManager->loadPrevPreset();
|
||||
};
|
||||
|
||||
content->presetCombo.setLookAndFeel(&laf);
|
||||
content->presetCombo.onChange = [this] {
|
||||
int idx = content->presetCombo.getSelectedItemIndex() - FDNReverb::NUM_ALGORITHMS;
|
||||
auto names = presetManager->getPresetNames();
|
||||
if (idx >= 0 && idx < names.size())
|
||||
presetManager->loadPreset(names[idx]);
|
||||
};
|
||||
|
||||
content->presetNextButton.setButtonText(">");
|
||||
content->presetNextButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
content->presetNextButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::TextPrimary);
|
||||
content->presetNextButton.onClick = [this] {
|
||||
presetManager->loadNextPreset();
|
||||
};
|
||||
|
||||
content->presetSaveButton.setButtonText("SAVE");
|
||||
content->presetSaveButton.setColour(juce::TextButton::buttonColourId,
|
||||
AmbivalenceColors::Accent.withAlpha(0.75f));
|
||||
content->presetSaveButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::Background);
|
||||
content->presetSaveButton.onClick = [this] { savePresetWithDialog(); };
|
||||
|
||||
content->presetLoadButton.setButtonText("LOAD");
|
||||
content->presetLoadButton.setColour(juce::TextButton::buttonColourId,
|
||||
AmbivalenceColors::AccentBlue.withAlpha(0.75f));
|
||||
content->presetLoadButton.setColour(juce::TextButton::textColourOffId,
|
||||
AmbivalenceColors::Background);
|
||||
content->presetLoadButton.onClick = [this] {
|
||||
int idx = content->presetCombo.getSelectedItemIndex() - FDNReverb::NUM_ALGORITHMS;
|
||||
auto names = presetManager->getPresetNames();
|
||||
if (idx >= 0 && idx < names.size())
|
||||
presetManager->loadPreset(names[idx]);
|
||||
};
|
||||
|
||||
content->presetDeleteButton.setButtonText("DELETE");
|
||||
content->presetDeleteButton.setColour(juce::TextButton::buttonColourId, AmbivalenceColors::Surface);
|
||||
content->presetDeleteButton.setColour(juce::TextButton::textColourOffId, AmbivalenceColors::TextSecondary);
|
||||
content->presetDeleteButton.onClick = [this] { deleteCurrentPreset(); };
|
||||
|
||||
// callback setup
|
||||
presetManager->onPresetListChanged = [this] { refreshPresetCombo(); };
|
||||
|
||||
// * fix : whenever the preset name changes Processor notify
|
||||
presetManager->onPresetLoaded = [this](const juce::String& name) {
|
||||
audioProcessor.setLastSavedPresetName(name);
|
||||
refreshPresetCombo();
|
||||
};
|
||||
|
||||
refreshPresetCombo();
|
||||
startTimerHz(60);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// destructor
|
||||
// -----------------------------------------------------------------------------
|
||||
FDNReverbEditor::~FDNReverbEditor() {
|
||||
stopTimer();
|
||||
setLookAndFeel(nullptr);
|
||||
content->satTypeCombo.setLookAndFeel(nullptr);
|
||||
content->presetCombo.setLookAndFeel(nullptr);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// timerCallback
|
||||
// -----------------------------------------------------------------------------
|
||||
void FDNReverbEditor::timerCallback()
|
||||
{
|
||||
content->vuIn.setLevels(audioProcessor.getInputRMSL(),
|
||||
audioProcessor.getInputRMSR());
|
||||
content->vuOut.setLevels(audioProcessor.getOutputRMSL(),
|
||||
audioProcessor.getOutputRMSR());
|
||||
content->vuIn.repaint();
|
||||
content->vuOut.repaint();
|
||||
|
||||
static int metricsCounter = 0;
|
||||
if (++metricsCounter >= 2) {
|
||||
metricsCounter = 0;
|
||||
content->labelD50Value.setText(
|
||||
juce::String(audioProcessor.getD50() * 100.0f, 1) + "%",
|
||||
juce::dontSendNotification);
|
||||
content->labelC50Value.setText(
|
||||
juce::String(audioProcessor.getC50(), 1) + "dB",
|
||||
juce::dontSendNotification);
|
||||
content->labelC80Value.setText(
|
||||
juce::String(audioProcessor.getC80(), 1) + "dB",
|
||||
juce::dontSendNotification);
|
||||
content->labelEDTValue.setText(
|
||||
juce::String(audioProcessor.getEDT(), 2) + "s",
|
||||
juce::dontSendNotification);
|
||||
}
|
||||
|
||||
bool newProMode = (*audioProcessor.apvts.getRawParameterValue("promode") > 0.5f);
|
||||
content->setProMode(newProMode);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// resized
|
||||
// -----------------------------------------------------------------------------
|
||||
void FDNReverbEditor::resized()
|
||||
{
|
||||
if (content == nullptr)
|
||||
return;
|
||||
|
||||
// No setTransform here: scaling is done by EditorContent applying
|
||||
// uiScale to every child bounds and to its own paint() graphics, which
|
||||
// renders correctly in all hosts (some hosts, e.g. FL Studio, fail to
|
||||
// composite a transformed top-level child and show a blank frame).
|
||||
content->setBounds(getLocalBounds());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// paint
|
||||
// -----------------------------------------------------------------------------
|
||||
void FDNReverbEditor::paint(juce::Graphics& g)
|
||||
{
|
||||
g.fillAll(AmbivalenceColors::Background);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Preset UI helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
void FDNReverbEditor::refreshPresetCombo()
|
||||
{
|
||||
presetCombo.clear(juce::dontSendNotification);
|
||||
content->presetCombo.clear(juce::dontSendNotification);
|
||||
auto names = presetManager->getPresetNames();
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
|
@ -606,29 +733,41 @@ void FDNReverbEditor::refreshPresetCombo()
|
|||
presetManager->setCurrentPresetName(saved);
|
||||
}
|
||||
|
||||
// -- built-in algorithm presets --
|
||||
content->presetCombo.addSectionHeading("ALGORITHM");
|
||||
static const char* algoNames[FDNReverb::NUM_ALGORITHMS] = {
|
||||
"ROOM1", "ROOM2", "HALL1", "HALL2", "PLATE", "SPRING", "GOLDFOIL"
|
||||
};
|
||||
for (int i = 0; i < FDNReverb::NUM_ALGORITHMS; ++i)
|
||||
content->presetCombo.addItem(algoNames[i], i + 1);
|
||||
|
||||
// -- saved user presets --
|
||||
if (names.isEmpty()) {
|
||||
presetCombo.addItem("-- No Presets --", 1);
|
||||
presetCombo.setSelectedItemIndex(0, juce::dontSendNotification);
|
||||
presetDeleteButton.setEnabled(false);
|
||||
presetLoadButton.setEnabled(false);
|
||||
presetPrevButton.setEnabled(false);
|
||||
presetNextButton.setEnabled(false);
|
||||
const int placeholderId = FDNReverb::NUM_ALGORITHMS + 1;
|
||||
content->presetCombo.addItem("-- No Presets --", placeholderId);
|
||||
content->presetCombo.setSelectedId(placeholderId, juce::dontSendNotification);
|
||||
content->presetDeleteButton.setEnabled(false);
|
||||
content->presetLoadButton.setEnabled(false);
|
||||
content->presetPrevButton.setEnabled(false);
|
||||
content->presetNextButton.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// user-preset IDs start after the algorithm IDs so no two combo items
|
||||
// share an ID (JUCE ticks every item whose ID matches the selection).
|
||||
for (int i = 0; i < names.size(); ++i)
|
||||
presetCombo.addItem(names[i], i + 1);
|
||||
content->presetCombo.addItem(names[i], FDNReverb::NUM_ALGORITHMS + i + 1);
|
||||
|
||||
int idx = presetManager->getCurrentPresetIndex();
|
||||
if (idx >= 0)
|
||||
presetCombo.setSelectedItemIndex(idx, juce::dontSendNotification);
|
||||
content->presetCombo.setSelectedItemIndex(idx, juce::dontSendNotification);
|
||||
else
|
||||
presetCombo.setSelectedItemIndex(0, juce::dontSendNotification);
|
||||
content->presetCombo.setSelectedItemIndex(0, juce::dontSendNotification);
|
||||
|
||||
presetDeleteButton.setEnabled(true);
|
||||
presetLoadButton.setEnabled(true);
|
||||
presetPrevButton.setEnabled(names.size() > 1);
|
||||
presetNextButton.setEnabled(names.size() > 1);
|
||||
content->presetDeleteButton.setEnabled(true);
|
||||
content->presetLoadButton.setEnabled(true);
|
||||
content->presetPrevButton.setEnabled(names.size() > 1);
|
||||
content->presetNextButton.setEnabled(names.size() > 1);
|
||||
}
|
||||
|
||||
void FDNReverbEditor::savePresetWithDialog()
|
||||
|
|
@ -687,4 +826,4 @@ void FDNReverbEditor::deleteCurrentPreset()
|
|||
}),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,31 +6,33 @@
|
|||
#include "GUI/AmbivalenceUI.h"
|
||||
#include "GUI/DecayCurveViz.h"
|
||||
|
||||
class FDNReverbEditor : public juce::AudioProcessorEditor,
|
||||
private juce::Timer
|
||||
// -----------------------------------------------------------------------------
|
||||
// EditorContent
|
||||
// -----------------------------------------------------------------------------
|
||||
// Holds every UI element of the editor and is the only component that gets a
|
||||
// zoom transform applied. The top-level AudioProcessorEditor stays
|
||||
// untransformed and is sized to exactly fit the transformed content, so the
|
||||
// host always sees a window that matches what the plugin draws (no double
|
||||
// scaling, no clipped / blank margins at any zoom level).
|
||||
// -----------------------------------------------------------------------------
|
||||
class EditorContent : public juce::Component
|
||||
{
|
||||
public:
|
||||
explicit FDNReverbEditor(FDNReverbAudioProcessor&);
|
||||
~FDNReverbEditor() override;
|
||||
explicit EditorContent(FDNReverbAudioProcessor& p, AmbivalenceLookAndFeel& laf);
|
||||
|
||||
void paint(juce::Graphics&) override;
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
void timerCallback() override;
|
||||
void updatePanelVisibility();
|
||||
void setProMode(bool pro);
|
||||
|
||||
// --- Preset UI helpers ---
|
||||
void refreshPresetCombo();
|
||||
void savePresetWithDialog();
|
||||
void deleteCurrentPreset();
|
||||
|
||||
FDNReverbAudioProcessor& audioProcessor;
|
||||
AmbivalenceLookAndFeel laf;
|
||||
// --- zoom ---
|
||||
static constexpr float kDefaultZoom = 1.5f;
|
||||
float currentZoom{ kDefaultZoom };
|
||||
float uiScale{ 1.0f }; // derived from getWidth()/W, applied to every child bounds
|
||||
std::function<void(float)> onZoomChange;
|
||||
AmbivalenceLookAndFeel* lookAndFeel{ nullptr };
|
||||
|
||||
// --- common ---
|
||||
AlgorithmSelector algoSelector;
|
||||
RT60Visualizer rt60Viz;
|
||||
DecayCurveViz decayCurveViz;
|
||||
VUMeter vuIn, vuOut;
|
||||
juce::Label titleLabel;
|
||||
|
|
@ -43,12 +45,13 @@ private:
|
|||
|
||||
juce::Label statusLabel;
|
||||
|
||||
juce::TextButton zoomButton;
|
||||
juce::TextButton proModeButton;
|
||||
juce::TextButton erSoloButton;
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> proModeAttachment;
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> erSoloAttachment;
|
||||
|
||||
bool isProMode{ false };
|
||||
bool proMode{ false };
|
||||
|
||||
// --- Normal Mode ---
|
||||
ArcKnob kPreDelay, kRoomSize, kDecay;
|
||||
|
|
@ -69,18 +72,16 @@ private:
|
|||
ArcKnob kLoCutPro, kHiCutPro;
|
||||
|
||||
// --- preset UI ---
|
||||
std::unique_ptr<PresetManager> presetManager;
|
||||
juce::TextButton presetPrevButton;
|
||||
juce::ComboBox presetCombo;
|
||||
juce::TextButton presetNextButton;
|
||||
// existing presetSaveButton / presetDeleteButton next to added
|
||||
juce::TextButton presetSaveButton;
|
||||
juce::TextButton presetLoadButton; // * added
|
||||
juce::TextButton presetLoadButton;
|
||||
juce::TextButton presetDeleteButton;
|
||||
|
||||
// --- layout constants ---
|
||||
static constexpr int W = 900;
|
||||
static constexpr int H = 540;
|
||||
static constexpr int H = 450;
|
||||
static constexpr int PAD = 8;
|
||||
static constexpr int KNOB_W = 64;
|
||||
static constexpr int KNOB_H = 72;
|
||||
|
|
@ -93,5 +94,38 @@ private:
|
|||
// DUCKING end (616) + gap(16) = 632
|
||||
static constexpr int PRESET_PANEL_X = 632;
|
||||
|
||||
void updatePanelVisibility();
|
||||
void showZoomMenu();
|
||||
void setZoomLevel(float scale);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EditorContent)
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Editor
|
||||
// -----------------------------------------------------------------------------
|
||||
class FDNReverbEditor : public juce::AudioProcessorEditor,
|
||||
private juce::Timer
|
||||
{
|
||||
public:
|
||||
explicit FDNReverbEditor(FDNReverbAudioProcessor&);
|
||||
~FDNReverbEditor() override;
|
||||
|
||||
void paint(juce::Graphics&) override;
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
void timerCallback() override;
|
||||
|
||||
// --- Preset UI helpers ---
|
||||
void refreshPresetCombo();
|
||||
void savePresetWithDialog();
|
||||
void deleteCurrentPreset();
|
||||
|
||||
FDNReverbAudioProcessor& audioProcessor;
|
||||
AmbivalenceLookAndFeel laf;
|
||||
std::unique_ptr<EditorContent> content;
|
||||
std::unique_ptr<PresetManager> presetManager;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FDNReverbEditor)
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public:
|
|||
juce::AudioProcessorEditor* createEditor() override;
|
||||
bool hasEditor() const override { return true; }
|
||||
|
||||
const juce::String getName() const override { return "Ambivalence1.1"; }
|
||||
const juce::String getName() const override { return "Ambivalence"; }
|
||||
double getTailLengthSeconds() const override { return 20.0; }
|
||||
bool acceptsMidi() const override { return false; }
|
||||
bool producesMidi() const override { return false; }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,37 @@
|
|||
#include "PresetManager.h"
|
||||
#include "PluginProcessor.h"
|
||||
#include "FactoryPresets.h"
|
||||
|
||||
PresetManager::PresetManager(FDNReverbAudioProcessor& p)
|
||||
: processor(p)
|
||||
{
|
||||
seedFactoryPresets();
|
||||
refreshPresetList();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// factory presets
|
||||
// -----------------------------------------------------------------------------
|
||||
// embeds Presets/*.ambpreset into the binary, then copies any that are
|
||||
// missing into the user folder, so the factory set survives fresh installs.
|
||||
// -----------------------------------------------------------------------------
|
||||
void PresetManager::seedFactoryPresets()
|
||||
{
|
||||
auto folder = getPresetsFolder();
|
||||
|
||||
for (int i = 0; i < FactoryPresets::namedResourceListSize; ++i)
|
||||
{
|
||||
const auto filename = juce::String::fromUTF8(FactoryPresets::originalFilenames[i]);
|
||||
auto dest = folder.getChildFile(filename);
|
||||
if (dest.exists())
|
||||
continue;
|
||||
|
||||
int size = 0;
|
||||
if (auto* data = FactoryPresets::getNamedResource(FactoryPresets::namedResourceList[i], size))
|
||||
dest.replaceWithData(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// filesystem
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public:
|
|||
|
||||
private:
|
||||
void refreshPresetList();
|
||||
void seedFactoryPresets();
|
||||
juce::File getPresetFile(const juce::String& name) const;
|
||||
|
||||
FDNReverbAudioProcessor& processor;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue