CrystalizerEQ v1.0.0

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,257 +9,265 @@
#include "PluginProcessor.h"
#include "PluginEditor.h"
// ==================== Parameter-Layout ====================
juce::AudioProcessorValueTreeState::ParameterLayout
CrystalizerEQAudioProcessor::createParameterLayout() {
std::vector<std::unique_ptr<juce::RangedAudioParameter>> params;
std::vector<std::unique_ptr<juce::RangedAudioParameter> > params;
//LOW-BAND
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"LowBandFreq", "LowBand Freq",
juce::NormalisableRange<float>(20.f, 20000.f, 1.f, 0.5f), 30.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>("LowBandGain", "LowBand Gain", juce::NormalisableRange<float>(-48.f, 48.f, 0.1f), 0.f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("LowBandGain", "LowBand Gain",
juce::NormalisableRange<float>(-48.f, 48.f, 0.1f),
0.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>("LowBandQ", "LowBand Q", juce::NormalisableRange<float>(0.1f, 10.f, 0.01f), 1.f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("LowBandQ", "LowBand Q",
juce::NormalisableRange<float>(0.1f, 10.f, 0.01f),
1.f));
params.push_back (std::make_unique<juce::AudioParameterChoice>(
params.push_back(std::make_unique<juce::AudioParameterChoice>(
"LowBandSlope", "LowBand Slope", // Anzeigename
juce::StringArray { "12", "24", "36", "48"}, 1));
juce::StringArray{"12", "24", "36", "48"}, 1));
params.push_back (std::make_unique<juce::AudioParameterChoice>(
params.push_back(std::make_unique<juce::AudioParameterChoice>(
"LowBandModes", // Parameter-ID
"Low Band Modes", // Anzeigename
juce::StringArray { "Off", "Cut", "Shelf", "Bell" }, // Einträge
juce::StringArray{"Off", "Cut", "Shelf", "Bell"}, // Einträge
2));
//PEAK 1
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak1Freq", "Peak1 Freq",
juce::NormalisableRange<float>(100.f, 1500.f, 1.f, 0.5f), 250.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak1Gain", "Peak1 Gain (dB)",
juce::NormalisableRange<float>(-48.f, 48.f, 0.1f), 0.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak1Q", "Peak1 Q",
juce::NormalisableRange<float>(0.1f, 10.f, 0.01f), 1.f));
params.push_back (std::make_unique<juce::AudioParameterBool>("Peak1Bypass", "Peak1 Bypass", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("Peak1Bypass", "Peak1 Bypass", false));
//PEAK 2
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak2Freq", "Peak2 Freq",
juce::NormalisableRange<float>(400.f, 6000.f, 1.f, 0.5f), 1500.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak2Gain", "Peak2 Gain (dB)",
juce::NormalisableRange<float>(-48.f, 48.f, 0.1f), 0.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak2Q", "Peak2 Q",
juce::NormalisableRange<float>(0.1f, 10.f, 0.01f), 1.f));
params.push_back (std::make_unique<juce::AudioParameterBool>("Peak2Bypass", "Peak2 Bypass", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("Peak2Bypass", "Peak2 Bypass", false));
//PEAK 3
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak3Freq", "Peak3 Freq",
juce::NormalisableRange<float>(1000.f, 16000.f, 1.f, 0.5f), 6000.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak3Gain", "Peak3 Gain (dB)",
juce::NormalisableRange<float>(-48.f, 48.f, 0.1f), 0.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"Peak3Q", "Peak3 Q",
juce::NormalisableRange<float>(0.1f, 10.f, 0.01f), 1.f));
params.push_back (std::make_unique<juce::AudioParameterBool>("Peak3Bypass", "Peak3 Bypass", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("Peak3Bypass", "Peak3 Bypass", false));
//HIGH BAND
params.push_back (std::make_unique<juce::AudioParameterFloat>(
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"HighBandFreq", "HighBand Freq",
juce::NormalisableRange<float>(20.f, 20000.f, 1.f, 0.5f), 17000.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>("HighBandGain", "HighBand Gain", juce::NormalisableRange<float>(-48.f, 48.f, 0.1f), 0.f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("HighBandGain", "HighBand Gain",
juce::NormalisableRange<float>(-48.f, 48.f, 0.1f),
0.f));
params.push_back (std::make_unique<juce::AudioParameterFloat>("HighBandQ", "HighBand Q", juce::NormalisableRange<float>(0.1f, 10.f, 0.01f), 1.f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("HighBandQ", "HighBand Q",
juce::NormalisableRange<float>(0.1f, 10.f, 0.01f),
1.f));
params.push_back (std::make_unique<juce::AudioParameterChoice>(
params.push_back(std::make_unique<juce::AudioParameterChoice>(
"HighBandSlope", "HighBand Slope", // Anzeigename
juce::StringArray { "12", "24", "36", "48"}, 1));
juce::StringArray{"12", "24", "36", "48"}, 1));
params.push_back (std::make_unique<juce::AudioParameterChoice>(
params.push_back(std::make_unique<juce::AudioParameterChoice>(
"HighBandModes", // Parameter-ID
"High Band Modes", // Anzeigename
juce::StringArray { "Off", "Cut", "Shelf", "Bell" }, // Einträge
juce::StringArray{"Off", "Cut", "Shelf", "Bell"}, // Einträge
2));
//
params.push_back (std::make_unique<juce::AudioParameterFloat>("InputGain", "Input Gain", juce::NormalisableRange<float>(-30.f, 30.0f, 0.1f), 0.0f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("InputGain", "Input Gain",
juce::NormalisableRange<float>(-30.f, 30.0f, 0.1f),
0.0f));
params.push_back (std::make_unique<juce::AudioParameterFloat>("OutputGain", "Output Gain", juce::NormalisableRange<float>(-30.f, 30.0f, 0.1f), 0.0f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("OutputGain", "Output Gain",
juce::NormalisableRange<float>(-30.f, 30.0f, 0.1f),
0.0f));
params.push_back (std::make_unique<juce::AudioParameterBool>("CrystalizeButton", "Crystalize Button", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("CrystalizeButton", "Crystalize Button", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("MasterBypass", "MasterBypass", false));
params.push_back (std::make_unique<juce::AudioParameterBool>("MasterBypass", "MasterBypass", false));
return { params.begin(), params.end() };
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()
{
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<juce::uint32> (samplesPerBlock);
spec.numChannels = static_cast<juce::uint32> (getTotalNumOutputChannels());
spec.maximumBlockSize = static_cast<juce::uint32>(samplesPerBlock);
spec.numChannels = static_cast<juce::uint32>(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<juce::uint32>(getTotalNumOutputChannels());
leftChain.prepare(spec);
rightChain.prepare(spec);
leftChain.reset();
rightChain.reset();
updateFilters(); // initiale Koeffizienten
updateFilters();
}
static void setCoeffs(juce::dsp::IIR::Filter<float>& f,
juce::dsp::IIR::Coefficients<float>::Ptr c)
{
f.coefficients = c; // ok
// oder: *f.coefficients = *c;
static void setCoeffs(juce::dsp::IIR::Filter<float> &f,
juce::dsp::IIR::Coefficients<float>::Ptr c) {
f.coefficients = c;
}
void CrystalizerEQAudioProcessor::updateFilters()
{
void CrystalizerEQAudioProcessor::updateFilters() {
const auto sr = getSampleRate();
const auto lowFreq = apvts.getRawParameterValue("LowBandFreq")->load();
@ -307,40 +315,31 @@ void CrystalizerEQAudioProcessor::updateFilters()
const auto crystalized = static_cast<int>(apvts.getRawParameterValue("CrystalizeButton")->load());
juce::dsp::IIR::Coefficients<float>::Ptr lowBand;
juce::dsp::IIR::Coefficients<float>::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<float>::makeHighShelf(sr, 12000.0f, 1.0f, juce::Decibels::decibelsToGain(4.0f));
const auto crystalizedShelf = juce::dsp::IIR::Coefficients<float>::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<float>::makePeakFilter(sr, 10000.0f, 1.0f, juce::Decibels::decibelsToGain(2.0f));
const auto crystalizedBell = juce::dsp::IIR::Coefficients<float>::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) {
@ -385,50 +384,46 @@ void CrystalizerEQAudioProcessor::updateFilters()
case 1: {
const auto q = lowQ;
lowBand = juce::dsp::IIR::Coefficients<float>::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<float>::makeLowShelf(sr, lowFreq, lowQ, lowGainLin);
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);
break;
case 3: // Bell (Glocke optional, falls du das Low-Band als Bell nutzen willst)
case 3:
lowBand = juce::dsp::IIR::Coefficients<float>::makePeakFilter(sr, lowFreq, lowQ, lowGainLin);
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);
break;
}
//HIGH-BAND
const float highGainHin = juce::Decibels::decibelsToGain(highGdB);
@ -447,74 +442,66 @@ void CrystalizerEQAudioProcessor::updateFilters()
case 1: {
const auto q = highQ;
highBand = juce::dsp::IIR::Coefficients<float>::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<float>::makeHighShelf(sr, highFreq, highQ, highGainHin);
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);
break;
case 3: // Bell (Glocke optional, falls du das Low-Band als Bell nutzen willst)
case 3:
highBand = juce::dsp::IIR::Coefficients<float>::makePeakFilter(sr, highFreq, highQ, highGainHin);
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);
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")
@ -525,18 +512,16 @@ juce::String CrystalizerEQAudioProcessor::savePresetToFile() const {
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<juce::RangedAudioParameter*>(p))
{
if (ranged->getParameterID() == "MasterBypass")
{continue;}
if (auto *ranged = dynamic_cast<juce::RangedAudioParameter *>(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,14 +529,13 @@ juce::String CrystalizerEQAudioProcessor::savePresetToFile() const {
}
}
std::unique_ptr<juce::XmlElement> xml (preset.createXml());
std::unique_ptr<juce::XmlElement> 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")
@ -559,23 +543,21 @@ void CrystalizerEQAudioProcessor::loadPreset(const juce::String &preset){
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<juce::XmlElement> xml (juce::XmlDocument::parse(f));
std::unique_ptr<juce::XmlElement> 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<juce::RangedAudioParameter*>(p)) {
if (auto* child = xml->getFirstChildElement())
{
while (child != nullptr)
{
if (auto *ranged = dynamic_cast<juce::RangedAudioParameter *>(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");
juce::Array<juce::File> 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,29 +595,23 @@ 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<juce::RangedAudioParameter*>(p))
{
const float def = ranged->getDefaultValue(); // normalisiert [0..1]
if (auto *ranged = dynamic_cast<juce::RangedAudioParameter *>(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")
@ -643,12 +619,12 @@ juce::StringArray CrystalizerEQAudioProcessor::getPresetNamesArray() const{
.getChildFile("Presets");
juce::Array<juce::File> 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,19 +632,17 @@ 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
@ -678,27 +652,24 @@ bool CrystalizerEQAudioProcessor::isBusesLayoutSupported (const BusesLayout& lay
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<float>& buffer, juce::MidiBuffer& midiMessages)
{
juce::ignoreUnused (midiMessages);
void CrystalizerEQAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages) {
juce::ignoreUnused(midiMessages);
juce::ScopedNoDenormals noDenormals;
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<float> lowBuf, highBuf;
@ -715,68 +686,66 @@ void CrystalizerEQAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer
}
updateFilters();
juce::dsp::AudioBlock<float> block (buffer);
juce::dsp::AudioBlock<float> 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<float> leftCtx (leftBlock);
juce::dsp::ProcessContextReplacing<float> leftCtx(leftBlock);
juce::dsp::ProcessContextReplacing<float> rightCtx(rightBlock);
leftChain.process (leftCtx);
rightChain.process (rightCtx);
leftChain.process(leftCtx);
rightChain.process(rightCtx);
audioFIFO.loadSamplesToFIFO(buffer);
}
juce::AudioBuffer<float> CrystalizerEQAudioProcessor::processMultiBand(juce::AudioBuffer<float>& lowBuf, juce::AudioBuffer<float>& highBuf) {
juce::AudioBuffer<float> CrystalizerEQAudioProcessor::processMultiBand(juce::AudioBuffer<float> &lowBuf,
juce::AudioBuffer<float> &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<float> 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<float> leftLowCtx(leftLowBlock);
juce::dsp::ProcessContextReplacing<float> rightLowCtx(rightLowBlock);
mbLowpass.process(leftLowCtx);
mbLowpass.process(rightLowCtx);
}
mbLowpassL.process(leftLowCtx);
mbLowpassR.process(rightLowCtx);
{
} {
//HIGH-BAND PROCESSING
juce::dsp::AudioBlock<float> 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<float> leftHighCtx(leftHighBlock);
juce::dsp::ProcessContextReplacing<float> rightHighCtx(rightHighBlock);
mbHighpass.process(leftHighCtx);
mbHighpass.process(rightHighCtx);
mbHighpassL.process(leftHighCtx);
mbHighpassR.process(rightHighCtx);
//WHITE NOISE ON HIGH-BAND
@ -784,25 +753,27 @@ juce::AudioBuffer<float> CrystalizerEQAudioProcessor::processMultiBand(juce::Aud
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
}
}
saturator.process(leftHighCtx);
saturator.process(rightHighCtx);
saturatorL.process(leftHighCtx);
saturatorR.process(rightHighCtx);
}
@ -811,79 +782,65 @@ juce::AudioBuffer<float> 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<float> &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
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<float> AudioFIFO::sendSamplesToEditor(){
juce::Array<float> AudioFIFO::sendSamplesToEditor() {
const juce::SpinLock::ScopedLockType guard(lock);
juce::Array<float> copiedSamples = sampleStack;
sampleStack.clear();
return copiedSamples;
}

View File

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