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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue