From e239d3e955c38bf69ba062c5406f940ef48cc67c Mon Sep 17 00:00:00 2001 From: Legaeli Date: Wed, 26 Nov 2025 16:44:22 +0100 Subject: [PATCH] CrystalizerEQ v1.0.0 --- CrystalizerEQ/AXIOMDesignSystem.h | 278 +++++------- CrystalizerEQ/CMakeLists.txt | 2 +- CrystalizerEQ/PluginEditor.cpp | 317 ++++---------- CrystalizerEQ/PluginEditor.h | 10 +- CrystalizerEQ/PluginProcessor.cpp | 677 ++++++++++++++---------------- CrystalizerEQ/PluginProcessor.h | 82 ++-- 6 files changed, 553 insertions(+), 813 deletions(-) diff --git a/CrystalizerEQ/AXIOMDesignSystem.h b/CrystalizerEQ/AXIOMDesignSystem.h index 716c77b..7407335 100644 --- a/CrystalizerEQ/AXIOMDesignSystem.h +++ b/CrystalizerEQ/AXIOMDesignSystem.h @@ -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(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(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( 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(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 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(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(0.0f, 0.0f, (float)width, (float)height); - auto arrowWidth = arrowZone.getWidth() * 0.1f; - auto arrowHeight = arrowZone.getHeight() * 0.1f; + auto arrowZone = juce::Rectangle(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 &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& 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(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(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) 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)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: }; diff --git a/CrystalizerEQ/CMakeLists.txt b/CrystalizerEQ/CMakeLists.txt index c1540bf..9238fd7 100644 --- a/CrystalizerEQ/CMakeLists.txt +++ b/CrystalizerEQ/CMakeLists.txt @@ -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 diff --git a/CrystalizerEQ/PluginEditor.cpp b/CrystalizerEQ/PluginEditor.cpp index da4517b..2db607a 100644 --- a/CrystalizerEQ/PluginEditor.cpp +++ b/CrystalizerEQ/PluginEditor.cpp @@ -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(audioProcessor.apvts, "LowBandFreq", lowBandFreqSlider); lowBandSlopeAttach = std::make_unique(audioProcessor.apvts, "LowBandSlope", lowBandSlopeSlider); lowBandGainAttach = std::make_unique(audioProcessor.apvts, "LowBandGain", lowBandGainSlider); @@ -86,15 +74,18 @@ void CrystalizerEQAudioProcessorEditor::setupAttachments() { inputAttach = std::make_unique(audioProcessor.apvts, "InputGain", inputSlider); outputAttach = std::make_unique(audioProcessor.apvts, "OutputGain", outputSlider); + + peak1BypassAttach = std::make_unique(audioProcessor.apvts, "Peak1Bypass", peak1BypassButton); + peak2BypassAttach = std::make_unique(audioProcessor.apvts, "Peak2Bypass", peak2BypassButton); + peak3BypassAttach = std::make_unique(audioProcessor.apvts, "Peak3Bypass", peak3BypassButton); + crystalizeAttach = std::make_unique(audioProcessor.apvts, "CrystalizeButton", crystalizeButton); + masterBypassAttach = std::make_unique(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(); svgToggleButtonLookAndFeel = std::make_unique(); @@ -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(); 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(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(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(analyzerWidth) * resolutionMultiplier; for (int i = 0; i < numPoints; ++i) { - // Berechne X-Position float x = (i / static_cast(numPoints)) * analyzerWidth; - // Berechne Frequenz (logarithmisch) float normalizedX = i / static_cast(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(analyzerRect.getY()), static_cast(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 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 a else Spacing::setUiScale(Spacing::uiScaleMode::XL); } -//endregion scalePluginWindow -//region setupMainGrid void CrystalizerEQAudioProcessorEditor::setupMainGrid(juce::Rectangle area) { const float headerHeight = static_cast(getHeight()) * 0.1f; const float footerHeight = static_cast(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 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(analyzerArea.getWidth()); - const auto analyzerAreaHeight = static_cast(analyzerArea.getHeight()); - const auto filterAreaBounds = filterArea.getLocalBounds(); const auto filterAreaWidth = static_cast(filterArea.getWidth()); - const auto filterAreaHeight = static_cast(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(bounds.getWidth()); @@ -1546,9 +1412,6 @@ void CrystalizerEQAudioProcessorEditor::setupLowBandLayout() { }); const auto modeBoxBounds = lowBandModeBox.getLocalBounds(); - const auto modeBoxAreaWidth = static_cast(modeBoxBounds.getWidth()); - const auto modeBoxAreaHeight = static_cast(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(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(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(bounds.getWidth()); @@ -1788,9 +1645,6 @@ void CrystalizerEQAudioProcessorEditor::setupHighBandLayout() { }); const auto modeBoxBounds = lowBandModeBox.getLocalBounds(); - const auto modeBoxAreaWidth = static_cast(modeBoxBounds.getWidth()); - const auto modeBoxAreaHeight = static_cast(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(bounds.getWidth()); - const auto footerHeight = static_cast(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(globalControlArea.getWidth()); - const auto globalControlAreaHeight = static_cast(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 CrystalizerEQAudioProcessorEditor::getReferenceCell() { const auto bounds = midFilterArea.getLocalBounds(); const auto areaWidth = static_cast(bounds.getWidth()); @@ -1890,18 +1734,14 @@ juce::Array 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 &samples) { const int needed = FFTSIZE - fftFrame.size(); if (needed <= 0) @@ -1925,18 +1765,14 @@ void SpectrumAnalyzer::getFftFrame(juce::Array &samples) { } } -//endregion getFftFrame -//region applyWindowOnFftFrame void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector &fullFrame) { if (fullFrame.size() != FFTSIZE) return; window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE); processWindowedFrame(fullFrame); } -//endregion applyWindowOnFftFrame -//region processWindowedFrame void SpectrumAnalyzer::processWindowedFrame(std::vector &windowedFrame) { if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return; fillFftDataFromFrame(windowedFrame); @@ -1947,11 +1783,7 @@ void SpectrumAnalyzer::processWindowedFrame(std::vector &windowedFrame) { renderValuesDb = getRenderValues(); } -//endregion processWindowedFrame - - -//region fillFftDataFromFrame void SpectrumAnalyzer::fillFftDataFromFrame(std::vector &windowedFrame) { for (int n = 0; n < FFTSIZE; ++n) { fftData[n] = windowedFrame[n]; @@ -1961,9 +1793,7 @@ void SpectrumAnalyzer::fillFftDataFromFrame(std::vector &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 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 SpectrumAnalyzer::getRenderValues() { std::vector renderValues = peakHoldMagnitudesDb; return renderValues; } - -//endregion getRenderValues diff --git a/CrystalizerEQ/PluginEditor.h b/CrystalizerEQ/PluginEditor.h index 413ffaf..70f8ecb 100644 --- a/CrystalizerEQ/PluginEditor.h +++ b/CrystalizerEQ/PluginEditor.h @@ -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 + 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) }; diff --git a/CrystalizerEQ/PluginProcessor.cpp b/CrystalizerEQ/PluginProcessor.cpp index 29fa9b6..f473670 100644 --- a/CrystalizerEQ/PluginProcessor.cpp +++ b/CrystalizerEQ/PluginProcessor.cpp @@ -9,338 +9,337 @@ #include "PluginProcessor.h" #include "PluginEditor.h" -// ==================== Parameter-Layout ==================== + juce::AudioProcessorValueTreeState::ParameterLayout CrystalizerEQAudioProcessor::createParameterLayout() { - std::vector> params; + std::vector > params; //LOW-BAND - params.push_back (std::make_unique( + params.push_back(std::make_unique( "LowBandFreq", "LowBand Freq", juce::NormalisableRange(20.f, 20000.f, 1.f, 0.5f), 30.f)); - params.push_back (std::make_unique("LowBandGain", "LowBand Gain", juce::NormalisableRange(-48.f, 48.f, 0.1f), 0.f)); + params.push_back(std::make_unique("LowBandGain", "LowBand Gain", + juce::NormalisableRange(-48.f, 48.f, 0.1f), + 0.f)); - params.push_back (std::make_unique("LowBandQ", "LowBand Q", juce::NormalisableRange(0.1f, 10.f, 0.01f), 1.f)); + params.push_back(std::make_unique("LowBandQ", "LowBand Q", + juce::NormalisableRange(0.1f, 10.f, 0.01f), + 1.f)); - params.push_back (std::make_unique( - "LowBandSlope", "LowBand Slope", // Anzeigename - juce::StringArray { "12", "24", "36", "48"}, 1)); + params.push_back(std::make_unique( + "LowBandSlope", "LowBand Slope", // Anzeigename + juce::StringArray{"12", "24", "36", "48"}, 1)); - params.push_back (std::make_unique( - "LowBandModes", // Parameter-ID - "Low Band Modes", // Anzeigename - juce::StringArray { "Off", "Cut", "Shelf", "Bell" }, // Einträge - 2)); + params.push_back(std::make_unique( + "LowBandModes", // Parameter-ID + "Low Band Modes", // Anzeigename + juce::StringArray{"Off", "Cut", "Shelf", "Bell"}, // Einträge + 2)); //PEAK 1 - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak1Freq", "Peak1 Freq", juce::NormalisableRange(100.f, 1500.f, 1.f, 0.5f), 250.f)); - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak1Gain", "Peak1 Gain (dB)", juce::NormalisableRange(-48.f, 48.f, 0.1f), 0.f)); - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak1Q", "Peak1 Q", juce::NormalisableRange(0.1f, 10.f, 0.01f), 1.f)); - params.push_back (std::make_unique("Peak1Bypass", "Peak1 Bypass", false)); + params.push_back(std::make_unique("Peak1Bypass", "Peak1 Bypass", false)); //PEAK 2 - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak2Freq", "Peak2 Freq", juce::NormalisableRange(400.f, 6000.f, 1.f, 0.5f), 1500.f)); - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak2Gain", "Peak2 Gain (dB)", juce::NormalisableRange(-48.f, 48.f, 0.1f), 0.f)); - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak2Q", "Peak2 Q", juce::NormalisableRange(0.1f, 10.f, 0.01f), 1.f)); - params.push_back (std::make_unique("Peak2Bypass", "Peak2 Bypass", false)); + params.push_back(std::make_unique("Peak2Bypass", "Peak2 Bypass", false)); //PEAK 3 - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak3Freq", "Peak3 Freq", juce::NormalisableRange(1000.f, 16000.f, 1.f, 0.5f), 6000.f)); - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak3Gain", "Peak3 Gain (dB)", juce::NormalisableRange(-48.f, 48.f, 0.1f), 0.f)); - params.push_back (std::make_unique( + params.push_back(std::make_unique( "Peak3Q", "Peak3 Q", juce::NormalisableRange(0.1f, 10.f, 0.01f), 1.f)); - params.push_back (std::make_unique("Peak3Bypass", "Peak3 Bypass", false)); + params.push_back(std::make_unique("Peak3Bypass", "Peak3 Bypass", false)); //HIGH BAND - params.push_back (std::make_unique( + params.push_back(std::make_unique( "HighBandFreq", "HighBand Freq", juce::NormalisableRange(20.f, 20000.f, 1.f, 0.5f), 17000.f)); - params.push_back (std::make_unique("HighBandGain", "HighBand Gain", juce::NormalisableRange(-48.f, 48.f, 0.1f), 0.f)); + params.push_back(std::make_unique("HighBandGain", "HighBand Gain", + juce::NormalisableRange(-48.f, 48.f, 0.1f), + 0.f)); - params.push_back (std::make_unique("HighBandQ", "HighBand Q", juce::NormalisableRange(0.1f, 10.f, 0.01f), 1.f)); + params.push_back(std::make_unique("HighBandQ", "HighBand Q", + juce::NormalisableRange(0.1f, 10.f, 0.01f), + 1.f)); - params.push_back (std::make_unique( - "HighBandSlope", "HighBand Slope", // Anzeigename - juce::StringArray { "12", "24", "36", "48"}, 1)); + params.push_back(std::make_unique( + "HighBandSlope", "HighBand Slope", // Anzeigename + juce::StringArray{"12", "24", "36", "48"}, 1)); - params.push_back (std::make_unique( - "HighBandModes", // Parameter-ID - "High Band Modes", // Anzeigename - juce::StringArray { "Off", "Cut", "Shelf", "Bell" }, // Einträge - 2)); - - // - - params.push_back (std::make_unique("InputGain", "Input Gain", juce::NormalisableRange(-30.f, 30.0f, 0.1f), 0.0f)); - - params.push_back (std::make_unique("OutputGain", "Output Gain", juce::NormalisableRange(-30.f, 30.0f, 0.1f), 0.0f)); - - params.push_back (std::make_unique("CrystalizeButton", "Crystalize Button", false)); + params.push_back(std::make_unique( + "HighBandModes", // Parameter-ID + "High Band Modes", // Anzeigename + juce::StringArray{"Off", "Cut", "Shelf", "Bell"}, // Einträge + 2)); + params.push_back(std::make_unique("InputGain", "Input Gain", + juce::NormalisableRange(-30.f, 30.0f, 0.1f), + 0.0f)); - params.push_back (std::make_unique("MasterBypass", "MasterBypass", false)); + params.push_back(std::make_unique("OutputGain", "Output Gain", + juce::NormalisableRange(-30.f, 30.0f, 0.1f), + 0.0f)); - return { params.begin(), params.end() }; + params.push_back(std::make_unique("CrystalizeButton", "Crystalize Button", false)); + + + params.push_back(std::make_unique("MasterBypass", "MasterBypass", false)); + + return {params.begin(), params.end()}; } //============================================================================== CrystalizerEQAudioProcessor::CrystalizerEQAudioProcessor() #ifndef JucePlugin_PreferredChannelConfigurations - : AudioProcessor (BusesProperties() - #if ! JucePlugin_IsMidiEffect - #if ! JucePlugin_IsSynth - .withInput ("Input", juce::AudioChannelSet::stereo(), true) - #endif - .withOutput ("Output", juce::AudioChannelSet::stereo(), true) - #endif - ) + : AudioProcessor(BusesProperties() +#if ! JucePlugin_IsMidiEffect +#if ! JucePlugin_IsSynth + .withInput("Input", juce::AudioChannelSet::stereo(), true) +#endif + .withOutput("Output", juce::AudioChannelSet::stereo(), true) +#endif + ) #endif { - } CrystalizerEQAudioProcessor::~CrystalizerEQAudioProcessor() = default; //============================================================================== -const juce::String CrystalizerEQAudioProcessor::getName() const -{ +const juce::String CrystalizerEQAudioProcessor::getName() const { return JucePlugin_Name; } -bool CrystalizerEQAudioProcessor::acceptsMidi() const -{ - #if JucePlugin_WantsMidiInput +bool CrystalizerEQAudioProcessor::acceptsMidi() const { +#if JucePlugin_WantsMidiInput return true; - #else +#else return false; - #endif +#endif } -bool CrystalizerEQAudioProcessor::producesMidi() const -{ - #if JucePlugin_ProducesMidiOutput +bool CrystalizerEQAudioProcessor::producesMidi() const { +#if JucePlugin_ProducesMidiOutput return true; - #else +#else return false; - #endif +#endif } -bool CrystalizerEQAudioProcessor::isMidiEffect() const -{ - #if JucePlugin_IsMidiEffect +bool CrystalizerEQAudioProcessor::isMidiEffect() const { +#if JucePlugin_IsMidiEffect return true; - #else +#else return false; - #endif +#endif } -double CrystalizerEQAudioProcessor::getTailLengthSeconds() const -{ +double CrystalizerEQAudioProcessor::getTailLengthSeconds() const { return 0.0; } -int CrystalizerEQAudioProcessor::getNumPrograms() -{ - return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, - // so this should be at least 1, even if you're not really implementing programs. +int CrystalizerEQAudioProcessor::getNumPrograms() { + return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, + // so this should be at least 1, even if you're not really implementing programs. } -int CrystalizerEQAudioProcessor::getCurrentProgram() -{ +int CrystalizerEQAudioProcessor::getCurrentProgram() { return 0; } -void CrystalizerEQAudioProcessor::setCurrentProgram (int index) -{ +void CrystalizerEQAudioProcessor::setCurrentProgram(int index) { } -const juce::String CrystalizerEQAudioProcessor::getProgramName (int index) -{ +const juce::String CrystalizerEQAudioProcessor::getProgramName(int index) { return {}; } -void CrystalizerEQAudioProcessor::changeProgramName (int index, const juce::String& newName) -{ +void CrystalizerEQAudioProcessor::changeProgramName(int index, const juce::String &newName) { } -AudioFIFO::AudioFIFO(){ - +AudioFIFO::AudioFIFO() { } AudioFIFO::~AudioFIFO() { - } - - //============================================================================== -void CrystalizerEQAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) -{ +void CrystalizerEQAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { // Use this method as the place to do any pre-playback // initialisation that you need.. juce::dsp::ProcessSpec spec; - spec.sampleRate = sampleRate; - spec.maximumBlockSize = static_cast (samplesPerBlock); - spec.numChannels = static_cast (getTotalNumOutputChannels()); + spec.sampleRate = sampleRate; + spec.maximumBlockSize = static_cast(samplesPerBlock); + spec.numChannels = static_cast(getTotalNumOutputChannels()); - mbLowpass.prepare(spec); - mbHighpass.prepare(spec); - mbLowpass.reset(); - mbHighpass.reset(); + mbLowpassL.prepare(spec); + mbLowpassR.prepare(spec); - mbHighPeak.prepare(spec); - mbHighPeak.reset(); + mbHighpassL.prepare(spec); + mbHighpassR.prepare(spec); - saturator.prepare(spec); - saturator.functionToUse = [] (float x) { + mbLowpassL.reset(); + mbLowpassR.reset(); + + mbHighpassL.reset(); + mbHighpassR.reset(); + + mbHighPeakL.prepare(spec); + mbHighPeakR.prepare(spec); + mbHighPeakL.reset(); + mbHighPeakR.reset(); + + + saturatorL.prepare(spec); + + + saturatorL.functionToUse = [](float x) { return std::tanh(x); }; - saturator.reset(); + saturatorL.reset(); - leftChain.prepare (spec); - rightChain.prepare (spec); + saturatorR.prepare(spec); + saturatorR.functionToUse = [](float x) { + return std::tanh(x); + }; + saturatorR.reset(); + + spec.numChannels = static_cast(getTotalNumOutputChannels()); + leftChain.prepare(spec); + rightChain.prepare(spec); leftChain.reset(); rightChain.reset(); - updateFilters(); // initiale Koeffizienten + updateFilters(); } -static void setCoeffs(juce::dsp::IIR::Filter& f, - juce::dsp::IIR::Coefficients::Ptr c) -{ - f.coefficients = c; // ok - // oder: *f.coefficients = *c; +static void setCoeffs(juce::dsp::IIR::Filter &f, + juce::dsp::IIR::Coefficients::Ptr c) { + f.coefficients = c; } - -void CrystalizerEQAudioProcessor::updateFilters() -{ - +void CrystalizerEQAudioProcessor::updateFilters() { const auto sr = getSampleRate(); - const auto lowFreq = apvts.getRawParameterValue("LowBandFreq")->load(); + const auto lowFreq = apvts.getRawParameterValue("LowBandFreq")->load(); const auto lowSlope = static_cast(apvts.getRawParameterValue("LowBandSlope")->load()); - const auto lowGdB = apvts.getRawParameterValue("LowBandGain")->load(); // falls angelegt - const auto lowQ = apvts.getRawParameterValue("LowBandQ")->load(); + const auto lowGdB = apvts.getRawParameterValue("LowBandGain")->load(); // falls angelegt + const auto lowQ = apvts.getRawParameterValue("LowBandQ")->load(); const int lowBandModes = - static_cast(apvts.getRawParameterValue("LowBandModes")->load()); + static_cast(apvts.getRawParameterValue("LowBandModes")->load()); - const auto peak1F = apvts.getRawParameterValue("Peak1Freq")->load(); + const auto peak1F = apvts.getRawParameterValue("Peak1Freq")->load(); const auto peak1GdB = apvts.getRawParameterValue("Peak1Gain")->load(); - const auto peak1Q = apvts.getRawParameterValue("Peak1Q")->load(); + const auto peak1Q = apvts.getRawParameterValue("Peak1Q")->load(); const auto peak1Bypass = apvts.getRawParameterValue("Peak1Bypass")->load(); - const auto peak2F = apvts.getRawParameterValue("Peak2Freq")->load(); + const auto peak2F = apvts.getRawParameterValue("Peak2Freq")->load(); const auto peak2GdB = apvts.getRawParameterValue("Peak2Gain")->load(); - const auto peak2Q = apvts.getRawParameterValue("Peak2Q")->load(); + const auto peak2Q = apvts.getRawParameterValue("Peak2Q")->load(); const auto peak2Bypass = apvts.getRawParameterValue("Peak2Bypass")->load(); - const auto peak3F = apvts.getRawParameterValue("Peak3Freq")->load(); + const auto peak3F = apvts.getRawParameterValue("Peak3Freq")->load(); const auto peak3GdB = apvts.getRawParameterValue("Peak3Gain")->load(); - const auto peak3Q = apvts.getRawParameterValue("Peak3Q")->load(); + const auto peak3Q = apvts.getRawParameterValue("Peak3Q")->load(); const auto peak3Bypass = apvts.getRawParameterValue("Peak3Bypass")->load(); - const auto highFreq = apvts.getRawParameterValue("HighBandFreq")->load(); + const auto highFreq = apvts.getRawParameterValue("HighBandFreq")->load(); const auto highSlope = static_cast(apvts.getRawParameterValue("HighBandSlope")->load()); - const auto highGdB = apvts.getRawParameterValue("HighBandGain")->load(); // falls angelegt - const auto highQ = apvts.getRawParameterValue("HighBandQ")->load(); + const auto highGdB = apvts.getRawParameterValue("HighBandGain")->load(); // falls angelegt + const auto highQ = apvts.getRawParameterValue("HighBandQ")->load(); const int highBandModes = - static_cast(apvts.getRawParameterValue("HighBandModes")->load()); + static_cast(apvts.getRawParameterValue("HighBandModes")->load()); const auto inputGdB = apvts.getRawParameterValue("InputGain")->load(); const auto outputGdB = apvts.getRawParameterValue("OutputGain")->load(); - auto peak1 = juce::dsp::IIR::Coefficients::makePeakFilter(sr, peak1F, peak1Q, - juce::Decibels::decibelsToGain(peak1GdB)); - auto peak2 = juce::dsp::IIR::Coefficients::makePeakFilter(sr, peak2F, peak2Q, - juce::Decibels::decibelsToGain(peak2GdB)); - auto peak3 = juce::dsp::IIR::Coefficients::makePeakFilter(sr, peak3F, peak3Q, - juce::Decibels::decibelsToGain(peak3GdB)); + auto peak1 = juce::dsp::IIR::Coefficients::makePeakFilter(sr, peak1F, peak1Q, + juce::Decibels::decibelsToGain(peak1GdB)); + auto peak2 = juce::dsp::IIR::Coefficients::makePeakFilter(sr, peak2F, peak2Q, + juce::Decibels::decibelsToGain(peak2GdB)); + auto peak3 = juce::dsp::IIR::Coefficients::makePeakFilter(sr, peak3F, peak3Q, + juce::Decibels::decibelsToGain(peak3GdB)); const auto crystalized = static_cast(apvts.getRawParameterValue("CrystalizeButton")->load()); - - - juce::dsp::IIR::Coefficients::Ptr lowBand; juce::dsp::IIR::Coefficients::Ptr highBand; + leftChain.get<0>().setGainDecibels(inputGdB); + rightChain.get<0>().setGainDecibels(inputGdB); + setCoeffs(leftChain.get<5>(), peak1); + setCoeffs(rightChain.get<5>(), peak1); + setCoeffs(leftChain.get<6>(), peak2); + setCoeffs(rightChain.get<6>(), peak2); + setCoeffs(leftChain.get<7>(), peak3); + setCoeffs(rightChain.get<7>(), peak3); - // links - leftChain.get<0>().setGainDecibels (inputGdB); - rightChain.get<0>().setGainDecibels (inputGdB); - - - - - setCoeffs (leftChain.get<5>(), peak1); - setCoeffs (rightChain.get<5>(), peak1); - setCoeffs (leftChain.get<6>(), peak2); - setCoeffs (rightChain.get<6>(), peak2); - setCoeffs (leftChain.get<7>(), peak3); - setCoeffs (rightChain.get<7>(), peak3); - - const auto crystalizedShelf = juce::dsp::IIR::Coefficients::makeHighShelf(sr, 12000.0f, 1.0f, juce::Decibels::decibelsToGain(4.0f)); + const auto crystalizedShelf = juce::dsp::IIR::Coefficients::makeHighShelf( + sr, 12000.0f, 1.0f, juce::Decibels::decibelsToGain(4.0f)); setCoeffs(leftChain.get<12>(), crystalizedShelf); setCoeffs(rightChain.get<12>(), crystalizedShelf); - const auto crystalizedBell = juce::dsp::IIR::Coefficients::makePeakFilter(sr, 10000.0f, 1.0f, juce::Decibels::decibelsToGain(2.0f)); + const auto crystalizedBell = juce::dsp::IIR::Coefficients::makePeakFilter( + sr, 10000.0f, 1.0f, juce::Decibels::decibelsToGain(2.0f)); setCoeffs(leftChain.get<13>(), crystalizedBell); setCoeffs(rightChain.get<13>(), crystalizedBell); - leftChain.get<14>().setGainDecibels (outputGdB); - rightChain.get<14>().setGainDecibels (outputGdB); - - + leftChain.get<14>().setGainDecibels(outputGdB); + rightChain.get<14>().setGainDecibels(outputGdB); if (peak1Bypass) { @@ -374,61 +373,57 @@ void CrystalizerEQAudioProcessor::updateFilters() case 0: leftChain.setBypassed<1>(true); rightChain.setBypassed<1>(true); - leftChain.setBypassed<2>(true); - rightChain.setBypassed<2>(true); - leftChain.setBypassed<3>(true); - rightChain.setBypassed<3>(true); - leftChain.setBypassed<4>(true); - rightChain.setBypassed<4>(true); + leftChain.setBypassed<2>(true); + rightChain.setBypassed<2>(true); + leftChain.setBypassed<3>(true); + rightChain.setBypassed<3>(true); + leftChain.setBypassed<4>(true); + rightChain.setBypassed<4>(true); - break; + break; case 1: { const auto q = lowQ; lowBand = juce::dsp::IIR::Coefficients::makeHighPass(sr, lowFreq, q); - setCoeffs(leftChain .get<1>(), lowBand); + setCoeffs(leftChain.get<1>(), lowBand); setCoeffs(rightChain.get<1>(), lowBand); - leftChain .setBypassed<1>(false); + leftChain.setBypassed<1>(false); rightChain.setBypassed<1>(false); - setCoeffs (leftChain.get<2>(), lowBand); + setCoeffs(leftChain.get<2>(), lowBand); setCoeffs(rightChain.get<2>(), lowBand); - setCoeffs (leftChain.get<3>(), lowBand); + setCoeffs(leftChain.get<3>(), lowBand); setCoeffs(rightChain.get<3>(), lowBand); - setCoeffs (leftChain.get<4>(), lowBand); + setCoeffs(leftChain.get<4>(), lowBand); setCoeffs(rightChain.get<4>(), lowBand); - //HIER SLOPE IMPLEMENTIEREN - leftChain .setBypassed<2>(lowSlope < 2); + leftChain.setBypassed<2>(lowSlope < 2); rightChain.setBypassed<2>(lowSlope < 2); - leftChain .setBypassed<3>(lowSlope < 3); + leftChain.setBypassed<3>(lowSlope < 3); rightChain.setBypassed<3>(lowSlope < 3); - leftChain .setBypassed<4>(lowSlope < 4); + leftChain.setBypassed<4>(lowSlope < 4); rightChain.setBypassed<4>(lowSlope < 4); break; } case 2: - // Q steuert die „Güte“/Steilheit des Übergangs - lowBand = juce::dsp::IIR::Coefficients::makeLowShelf(sr, lowFreq, lowQ, lowGainLin); - setCoeffs(leftChain .get<1>(), lowBand); - setCoeffs(rightChain.get<1>(), lowBand); - leftChain .setBypassed<1>(false); - rightChain.setBypassed<1>(false); - - break; - case 3: // Bell (Glocke – optional, falls du das Low-Band als Bell nutzen willst) - lowBand = juce::dsp::IIR::Coefficients::makePeakFilter(sr, lowFreq, lowQ, lowGainLin); - setCoeffs(leftChain .get<1>(), lowBand); + lowBand = juce::dsp::IIR::Coefficients::makeLowShelf(sr, lowFreq, lowQ, lowGainLin); + setCoeffs(leftChain.get<1>(), lowBand); setCoeffs(rightChain.get<1>(), lowBand); - leftChain .setBypassed<1>(false); + leftChain.setBypassed<1>(false); + rightChain.setBypassed<1>(false); + + break; + case 3: + lowBand = juce::dsp::IIR::Coefficients::makePeakFilter(sr, lowFreq, lowQ, lowGainLin); + setCoeffs(leftChain.get<1>(), lowBand); + setCoeffs(rightChain.get<1>(), lowBand); + leftChain.setBypassed<1>(false); rightChain.setBypassed<1>(false); break; - } - //HIGH-BAND const float highGainHin = juce::Decibels::decibelsToGain(highGdB); @@ -436,107 +431,97 @@ void CrystalizerEQAudioProcessor::updateFilters() case 0: leftChain.setBypassed<8>(true); rightChain.setBypassed<8>(true); - leftChain.setBypassed<9>(true); - rightChain.setBypassed<9>(true); - leftChain.setBypassed<10>(true); - rightChain.setBypassed<10>(true); - leftChain.setBypassed<11>(true); - rightChain.setBypassed<11>(true); + leftChain.setBypassed<9>(true); + rightChain.setBypassed<9>(true); + leftChain.setBypassed<10>(true); + rightChain.setBypassed<10>(true); + leftChain.setBypassed<11>(true); + rightChain.setBypassed<11>(true); - break; + break; case 1: { const auto q = highQ; highBand = juce::dsp::IIR::Coefficients::makeLowPass(sr, highFreq, q); - setCoeffs(leftChain .get<8>(), highBand); + setCoeffs(leftChain.get<8>(), highBand); setCoeffs(rightChain.get<8>(), highBand); - leftChain .setBypassed<8>(false); + leftChain.setBypassed<8>(false); rightChain.setBypassed<8>(false); - setCoeffs (leftChain.get<9>(), highBand); + setCoeffs(leftChain.get<9>(), highBand); setCoeffs(rightChain.get<9>(), highBand); - setCoeffs (leftChain.get<10>(), highBand); + setCoeffs(leftChain.get<10>(), highBand); setCoeffs(rightChain.get<10>(), highBand); - setCoeffs (leftChain.get<11>(), highBand); + setCoeffs(leftChain.get<11>(), highBand); setCoeffs(rightChain.get<11>(), highBand); - //HIER SLOPE IMPLEMENTIEREN - leftChain .setBypassed<9>(highSlope < 2); + leftChain.setBypassed<9>(highSlope < 2); rightChain.setBypassed<9>(highSlope < 2); - leftChain .setBypassed<10>(highSlope < 3); + leftChain.setBypassed<10>(highSlope < 3); rightChain.setBypassed<10>(highSlope < 3); - leftChain .setBypassed<11>(highSlope < 4); + leftChain.setBypassed<11>(highSlope < 4); rightChain.setBypassed<11>(highSlope < 4); break; } case 2: - // Q steuert die „Güte“/Steilheit des Übergangs - highBand = juce::dsp::IIR::Coefficients::makeHighShelf(sr, highFreq, highQ, highGainHin); - setCoeffs(leftChain .get<8>(), highBand); - setCoeffs(rightChain.get<8>(), highBand); - leftChain .setBypassed<8>(false); - rightChain.setBypassed<8>(false); - - break; - case 3: // Bell (Glocke – optional, falls du das Low-Band als Bell nutzen willst) - highBand = juce::dsp::IIR::Coefficients::makePeakFilter(sr, highFreq, highQ, highGainHin); - setCoeffs(leftChain .get<8>(), highBand); + highBand = juce::dsp::IIR::Coefficients::makeHighShelf(sr, highFreq, highQ, highGainHin); + setCoeffs(leftChain.get<8>(), highBand); setCoeffs(rightChain.get<8>(), highBand); - leftChain .setBypassed<8>(false); + leftChain.setBypassed<8>(false); rightChain.setBypassed<8>(false); break; + case 3: + highBand = juce::dsp::IIR::Coefficients::makePeakFilter(sr, highFreq, highQ, highGainHin); + setCoeffs(leftChain.get<8>(), highBand); + setCoeffs(rightChain.get<8>(), highBand); + leftChain.setBypassed<8>(false); + rightChain.setBypassed<8>(false); + break; } if (!crystalized) { - leftChain .setBypassed<12>(true); + leftChain.setBypassed<12>(true); rightChain.setBypassed<12>(true); - leftChain .setBypassed<13>(true); + leftChain.setBypassed<13>(true); rightChain.setBypassed<13>(true); } else { - leftChain .setBypassed<12>(false); + leftChain.setBypassed<12>(false); rightChain.setBypassed<12>(false); - leftChain .setBypassed<13>(false); + leftChain.setBypassed<13>(false); rightChain.setBypassed<13>(false); } - - - lowCutActive = (lowBandModes == 1); highCutActive = (highBandModes == 1); - } juce::String CrystalizerEQAudioProcessor::savePresetToFile() const { const auto nameInput = getPresetName(); - auto appData = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory); auto presetFolder = appData.getChildFile("AXIOM") - .getChildFile("CrystalizerEQ") - .getChildFile("Presets"); + .getChildFile("CrystalizerEQ") + .getChildFile("Presets"); presetFolder.createDirectory(); auto file = presetFolder.getNonexistentChildFile(nameInput, ".xml"); - juce::ValueTree preset ("Preset"); + juce::ValueTree preset("Preset"); preset.setProperty("name", nameInput, nullptr); - for (auto* p : getParameters()) { + for (auto *p: getParameters()) { if (p == nullptr) continue; - if (auto* ranged = dynamic_cast(p)) - { - if (ranged->getParameterID() == "MasterBypass") - {continue;} + if (auto *ranged = dynamic_cast(p)) { + if (ranged->getParameterID() == "MasterBypass") { continue; } - juce::ValueTree param ("Param"); + juce::ValueTree param("Param"); param.setProperty("id", ranged->getParameterID(), nullptr); param.setProperty("value", ranged->getValue(), nullptr); @@ -544,38 +529,35 @@ juce::String CrystalizerEQAudioProcessor::savePresetToFile() const { } } - std::unique_ptr xml (preset.createXml()); + std::unique_ptr xml(preset.createXml()); xml->writeToFile(juce::File(file), {}); return file.getFileNameWithoutExtension(); } -void CrystalizerEQAudioProcessor::loadPreset(const juce::String &preset){ - +void CrystalizerEQAudioProcessor::loadPreset(const juce::String &preset) { auto appData = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory); auto presetFolder = appData.getChildFile("AXIOM") - .getChildFile("CrystalizerEQ") - .getChildFile("Presets"); + .getChildFile("CrystalizerEQ") + .getChildFile("Presets"); auto files = presetFolder.findChildFiles(juce::File::findFiles, false, "*.xml"); - for (const auto& f : files) { + for (const auto &f: files) { if (f.getFileName() != preset) { continue; } - std::unique_ptr xml (juce::XmlDocument::parse(f)); + std::unique_ptr xml(juce::XmlDocument::parse(f)); if (xml == nullptr) return; - for (auto* p : getParameters()) { + for (auto *p: getParameters()) { if (p == nullptr) continue; - if (auto* ranged = dynamic_cast(p)) { - if (auto* child = xml->getFirstChildElement()) - { - while (child != nullptr) - { + if (auto *ranged = dynamic_cast(p)) { + if (auto *child = xml->getFirstChildElement()) { + while (child != nullptr) { juce::String id = child->getStringAttribute("id"); if (id == ranged->getParameterID()) { @@ -593,19 +575,19 @@ void CrystalizerEQAudioProcessor::loadPreset(const juce::String &preset){ } } -void CrystalizerEQAudioProcessor::deletePreset(const juce::String &preset) const{ +void CrystalizerEQAudioProcessor::deletePreset(const juce::String &preset) const { auto appData = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory); auto presetFolder = appData.getChildFile("AXIOM") - .getChildFile("CrystalizerEQ") - .getChildFile("Presets"); + .getChildFile("CrystalizerEQ") + .getChildFile("Presets"); juce::Array files = presetFolder.findChildFiles( - juce::File::findFiles, // nur Dateien (nicht Ordner) - false, // nicht rekursiv (kein Durchsuchen von Unterordnern) - "*.xml" // Pattern: alle Dateien - ); + juce::File::findFiles, + false, + "*.xml" + ); - for (const auto& f : files) { + for (const auto &f: files) { if (f.getFileName() != preset) { continue; } @@ -613,42 +595,36 @@ void CrystalizerEQAudioProcessor::deletePreset(const juce::String &preset) const } } -void CrystalizerEQAudioProcessor::resetAllParameters() const{ - for (auto* p : getParameters()) { +void CrystalizerEQAudioProcessor::resetAllParameters() const { + for (auto *p: getParameters()) { if (p == nullptr) continue; - if (auto* ranged = dynamic_cast(p)) - { - const float def = ranged->getDefaultValue(); // normalisiert [0..1] + if (auto *ranged = dynamic_cast(p)) { + const float def = ranged->getDefaultValue(); ranged->beginChangeGesture(); ranged->setValueNotifyingHost(def); ranged->endChangeGesture(); } } - - } -void CrystalizerEQAudioProcessor::parameterChanged (const juce::String& id, float v) -{ - +void CrystalizerEQAudioProcessor::parameterChanged(const juce::String &id, float v) { } -juce::StringArray CrystalizerEQAudioProcessor::getPresetNamesArray() const{ - +juce::StringArray CrystalizerEQAudioProcessor::getPresetNamesArray() const { juce::StringArray presetNames = {"Init"}; auto appData = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory); auto presetFolder = appData.getChildFile("AXIOM") - .getChildFile("CrystalizerEQ") - .getChildFile("Presets"); + .getChildFile("CrystalizerEQ") + .getChildFile("Presets"); juce::Array files = presetFolder.findChildFiles( - juce::File::findFiles, // nur Dateien (nicht Ordner) - false, // nicht rekursiv (kein Durchsuchen von Unterordnern) - "*.xml" // Pattern: alle Dateien - ); + juce::File::findFiles, + false, + "*.xml" + ); - for (const auto& f : files) { + for (const auto &f: files) { const auto presetName = f.getFileNameWithoutExtension(); presetNames.add(presetName); } @@ -656,49 +632,44 @@ juce::StringArray CrystalizerEQAudioProcessor::getPresetNamesArray() const{ return presetNames; } -void CrystalizerEQAudioProcessor::releaseResources() -{ +void CrystalizerEQAudioProcessor::releaseResources() { // When playback stops, you can use this as an opportunity to free up any // spare memory, etc. } #ifndef JucePlugin_PreferredChannelConfigurations -bool CrystalizerEQAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const -{ - #if JucePlugin_IsMidiEffect +bool CrystalizerEQAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const { +#if JucePlugin_IsMidiEffect juce::ignoreUnused (layouts); return true; - #else +#else // This is the place where you check if the layout is supported. // In this template code we only support mono or stereo. // Some plugin hosts, such as certain GarageBand versions, will only // load plugins that support stereo bus layouts. if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() - && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) + && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) return false; // This checks if the input layout matches the output layout - #if ! JucePlugin_IsSynth +#if ! JucePlugin_IsSynth if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; - #endif +#endif return true; - #endif +#endif } #endif -void CrystalizerEQAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) -{ - - - juce::ignoreUnused (midiMessages); +void CrystalizerEQAudioProcessor::processBlock(juce::AudioBuffer &buffer, juce::MidiBuffer &midiMessages) { + juce::ignoreUnused(midiMessages); juce::ScopedNoDenormals noDenormals; - auto totalNumInputChannels = getTotalNumInputChannels(); + auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels(); for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) - buffer.clear (i, 0, buffer.getNumSamples()); + buffer.clear(i, 0, buffer.getNumSamples()); juce::AudioBuffer lowBuf, highBuf; @@ -715,94 +686,94 @@ void CrystalizerEQAudioProcessor::processBlock (juce::AudioBuffer& buffer } - - updateFilters(); - - juce::dsp::AudioBlock block (buffer); + juce::dsp::AudioBlock block(buffer); - auto leftBlock = block.getSingleChannelBlock (0); - auto rightBlock = block.getSingleChannelBlock (1); + auto leftBlock = block.getSingleChannelBlock(0); + auto rightBlock = block.getSingleChannelBlock(1); - juce::dsp::ProcessContextReplacing leftCtx (leftBlock); + juce::dsp::ProcessContextReplacing leftCtx(leftBlock); juce::dsp::ProcessContextReplacing rightCtx(rightBlock); - leftChain.process (leftCtx); - rightChain.process (rightCtx); + leftChain.process(leftCtx); + rightChain.process(rightCtx); audioFIFO.loadSamplesToFIFO(buffer); - } -juce::AudioBuffer CrystalizerEQAudioProcessor::processMultiBand(juce::AudioBuffer& lowBuf, juce::AudioBuffer& highBuf) { - +juce::AudioBuffer CrystalizerEQAudioProcessor::processMultiBand(juce::AudioBuffer &lowBuf, + juce::AudioBuffer &highBuf) { const auto sr = getSampleRate(); float fc = 10000.0f; - fc = juce::jlimit(20.0f, 0.49f * (float)sr, fc); + fc = juce::jlimit(20.0f, 0.49f * (float) sr, fc); auto peakGain = juce::Decibels::gainToDecibels(1.0f); - auto lp = Coeff::makeLowPass (sr, fc, 0.7071f); + auto lp = Coeff::makeLowPass(sr, fc, 0.7071f); auto hp = Coeff::makeHighPass(sr, fc, 0.7071f); - mbLowpass.setType(juce::dsp::LinkwitzRileyFilterType::lowpass); - mbLowpass.setCutoffFrequency(fc); - - mbHighpass.setType(juce::dsp::LinkwitzRileyFilterType::highpass); - mbHighpass.setCutoffFrequency(fc); + mbLowpassL.setType(juce::dsp::LinkwitzRileyFilterType::lowpass); + mbLowpassL.setCutoffFrequency(fc); + mbLowpassR.setType(juce::dsp::LinkwitzRileyFilterType::lowpass); + mbLowpassR.setCutoffFrequency(fc); + mbHighpassL.setType(juce::dsp::LinkwitzRileyFilterType::highpass); + mbHighpassL.setCutoffFrequency(fc); + mbHighpassR.setType(juce::dsp::LinkwitzRileyFilterType::highpass); + mbHighpassR.setCutoffFrequency(fc); { juce::dsp::AudioBlock lowBlock(lowBuf); - auto leftLowBlock = lowBlock.getSingleChannelBlock (0); - auto rightLowBlock = lowBlock.getSingleChannelBlock (1); + auto leftLowBlock = lowBlock.getSingleChannelBlock(0); + auto rightLowBlock = lowBlock.getSingleChannelBlock(1); juce::dsp::ProcessContextReplacing leftLowCtx(leftLowBlock); juce::dsp::ProcessContextReplacing rightLowCtx(rightLowBlock); - mbLowpass.process(leftLowCtx); - mbLowpass.process(rightLowCtx); - } + mbLowpassL.process(leftLowCtx); + mbLowpassR.process(rightLowCtx); - { + } { //HIGH-BAND PROCESSING juce::dsp::AudioBlock highBlock(highBuf); - auto leftHighBlock = highBlock.getSingleChannelBlock (0); - auto rightHighBlock = highBlock.getSingleChannelBlock (1); + auto leftHighBlock = highBlock.getSingleChannelBlock(0); + auto rightHighBlock = highBlock.getSingleChannelBlock(1); juce::dsp::ProcessContextReplacing leftHighCtx(leftHighBlock); juce::dsp::ProcessContextReplacing rightHighCtx(rightHighBlock); - mbHighpass.process(leftHighCtx); - mbHighpass.process(rightHighCtx); + mbHighpassL.process(leftHighCtx); + mbHighpassR.process(rightHighCtx); //WHITE NOISE ON HIGH-BAND - const int numSamples = highBuf.getNumSamples(); + const int numSamples = highBuf.getNumSamples(); const int numChannels = highBuf.getNumChannels(); - const float rms = highBuf.getRMSLevel(0, 0, numSamples); // oder Mittelwert über beide Kanäle - const float dyn = juce::jlimit(0.0f, 1.0f, rms * 2.0f); // einfacher Kompressor - const float gain = dyn * juce::Decibels::decibelsToGain(.5f); + const float rmsL = highBuf.getRMSLevel(0, 0, numSamples); + const float rmsR = highBuf.getRMSLevel(1, 0, numSamples); + const float rms = (rmsL + rmsR) * 0.5f; + + const float dyn = juce::jlimit(0.0f, 1.0f, rms * 2.0f); + const float gain = dyn * juce::Decibels::decibelsToGain(-3.0f); for (int ch = 0; ch < numChannels; ++ch) { - auto* write = highBuf.getWritePointer (ch); - auto& rng = (ch == 0 ? noiseRandL : noiseRandR); + auto *write = highBuf.getWritePointer(ch); + auto &rng = (ch == 0 ? noiseRandL : noiseRandR); - for (int n = 0; n < numSamples; ++n) - { + for (int n = 0; n < numSamples; ++n) { // rng.nextFloat() ∈ [0,1) → in [-1,1) const float white = 2.0f * rng.nextFloat() - 1.0f; - write[n] += white * gain; // ERSETZEN des Host-Inputs + write[n] += white * gain; // ERSETZEN des Host-Inputs } } - saturator.process(leftHighCtx); - saturator.process(rightHighCtx); + saturatorL.process(leftHighCtx); + saturatorR.process(rightHighCtx); } @@ -811,79 +782,65 @@ juce::AudioBuffer CrystalizerEQAudioProcessor::processMultiBand(juce::Aud const int numCh = out.getNumChannels(); const int numSm = out.getNumSamples(); + const float highBandGain = juce::Decibels::decibelsToGain(3.0f); for (int ch = 0; ch < numCh; ++ch) - out.addFrom(ch, 0, highBuf, ch, 0, numSm, 1.0f); + out.addFrom(ch, 0, highBuf, ch, 0, numSm, highBandGain); return out; - } - -void CrystalizerEQAudioProcessor::setPresetName (const juce::String& name) -{ +void CrystalizerEQAudioProcessor::setPresetName(const juce::String &name) { presetName = name; } //============================================================================== -bool CrystalizerEQAudioProcessor::hasEditor() const -{ +bool CrystalizerEQAudioProcessor::hasEditor() const { return true; // (change this to false if you choose to not supply an editor) } -juce::AudioProcessorEditor* CrystalizerEQAudioProcessor::createEditor() -{ - return new CrystalizerEQAudioProcessorEditor (*this); +juce::AudioProcessorEditor *CrystalizerEQAudioProcessor::createEditor() { + return new CrystalizerEQAudioProcessorEditor(*this); } //============================================================================== -void CrystalizerEQAudioProcessor::getStateInformation (juce::MemoryBlock& destData) -{ +void CrystalizerEQAudioProcessor::getStateInformation(juce::MemoryBlock &destData) { // You should use this method to store your parameters in the memory block. // You could do that either as raw data, or use the XML or ValueTree classes // as intermediaries to make it easy to save and load complex data. - - } -void CrystalizerEQAudioProcessor::setStateInformation (const void* data, int sizeInBytes) -{ +void CrystalizerEQAudioProcessor::setStateInformation(const void *data, int sizeInBytes) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. } //============================================================================== // This creates new instances of the plugin.. -juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() -{ +juce::AudioProcessor * JUCE_CALLTYPE createPluginFilter() { return new CrystalizerEQAudioProcessor(); } - void AudioFIFO::loadSamplesToFIFO(const juce::AudioBuffer &samples) { const int numSamples = samples.getNumSamples(); - const float* channelData = samples.getReadPointer(0); + const float *channelData = samples.getReadPointer(0); - const juce::SpinLock::ScopedLockType guard(lock); // <— NEU + const juce::SpinLock::ScopedLockType guard(lock); // <— NEU sampleStack.ensureStorageAllocated(sampleStack.size() + numSamples); - for (int i = 0; i < numSamples; ++i) - { + for (int i = 0; i < numSamples; ++i) { sampleStack.add(channelData[i]); } - } -juce::Array AudioFIFO::sendSamplesToEditor(){ - +juce::Array AudioFIFO::sendSamplesToEditor() { const juce::SpinLock::ScopedLockType guard(lock); juce::Array copiedSamples = sampleStack; sampleStack.clear(); return copiedSamples; } - diff --git a/CrystalizerEQ/PluginProcessor.h b/CrystalizerEQ/PluginProcessor.h index cdf92c7..5db86dd 100644 --- a/CrystalizerEQ/PluginProcessor.h +++ b/CrystalizerEQ/PluginProcessor.h @@ -18,66 +18,81 @@ class AudioFIFO { public: AudioFIFO(); + ~AudioFIFO(); juce::Array sampleStack; + void loadSamplesToFIFO(const juce::AudioBuffer &samples); + juce::Array 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&, juce::MidiBuffer&) override; + void processBlock(juce::AudioBuffer &, 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; using Filter = juce::dsp::IIR::Filter; - using Chain = juce::dsp::ProcessorChain; // 0: LowCut (HP), 1: Peak, 2: HighCut (LP) + using Chain = juce::dsp::ProcessorChain; // 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 processMultiBand(juce::AudioBuffer& lowBuf, juce::AudioBuffer& highBuf); + juce::AudioBuffer processMultiBand(juce::AudioBuffer &lowBuf, juce::AudioBuffer &highBuf); using Coeff = juce::dsp::IIR::Coefficients; - juce::dsp::LinkwitzRileyFilter mbLowpass; - juce::dsp::LinkwitzRileyFilter mbHighpass; - juce::dsp::IIR::Filter mbHighPeak; - juce::dsp::WaveShaper saturator; + + juce::dsp::IIR::Filter mbHighPeakL; + juce::dsp::IIR::Filter mbHighPeakR; + + juce::dsp::LinkwitzRileyFilter mbLowpassL, mbLowpassR; + juce::dsp::LinkwitzRileyFilter mbHighpassL, mbHighpassR; + juce::dsp::WaveShaper 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) }; - -