mirror of
https://codeberg.org/armin/horizont.git
synced 2026-09-01 04:10:46 +02:00
update
This commit is contained in:
parent
ed28db30a3
commit
6c0d072465
19 changed files with 2212 additions and 0 deletions
349
Source/PluginEditor.cpp
Normal file
349
Source/PluginEditor.cpp
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
#include "PluginEditor.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace
|
||||
{
|
||||
juce::String formatHz (float v)
|
||||
{
|
||||
return juce::String (juce::roundToInt (v)) + " Hz";
|
||||
}
|
||||
|
||||
juce::String formatSec (float v)
|
||||
{
|
||||
return juce::String (v, 1) + " s";
|
||||
}
|
||||
|
||||
juce::String formatSize (float v)
|
||||
{
|
||||
return juce::String (v, 2) + "x";
|
||||
}
|
||||
|
||||
juce::String formatPct (float v)
|
||||
{
|
||||
return juce::String (juce::roundToInt (v * 100.0f)) + " %";
|
||||
}
|
||||
|
||||
juce::String formatSemis (float v)
|
||||
{
|
||||
const int semis = juce::roundToInt (v);
|
||||
if (semis > 0)
|
||||
return "+" + juce::String (semis) + " st";
|
||||
if (semis < 0)
|
||||
return juce::String (semis) + " st";
|
||||
return "0 st";
|
||||
}
|
||||
|
||||
juce::StringArray presetNames()
|
||||
{
|
||||
juce::StringArray names;
|
||||
for (const auto& p : getPresets())
|
||||
names.add (p.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
constexpr float scalePresets[] = { 0.75f, 0.9f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f };
|
||||
constexpr int numScalePresets = (int) (sizeof (scalePresets) / sizeof (scalePresets[0]));
|
||||
|
||||
juce::StringArray scaleNames()
|
||||
{
|
||||
juce::StringArray names;
|
||||
for (float v : scalePresets)
|
||||
names.add (juce::String (juce::roundToInt (v * 100.0f)) + " %");
|
||||
return names;
|
||||
}
|
||||
|
||||
juce::String buildFooterText()
|
||||
{
|
||||
#ifndef HORIZONT_VERSION
|
||||
#define HORIZONT_VERSION "0.0.0"
|
||||
#endif
|
||||
#ifndef HORIZONT_GIT_REVISION
|
||||
#define HORIZONT_GIT_REVISION "unknown"
|
||||
#endif
|
||||
#ifndef HORIZONT_BUILD_TIMESTAMP
|
||||
#define HORIZONT_BUILD_TIMESTAMP "unknown"
|
||||
#endif
|
||||
|
||||
juce::String text ("HORIZONT v" + juce::String (HORIZONT_VERSION));
|
||||
text << " · git " << HORIZONT_GIT_REVISION;
|
||||
text << " · build " << HORIZONT_BUILD_TIMESTAMP;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontAudioProcessorEditor::HorizontAudioProcessorEditor (HorizontAudioProcessor& p,
|
||||
juce::AudioProcessorValueTreeState& s)
|
||||
: AudioProcessorEditor (&p),
|
||||
processor (p),
|
||||
apvts (s),
|
||||
display (p)
|
||||
{
|
||||
lookAndFeel = std::make_unique<HorizontLookAndFeel>();
|
||||
setLookAndFeel (lookAndFeel.get());
|
||||
|
||||
titleLabel.setText ("HORIZONT", juce::dontSendNotification);
|
||||
titleLabel.setFont (juce::Font (juce::FontOptions (22.0f, juce::Font::bold)));
|
||||
titleLabel.setColour (juce::Label::textColourId, juce::Colour (0xFFE8F6F4));
|
||||
addAndMakeVisible (titleLabel);
|
||||
|
||||
subtitleLabel.setText ("Algorithmic Spatial Processor", juce::dontSendNotification);
|
||||
subtitleLabel.setFont (juce::Font (juce::FontOptions (11.0f)));
|
||||
subtitleLabel.setColour (juce::Label::textColourId, juce::Colour (0xFF5E8688));
|
||||
addAndMakeVisible (subtitleLabel);
|
||||
|
||||
presetBox.addItemList (presetNames(), 1);
|
||||
presetBox.setSelectedItemIndex (0);
|
||||
presetBox.addListener (this);
|
||||
presetBox.setTooltip ("Recall a factory preset");
|
||||
addAndMakeVisible (presetBox);
|
||||
|
||||
scaleBox.addItemList (scaleNames(), 1);
|
||||
scaleBox.setSelectedItemIndex (2);
|
||||
scaleBox.addListener (this);
|
||||
scaleBox.setTooltip ("Set the UI scale");
|
||||
addAndMakeVisible (scaleBox);
|
||||
|
||||
footerLabel.setText (buildFooterText(), juce::dontSendNotification);
|
||||
footerLabel.setJustificationType (juce::Justification::centred);
|
||||
footerLabel.setInterceptsMouseClicks (false, false);
|
||||
addAndMakeVisible (footerLabel);
|
||||
|
||||
addAndMakeVisible (display);
|
||||
|
||||
auto makeKnob = [this] (const juce::String& title, const juce::String& id,
|
||||
const juce::String& tip, std::function<juce::String (float)> fmt,
|
||||
float defVal) -> std::unique_ptr<Knob>
|
||||
{
|
||||
return std::make_unique<Knob> (title, apvts, id, defVal, fmt, tip);
|
||||
};
|
||||
|
||||
knobs.push_back (makeKnob ("LOW CUT", "lowCut", "Input high-pass filter (20 Hz - 500 Hz)", formatHz, 40.0f));
|
||||
knobs.push_back (makeKnob ("HIGH CUT", "highCut", "Input low-pass filter (800 Hz - 20 kHz)", formatHz, 14000.0f));
|
||||
knobs.push_back (makeKnob ("DECAY", "decay", "Length of the reverb tail (RT60)", formatSec, 3.5f));
|
||||
knobs.push_back (makeKnob ("SIZE", "size", "Room size scaling", formatSize, 1.0f));
|
||||
knobs.push_back (makeKnob ("DIFFUSION", "diffusion", "Echo diffusion and smearing", formatPct, 0.6f));
|
||||
knobs.push_back (makeKnob ("BRIGHTNESS", "brightness", "High-frequency damping of the tail", formatPct, 0.75f));
|
||||
knobs.push_back (makeKnob ("FEEDBACK", "feedback", "Recirculation through the pitch shifter", formatPct, 0.6f));
|
||||
knobs.push_back (makeKnob ("PITCH", "pitch", "Pitch shift applied per feedback pass", formatSemis, 0.0f));
|
||||
knobs.push_back (makeKnob ("DRY / WET", "dryWet", "Dry / wet mix", formatPct, 0.35f));
|
||||
|
||||
for (auto& k : knobs)
|
||||
addAndMakeVisible (k.get());
|
||||
|
||||
setSize (juce::roundToInt (baseWidth), juce::roundToInt (baseHeight));
|
||||
setResizeLimits (juce::roundToInt (baseWidth * 0.75f), juce::roundToInt (baseHeight * 0.75f),
|
||||
juce::roundToInt (baseWidth * 2.0f), juce::roundToInt (baseHeight * 2.0f));
|
||||
}
|
||||
|
||||
HorizontAudioProcessorEditor::~HorizontAudioProcessorEditor()
|
||||
{
|
||||
}
|
||||
|
||||
void HorizontAudioProcessorEditor::setScaleFactor (float newScale)
|
||||
{
|
||||
newScale = juce::jlimit (0.6f, 2.0f, newScale);
|
||||
|
||||
setSize (juce::roundToInt (baseWidth * newScale),
|
||||
juce::roundToInt (baseHeight * newScale));
|
||||
}
|
||||
|
||||
void HorizontAudioProcessorEditor::updateScaleFactor()
|
||||
{
|
||||
const float newScale = juce::jmin ((float) getWidth() / baseWidth,
|
||||
(float) getHeight() / baseHeight);
|
||||
|
||||
if (juce::approximatelyEqual (scaleFactor, newScale))
|
||||
return;
|
||||
|
||||
scaleFactor = newScale;
|
||||
|
||||
for (auto& k : knobs)
|
||||
k->setScaleFactor (scaleFactor);
|
||||
|
||||
display.setScaleFactor (scaleFactor);
|
||||
}
|
||||
|
||||
void HorizontAudioProcessorEditor::comboBoxChanged (juce::ComboBox* box)
|
||||
{
|
||||
if (box == &presetBox)
|
||||
{
|
||||
const int idx = presetBox.getSelectedItemIndex();
|
||||
const auto& presets = processor.getPresets();
|
||||
|
||||
if (idx >= 0 && idx < (int) presets.size())
|
||||
processor.applyPreset (presets[(size_t) idx]);
|
||||
}
|
||||
else if (box == &scaleBox)
|
||||
{
|
||||
const int idx = scaleBox.getSelectedItemIndex();
|
||||
|
||||
if (idx >= 0 && idx < numScalePresets)
|
||||
setScaleFactor (scalePresets[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
void HorizontAudioProcessorEditor::updateScaleSelection()
|
||||
{
|
||||
int best = 0;
|
||||
float bestDiff = std::numeric_limits<float>::max();
|
||||
|
||||
for (int i = 0; i < numScalePresets; ++i)
|
||||
{
|
||||
const float diff = std::abs (scaleFactor - scalePresets[i]);
|
||||
if (diff < bestDiff)
|
||||
{
|
||||
bestDiff = diff;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
|
||||
scaleBox.setSelectedItemIndex (best, juce::dontSendNotification);
|
||||
}
|
||||
|
||||
void HorizontAudioProcessorEditor::layoutKnobRow (juce::Rectangle<int> area, int startIndex, int count)
|
||||
{
|
||||
juce::FlexBox fb;
|
||||
fb.flexDirection = juce::FlexBox::Direction::row;
|
||||
fb.justifyContent = juce::FlexBox::JustifyContent::center;
|
||||
fb.alignItems = juce::FlexBox::AlignItems::stretch;
|
||||
|
||||
const float minW = 80.0f * scaleFactor;
|
||||
const float minH = 96.0f * scaleFactor;
|
||||
|
||||
for (int i = startIndex; i < startIndex + count; ++i)
|
||||
fb.items.add (juce::FlexItem (*knobs[(size_t) i]).withMinWidth (minW).withMinHeight (minH).withFlex (1.0f));
|
||||
|
||||
fb.performLayout (area);
|
||||
}
|
||||
|
||||
void HorizontAudioProcessorEditor::mouseWheelMove (const juce::MouseEvent&, const juce::MouseWheelDetails& wheel)
|
||||
{
|
||||
if (wheel.deltaY != 0.0f)
|
||||
{
|
||||
const float zoomStep = 0.1f;
|
||||
setScaleFactor (scaleFactor - wheel.deltaY * zoomStep);
|
||||
}
|
||||
}
|
||||
|
||||
void HorizontAudioProcessorEditor::resized()
|
||||
{
|
||||
updateScaleFactor();
|
||||
updateScaleSelection();
|
||||
|
||||
auto area = getLocalBounds();
|
||||
|
||||
const float s = scaleFactor;
|
||||
const int headerH = juce::roundToInt (64.0f * s);
|
||||
const int footerH = juce::roundToInt (26.0f * s);
|
||||
const int presetBoxW = juce::roundToInt (180.0f * s);
|
||||
const int scaleBoxW = juce::roundToInt (72.0f * s);
|
||||
const int boxGap = juce::roundToInt (10.0f * s);
|
||||
const int presetBoxMargin = juce::roundToInt (20.0f * s);
|
||||
const int titleH = juce::roundToInt (32.0f * s);
|
||||
const int titleMarginH = juce::roundToInt (18.0f * s);
|
||||
const int titleMarginV = juce::roundToInt (4.0f * s);
|
||||
const int subtitleMarginH = juce::roundToInt (18.0f * s);
|
||||
const int subtitleMarginV = juce::roundToInt (2.0f * s);
|
||||
const int rowsMarginH = juce::roundToInt (16.0f * s);
|
||||
const int rowsMarginV = juce::roundToInt (4.0f * s);
|
||||
const int gap = juce::roundToInt (10.0f * s);
|
||||
|
||||
footerArea = area.removeFromBottom (footerH);
|
||||
footerLabel.setBounds (footerArea.reduced (rowsMarginH, 0));
|
||||
footerLabel.setFont (juce::Font (juce::FontOptions (juce::roundToInt (9.0f * s))));
|
||||
footerLabel.setColour (juce::Label::textColourId, juce::Colour (0xFF5E8688));
|
||||
|
||||
auto header = area.removeFromTop (headerH);
|
||||
|
||||
const int comboW = presetBoxW + boxGap + scaleBoxW;
|
||||
auto comboArea = header.removeFromRight (comboW).reduced (0, presetBoxMargin);
|
||||
auto scaleArea = comboArea.removeFromLeft (scaleBoxW);
|
||||
scaleBox.setBounds (scaleArea);
|
||||
presetBox.setBounds (comboArea);
|
||||
|
||||
auto titleArea = header.removeFromTop (titleH).reduced (titleMarginH, titleMarginV);
|
||||
titleLabel.setBounds (titleArea);
|
||||
subtitleLabel.setBounds (header.reduced (subtitleMarginH, subtitleMarginV));
|
||||
|
||||
titleLabel.setFont (juce::Font (juce::FontOptions (juce::roundToInt (22.0f * s), juce::Font::bold)));
|
||||
subtitleLabel.setFont (juce::Font (juce::FontOptions (juce::roundToInt (11.0f * s))));
|
||||
|
||||
auto modules = area.reduced (rowsMarginH, rowsMarginV);
|
||||
const int panelH = (modules.getHeight() - 2 * gap) / 3;
|
||||
|
||||
auto displayPanel = modules.removeFromTop (panelH);
|
||||
display.setBounds (displayPanel);
|
||||
|
||||
knobRow1Area = modules.removeFromTop (panelH + gap);
|
||||
knobRow2Area = modules.removeFromTop (panelH + gap);
|
||||
|
||||
layoutKnobRow (knobRow1Area, 0, 5);
|
||||
layoutKnobRow (knobRow2Area, 5, 4);
|
||||
}
|
||||
|
||||
void HorizontAudioProcessorEditor::paint (juce::Graphics& g)
|
||||
{
|
||||
const auto bounds = getLocalBounds();
|
||||
|
||||
g.setGradientFill (juce::ColourGradient (juce::Colour (0xFF0E2A2E), { 0.0f, 0.0f },
|
||||
juce::Colour (0xFF060B0D), { 0.0f, (float) bounds.getHeight() },
|
||||
false));
|
||||
g.fillRect (bounds);
|
||||
|
||||
g.setColour (juce::Colour (0x1AFFFFFF));
|
||||
g.fillRect (0, 0, bounds.getWidth(), 1);
|
||||
g.setColour (juce::Colour (0x14000000));
|
||||
g.fillRect (0, bounds.getHeight() - 1, bounds.getWidth(), 1);
|
||||
|
||||
if (footerArea.getHeight() > 0)
|
||||
{
|
||||
g.setColour (juce::Colour (0x1AFFFFFF));
|
||||
g.fillRect (0, footerArea.getY(), bounds.getWidth(), 1);
|
||||
g.setColour (juce::Colour (0x14000000));
|
||||
g.fillRect (0, footerArea.getY() + 1, bounds.getWidth(), 1);
|
||||
}
|
||||
|
||||
auto drawModule = [&g] (juce::Rectangle<int> moduleArea)
|
||||
{
|
||||
const auto r = moduleArea.toFloat();
|
||||
|
||||
auto shRect = r.expanded (4.0f, 3.0f).translated (0.0f, 3.0f);
|
||||
auto shadowGrad = juce::ColourGradient (juce::Colour (0x00000000), { 0.0f, shRect.getY() },
|
||||
juce::Colour (0x00000000), { 0.0f, shRect.getBottom() },
|
||||
false);
|
||||
shadowGrad.addColour (0.45f, juce::Colour (0x70000000));
|
||||
g.setGradientFill (shadowGrad);
|
||||
g.fillRoundedRectangle (shRect, 13.0f);
|
||||
|
||||
g.setGradientFill (juce::ColourGradient (juce::Colour (0xFF24323A), { 0.0f, r.getY() },
|
||||
juce::Colour (0xFF0B1216), { 0.0f, r.getBottom() },
|
||||
false));
|
||||
g.fillRoundedRectangle (r, 12.0f);
|
||||
|
||||
g.setColour (juce::Colour (0x2A3CE0C8));
|
||||
g.drawRoundedRectangle (r.expanded (1.0f), 13.0f, 1.0f);
|
||||
|
||||
g.setColour (juce::Colour (0x38FFFFFF));
|
||||
g.drawHorizontalLine ((int) r.getY() + 1, (int) r.getX() + 3, (int) r.getRight() - 3);
|
||||
g.drawVerticalLine ((int) r.getX() + 1, (int) r.getY() + 3, (int) r.getBottom() - 3);
|
||||
g.setColour (juce::Colour (0x50000000));
|
||||
g.drawHorizontalLine ((int) r.getBottom() - 1, (int) r.getX() + 3, (int) r.getRight() - 3);
|
||||
g.drawVerticalLine ((int) r.getRight() - 1, (int) r.getY() + 3, (int) r.getBottom() - 3);
|
||||
|
||||
g.saveState();
|
||||
g.reduceClipRegion (r.toNearestInt().reduced (1));
|
||||
auto refl = juce::ColourGradient (juce::Colour (0x28FFFFFF), { 0.0f, r.getY() },
|
||||
juce::Colour (0x00FFFFFF), { 0.0f, r.getY() + r.getHeight() * 0.45f },
|
||||
false);
|
||||
g.setGradientFill (refl);
|
||||
g.fillRect (r);
|
||||
g.setColour (juce::Colour (0x45FFFFFF));
|
||||
g.fillRect (r.getX() + 2.0f, r.getY() + 1.0f, r.getWidth() - 4.0f, 1.0f);
|
||||
g.restoreState();
|
||||
};
|
||||
|
||||
drawModule (knobRow1Area);
|
||||
drawModule (knobRow2Area);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue