CrystalizerEQ v1.0.0

This commit is contained in:
Legaeli 2025-11-26 16:44:22 +01:00
parent f270e4613e
commit e239d3e955
6 changed files with 553 additions and 813 deletions

View File

@ -179,7 +179,6 @@ namespace AXIOM {
float ratio = 1.125f;
float sizeFor(int step) const noexcept {
// step = 0 => basePx, step = 1 => base*ratio, step = -1 => base/ratio, ...
return basePx * std::pow(ratio, static_cast<float>(step));
}
};
@ -556,7 +555,6 @@ namespace AXIOM {
float bypassOpacity = 1.0f;
if (svgXml != nullptr) {
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable!
auto icon = juce::Drawable::createFromSVG(*svgXml);
if (icon != nullptr) {
juce::Colour colour;
@ -570,8 +568,6 @@ namespace AXIOM {
: Colours::ACCENTCOLOUR;
}
// Icon zeichnen
icon->replaceColour(juce::Colours::black, colour);
icon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
@ -594,7 +590,6 @@ namespace AXIOM {
bool shouldDrawButtonAsDown) override {
bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled();
auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 1.5f;
auto hoverFactor = isHovered ? 1.05f : 1.0f;
@ -602,8 +597,6 @@ namespace AXIOM {
auto iconBounds = juce::Rectangle<float>(iconSize * hoverFactor, iconSize * hoverFactor)
.withCentre(bounds.getCentre());
float bypassOpacity = 1.0f;
if (presetMenuButton != nullptr) {
auto menuIcon = juce::Drawable::createFromSVG(*presetMenuButton);
juce::Colour colour;
@ -615,8 +608,6 @@ namespace AXIOM {
: Colours::BACKGROUNDCOLOUR;
}
// Icon zeichnen
menuIcon->replaceColour(juce::Colours::black, colour);
menuIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, 1.0f);
@ -641,7 +632,6 @@ namespace AXIOM {
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override {
const bool isHovered = button.isMouseOverOrDragging();
const bool isToggled = button.getToggleState();
auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 2 * 0.95f;
@ -693,7 +683,6 @@ namespace AXIOM {
if (lowBypassIcon != nullptr && lowCutIcon != nullptr && lowBellIcon != nullptr && lowShelfIcon !=
nullptr) {
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable!
auto bypassIcon = juce::Drawable::createFromSVG(*lowBypassIcon);
auto cutIcon = juce::Drawable::createFromSVG(*lowCutIcon);
auto bellIcon = juce::Drawable::createFromSVG(*lowBellIcon);
@ -712,8 +701,6 @@ namespace AXIOM {
: Colours::SURFACECOLOUR;
}
// Icon zeichnen
if (button.getName() == "LowBypass") {
bypassIcon->replaceColour(juce::Colours::black, colour);
bypassIcon->drawWithin(g, iconBounds,
@ -769,7 +756,6 @@ namespace AXIOM {
if (highBypassIcon != nullptr && highCutIcon != nullptr && highBellIcon != nullptr && highShelfIcon
!= nullptr) {
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable!
auto bypassIcon = juce::Drawable::createFromSVG(*highBypassIcon);
auto cutIcon = juce::Drawable::createFromSVG(*highCutIcon);
auto bellIcon = juce::Drawable::createFromSVG(*highBellIcon);
@ -788,8 +774,6 @@ namespace AXIOM {
: Colours::SURFACECOLOUR;
}
// Icon zeichnen
if (button.getName() == "HighBypass") {
bypassIcon->replaceColour(juce::Colours::black, colour);
bypassIcon->drawWithin(g, iconBounds,
@ -862,17 +846,14 @@ namespace AXIOM {
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider &slider) override {
// Basis-Implementation - wird in Subklassen überschrieben
auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
// Outer ring
g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
// Indicator
juce::Path p;
auto pointerLength = radius * 0.6f;
auto pointerThickness = 2.0f;
@ -911,7 +892,6 @@ namespace AXIOM {
auto mid = (rotaryEndAngle - rotaryStartAngle) / 2 + rotaryStartAngle;
const float arcPathWidth = 3.0f;
// Background
g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
@ -919,7 +899,6 @@ namespace AXIOM {
g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, arcPathWidth);
// Arc für Gain-Anzeige
juce::Path valueArc;
valueArc.addCentredArc(centreX, centreY, radius, radius,
0.0f, mid, angle, true);
@ -927,7 +906,6 @@ namespace AXIOM {
g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Pointer
juce::Path p;
p.addRectangle(-arcPathWidth / 2, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.5f * hoverFactor);
@ -961,7 +939,6 @@ namespace AXIOM {
}
// Einfacher Ring
g.setColour(surfaceCol);
g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 2.0f);
@ -971,7 +948,7 @@ namespace AXIOM {
g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Dünner Pointer
juce::Path p;
p.addRectangle(-1.0f, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.4f * hoverFactor);
@ -990,7 +967,6 @@ namespace AXIOM {
juce::Slider::SliderLayout layout;
auto bounds = slider.getLocalBounds();
// TextBox in der Mitte (wie bei BaseSliderLookAndFeel)
layout.textBoxBounds = juce::Rectangle<int>(
bounds.getCentreX() - bounds.getWidth() / 2,
bounds.getCentreY() - bounds.getHeight() / 2,
@ -998,8 +974,6 @@ namespace AXIOM {
bounds.getHeight()
);
// HIER IST DER TRICK: Knob-Bereich kleiner als Slider-Bounds!
// So bleibt Platz für die Labels außerhalb
layout.sliderBounds = bounds.reduced(labelPadding);
return layout;
@ -1008,21 +982,17 @@ namespace AXIOM {
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider &slider) override {
// Basis-Implementation - wird in Subklassen überschrieben
auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
bool isEnabled = slider.isEnabled();
bool isHovered = slider.isMouseOverOrDragging();
// Outer ring
g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
juce::Colour accentCol = isEnabled ? Colours::ACCENTCOLOUR : Colours::ACCENTWEAKCOLOUR;
// Indicator
juce::Path p;
auto pointerLength = radius * 0.6f;
auto pointerThickness = 2.0f;
@ -1033,7 +1003,6 @@ namespace AXIOM {
g.fillPath(p);
// Dann zeichne die Labels
const auto bounds = juce::Rectangle<int>(x, y, width, height);
const auto centre = bounds.getCentre().toFloat();
const float textRadius = std::min(width, height) * 0.5f + 5.0f;
@ -1044,7 +1013,6 @@ namespace AXIOM {
auto value = slider.getValue();
for (int i = 0; i < labels.size(); ++i) {
const double labelVal = labels[i].getDoubleValue(); // "12" -> 12.0 usw.
const bool isMatch = value == i; // Toleranz: 0.5
juce::Colour colour;
@ -1105,7 +1073,6 @@ namespace AXIOM {
}
// Einfacher Ring
g.setColour(surfaceCol);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
@ -1118,7 +1085,7 @@ namespace AXIOM {
g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Dünner Pointer
juce::Path p;
p.addRectangle(-1.0f, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.4f * hoverFactor);
@ -1168,9 +1135,6 @@ namespace AXIOM {
};
struct Layout {
///
/// @param mode Spacing mode -- e.g. Spacing::SizeMode::XS
/// @return Gap size in px as int
static int gap(Spacing::SizeMode mode) {
return juce::roundToInt(Spacing::scale.space(mode));
}
@ -1327,10 +1291,8 @@ namespace AXIOM {
bool shouldDrawButtonAsDown) override {
auto bounds = button.getLocalBounds();
// Text holen
auto text = button.getButtonText();
// Farbe wählen
auto isHovered = button.isMouseOver();
bool isEnabled = button.isEnabled();
juce::Colour colour = isHovered ? Colours::FOREGROUNDHOVER : Colours::FOREGROUNDCOLOUR;
@ -1340,11 +1302,9 @@ namespace AXIOM {
g.setColour(colour);
// Font einstellen
auto font = Typography::getFont(Typography::Style::Button, 1.0f);
g.setFont(font);
// Text zeichnen
g.drawFittedText(text,
bounds.reduced(4),
juce::Justification::centred,
@ -1373,7 +1333,7 @@ namespace AXIOM {
std::function<void()> onMouseDown;
void mouseDown(const juce::MouseEvent &event) override {
juce::TextEditor::mouseDown(event); // Wichtig: Original-Verhalten beibehalten
juce::TextEditor::mouseDown(event);
if (onMouseDown)
onMouseDown();
}
@ -1383,7 +1343,6 @@ namespace AXIOM {
public:
void fillTextEditorBackground(juce::Graphics &g, int width, int height,
juce::TextEditor &textEditor) override {
// Hintergrundfarbe
if (textEditor.isEnabled()) {
g.setColour(Colours::SURFACECOLOUR);
} else {
@ -1404,7 +1363,6 @@ namespace AXIOM {
void drawTextEditorOutline(juce::Graphics &g, int width, int height,
juce::TextEditor &textEditor) override {
// Rahmen
auto bounds = juce::Rectangle<float>(0, 0, width, height);
auto radius = Shape::getRadius(Shape::RadiusMode::S, bounds);
auto strokeWidth = Shape::getStrokeWidth(Shape::StrokeMode::Thin);
@ -1414,13 +1372,10 @@ namespace AXIOM {
if (!textEditor.isEnabled()) {
outlineColour = Colours::FOREGROUNDBYPASS.withAlpha(0.3f);
} else if (textEditor.hasKeyboardFocus(true)) {
// Fokussiert - Accent-Farbe
outlineColour = Colours::ACCENTCOLOUR;
} else if (textEditor.isMouseOver()) {
// Hover
outlineColour = Colours::FOREGROUNDHOVER.withAlpha(0.5f);
} else {
// Normal
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.3f);
}
@ -1440,153 +1395,136 @@ namespace AXIOM {
};
class PresetComboBoxLookAndFeel : public juce::LookAndFeel_V4,
private juce::ComponentListener
{
public:
PresetComboBoxLookAndFeel() = default;
private juce::ComponentListener {
public:
PresetComboBoxLookAndFeel() = default;
~PresetComboBoxLookAndFeel() override
{
}
~PresetComboBoxLookAndFeel() override {
}
void getIdealPopupMenuItemSize (const juce::String& text, bool /*isSeparator*/,
int /*standardMenuItemHeight*/, int& idealWidth,
int& idealHeight) override
{
idealHeight = 50;
void getIdealPopupMenuItemSize(const juce::String &text, bool isSeparator,
int standardMenuItemHeight, int &idealWidth,
int &idealHeight) override {
idealHeight = 50;
auto font = Typography::getFont (Typography::Style::Body, 1.0f);
idealWidth = font.getStringWidth (text);
}
auto font = Typography::getFont(Typography::Style::Body, 1.0f);
idealWidth = font.getStringWidth(text);
}
void drawPopupMenuUpDownArrow(juce::Graphics& g, int width, int height,
bool isScrollUpArrow) override
{
g.setColour(Colours::ACCENTCOLOUR);
void drawPopupMenuUpDownArrow(juce::Graphics &g, int width, int height,
bool isScrollUpArrow) override {
g.setColour(Colours::ACCENTCOLOUR);
auto arrowZone = juce::Rectangle<float>(0.0f, 0.0f, (float)width, (float)height);
auto arrowWidth = arrowZone.getWidth() * 0.1f;
auto arrowHeight = arrowZone.getHeight() * 0.1f;
auto arrowZone = juce::Rectangle<float>(0.0f, 0.0f, (float) width, (float) height);
auto arrowWidth = arrowZone.getWidth() * 0.1f;
auto arrowHeight = arrowZone.getHeight() * 0.1f;
juce::Path arrow;
arrow.startNewSubPath(arrowZone.getCentreX() - arrowWidth * 0.5f,
arrowZone.getCentreY() + (isScrollUpArrow ? arrowHeight * 0.5f : -arrowHeight * 0.5f));
arrow.lineTo(arrowZone.getCentreX(),
arrowZone.getCentreY() + (isScrollUpArrow ? -arrowHeight * 0.5f : arrowHeight * 0.5f));
arrow.lineTo(arrowZone.getCentreX() + arrowWidth * 0.5f,
arrowZone.getCentreY() + (isScrollUpArrow ? arrowHeight * 0.5f : -arrowHeight * 0.5f));
juce::Path arrow;
arrow.startNewSubPath(arrowZone.getCentreX() - arrowWidth * 0.5f,
arrowZone.getCentreY() + (isScrollUpArrow
? arrowHeight * 0.5f
: -arrowHeight * 0.5f));
arrow.lineTo(arrowZone.getCentreX(),
arrowZone.getCentreY() + (isScrollUpArrow ? -arrowHeight * 0.5f : arrowHeight * 0.5f));
arrow.lineTo(arrowZone.getCentreX() + arrowWidth * 0.5f,
arrowZone.getCentreY() + (isScrollUpArrow ? arrowHeight * 0.5f : -arrowHeight * 0.5f));
g.strokePath(arrow, juce::PathStrokeType(1.5f));
}
g.strokePath(arrow, juce::PathStrokeType(1.5f));
}
void drawPopupMenuBackground(juce::Graphics& g, int width, int height) override
{
g.fillAll(Colours::SURFACECOLOUR);
void drawPopupMenuBackground(juce::Graphics &g, int width, int height) override {
g.fillAll(Colours::SURFACECOLOUR);
}
}
void drawPopupMenuItem(juce::Graphics &g, const juce::Rectangle<int> &area,
bool isSeparator, bool isActive, bool isHighlighted,
bool isTicked, bool hasSubMenu, const juce::String &text,
const juce::String &shortcutKeyText, const juce::Drawable *icon,
const juce::Colour *textColourToUse) override {
juce::Colour backGroundColour = isHighlighted ? Colours::SURFACEHOVER : Colours::SURFACECOLOUR;
void drawPopupMenuItem (juce::Graphics& g, const juce::Rectangle<int>& area,
bool isSeparator, bool isActive, bool isHighlighted,
bool isTicked, bool hasSubMenu, const juce::String& text,
const juce::String& shortcutKeyText, const juce::Drawable* icon,
const juce::Colour* textColourToUse) override {
g.fillAll(backGroundColour);
juce::Colour backGroundColour = isHighlighted ? Colours::SURFACEHOVER : Colours::SURFACECOLOUR;
g.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
if (isTicked) {
juce::Colour textColour = isHighlighted ? Colours::ACCENTHOVER : Colours::ACCENTCOLOUR;
g.setColour(textColour);
} else {
juce::Colour textColour = isHighlighted ? Colours::FOREGROUNDHOVER : Colours::FOREGROUNDCOLOUR;
g.setColour(textColour);
}
g.drawFittedText(text, area.reduced(10), juce::Justification::centredLeft, 1);
}
g.fillAll(backGroundColour);
void drawComboBox(juce::Graphics &g, int width, int height, bool isButtonDown,
int buttonX, int buttonY, int buttonW, int buttonH,
juce::ComboBox &box) override {
auto bounds = juce::Rectangle<float>(0, 0, (float) width, (float) height);
auto cornerSize = Shape::getRadius(Shape::RadiusMode::S, bounds);
g.setFont (Typography::getFont (Typography::Style::Mono, 1.0f));
if (isTicked) {
juce::Colour textColour = isHighlighted ? Colours::ACCENTHOVER : Colours::ACCENTCOLOUR;
g.setColour (textColour);
} else {
juce::Colour textColour = isHighlighted ? Colours::FOREGROUNDHOVER : Colours::FOREGROUNDCOLOUR;
g.setColour (textColour);
}
g.drawFittedText (text, area.reduced (10), juce::Justification::centredLeft, 1);
}
bool isHovered = box.isMouseOver();
bool isEnabled = box.isEnabled();
void drawComboBox(juce::Graphics& g, int width, int height, bool isButtonDown,
int buttonX, int buttonY, int buttonW, int buttonH,
juce::ComboBox& box) override
{
auto bounds = juce::Rectangle<float>(0, 0, (float)width, (float)height);
auto cornerSize = Shape::getRadius(Shape::RadiusMode::S, bounds);
juce::Colour bgColour;
if (!isEnabled) {
bgColour = Colours::SURFACEBYPASS;
} else if (isButtonDown) {
bgColour = Colours::SURFACEHOVER;
} else if (isHovered) {
bgColour = Colours::SURFACECOLOUR.brighter(0.1f);
} else {
bgColour = Colours::SURFACECOLOUR;
}
bool isHovered = box.isMouseOver();
bool isEnabled = box.isEnabled();
g.setColour(bgColour);
g.fillRoundedRectangle(bounds, cornerSize);
juce::Colour bgColour;
if (!isEnabled) {
bgColour = Colours::SURFACEBYPASS;
} else if (isButtonDown) {
bgColour = Colours::SURFACEHOVER;
} else if (isHovered) {
bgColour = Colours::SURFACECOLOUR.brighter(0.1f);
} else {
bgColour = Colours::SURFACECOLOUR;
}
juce::Colour outlineColour;
if (!isEnabled) {
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.2f);
} else if (isButtonDown) {
outlineColour = Colours::ACCENTCOLOUR;
} else if (isHovered) {
outlineColour = Colours::FOREGROUNDHOVER.withAlpha(0.5f);
} else {
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.3f);
}
g.setColour(bgColour);
g.fillRoundedRectangle(bounds, cornerSize);
auto strokeWidth = Shape::getStrokeWidth(Shape::StrokeMode::Thin);
g.setColour(outlineColour);
g.drawRoundedRectangle(bounds.reduced(strokeWidth * 0.5f), cornerSize, strokeWidth);
juce::Colour outlineColour;
if (!isEnabled) {
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.2f);
} else if (isButtonDown) {
outlineColour = Colours::ACCENTCOLOUR;
} else if (isHovered) {
outlineColour = Colours::FOREGROUNDHOVER.withAlpha(0.5f);
} else {
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.3f);
}
auto arrowZone = juce::Rectangle<float>((float) buttonX, (float) buttonY,
(float) buttonW, (float) buttonH);
auto strokeWidth = Shape::getStrokeWidth(Shape::StrokeMode::Thin);
g.setColour(outlineColour);
g.drawRoundedRectangle(bounds.reduced(strokeWidth * 0.5f), cornerSize, strokeWidth);
juce::Colour arrowColour = isEnabled ? Colours::ACCENTCOLOUR : Colours::ACCENTWEAKCOLOUR;
if (isHovered && isEnabled) {
arrowColour = Colours::ACCENTHOVER;
}
auto arrowZone = juce::Rectangle<float>((float)buttonX, (float)buttonY,
(float)buttonW, (float)buttonH);
g.setColour(arrowColour);
juce::Colour arrowColour = isEnabled ? Colours::ACCENTCOLOUR : Colours::ACCENTWEAKCOLOUR;
if (isHovered && isEnabled) {
arrowColour = Colours::ACCENTHOVER;
}
juce::Path arrow;
auto arrowW = arrowZone.getWidth() * 0.3f;
auto arrowH = arrowZone.getHeight() * 0.15f;
auto centreX = arrowZone.getCentreX();
auto centreY = arrowZone.getCentreY();
g.setColour(arrowColour);
arrow.startNewSubPath(centreX - arrowW * 0.5f, centreY - arrowH * 0.5f);
arrow.lineTo(centreX, centreY + arrowH * 0.5f);
arrow.lineTo(centreX + arrowW * 0.5f, centreY - arrowH * 0.5f);
juce::Path arrow;
auto arrowW = arrowZone.getWidth() * 0.3f;
auto arrowH = arrowZone.getHeight() * 0.15f;
auto centreX = arrowZone.getCentreX();
auto centreY = arrowZone.getCentreY();
g.strokePath(arrow, juce::PathStrokeType(1.5f));
}
arrow.startNewSubPath(centreX - arrowW * 0.5f, centreY - arrowH * 0.5f);
arrow.lineTo(centreX, centreY + arrowH * 0.5f);
arrow.lineTo(centreX + arrowW * 0.5f, centreY - arrowH * 0.5f);
g.strokePath(arrow, juce::PathStrokeType(1.5f));
}
void positionComboBoxText(juce::ComboBox& box, juce::Label& label) override
{
label.setBounds(8, 0, box.getWidth() - 30, box.getHeight());
label.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
label.setJustificationType(juce::Justification::centredLeft);
}
private:
};
void positionComboBoxText(juce::ComboBox &box, juce::Label &label) override {
label.setBounds(8, 0, box.getWidth() - 30, box.getHeight());
label.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
label.setJustificationType(juce::Justification::centredLeft);
}
private:
};
private:
};

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.15)
project(CrystalizerEQ VERSION 0.1.0)
project(CrystalizerEQ VERSION 1.0.0)
# JUCE einbinden (lokaler Pfad zu JUCE, z.B. als Submodul oder manuell kopiert)
add_subdirectory(juce) # <-- Pfad zu deinem JUCE-Ordner relativ zum Projekt

View File

@ -1,11 +1,3 @@
/*
==============================================================================
This file contains the basic framework code for a JUCE plugin editor.
==============================================================================
*/
#include "PluginProcessor.h"
#include "PluginEditor.h"
#include "AXIOMDesignSystem.h"
@ -22,7 +14,6 @@ using Components = DesignSystem::Components;
using SliderStyles = Components::SliderStyles;
//region setupSliders
void CrystalizerEQAudioProcessorEditor::setupSliders() {
auto style = juce::Slider::RotaryHorizontalVerticalDrag;
for (auto *s: sliders) {
@ -57,11 +48,8 @@ void CrystalizerEQAudioProcessorEditor::setupSliders() {
highBandSlopeSlider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0);
}
//endregion setupSliders
//region setupAttachments
void CrystalizerEQAudioProcessorEditor::setupAttachments() {
lowBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandFreq", lowBandFreqSlider);
lowBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandSlope", lowBandSlopeSlider);
lowBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandGain", lowBandGainSlider);
@ -86,15 +74,18 @@ void CrystalizerEQAudioProcessorEditor::setupAttachments() {
inputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "InputGain", inputSlider);
outputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "OutputGain", outputSlider);
peak1BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak1Bypass", peak1BypassButton);
peak2BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak2Bypass", peak2BypassButton);
peak3BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak3Bypass", peak3BypassButton);
crystalizeAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "CrystalizeButton", crystalizeButton);
masterBypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "MasterBypass", masterBypassButton);
}
//endregion setupAttachments
//region setupDisplayNames
void CrystalizerEQAudioProcessorEditor::setupDisplayNames() {
titleLabel.setText("Crystalizer", juce::dontSendNotification);
lowBypass.setName("LowBypass");
lowBell.setName("LowBell");
lowCut.setName("LowCut");
@ -130,38 +121,22 @@ void CrystalizerEQAudioProcessorEditor::setupDisplayNames() {
inputSlider.setName("Input");
outputSlider.setName("Output");
//lowBandFreqSlider.setTextValueSuffix ("\nHz");
//lowBandSlopeSlider.setTextValueSuffix ("\ndB/Oct");
lowBandGainSlider.setTextValueSuffix("\ndB");
//lowBandQSlider.setTextValueSuffix ("\nQ");
//peak1FreqSlider.setTextValueSuffix("\nHz");
peak1GainSlider.setTextValueSuffix("\ndB");
//peak1QSlider.setTextValueSuffix("\nQ");
peak1BypassButton.setName("peak1Bypass");
//peak2FreqSlider.setTextValueSuffix("\nHz");
peak2GainSlider.setTextValueSuffix("\ndB");
//peak2QSlider.setTextValueSuffix("\nQ");
peak2BypassButton.setName("peak2Bypass");
//peak3FreqSlider.setTextValueSuffix("\nHz");
peak3GainSlider.setTextValueSuffix("\ndB");
//peak3QSlider.setTextValueSuffix("\nQ");
peak3BypassButton.setName("peak3Bypass");
//highBandFreqSlider.setTextValueSuffix ("\nHz");
//highBandSlopeSlider.setTextValueSuffix ("\ndB/Oct");
highBandGainSlider.setTextValueSuffix("\ndB");
// highBandQSlider.setTextValueSuffix ("\nQ");
inputSlider.setTextValueSuffix("\nIN");
outputSlider.setTextValueSuffix("\nOUT");
crystalizeButton.setName("CrystalizeButton");
resetButton.setName("ResetButton");
@ -176,9 +151,7 @@ void CrystalizerEQAudioProcessorEditor::setupDisplayNames() {
deletePresetButton.setButtonText("Delete Preset");
}
//endregion setupDisplayNames
//region setupToggleButtons
void CrystalizerEQAudioProcessorEditor::setupToggleButtons() {
bypassButtonLookAndFeel = std::make_unique<Components::BypassButtonLookAndFeel>();
svgToggleButtonLookAndFeel = std::make_unique<Components::SvgToggleButtonLookAndFeel>();
@ -212,40 +185,23 @@ void CrystalizerEQAudioProcessorEditor::setupToggleButtons() {
&peak1BypassButton, &peak2BypassButton, &peak3BypassButton, &crystalizeButton, &masterBypassButton
}) {
b->onClick = [this, b]() {
juce::String paramID; // nimm juce::String statt std::string
juce::String mode;
if (b == &peak1BypassButton) {
paramID = "Peak1Bypass";
mode = "Low-Mid";
} else if (b == &peak2BypassButton) {
paramID = "Peak2Bypass";
mode = "Mid";
} else if (b == &peak3BypassButton) {
paramID = "Peak3Bypass";
mode = "High-Mid";
} else if (b == &crystalizeButton) {
paramID = "CrystalizeButton";
} else if (b == &masterBypassButton) {
paramID = "MasterBypass";
mode = "Master";
}
if (auto *param = audioProcessor.apvts.getParameter(paramID)) {
const bool isToggled = b->getToggleState();
const float target = b->getToggleState() ? 1.0f : 0.0f;
const float target = isToggled ? 1.0f : 0.0f;
// Nur senden, wenn sich wirklich etwas ändert (Schwelle 0.5f)
if ((param->getValue() >= 0.5f) != isToggled) {
param->beginChangeGesture();
param->setValueNotifyingHost(target);
param->endChangeGesture();
}
if (mode == "Low-Mid") disableLowMidBand(target);
else if (mode == "Mid") disableMidBand(target);
else if (mode == "High-Mid") disableHighMidBand(target);
else if (mode == "Master") disableEverything(target);
}
if (mode == "Low-Mid") disableLowMidBand(target);
else if (mode == "Mid") disableMidBand(target);
else if (mode == "High-Mid") disableHighMidBand(target);
else if (mode == "Master") disableEverything(target);
if (b == &crystalizeButton) {
isAnimatingCrystalize = true;
@ -255,9 +211,7 @@ void CrystalizerEQAudioProcessorEditor::setupToggleButtons() {
}
}
//endregion setupToggleButtons
//region disableLowBand
void CrystalizerEQAudioProcessorEditor::disableLowBand(const float target) {
bool isToggled = target <= 0.5f;
lowBandFreqSlider.setEnabled(isToggled);
@ -269,9 +223,8 @@ void CrystalizerEQAudioProcessorEditor::disableLowBand(const float target) {
lowBandQSlider.setEnabled(isToggled);
lowBandQLabel.setEnabled(isToggled);
};
//endregion disableLowBand
//region disableLowMidBand
void CrystalizerEQAudioProcessorEditor::disableLowMidBand(const float target) {
bool isToggled = target <= 0.5f;
peak1GainSlider.setEnabled(isToggled);
@ -282,9 +235,7 @@ void CrystalizerEQAudioProcessorEditor::disableLowMidBand(const float target) {
peak1FreqLabel.setEnabled(isToggled);
}
//endregion disableLowMidBand
//region disableMidBand
void CrystalizerEQAudioProcessorEditor::disableMidBand(const float target) {
bool isToggled = target <= 0.5f;
peak2GainSlider.setEnabled(isToggled);
@ -295,9 +246,7 @@ void CrystalizerEQAudioProcessorEditor::disableMidBand(const float target) {
peak2FreqLabel.setEnabled(isToggled);
}
//endregion disableMidBand
//region disableHighMidBand
void CrystalizerEQAudioProcessorEditor::disableHighMidBand(const float target) {
bool isToggled = target <= 0.5f;
peak3GainSlider.setEnabled(isToggled);
@ -308,9 +257,7 @@ void CrystalizerEQAudioProcessorEditor::disableHighMidBand(const float target) {
peak3FreqLabel.setEnabled(isToggled);
}
//endregion disableHighMidBand
//region disableHighBand
void CrystalizerEQAudioProcessorEditor::disableHighBand(const float target) {
bool isToggled = target <= 0.5f;
highBandFreqSlider.setEnabled(isToggled);
@ -322,9 +269,8 @@ void CrystalizerEQAudioProcessorEditor::disableHighBand(const float target) {
highBandQSlider.setEnabled(isToggled);
highBandQLabel.setEnabled(isToggled);
};
//endregion disableHighBand
//region disableEverything
void CrystalizerEQAudioProcessorEditor::disableEverything(const float target) {
bool isToggled = target <= 0.5f;
@ -372,9 +318,7 @@ void CrystalizerEQAudioProcessorEditor::disableEverything(const float target) {
}
}
//endregion disableEverything
//region addComponentsToLayout
void CrystalizerEQAudioProcessorEditor::addComponentsToLayout() {
addAndMakeVisible(headerBar);
headerBar.addAndMakeVisible(titleLabel);
@ -489,19 +433,14 @@ void CrystalizerEQAudioProcessorEditor::addComponentsToLayout() {
addAndMakeVisible(footerBar);
}
//endregion addComponentsToLayout
//region setupLabels
void CrystalizerEQAudioProcessorEditor::setupLabels() {
auto setupLabel = [](juce::Label &label, const juce::String &text) {
label.setText(text, juce::dontSendNotification);
label.setJustificationType(juce::Justification::centred);
label.setColour(juce::Label::textColourId, juce::Colours::black);
label.setInterceptsMouseClicks(false, false); // Klicks gehen an den Slider
};
//SetupLabels
{
}; {
//LOW-BAND
setupLabel(lowBandFreqLabel, "Low\nHz");
setupLabel(lowBandSlopeLabel, "Slope");
@ -543,13 +482,9 @@ void CrystalizerEQAudioProcessorEditor::setupLabels() {
setupLabel(presetBoxLabel, "Presets");
/*setupLabel(inputLabel, "IN");
setupLabel(outputLabel, "OUT");*/
}
}
//endregion setupLabels
void CrystalizerEQAudioProcessorEditor::setupFontsWithColours() {
Typography::applyToLabel(titleLabel, Typography::Style::Display, 1.f);
@ -564,7 +499,7 @@ void CrystalizerEQAudioProcessorEditor::setupFontsWithColours() {
}
}
//region setupSliderTextBoxes
void CrystalizerEQAudioProcessorEditor::setupSliderTextBoxes() {
baseLookAndFeel = std::make_unique<AXIOM::DesignSystem::Components::BaseSliderLookAndFeel>();
for (auto *s: sliders) {
@ -572,9 +507,7 @@ void CrystalizerEQAudioProcessorEditor::setupSliderTextBoxes() {
}
}
//endregion setupSliderTextBoxes
//region handleLowBandModes
void CrystalizerEQAudioProcessorEditor::handleLowBandModes() {
for (int i = 0; i < lowBandModeButtons.size(); ++i) {
lowBandModeButtons[i]->onClick = [this, i] {
@ -589,7 +522,6 @@ void CrystalizerEQAudioProcessorEditor::handleLowBandModes() {
lowBandModeButtons[j]->setToggleState(false, juce::dontSendNotification);
}
}
// Aktuellen aktivieren
lowBandBools[i] = true;
param->setValueNotifyingHost(param->convertTo0to1((float) i));
@ -604,9 +536,7 @@ void CrystalizerEQAudioProcessorEditor::handleLowBandModes() {
}
}
//endregion handleLowBandModes
//region handleHighBandModes
void CrystalizerEQAudioProcessorEditor::handleHighBandModes() {
for (int i = 0; i < highBandModeButtons.size(); ++i) {
highBandModeButtons[i]->onClick = [this, i] {
@ -627,7 +557,6 @@ void CrystalizerEQAudioProcessorEditor::handleHighBandModes() {
param->endChangeGesture();
updateFrequencyRanges();
}
} else if (highBandBools[i]) {
highBandModeButtons[i]->setToggleState(true, juce::dontSendNotification);
}
@ -635,9 +564,7 @@ void CrystalizerEQAudioProcessorEditor::handleHighBandModes() {
}
}
//endregion handleHighBandModes
//region setupEventListeners
void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
presetMenuButton.onClick = [this]() {
auto *menu = new DesignSystem::PresetMenu(&presetNameInput, &savePresetButton, &deletePresetButton);
@ -657,10 +584,6 @@ void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
nullptr
);
box.setLookAndFeel(presetMenuLookAndFeel.get());
/*juce::Timer::callAfterDelay(100, [this]() {
presetNameInput.giveAwayKeyboardFocus();
});*/
};
savePresetButton.onClick = [this]() {
@ -707,7 +630,7 @@ void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
}
const auto selectedPresetWithExt = selectedPreset + ".xml";
audioProcessor.loadPreset(selectedPresetWithExt);
updateModeButtons(); // ← NEU!
updateModeButtons(); // ← NEU!
updateFrequencyRanges();
};
@ -737,9 +660,8 @@ void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
};
}
//endregion setupEventListeners
void CrystalizerEQAudioProcessorEditor::updateModeButtons() {
// Low Band Modes aktualisieren
int lowMode = static_cast<int>(audioProcessor.apvts.getRawParameterValue("LowBandModes")->load());
for (int i = 0; i < lowBandModeButtons.size(); ++i) {
@ -747,7 +669,6 @@ void CrystalizerEQAudioProcessorEditor::updateModeButtons() {
lowBandModeButtons[i]->setToggleState(lowBandBools[i], juce::dontSendNotification);
}
// High Band Modes aktualisieren
int highMode = static_cast<int>(audioProcessor.apvts.getRawParameterValue("HighBandModes")->load());
for (int i = 0; i < highBandModeButtons.size(); ++i) {
@ -755,7 +676,17 @@ void CrystalizerEQAudioProcessorEditor::updateModeButtons() {
highBandModeButtons[i]->setToggleState(highBandBools[i], juce::dontSendNotification);
}
}
//region initPresetSystem
void CrystalizerEQAudioProcessorEditor::parameterChanged(const juce::String& parameterID, float newValue) {
if (parameterID == "LowBandModes" || parameterID == "HighBandModes") {
juce::MessageManager::callAsync([this]() {
updateModeButtons();
repaint();
});
}
}
void CrystalizerEQAudioProcessorEditor::initPresetSystem() {
auto presets = audioProcessor.getPresetNamesArray();
presetBox.addItemList(presets, 1);
@ -780,8 +711,6 @@ void CrystalizerEQAudioProcessorEditor::initPresetSystem() {
presetBox.setLookAndFeel(presetComboBoxLookAndFeel.get());
}
//endregion initPresetSystem
//==============================================================================
CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor(CrystalizerEQAudioProcessor &p)
: AudioProcessorEditor(&p), audioProcessor(p), spectrumAnalyzer(p.audioFIFO, p) {
@ -803,8 +732,10 @@ CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor(Crystalizer
auto logoIcon = juce::XmlDocument::parse(BinaryData::logo_icon_svg);
if (logoIcon != nullptr)
logoDrawable = juce::Drawable::createFromSVG(*logoIcon);
audioProcessor.apvts.addParameterListener("LowBandModes", this);
audioProcessor.apvts.addParameterListener("HighBandModes", this);
updateModeButtons();
}
CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() {
@ -821,7 +752,8 @@ CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() {
presetNameInput.setLookAndFeel(nullptr);
presetBox.setLookAndFeel(nullptr);
audioProcessor.apvts.removeParameterListener("LowBandModes", this);
audioProcessor.apvts.removeParameterListener("HighBandModes", this);
for (auto *b: lowBandModeButtons) {
b->setLookAndFeel(nullptr);
}
@ -847,7 +779,6 @@ float CrystalizerEQAudioProcessorEditor::getInterpolatedDb(float exactBin) {
return y1 + fraction * (y2 - y1);
}
//region paintAnalyzer
void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
analyzerRect = getLocalArea(&analyzerArea, analyzerArea.getLocalBounds());
juce::Graphics::ScopedSaveState guard(g);
@ -855,15 +786,15 @@ void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
auto r = analyzerRect.toFloat();
// Hintergrund
// BACKGROUND
g.setColour(juce::Colours::black);
g.fillRect(r);
// Rahmen
// BORDER
g.setColour(juce::Colours::grey);
g.drawRect(analyzerRect);
// === Spektrum zeichnen ===
// SPECTRUM
g.setColour(Colours::ACCENTCOLOUR);
const float analyzerWidth = analyzerRect.getWidth();
@ -874,49 +805,36 @@ void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
const float maxDb = spectrumAnalyzer.MAXDB;
const float dbRange = maxDb - minDb; // = 96 dB
const float zeroDbY = analyzerBottom - (analyzerHeight * (0.0f - minDb) / dbRange); // Position von 0dB
const float minFreq = spectrumAnalyzer.MINFREQ; // 20 Hz
const float maxFreq = spectrumAnalyzer.MAXFREQ; // 20 kHz
float sampleRate = spectrumAnalyzer.sampleRate;
float deltaF = spectrumAnalyzer.deltaF;
// Zeichne für jede X-Position
juce::Path spectrumPath;
bool pathStarted = false;
// Erhöhe die Auflösung - zeichne mehr Punkte als Pixel
const int resolutionMultiplier = 4; // 4x mehr Punkte
const int resolutionMultiplier = 4;
const int numPoints = static_cast<int>(analyzerWidth) * resolutionMultiplier;
for (int i = 0; i < numPoints; ++i) {
// Berechne X-Position
float x = (i / static_cast<float>(numPoints)) * analyzerWidth;
// Berechne Frequenz (logarithmisch)
float normalizedX = i / static_cast<float>(numPoints);
float logMin = std::log10(minFreq);
float logMax = std::log10(maxFreq);
float logFreq = logMin + normalizedX * (logMax - logMin);
float freq = std::pow(10.0f, logFreq);
// Finde Bin und interpoliere
float exactBin = freq / deltaF;
float dB = getInterpolatedDb(exactBin);
// Normalisiere
float normalized = (dB - minDb) / dbRange;
normalized = juce::jlimit(0.0f, 1.0f, normalized);
// Berechne Y-Position
float pixelHeight = normalized * analyzerHeight;
float y = analyzerBottom - pixelHeight;
// Füge zum Path hinzu
if (!pathStarted) {
spectrumPath.startNewSubPath(x + analyzerRect.getX(), y);
pathStarted = true;
@ -925,7 +843,6 @@ void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
}
}
// Zeichne die Linie mit Anti-Aliasing
if (pathStarted) {
juce::PathStrokeType stroke(2.0f);
stroke.setJointStyle(juce::PathStrokeType::curved); // Runde Ecken
@ -939,13 +856,11 @@ void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
g.fillPath(spectrumPath);
}
// Optional: Zeichne Frequenz-Grid-Linien
drawFrequencyGrid(g, dbRange);
}
float CrystalizerEQAudioProcessorEditor::mapFrequencyToX(float freq, float minFreq, float maxFreq, float width) {
// Logarithmische Skalierung: log(freq) zwischen log(minFreq) und log(maxFreq)
float logMin = std::log10(minFreq);
float logMax = std::log10(maxFreq);
float logFreq = std::log10(freq);
@ -954,6 +869,7 @@ float CrystalizerEQAudioProcessorEditor::mapFrequencyToX(float freq, float minFr
return normalized * width;
}
void CrystalizerEQAudioProcessorEditor::drawFrequencyGrid(juce::Graphics &g, const float dbRange) {
g.setColour(Colours::FOREGROUNDCOLOUR.withAlpha(0.3f));
@ -973,11 +889,9 @@ void CrystalizerEQAudioProcessorEditor::drawFrequencyGrid(juce::Graphics &g, con
for (int i = 0; i < gridDB.size(); ++i) {
float db = gridDB[i];
// 1: dB normalisieren (wie bei der Kurve)
float normalized = (db - minDB) / dbRange;
normalized = juce::jlimit(0.0f, 1.0f, normalized);
// 2: Y-Position wie beim Spektrum berechnen
float pixelHeight = normalized * analyzerHeight;
float y = analyzerBottom - pixelHeight;
@ -997,7 +911,6 @@ void CrystalizerEQAudioProcessorEditor::drawFrequencyGrid(juce::Graphics &g, con
static_cast<float>(analyzerRect.getY()),
static_cast<float>(analyzerRect.getBottom()));
// Optional: Frequenz-Label zeichnen
if (freq == 20) continue;
g.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
juce::String label = (freq >= 1000) ? juce::String(freq / 1000) + "k" : juce::String(freq);
@ -1006,7 +919,6 @@ void CrystalizerEQAudioProcessorEditor::drawFrequencyGrid(juce::Graphics &g, con
}
}
//endregion paintAnalyzer
void CrystalizerEQAudioProcessorEditor::paintModeBoxBorders(juce::Graphics &g) {
for (auto *b: lowBandModeButtons) {
@ -1028,7 +940,7 @@ void CrystalizerEQAudioProcessorEditor::paintModeBoxBorders(juce::Graphics &g) {
}
}
//==============================================================================
void CrystalizerEQAudioProcessorEditor::paint(juce::Graphics &g) {
g.fillAll(Colours::BACKGROUNDCOLOUR);
g.setColour(AXIOM::DesignSystem::Colours::FOREGROUNDCOLOUR);
@ -1051,7 +963,6 @@ void CrystalizerEQAudioProcessorEditor::paint(juce::Graphics &g) {
g.fillRect(hBX, hBY, hBW, mPY - hBPad);
g.setColour(Colours::SURFACECOLOUR);
const auto pA = getLocalArea(&presetArea, presetArea.getLocalBounds());
auto pAX = pA.getX();
@ -1077,7 +988,6 @@ void CrystalizerEQAudioProcessorEditor::paint(juce::Graphics &g) {
g.fillRect(fABorder);
fABorder = fABorder.withX(fABorder.getX() - 2 * fABorderWidth).withY(fABorder.getY() - 2 * fABorderWidth);
//g.setColour(Colours::FOREGROUNDBYPASS);
g.fillRect(fABorder);
g.setColour(Colours::BACKGROUNDBYPASS);
@ -1095,10 +1005,9 @@ void CrystalizerEQAudioProcessorEditor::paint(juce::Graphics &g) {
logoDrawable->drawWithin(g, logoArea, juce::RectanglePlacement::xLeft, 1.0f);
*/
}
//region paintBorderLines
void CrystalizerEQAudioProcessorEditor::paintBorderLines(juce::Graphics &g) {
g.setColour(DesignSystem::Colours::BACKGROUNDCOLOUR);
auto prevRight = (float) lowFilterArea.getRight();
@ -1140,12 +1049,10 @@ void CrystalizerEQAudioProcessorEditor::paintBorderLines(juce::Graphics &g) {
}
}
//endregion paintBorderLines
//region setKnobVisibility
void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
int lowMode = (int) audioProcessor.apvts
.getRawParameterValue("LowBandModes")->load(); // 0..3
.getRawParameterValue("LowBandModes")->load();
const bool masterIsToggled = masterBypassButton.getToggleState();
const float target = masterIsToggled ? 1.0f : 0.0f;
@ -1166,7 +1073,7 @@ void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
lowBandModeLabel.setEnabled(lowMode >= 1);
int highMode = (int) audioProcessor.apvts
.getRawParameterValue("HighBandModes")->load(); // 0..3
.getRawParameterValue("HighBandModes")->load();
if (masterIsToggled) {
highMode = 0;
@ -1184,56 +1091,38 @@ void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
highBandModeLabel.setEnabled(highMode >= 1);
}
//endregion setKnobVisibility
void CrystalizerEQAudioProcessorEditor::updateFrequencyRanges()
{
// Low Band Range anpassen
int lowMode = (int)audioProcessor.apvts.getRawParameterValue("LowBandModes")->load();
void CrystalizerEQAudioProcessorEditor::updateFrequencyRanges() {
int lowMode = (int) audioProcessor.apvts.getRawParameterValue("LowBandModes")->load();
if (lowMode == 3) // Bell Mode
{
// Begrenzter Bereich für Low Bell
if (lowMode == 3) {
lowBandFreqSlider.setRange(20.0, 500.0, 1.0);
// Aktuellen Wert prüfen und ggf. anpassen
float currentFreq = audioProcessor.apvts.getRawParameterValue("LowBandFreq")->load();
if (currentFreq > 500.0f)
{
if (currentFreq > 500.0f) {
lowBandFreqSlider.setValue(500.0, juce::sendNotificationSync);
}
}
else if (lowMode >= 1) // Cut oder Shelf Mode
{
// Volle Bandbreite
} else if (lowMode >= 1) {
lowBandFreqSlider.setRange(20.0, 20000.0, 1.0);
}
lowBandFreqSlider.repaint();
// High Band Range anpassen
int highMode = (int)audioProcessor.apvts.getRawParameterValue("HighBandModes")->load();
int highMode = (int) audioProcessor.apvts.getRawParameterValue("HighBandModes")->load();
if (highMode == 3) // Bell Mode
{
// Begrenzter Bereich für High Bell
if (highMode == 3) {
highBandFreqSlider.setRange(8000.0, 20000.0, 1.0);
// Aktuellen Wert prüfen und ggf. anpassen
float currentFreq = audioProcessor.apvts.getRawParameterValue("HighBandFreq")->load();
if (currentFreq < 8000.0f)
{
if (currentFreq < 8000.0f) {
highBandFreqSlider.setValue(8000.0, juce::sendNotificationSync);
}
}
else if (highMode >= 1) // Cut oder Shelf Mode
{
// Volle Bandbreite
} else if (highMode >= 1) {
highBandFreqSlider.setRange(20.0, 20000.0, 1.0);
}
highBandFreqSlider.repaint();
}
//region animateCrystalizeButton
void CrystalizerEQAudioProcessorEditor::animateCrystalizeButton() {
if (isAnimatingCrystalize) {
const float step = 0.1f;
@ -1260,9 +1149,7 @@ void CrystalizerEQAudioProcessorEditor::animateCrystalizeButton() {
}
}
//endregion animateCrystalizeButton
//region timerCallback
void CrystalizerEQAudioProcessorEditor::timerCallback() {
setKnobVisibility();
spectrumAnalyzer.processSamples();
@ -1279,7 +1166,6 @@ void CrystalizerEQAudioProcessorEditor::timerCallback() {
}
}
//endregion timerCallback
void CrystalizerEQAudioProcessorEditor::resized() {
auto pluginArea = getLocalBounds();
@ -1288,10 +1174,8 @@ void CrystalizerEQAudioProcessorEditor::resized() {
setupHeader();
setupBody();
setupFooter();
}
//region resetAllCheckboxes
void CrystalizerEQAudioProcessorEditor::resetAllCheckboxes() {
const auto notify = juce::dontSendNotification;
masterBypassButton.setToggleState(false, notify);
@ -1311,9 +1195,7 @@ void CrystalizerEQAudioProcessorEditor::resetAllCheckboxes() {
highShelf.setToggleState(true, notify);
}
//endregion resetAllCheckboxes
//region scalePluginWindow
void CrystalizerEQAudioProcessorEditor::scalePluginWindow(juce::Rectangle<int> area) {
if (area.getWidth() < 520) Spacing::setUiScale(Spacing::uiScaleMode::XS);
else if (area.getWidth() < 720) Spacing::setUiScale(Spacing::uiScaleMode::S);
@ -1322,15 +1204,13 @@ void CrystalizerEQAudioProcessorEditor::scalePluginWindow(juce::Rectangle<int> a
else Spacing::setUiScale(Spacing::uiScaleMode::XL);
}
//endregion scalePluginWindow
//region setupMainGrid
void CrystalizerEQAudioProcessorEditor::setupMainGrid(juce::Rectangle<int> area) {
const float headerHeight = static_cast<float>(getHeight()) * 0.1f;
const float footerHeight = static_cast<float>(getHeight()) * 0.1f;
Layout::GridSpec spec{
/* cols */ {Layout::fr(1)}, // eine Spalte, füllt Breite
/* cols */ {Layout::fr(1)},
/* rows */ {Layout::pxTrack(headerHeight), Layout::fr(1), Layout::pxTrack(footerHeight)},
// Header / Body / Footer
/* colGap */ Spacing::SizeMode::S,
@ -1340,9 +1220,7 @@ void CrystalizerEQAudioProcessorEditor::setupMainGrid(juce::Rectangle<int> area)
Layout::grid(area, spec, {&headerBar, &mainPanel, &footerBar});
}
//endregion setupMainGrid
//region setupHeader
void CrystalizerEQAudioProcessorEditor::setupHeader() {
const auto bounds = headerBar.getLocalBounds();
@ -1393,12 +1271,9 @@ void CrystalizerEQAudioProcessorEditor::setupHeader() {
/* pad */ Layout::padding(Spacing::SizeMode::S)
};
Layout::grid(presetAreaBounds, presetSpec, {
// Label über beide Spalten (row1, col1..2)
Layout::area(presetBoxLabel, 1, 2, 2, 3),
// Box unten links (row2, col1)
Layout::area(presetBox, 2, 2, 3, 3),
Layout::area(resetButton, 2, 1, 2, 2),
// Menütaste unten rechts (row2, col2)
Layout::area(presetMenuButton, 2, 3, 3, 4)
.withWidth(iconSize)
.withHeight(iconSize)
@ -1406,9 +1281,7 @@ void CrystalizerEQAudioProcessorEditor::setupHeader() {
});
}
//endregion setupHeader
//region setupBody
void CrystalizerEQAudioProcessorEditor::setupBody() {
const auto bounds = mainPanel.getLocalBounds();
@ -1432,22 +1305,17 @@ void CrystalizerEQAudioProcessorEditor::setupBody() {
Layout::area(analyzerArea, 1, 2, 2, 5)
.withMargin(juce::GridItem::Margin(filterAreaMargin, 0, filterAreaMargin, 0)),
Layout::area(crystalizeButton, 1, 5, 2, 6)
.withWidth(crystalizeIconSize)
.withHeight(crystalizeIconSize * 0.5f)
.withAlignSelf(juce::GridItem::AlignSelf::center)
.withJustifySelf(juce::GridItem::JustifySelf::center),
.withWidth(crystalizeIconSize)
.withHeight(crystalizeIconSize * 0.5f)
.withAlignSelf(juce::GridItem::AlignSelf::center)
.withJustifySelf(juce::GridItem::JustifySelf::center),
Layout::area(filterArea, 2, 1, 3, 6)
.withMargin(juce::GridItem::Margin(0, filterAreaMargin, 0, filterAreaMargin))
});
const auto analyzerAreaBounds = analyzerArea.getLocalBounds();
const auto analyzerAreaWidth = static_cast<float>(analyzerArea.getWidth());
const auto analyzerAreaHeight = static_cast<float>(analyzerArea.getHeight());
const auto filterAreaBounds = filterArea.getLocalBounds();
const auto filterAreaWidth = static_cast<float>(filterArea.getWidth());
const auto filterAreaHeight = static_cast<float>(filterArea.getHeight());
const auto filterColWidth = filterAreaWidth * 0.2f;
@ -1478,9 +1346,7 @@ void CrystalizerEQAudioProcessorEditor::setupBody() {
setupHighBandLayout();
}
//endregion setupBody
//region setupLowBandLayout
void CrystalizerEQAudioProcessorEditor::setupLowBandLayout() {
const auto bounds = lowFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
@ -1546,9 +1412,6 @@ void CrystalizerEQAudioProcessorEditor::setupLowBandLayout() {
});
const auto modeBoxBounds = lowBandModeBox.getLocalBounds();
const auto modeBoxAreaWidth = static_cast<float>(modeBoxBounds.getWidth());
const auto modeBoxAreaHeight = static_cast<float>(modeBoxBounds.getHeight());
const auto modeBoxButtonColWidth = modeBoxAreaWidth / 4.0f;
const auto slopeH = lowBandSlopeSlider.getY() - lowBandModeBox.getY() + lowBandModeLabel.getFont().getHeight() / 2;
Layout::GridSpec lowBandModeBoxSpec{
@ -1569,9 +1432,7 @@ void CrystalizerEQAudioProcessorEditor::setupLowBandLayout() {
});
}
//endregion setupLowBandLayout
//region setupLowMidBandLayout
void CrystalizerEQAudioProcessorEditor::setupLowMidBandLayout() {
const auto bounds = lowMidFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
@ -1671,9 +1532,7 @@ void CrystalizerEQAudioProcessorEditor::setupMidBandLayout() {
});
}
//endregion setupMidBandLayout
//region setupHighMidBandLayout
void CrystalizerEQAudioProcessorEditor::setupHighMidBandLayout() {
const auto bounds = highMidFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
@ -1721,9 +1580,7 @@ void CrystalizerEQAudioProcessorEditor::setupHighMidBandLayout() {
});
}
//endregion setupHighMidBandLayout
//region setupHighBandLayout
void CrystalizerEQAudioProcessorEditor::setupHighBandLayout() {
const auto bounds = highFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
@ -1788,9 +1645,6 @@ void CrystalizerEQAudioProcessorEditor::setupHighBandLayout() {
});
const auto modeBoxBounds = lowBandModeBox.getLocalBounds();
const auto modeBoxAreaWidth = static_cast<float>(modeBoxBounds.getWidth());
const auto modeBoxAreaHeight = static_cast<float>(modeBoxBounds.getHeight());
const auto modeBoxButtonColWidth = modeBoxAreaWidth / 4.0f;
const auto slopeH = highBandSlopeSlider.getY() - highBandModeBox.getY() + highBandModeLabel.getFont().getHeight() /
2;
@ -1812,15 +1666,10 @@ void CrystalizerEQAudioProcessorEditor::setupHighBandLayout() {
});
}
//endregion setupHighBandLayout
//region setupFooter
void CrystalizerEQAudioProcessorEditor::setupFooter() {
const auto bounds = footerBar.getLocalBounds();
const auto footerWidth = static_cast<float>(bounds.getWidth());
const auto footerHeight = static_cast<float>(bounds.getHeight());
const auto refW = getReferenceCell()[0];
const auto refH = getReferenceCell()[1];
@ -1836,9 +1685,6 @@ void CrystalizerEQAudioProcessorEditor::setupFooter() {
});
const auto globalControlAreaBounds = globalControlArea.getLocalBounds();
const auto globalControlAreaWidth = static_cast<float>(globalControlArea.getWidth());
const auto globalControlAreaHeight = static_cast<float>(globalControlArea.getHeight());
const auto globalControlColWidth = globalControlAreaWidth / 3.0f;
const auto sliderRadius = outputSlider.getWidth() / 2.0f;
const auto sliderWidth = outputSlider.getWidth();
@ -1874,9 +1720,7 @@ void CrystalizerEQAudioProcessorEditor::setupFooter() {
});
}
//endregion setupFooter
//region getReferenceCell
juce::Array<float> CrystalizerEQAudioProcessorEditor::getReferenceCell() {
const auto bounds = midFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
@ -1890,18 +1734,14 @@ juce::Array<float> CrystalizerEQAudioProcessorEditor::getReferenceCell() {
return refCell;
}
//endregion getReferenceCell
//region processSamples
void SpectrumAnalyzer::processSamples() {
auto samples = audioFIFO.sendSamplesToEditor();
getFftFrame(samples);
}
//endregion processSamples
//region getFftFrame
void SpectrumAnalyzer::getFftFrame(juce::Array<float> &samples) {
const int needed = FFTSIZE - fftFrame.size();
if (needed <= 0)
@ -1925,18 +1765,14 @@ void SpectrumAnalyzer::getFftFrame(juce::Array<float> &samples) {
}
}
//endregion getFftFrame
//region applyWindowOnFftFrame
void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector<float> &fullFrame) {
if (fullFrame.size() != FFTSIZE) return;
window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE);
processWindowedFrame(fullFrame);
}
//endregion applyWindowOnFftFrame
//region processWindowedFrame
void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return;
fillFftDataFromFrame(windowedFrame);
@ -1947,11 +1783,7 @@ void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
renderValuesDb = getRenderValues();
}
//endregion processWindowedFrame
//region fillFftDataFromFrame
void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
for (int n = 0; n < FFTSIZE; ++n) {
fftData[n] = windowedFrame[n];
@ -1961,9 +1793,7 @@ void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
}
}
//endregion fillFftDataFromFrame
//region buildMagnitudeSpectrum
void SpectrumAnalyzer::buildMagnitudeSpectrum() {
for (int k = 0; k < BINS; ++k) {
float mag = fftData[k];
@ -1974,15 +1804,27 @@ void SpectrumAnalyzer::buildMagnitudeSpectrum() {
}
}
//endregion buildMagnitudeSpectrum
float getFrequencyWeighting(float freq) {
// Pre-Emphasis: +3dB pro Oktave (kompensiert Pink Noise Charakteristik)
// Basis: 1kHz = 0dB
const float referenceFreq = 1000.0f;
float octaves = std::log2(freq / referenceFreq);
float weightingDb = octaves * 3.0f; // 3dB pro Oktave
// Begrenzen auf sinnvolle Werte
weightingDb = juce::jlimit(-12.0f, 12.0f, weightingDb);
return weightingDb;
}
//region convertToDb
void SpectrumAnalyzer::convertToDb() {
for (int k = 0; k < magnitudes.size(); ++k) {
float mag = std::max(magnitudes[k], 1e-9f);
float dB = juce::Decibels::gainToDecibels(mag);
// SOFORT auf MINDB setzen wenn unter -60dB, nicht erst limitieren
float freq = k * deltaF;
float weighting = getFrequencyWeighting(freq);
dB += weighting;
if (dB < -60.0f) {
dB = MINDB;
}
@ -1991,18 +1833,14 @@ void SpectrumAnalyzer::convertToDb() {
}
}
//endregion convertToDb
//region applySmoothing
void SpectrumAnalyzer::applySmoothing() {
applyEMA();
applyFreqSmoothing();
applyPeakHoldAndFalloff();
}
//endregion applySmoothing
//region applyEMA
void SpectrumAnalyzer::applyEMA() {
for (int k = 0; k < magnitudesDb.size(); ++k) {
float smoothedVal = smoothingFactor * magnitudesDb[k] + (1 - smoothingFactor) * emaSmoothedMagnitudesDb[k];
@ -2010,9 +1848,7 @@ void SpectrumAnalyzer::applyEMA() {
}
}
//endregion applyEMA
//region applyFreqSmoothing
void SpectrumAnalyzer::applyFreqSmoothing() {
for (int k = 0; k < BINS; ++k) {
double freq = k * deltaF;
@ -2043,9 +1879,7 @@ void SpectrumAnalyzer::applyFreqSmoothing() {
}
}
//endregion applyFreqSmoothing
//region applyPeakHoldAndFalloff
void SpectrumAnalyzer::applyPeakHoldAndFalloff() {
std::vector<float> prevPeak = peakHoldMagnitudesDb;
for (int k = 0; k < BINS; ++k) {
@ -2053,17 +1887,12 @@ void SpectrumAnalyzer::applyPeakHoldAndFalloff() {
float prev = prevPeak[k];
peakHoldMagnitudesDb[k] = std::max(current, prev - FALLOFFRATE * DELTAT);
}
}
//endregion applyPeakHoldAndFalloff
//region getRenderValues
std::vector<float> SpectrumAnalyzer::getRenderValues() {
std::vector<float> renderValues = peakHoldMagnitudesDb;
return renderValues;
}
//endregion getRenderValues

View File

@ -131,7 +131,7 @@ public:
};
class CrystalizerEQAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer {
class CrystalizerEQAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer, private juce::AudioProcessorValueTreeState::Listener {
public:
CrystalizerEQAudioProcessorEditor(CrystalizerEQAudioProcessor &);
@ -184,6 +184,10 @@ public:
highBandFreqAttach, highBandSlopeAttach, highBandGainAttach, highBandQAttach,
inputAttach, outputAttach;
std::unique_ptr<ButtonAttach>
peak1BypassAttach, peak2BypassAttach, peak3BypassAttach,
crystalizeAttach, masterBypassAttach;
juce::Label titleLabel,
lowBandFreqLabel, lowBandSlopeLabel, lowBandGainLabel, lowBandQLabel, lowBandModeLabel,
low12, low24, low36, low48, lowdBOctLabel,
@ -233,8 +237,6 @@ public:
DesignSystem::ClickableTextEditor presetNameInput;
private:
// This reference is provided as a quick way for your editor to
// access the processor object that created it.
@ -378,5 +380,7 @@ private:
float getInterpolatedDb(float exactBin);
void parameterChanged(const juce::String& parameterID, float newValue) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CrystalizerEQAudioProcessorEditor)
};

File diff suppressed because it is too large Load Diff

View File

@ -18,66 +18,81 @@
class AudioFIFO {
public:
AudioFIFO();
~AudioFIFO();
juce::Array<float> sampleStack;
void loadSamplesToFIFO(const juce::AudioBuffer<float> &samples);
juce::Array<float> sendSamplesToEditor();
juce::SpinLock lock;
juce::SpinLock lock;
private:
};
class CrystalizerEQAudioProcessor : public juce::AudioProcessor , public juce::AudioProcessorValueTreeState::Listener
{
class CrystalizerEQAudioProcessor : public juce::AudioProcessor, public juce::AudioProcessorValueTreeState::Listener {
public:
//==============================================================================
CrystalizerEQAudioProcessor();
~CrystalizerEQAudioProcessor() override;
void parameterChanged (const juce::String& id, float newValue) override;
void parameterChanged(const juce::String &id, float newValue) override;
//==============================================================================
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
#ifndef JucePlugin_PreferredChannelConfigurations
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
#endif
#ifndef JucePlugin_PreferredChannelConfigurations
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
#endif
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
//==============================================================================
juce::AudioProcessorEditor* createEditor() override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override;
//==============================================================================
const juce::String getName() const override;
bool acceptsMidi() const override;
bool producesMidi() const override;
bool isMidiEffect() const override;
double getTailLengthSeconds() const override;
//==============================================================================
int getNumPrograms() override;
int getCurrentProgram() override;
void setCurrentProgram (int index) override;
const juce::String getProgramName (int index) override;
void changeProgramName (int index, const juce::String& newName) override;
void setCurrentProgram(int index) override;
const juce::String getProgramName(int index) override;
void changeProgramName(int index, const juce::String &newName) override;
//==============================================================================
void getStateInformation (juce::MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
void getStateInformation(juce::MemoryBlock &destData) override;
void setStateInformation(const void *data, int sizeInBytes) override;
// --------- Parameter (APVTS) ----------
static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "PARAMS", createParameterLayout() };
void setPresetName (const juce::String& s);
juce::AudioProcessorValueTreeState apvts{*this, nullptr, "PARAMS", createParameterLayout()};
void setPresetName(const juce::String &s);
juce::String getPresetName() const noexcept { return presetName; }
juce::StringArray getPresetNamesArray() const;
@ -95,37 +110,34 @@ public:
AudioFIFO audioFIFO;
private:
// --------- EQ-Kette ----------
using Gain = juce::dsp::Gain<float>;
using Filter = juce::dsp::IIR::Filter<float>;
using Chain = juce::dsp::ProcessorChain<Gain, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Gain>; // 0: LowCut (HP), 1: Peak, 2: HighCut (LP)
using Chain = juce::dsp::ProcessorChain<Gain, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Filter, Filter
, Filter, Filter, Filter, Filter, Gain>; // 0: LowCut (HP), 1: Peak, 2: HighCut (LP)
juce::Random noiseRandL, noiseRandR;
Chain leftChain, rightChain;
void updateFilters(); // Koeffizienten aus Parametern berechnen
void updateFilters();
juce::AudioBuffer<float> processMultiBand(juce::AudioBuffer<float>& lowBuf, juce::AudioBuffer<float>& highBuf);
juce::AudioBuffer<float> processMultiBand(juce::AudioBuffer<float> &lowBuf, juce::AudioBuffer<float> &highBuf);
using Coeff = juce::dsp::IIR::Coefficients<float>;
juce::dsp::LinkwitzRileyFilter<float> mbLowpass;
juce::dsp::LinkwitzRileyFilter<float> mbHighpass;
juce::dsp::IIR::Filter<float> mbHighPeak;
juce::dsp::WaveShaper<float> saturator;
juce::dsp::IIR::Filter<float> mbHighPeakL;
juce::dsp::IIR::Filter<float> mbHighPeakR;
juce::dsp::LinkwitzRileyFilter<float> mbLowpassL, mbLowpassR;
juce::dsp::LinkwitzRileyFilter<float> mbHighpassL, mbHighpassR;
juce::dsp::WaveShaper<float> saturatorL, saturatorR;
juce::String presetName { "Init" };
juce::String presetName{"Init"};
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CrystalizerEQAudioProcessor)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CrystalizerEQAudioProcessor)
};