Compare commits

...

2 Commits

Author SHA1 Message Date
Legaeli
e239d3e955 CrystalizerEQ v1.0.0 2025-11-26 16:44:22 +01:00
Legaeli
f270e4613e Almost finished 2025-11-26 14:31:10 +01:00
8 changed files with 1544 additions and 1822 deletions

View File

@ -38,7 +38,6 @@ namespace AXIOM {
static inline const juce::Colour SURFACEBYPASS = juce::Colour::fromRGB(42, 42, 42); // #2A2A2A static inline const juce::Colour SURFACEBYPASS = juce::Colour::fromRGB(42, 42, 42); // #2A2A2A
static inline const juce::Colour MUTEDTEXTBYPASS = juce::Colour::fromRGB(230, 230, 230); // #E6E6E6 static inline const juce::Colour MUTEDTEXTBYPASS = juce::Colour::fromRGB(230, 230, 230); // #E6E6E6
static inline const juce::Colour ACCENTWEAKBYPASS = juce::Colour::fromRGB(82, 210, 210); // #52D2D2 static inline const juce::Colour ACCENTWEAKBYPASS = juce::Colour::fromRGB(82, 210, 210); // #52D2D2
}; };
struct Spacing { struct Spacing {
@ -180,7 +179,6 @@ namespace AXIOM {
float ratio = 1.125f; float ratio = 1.125f;
float sizeFor(int step) const noexcept { 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)); return basePx * std::pow(ratio, static_cast<float>(step));
} }
}; };
@ -291,12 +289,14 @@ namespace AXIOM {
L, L,
XL XL
}; };
enum class StrokeMode { enum class StrokeMode {
Hairline, Hairline,
Thin, Thin,
Regular, Regular,
Bold Bold
}; };
enum class CornerStyle { enum class CornerStyle {
Rounded, Rounded,
Cut, Cut,
@ -313,6 +313,7 @@ namespace AXIOM {
static constexpr float MINSTROKE = 1.0f; static constexpr float MINSTROKE = 1.0f;
static constexpr float MAXSTROKE = 3.0f; static constexpr float MAXSTROKE = 3.0f;
}; };
inline static Scale scale{}; inline static Scale scale{};
static constexpr int getRadiusUnits(RadiusMode mode) { static constexpr int getRadiusUnits(RadiusMode mode) {
@ -359,7 +360,6 @@ namespace AXIOM {
width = juce::jlimit(scale.MINSTROKE, scale.MAXSTROKE, width); width = juce::jlimit(scale.MINSTROKE, scale.MAXSTROKE, width);
return width; return width;
}; };
}; };
struct Shadows { struct Shadows {
@ -369,22 +369,27 @@ namespace AXIOM {
static constexpr float MINALPHA = 0.04f; static constexpr float MINALPHA = 0.04f;
static constexpr float MAXALPHA = 0.95f; static constexpr float MAXALPHA = 0.95f;
static constexpr float MUTEDMUL = 0.5f; static constexpr float MUTEDMUL = 0.5f;
struct AlphaToken { struct AlphaToken {
enum class Role { enum class Role {
Text, Icon, Surface, Overlay, Stroke, FocusGlow, Disabled, InteractiveFill Text, Icon, Surface, Overlay, Stroke, FocusGlow, Disabled, InteractiveFill
}; };
enum class State { enum class State {
Rest, Hover, Active, Checked, Dragged, Disabled Rest, Hover, Active, Checked, Dragged, Disabled
}; };
enum class Emphasis { enum class Emphasis {
High, Medium, Low, Muted High, Medium, Low, Muted
}; };
Role role; Role role;
State state; State state;
Emphasis emphasis; Emphasis emphasis;
}; };
static AlphaToken getAlphaToken(AlphaToken::Role role, AlphaToken::State state, AlphaToken::Emphasis emphasis) { static AlphaToken getAlphaToken(AlphaToken::Role role, AlphaToken::State state,
AlphaToken::Emphasis emphasis) {
return AlphaToken{role, state, emphasis}; return AlphaToken{role, state, emphasis};
}; };
@ -527,20 +532,16 @@ namespace AXIOM {
};*/ };*/
struct Components { struct Components {
class BypassButtonLookAndFeel : public juce::LookAndFeel_V4 { class BypassButtonLookAndFeel : public juce::LookAndFeel_V4 {
public: public:
BypassButtonLookAndFeel() BypassButtonLookAndFeel() {
{ svgXml = juce::XmlDocument::parse(BinaryData::bypass_icon_svg);
svgXml = juce::XmlDocument::parse(BinaryData::bypass_icon_svg);} }
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button, void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override bool shouldDrawButtonAsDown) override {
{
bool isHovered = button.isMouseOverOrDragging(); bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState(); bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled(); bool isEnabled = button.isEnabled();
@ -554,16 +555,12 @@ namespace AXIOM {
float bypassOpacity = 1.0f; float bypassOpacity = 1.0f;
if (svgXml != nullptr) { if (svgXml != nullptr) {
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable!
auto icon = juce::Drawable::createFromSVG(*svgXml); auto icon = juce::Drawable::createFromSVG(*svgXml);
if (icon != nullptr) if (icon != nullptr) {
{
juce::Colour colour; juce::Colour colour;
if (!isEnabled) { if (!isEnabled) {
colour = isToggled ? Colours::SURFACEBYPASS : Colours::ACCENTWEAKCOLOUR; colour = isToggled ? Colours::SURFACEBYPASS : Colours::ACCENTWEAKCOLOUR;
} } else if (isHovered) {
else if (isHovered) {
colour = isToggled ? Colours::SURFACEHOVER : Colours::ACCENTHOVER; colour = isToggled ? Colours::SURFACEHOVER : Colours::ACCENTHOVER;
} else { } else {
colour = isToggled colour = isToggled
@ -571,14 +568,11 @@ namespace AXIOM {
: Colours::ACCENTCOLOUR; : Colours::ACCENTCOLOUR;
} }
// Icon zeichnen
icon->replaceColour(juce::Colours::black, colour); icon->replaceColour(juce::Colours::black, colour);
icon->drawWithin(g, iconBounds, icon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} }
} }
} }
private: private:
@ -587,19 +581,15 @@ namespace AXIOM {
class PresetMenuButtonLookAndFeel : public juce::LookAndFeel_V4 { class PresetMenuButtonLookAndFeel : public juce::LookAndFeel_V4 {
public: public:
PresetMenuButtonLookAndFeel() PresetMenuButtonLookAndFeel() {
{
presetMenuButton = juce::XmlDocument::parse(BinaryData::preset_menu_icon_svg); presetMenuButton = juce::XmlDocument::parse(BinaryData::preset_menu_icon_svg);
} }
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button, void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override bool shouldDrawButtonAsDown) override {
{
bool isHovered = button.isMouseOverOrDragging(); bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState(); bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled();
auto bounds = button.getLocalBounds().toFloat(); auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 1.5f; auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 1.5f;
auto hoverFactor = isHovered ? 1.05f : 1.0f; auto hoverFactor = isHovered ? 1.05f : 1.0f;
@ -607,8 +597,6 @@ namespace AXIOM {
auto iconBounds = juce::Rectangle<float>(iconSize * hoverFactor, iconSize * hoverFactor) auto iconBounds = juce::Rectangle<float>(iconSize * hoverFactor, iconSize * hoverFactor)
.withCentre(bounds.getCentre()); .withCentre(bounds.getCentre());
float bypassOpacity = 1.0f;
if (presetMenuButton != nullptr) { if (presetMenuButton != nullptr) {
auto menuIcon = juce::Drawable::createFromSVG(*presetMenuButton); auto menuIcon = juce::Drawable::createFromSVG(*presetMenuButton);
juce::Colour colour; juce::Colour colour;
@ -620,13 +608,10 @@ namespace AXIOM {
: Colours::BACKGROUNDCOLOUR; : Colours::BACKGROUNDCOLOUR;
} }
// Icon zeichnen
menuIcon->replaceColour(juce::Colours::black, colour); menuIcon->replaceColour(juce::Colours::black, colour);
menuIcon->drawWithin(g, iconBounds, menuIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, 1.0f); juce::RectanglePlacement::centred, 1.0f);
} }
} }
private: private:
@ -646,12 +631,9 @@ namespace AXIOM {
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button, void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override { bool shouldDrawButtonAsDown) override {
const bool isHovered = button.isMouseOverOrDragging(); const bool isHovered = button.isMouseOverOrDragging();
const bool isToggled = button.getToggleState();
auto bounds = button.getLocalBounds().toFloat(); auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 0.5f; auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 2 * 0.95f;
auto hoverFactor = isHovered ? 1.05f : 1.0f; auto hoverFactor = isHovered ? 1.05f : 1.0f;
@ -662,39 +644,31 @@ namespace AXIOM {
auto activeIcon = juce::Drawable::createFromSVG(*activeSvg); auto activeIcon = juce::Drawable::createFromSVG(*activeSvg);
if (passiveIcon != nullptr && activeIcon != nullptr) { if (passiveIcon != nullptr && activeIcon != nullptr) {
activeIcon->drawWithin(g, iconBounds, activeIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, activeIconOpacity); juce::RectanglePlacement::centred, activeIconOpacity);
passiveIcon->drawWithin(g, iconBounds, passiveIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, passiveIconOpacity); juce::RectanglePlacement::centred, passiveIconOpacity);
} }
} }
private: private:
std::unique_ptr<juce::XmlElement> passiveSvg; std::unique_ptr<juce::XmlElement> passiveSvg;
std::unique_ptr<juce::XmlElement> activeSvg; std::unique_ptr<juce::XmlElement> activeSvg;
}; };
class lowBandButtonLookAndFeel : public juce::LookAndFeel_V4 { class lowBandButtonLookAndFeel : public juce::LookAndFeel_V4 {
public: public:
lowBandButtonLookAndFeel() lowBandButtonLookAndFeel() {
{
lowBypassIcon = juce::XmlDocument::parse(BinaryData::bypass_icon_svg); lowBypassIcon = juce::XmlDocument::parse(BinaryData::bypass_icon_svg);
lowCutIcon = juce::XmlDocument::parse(BinaryData::low_cut_icon_svg); lowCutIcon = juce::XmlDocument::parse(BinaryData::low_cut_icon_svg);
lowBellIcon = juce::XmlDocument::parse(BinaryData::bell_icon_svg); lowBellIcon = juce::XmlDocument::parse(BinaryData::bell_icon_svg);
lowShelfIcon = juce::XmlDocument::parse(BinaryData::low_shelf_icon_svg); lowShelfIcon = juce::XmlDocument::parse(BinaryData::low_shelf_icon_svg);
} }
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button, void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override bool shouldDrawButtonAsDown) override {
{
bool isHovered = button.isMouseOverOrDragging(); bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState(); bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled(); bool isEnabled = button.isEnabled();
@ -707,21 +681,19 @@ namespace AXIOM {
float bypassOpacity = 1.0f; float bypassOpacity = 1.0f;
if (lowBypassIcon != nullptr && lowCutIcon != nullptr && lowBellIcon != nullptr && lowShelfIcon != nullptr) { if (lowBypassIcon != nullptr && lowCutIcon != nullptr && lowBellIcon != nullptr && lowShelfIcon !=
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable! nullptr) {
auto bypassIcon = juce::Drawable::createFromSVG(*lowBypassIcon); auto bypassIcon = juce::Drawable::createFromSVG(*lowBypassIcon);
auto cutIcon = juce::Drawable::createFromSVG(*lowCutIcon); auto cutIcon = juce::Drawable::createFromSVG(*lowCutIcon);
auto bellIcon = juce::Drawable::createFromSVG(*lowBellIcon); auto bellIcon = juce::Drawable::createFromSVG(*lowBellIcon);
auto shelfIcon = juce::Drawable::createFromSVG(*lowShelfIcon); auto shelfIcon = juce::Drawable::createFromSVG(*lowShelfIcon);
if (bypassIcon != nullptr && cutIcon != nullptr && bellIcon != nullptr && shelfIcon != nullptr) if (bypassIcon != nullptr && cutIcon != nullptr && bellIcon != nullptr && shelfIcon !=
{ nullptr) {
juce::Colour colour; juce::Colour colour;
if (!isEnabled) { if (!isEnabled) {
colour = isToggled ? Colours::ACCENTWEAKCOLOUR : Colours::SURFACEBYPASS; colour = isToggled ? Colours::ACCENTWEAKCOLOUR : Colours::SURFACEBYPASS;
} } else if (isHovered) {
else if (isHovered) {
colour = isToggled ? Colours::ACCENTHOVER : Colours::SURFACEHOVER; colour = isToggled ? Colours::ACCENTHOVER : Colours::SURFACEHOVER;
} else { } else {
colour = isToggled colour = isToggled
@ -729,32 +701,25 @@ namespace AXIOM {
: Colours::SURFACECOLOUR; : Colours::SURFACECOLOUR;
} }
// Icon zeichnen
if (button.getName() == "LowBypass") { if (button.getName() == "LowBypass") {
bypassIcon->replaceColour(juce::Colours::black, colour); bypassIcon->replaceColour(juce::Colours::black, colour);
bypassIcon->drawWithin(g, iconBounds, bypassIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} } else if (button.getName() == "LowCut") {
else if (button.getName() == "LowCut") {
cutIcon->replaceColour(juce::Colours::black, colour); cutIcon->replaceColour(juce::Colours::black, colour);
cutIcon->drawWithin(g, iconBounds, cutIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} } else if (button.getName() == "LowBell") {
else if (button.getName() == "LowBell") {
bellIcon->replaceColour(juce::Colours::black, colour); bellIcon->replaceColour(juce::Colours::black, colour);
bellIcon->drawWithin(g, iconBounds, bellIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} } else if (button.getName() == "LowShelf") {
else if (button.getName() == "LowShelf") {
shelfIcon->replaceColour(juce::Colours::black, colour); shelfIcon->replaceColour(juce::Colours::black, colour);
shelfIcon->drawWithin(g, iconBounds, shelfIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} }
} }
} }
} }
private: private:
@ -766,22 +731,17 @@ namespace AXIOM {
class highBandButtonLookAndFeel : public juce::LookAndFeel_V4 { class highBandButtonLookAndFeel : public juce::LookAndFeel_V4 {
public: public:
highBandButtonLookAndFeel() highBandButtonLookAndFeel() {
{
highBypassIcon = juce::XmlDocument::parse(BinaryData::bypass_icon_svg); highBypassIcon = juce::XmlDocument::parse(BinaryData::bypass_icon_svg);
highCutIcon = juce::XmlDocument::parse(BinaryData::high_cut_icon_svg); highCutIcon = juce::XmlDocument::parse(BinaryData::high_cut_icon_svg);
highBellIcon = juce::XmlDocument::parse(BinaryData::bell_icon_svg); highBellIcon = juce::XmlDocument::parse(BinaryData::bell_icon_svg);
highShelfIcon = juce::XmlDocument::parse(BinaryData::high_shelf_icon_svg); highShelfIcon = juce::XmlDocument::parse(BinaryData::high_shelf_icon_svg);
} }
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button, void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override bool shouldDrawButtonAsDown) override {
{
bool isHovered = button.isMouseOverOrDragging(); bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState(); bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled(); bool isEnabled = button.isEnabled();
@ -794,21 +754,19 @@ namespace AXIOM {
float bypassOpacity = 1.0f; float bypassOpacity = 1.0f;
if (highBypassIcon != nullptr && highCutIcon != nullptr && highBellIcon != nullptr && highShelfIcon != nullptr) { if (highBypassIcon != nullptr && highCutIcon != nullptr && highBellIcon != nullptr && highShelfIcon
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable! != nullptr) {
auto bypassIcon = juce::Drawable::createFromSVG(*highBypassIcon); auto bypassIcon = juce::Drawable::createFromSVG(*highBypassIcon);
auto cutIcon = juce::Drawable::createFromSVG(*highCutIcon); auto cutIcon = juce::Drawable::createFromSVG(*highCutIcon);
auto bellIcon = juce::Drawable::createFromSVG(*highBellIcon); auto bellIcon = juce::Drawable::createFromSVG(*highBellIcon);
auto shelfIcon = juce::Drawable::createFromSVG(*highShelfIcon); auto shelfIcon = juce::Drawable::createFromSVG(*highShelfIcon);
if (bypassIcon != nullptr && cutIcon != nullptr && bellIcon != nullptr && shelfIcon != nullptr) if (bypassIcon != nullptr && cutIcon != nullptr && bellIcon != nullptr && shelfIcon !=
{ nullptr) {
juce::Colour colour; juce::Colour colour;
if (!isEnabled) { if (!isEnabled) {
colour = isToggled ? Colours::ACCENTWEAKCOLOUR : Colours::SURFACEBYPASS; colour = isToggled ? Colours::ACCENTWEAKCOLOUR : Colours::SURFACEBYPASS;
} } else if (isHovered) {
else if (isHovered) {
colour = isToggled ? Colours::ACCENTHOVER : Colours::SURFACEHOVER; colour = isToggled ? Colours::ACCENTHOVER : Colours::SURFACEHOVER;
} else { } else {
colour = isToggled colour = isToggled
@ -816,31 +774,25 @@ namespace AXIOM {
: Colours::SURFACECOLOUR; : Colours::SURFACECOLOUR;
} }
// Icon zeichnen
if (button.getName() == "HighBypass") { if (button.getName() == "HighBypass") {
bypassIcon->replaceColour(juce::Colours::black, colour); bypassIcon->replaceColour(juce::Colours::black, colour);
bypassIcon->drawWithin(g, iconBounds, bypassIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} } else if (button.getName() == "HighCut") {
else if (button.getName() == "HighCut") {
cutIcon->replaceColour(juce::Colours::black, colour); cutIcon->replaceColour(juce::Colours::black, colour);
cutIcon->drawWithin(g, iconBounds, cutIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} } else if (button.getName() == "HighBell") {
else if (button.getName() == "HighBell") {
bellIcon->replaceColour(juce::Colours::black, colour); bellIcon->replaceColour(juce::Colours::black, colour);
bellIcon->drawWithin(g, iconBounds, bellIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} } else if (button.getName() == "HighShelf") {
else if (button.getName() == "HighShelf") {
shelfIcon->replaceColour(juce::Colours::black, colour); shelfIcon->replaceColour(juce::Colours::black, colour);
shelfIcon->drawWithin(g, iconBounds, shelfIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity); juce::RectanglePlacement::centred, bypassOpacity);
} }
} }
} }
} }
private: private:
@ -855,11 +807,9 @@ namespace AXIOM {
}; };
class BaseSliderLookAndFeel : public juce::LookAndFeel_V4 class BaseSliderLookAndFeel : public juce::LookAndFeel_V4 {
{
public: public:
juce::Label* createSliderTextBox(juce::Slider& slider) override juce::Label *createSliderTextBox(juce::Slider &slider) override {
{
auto *label = juce::LookAndFeel_V4::createSliderTextBox(slider); auto *label = juce::LookAndFeel_V4::createSliderTextBox(slider);
label->setColour(juce::Label::backgroundColourId, juce::Colours::transparentBlack); label->setColour(juce::Label::backgroundColourId, juce::Colours::transparentBlack);
@ -870,8 +820,8 @@ namespace AXIOM {
Typography::applyToLabel(*label, Typography::Style::Mono, 1.f); Typography::applyToLabel(*label, Typography::Style::Mono, 1.f);
return label; return label;
} }
juce::Slider::SliderLayout getSliderLayout (juce::Slider& slider) override
{ juce::Slider::SliderLayout getSliderLayout(juce::Slider &slider) override {
juce::Slider::SliderLayout layout; juce::Slider::SliderLayout layout;
if (slider.getName() == "LowBand Slope" || slider.getName() == "HighBand Slope") { if (slider.getName() == "LowBand Slope" || slider.getName() == "HighBand Slope") {
layout.textBoxBounds = {}; layout.textBoxBounds = {};
@ -895,19 +845,15 @@ namespace AXIOM {
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height, void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle, float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider& slider) override juce::Slider &slider) override {
{
// Basis-Implementation - wird in Subklassen überschrieben
auto radius = juce::jmin(width / 2, height / 2) - 4.0f; auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f; auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f; auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
// Outer ring
g.setColour(Colours::BACKGROUNDCOLOUR); g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f); g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
// Indicator
juce::Path p; juce::Path p;
auto pointerLength = radius * 0.6f; auto pointerLength = radius * 0.6f;
auto pointerThickness = 2.0f; auto pointerThickness = 2.0f;
@ -918,13 +864,12 @@ namespace AXIOM {
g.fillPath(p); g.fillPath(p);
} }
}; };
class GainSliderLookAndFeel : public BaseSliderLookAndFeel
{ class GainSliderLookAndFeel : public BaseSliderLookAndFeel {
public: public:
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height, void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle, float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider& slider) override juce::Slider &slider) override {
{
bool isHovered = slider.isMouseOverOrDragging(); bool isHovered = slider.isMouseOverOrDragging();
auto isEnabled = slider.isEnabled(); auto isEnabled = slider.isEnabled();
float hoverFactor = 1.0f; float hoverFactor = 1.0f;
@ -947,7 +892,6 @@ namespace AXIOM {
auto mid = (rotaryEndAngle - rotaryStartAngle) / 2 + rotaryStartAngle; auto mid = (rotaryEndAngle - rotaryStartAngle) / 2 + rotaryStartAngle;
const float arcPathWidth = 3.0f; const float arcPathWidth = 3.0f;
// Background
g.setColour(Colours::BACKGROUNDCOLOUR); g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f); g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
@ -955,7 +899,6 @@ namespace AXIOM {
g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, arcPathWidth); g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, arcPathWidth);
// Arc für Gain-Anzeige
juce::Path valueArc; juce::Path valueArc;
valueArc.addCentredArc(centreX, centreY, radius, radius, valueArc.addCentredArc(centreX, centreY, radius, radius,
0.0f, mid, angle, true); 0.0f, mid, angle, true);
@ -963,22 +906,19 @@ namespace AXIOM {
g.setColour(accentCol); g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth)); g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Pointer
juce::Path p; juce::Path p;
p.addRectangle(-arcPathWidth / 2, (-radius - arcPathWidth) * hoverFactor, arcPathWidth, radius * 0.5f * hoverFactor); p.addRectangle(-arcPathWidth / 2, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.5f * hoverFactor);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY)); p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.fillPath(p); g.fillPath(p);
} }
}; };
class FreqQSliderLookAndFeel : public BaseSliderLookAndFeel class FreqQSliderLookAndFeel : public BaseSliderLookAndFeel {
{
public: public:
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height, void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle, float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider& slider) override juce::Slider &slider) override {
{
auto radius = juce::jmin(width / 2, height / 2) - 4.0f; auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f; auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f; auto centreY = y + height * 0.5f;
@ -999,7 +939,6 @@ namespace AXIOM {
} }
// Einfacher Ring
g.setColour(surfaceCol); g.setColour(surfaceCol);
g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 2.0f); g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 2.0f);
@ -1009,9 +948,10 @@ namespace AXIOM {
g.setColour(accentCol); g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth)); g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Dünner Pointer
juce::Path p; juce::Path p;
p.addRectangle(-1.0f, (-radius - arcPathWidth) * hoverFactor, arcPathWidth, radius * 0.4f * hoverFactor); p.addRectangle(-1.0f, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.4f * hoverFactor);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY)); p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(accentCol); g.setColour(accentCol);
@ -1023,12 +963,10 @@ namespace AXIOM {
public: public:
static constexpr float labelPadding = 20.0f; static constexpr float labelPadding = 20.0f;
juce::Slider::SliderLayout getSliderLayout(juce::Slider& slider) override juce::Slider::SliderLayout getSliderLayout(juce::Slider &slider) override {
{
juce::Slider::SliderLayout layout; juce::Slider::SliderLayout layout;
auto bounds = slider.getLocalBounds(); auto bounds = slider.getLocalBounds();
// TextBox in der Mitte (wie bei BaseSliderLookAndFeel)
layout.textBoxBounds = juce::Rectangle<int>( layout.textBoxBounds = juce::Rectangle<int>(
bounds.getCentreX() - bounds.getWidth() / 2, bounds.getCentreX() - bounds.getWidth() / 2,
bounds.getCentreY() - bounds.getHeight() / 2, bounds.getCentreY() - bounds.getHeight() / 2,
@ -1036,8 +974,6 @@ namespace AXIOM {
bounds.getHeight() 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); layout.sliderBounds = bounds.reduced(labelPadding);
return layout; return layout;
@ -1045,23 +981,18 @@ namespace AXIOM {
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height, void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle, float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider& slider) override juce::Slider &slider) override {
{
// Basis-Implementation - wird in Subklassen überschrieben
auto radius = juce::jmin(width / 2, height / 2) - 4.0f; auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f; auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f; auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
bool isEnabled = slider.isEnabled(); bool isEnabled = slider.isEnabled();
bool isHovered = slider.isMouseOverOrDragging();
// Outer ring
g.setColour(Colours::BACKGROUNDCOLOUR); g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f); g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
juce::Colour accentCol = isEnabled ? Colours::ACCENTCOLOUR : Colours::ACCENTWEAKCOLOUR; juce::Colour accentCol = isEnabled ? Colours::ACCENTCOLOUR : Colours::ACCENTWEAKCOLOUR;
// Indicator
juce::Path p; juce::Path p;
auto pointerLength = radius * 0.6f; auto pointerLength = radius * 0.6f;
auto pointerThickness = 2.0f; auto pointerThickness = 2.0f;
@ -1072,7 +1003,6 @@ namespace AXIOM {
g.fillPath(p); g.fillPath(p);
// Dann zeichne die Labels
const auto bounds = juce::Rectangle<int>(x, y, width, height); const auto bounds = juce::Rectangle<int>(x, y, width, height);
const auto centre = bounds.getCentre().toFloat(); const auto centre = bounds.getCentre().toFloat();
const float textRadius = std::min(width, height) * 0.5f + 5.0f; const float textRadius = std::min(width, height) * 0.5f + 5.0f;
@ -1082,16 +1012,15 @@ namespace AXIOM {
g.setFont(12.0f); g.setFont(12.0f);
auto value = slider.getValue(); auto value = slider.getValue();
for (int i = 0; i < labels.size(); ++i) 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 const bool isMatch = value == i; // Toleranz: 0.5
juce::Colour colour; juce::Colour colour;
if (!isEnabled) { if (!isEnabled) {
colour = isMatch ? Colours::ACCENTWEAKCOLOUR : Colours::FOREGROUNDCOLOUR.withMultipliedAlpha (0.4f); colour = isMatch
} ? Colours::ACCENTWEAKCOLOUR
else { : Colours::FOREGROUNDCOLOUR.withMultipliedAlpha(0.4f);
} else {
colour = isMatch colour = isMatch
? Colours::ACCENTCOLOUR ? Colours::ACCENTCOLOUR
: Colours::FOREGROUNDCOLOUR; : Colours::FOREGROUNDCOLOUR;
@ -1112,17 +1041,14 @@ namespace AXIOM {
g.drawText(labels[i], textRect, juce::Justification::centred); g.drawText(labels[i], textRect, juce::Justification::centred);
} }
} }
}; };
class GlobalSliderLookAndFeel : public BaseSliderLookAndFeel class GlobalSliderLookAndFeel : public BaseSliderLookAndFeel {
{
public: public:
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height, void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle, float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider& slider) override juce::Slider &slider) override {
{
auto radius = juce::jmin(width / 2, height / 2) - 4.0f; auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f; auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f; auto centreY = y + height * 0.5f;
@ -1147,7 +1073,6 @@ namespace AXIOM {
} }
// Einfacher Ring
g.setColour(surfaceCol); g.setColour(surfaceCol);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f); g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
@ -1160,9 +1085,10 @@ namespace AXIOM {
g.setColour(accentCol); g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth)); g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Dünner Pointer
juce::Path p; juce::Path p;
p.addRectangle(-1.0f, (-radius - arcPathWidth) * hoverFactor, arcPathWidth, radius * 0.4f * hoverFactor); p.addRectangle(-1.0f, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.4f * hoverFactor);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY)); p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(accentCol); g.setColour(accentCol);
@ -1180,6 +1106,7 @@ namespace AXIOM {
Global, Global,
Mix Mix
}; };
static float getSliderSizeMod(SizeMode mode) { static float getSliderSizeMod(SizeMode mode) {
switch (mode) { switch (mode) {
case SizeMode::Gain: return 1.25f; case SizeMode::Gain: return 1.25f;
@ -1191,7 +1118,6 @@ namespace AXIOM {
default: return 1.0f; default: return 1.0f;
} }
} }
}; };
}; };
@ -1209,10 +1135,6 @@ namespace AXIOM {
}; };
struct Layout { struct Layout {
///
/// @param mode Spacing mode -- e.g. Spacing::SizeMode::XS
/// @return Gap size in px as int
static int gap(Spacing::SizeMode mode) { static int gap(Spacing::SizeMode mode) {
return juce::roundToInt(Spacing::scale.space(mode)); return juce::roundToInt(Spacing::scale.space(mode));
} }
@ -1233,7 +1155,6 @@ namespace AXIOM {
float flexShrink = 1.f, float flexShrink = 1.f,
float basisPx = -1.f, float basisPx = -1.f,
Spacing::SizeMode margin = Spacing::SizeMode::XS) { Spacing::SizeMode margin = Spacing::SizeMode::XS) {
juce::FlexItem item{component}; juce::FlexItem item{component};
item.flexGrow = flexGrow; item.flexGrow = flexGrow;
item.flexShrink = flexShrink; item.flexShrink = flexShrink;
@ -1263,7 +1184,6 @@ namespace AXIOM {
fb.items.addArray(items); fb.items.addArray(items);
fb.performLayout(content(bounds, pad)); fb.performLayout(content(bounds, pad));
} }
static void flexColumn(juce::Rectangle<int> bounds, static void flexColumn(juce::Rectangle<int> bounds,
@ -1308,11 +1228,9 @@ namespace AXIOM {
static juce::GridItem area(juce::Component &c, static juce::GridItem area(juce::Component &c,
int rowStart, int colStart, int rowStart, int colStart,
int rowEnd, int colEnd) int rowEnd, int colEnd) {
{
return juce::GridItem(c) return juce::GridItem(c)
.withArea(rowStart, colStart, rowEnd, colEnd); .withArea(rowStart, colStart, rowEnd, colEnd);
} }
static void grid(juce::Rectangle<int> bounds, static void grid(juce::Rectangle<int> bounds,
@ -1348,11 +1266,12 @@ namespace AXIOM {
addAndMakeVisible(*saveButton); addAndMakeVisible(*saveButton);
addAndMakeVisible(*deleteButton); addAndMakeVisible(*deleteButton);
} }
}; };
class PresetMenuLookAndFeel : public juce::LookAndFeel_V4 { class PresetMenuLookAndFeel : public juce::LookAndFeel_V4 {
public: public:
void drawCallOutBoxBackground(juce::CallOutBox &callOutBox, juce::Graphics &g, const juce::Path &path, juce::Image &image) override { void drawCallOutBoxBackground(juce::CallOutBox &callOutBox, juce::Graphics &g, const juce::Path &path,
juce::Image &image) override {
g.setColour(Colours::SURFACECOLOUR); g.setColour(Colours::SURFACECOLOUR);
g.fillPath(path); g.fillPath(path);
@ -1361,22 +1280,19 @@ namespace AXIOM {
}; };
int getCallOutBoxBorderSize (const juce::CallOutBox&) override int getCallOutBoxBorderSize(const juce::CallOutBox &) override {
{
return 4; return 4;
} }
}; };
class PresetButtonsLookAndFeel : public juce::LookAndFeel_V4 { class PresetButtonsLookAndFeel : public juce::LookAndFeel_V4 {
public: public:
void drawButtonText(juce::Graphics &g, juce::TextButton &button, bool shouldDrawButtonAsHighlighted,
void drawButtonText(juce::Graphics &g, juce::TextButton &button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override { bool shouldDrawButtonAsDown) override {
auto bounds = button.getLocalBounds(); auto bounds = button.getLocalBounds();
// Text holen
auto text = button.getButtonText(); auto text = button.getButtonText();
// Farbe wählen
auto isHovered = button.isMouseOver(); auto isHovered = button.isMouseOver();
bool isEnabled = button.isEnabled(); bool isEnabled = button.isEnabled();
juce::Colour colour = isHovered ? Colours::FOREGROUNDHOVER : Colours::FOREGROUNDCOLOUR; juce::Colour colour = isHovered ? Colours::FOREGROUNDHOVER : Colours::FOREGROUNDCOLOUR;
@ -1386,21 +1302,19 @@ namespace AXIOM {
g.setColour(colour); g.setColour(colour);
// Font einstellen
auto font = Typography::getFont(Typography::Style::Button, 1.0f); auto font = Typography::getFont(Typography::Style::Button, 1.0f);
g.setFont(font); g.setFont(font);
// Text zeichnen
g.drawFittedText(text, g.drawFittedText(text,
bounds.reduced(4), bounds.reduced(4),
juce::Justification::centred, juce::Justification::centred,
1); 1);
} }
void drawButtonBackground(juce::Graphics &g, juce::Button &button, void drawButtonBackground(juce::Graphics &g, juce::Button &button,
const juce::Colour &backgroundColour, const juce::Colour &backgroundColour,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override bool shouldDrawButtonAsDown) override {
{
auto bounds = button.getLocalBounds().toFloat(); auto bounds = button.getLocalBounds().toFloat();
bool isHovered = button.isMouseOver(); bool isHovered = button.isMouseOver();
@ -1414,47 +1328,203 @@ namespace AXIOM {
} }
}; };
class PresetComboBoxLookAndFeel : public juce::LookAndFeel_V4 { class ClickableTextEditor : public juce::TextEditor {
public: public:
juce::PopupMenu::Options getOptionsForComboBoxPopupMenu(juce::ComboBox& box, std::function<void()> onMouseDown;
juce::Label& label) override
{
// Basis-Options holen
auto options = juce::LookAndFeel_V4::getOptionsForComboBoxPopupMenu(box, label);
// Anzahl der sichtbaren Einträge void mouseDown(const juce::MouseEvent &event) override {
const int visibleRows = 5; juce::TextEditor::mouseDown(event);
if (onMouseDown)
// Höhe einer Zeile onMouseDown();
auto font = getComboBoxFont(box);
int rowHeight = (int)std::ceil(font.getHeight() * 1.6f);
// Maximale Höhe des Popups
int maxPopupHeight = visibleRows * rowHeight;
// Screen-Bounds der ComboBox
auto boxScreen = box.getScreenBounds();
// Wichtig: Ein sehr breiter aber in der Höhe begrenzter Bereich
// JUCE wird das Popup darin zentrieren
juce::Rectangle<int> constrainedArea(
0, // X: ganze Screen-Breite
boxScreen.getBottom(), // Y: direkt unter der Box
juce::Desktop::getInstance().getDisplays()
.getPrimaryDisplay()->totalArea.getWidth(),
maxPopupHeight // Höhe: nur unsere gewünschten Zeilen
);
options = options.withTargetScreenArea(constrainedArea)
.withStandardItemHeight(rowHeight);
return options;
} }
}; };
class PresetInputLookAndFeel : public juce::LookAndFeel_V4 {
public:
void fillTextEditorBackground(juce::Graphics &g, int width, int height,
juce::TextEditor &textEditor) override {
if (textEditor.isEnabled()) {
g.setColour(Colours::SURFACECOLOUR);
} else {
g.setColour(Colours::SURFACEBYPASS);
}
g.fillRoundedRectangle(0, 0, width, height,
Shape::getRadius(Shape::RadiusMode::S,
juce::Rectangle<float>(0, 0, width, height)));
if (textEditor.getText().isEmpty() && !textEditor.hasKeyboardFocus(true)) {
g.setColour(placeholderColour);
g.setFont(Typography::getFont(Typography::Style::Body, 1.0f));
auto textBounds = juce::Rectangle<int>(0, 0, width, height).reduced(5);
g.drawText(placeholderText, textBounds, juce::Justification::centred, true);
}
}
void drawTextEditorOutline(juce::Graphics &g, int width, int height,
juce::TextEditor &textEditor) override {
auto bounds = juce::Rectangle<float>(0, 0, width, height);
auto radius = Shape::getRadius(Shape::RadiusMode::S, bounds);
auto strokeWidth = Shape::getStrokeWidth(Shape::StrokeMode::Thin);
juce::Colour outlineColour;
if (!textEditor.isEnabled()) {
outlineColour = Colours::FOREGROUNDBYPASS.withAlpha(0.3f);
} else if (textEditor.hasKeyboardFocus(true)) {
outlineColour = Colours::ACCENTCOLOUR;
} else if (textEditor.isMouseOver()) {
outlineColour = Colours::FOREGROUNDHOVER.withAlpha(0.5f);
} else {
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.3f);
}
g.setColour(outlineColour);
g.drawRoundedRectangle(bounds.reduced(strokeWidth * 0.5f), radius, strokeWidth);
}
juce::CaretComponent *createCaretComponent(juce::Component *keyFocusOwner) override {
auto *caret = new juce::CaretComponent(keyFocusOwner);
caret->setColour(juce::CaretComponent::caretColourId, Colours::ACCENTCOLOUR);
return caret;
}
private:
juce::String placeholderText = "Preset Name";
juce::Colour placeholderColour = Colours::MUTEDTEXTCOLOUR;
};
class PresetComboBoxLookAndFeel : public juce::LookAndFeel_V4,
private juce::ComponentListener {
public:
PresetComboBoxLookAndFeel() = default;
~PresetComboBoxLookAndFeel() 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);
}
void drawPopupMenuUpDownArrow(juce::Graphics &g, int width, int height,
bool isScrollUpArrow) override {
g.setColour(Colours::ACCENTCOLOUR);
auto arrowZone = juce::Rectangle<float>(0.0f, 0.0f, (float) width, (float) height);
auto arrowWidth = arrowZone.getWidth() * 0.1f;
auto arrowHeight = arrowZone.getHeight() * 0.1f;
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));
}
void drawPopupMenuBackground(juce::Graphics &g, int width, int height) override {
g.fillAll(Colours::SURFACECOLOUR);
}
void drawPopupMenuItem(juce::Graphics &g, const juce::Rectangle<int> &area,
bool isSeparator, bool isActive, bool isHighlighted,
bool isTicked, bool hasSubMenu, const juce::String &text,
const juce::String &shortcutKeyText, const juce::Drawable *icon,
const juce::Colour *textColourToUse) override {
juce::Colour backGroundColour = isHighlighted ? Colours::SURFACEHOVER : Colours::SURFACECOLOUR;
g.fillAll(backGroundColour);
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);
}
void drawComboBox(juce::Graphics &g, int width, int height, bool isButtonDown,
int buttonX, int buttonY, int buttonW, int buttonH,
juce::ComboBox &box) override {
auto bounds = juce::Rectangle<float>(0, 0, (float) width, (float) height);
auto cornerSize = Shape::getRadius(Shape::RadiusMode::S, bounds);
bool isHovered = box.isMouseOver();
bool isEnabled = box.isEnabled();
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;
}
g.setColour(bgColour);
g.fillRoundedRectangle(bounds, cornerSize);
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 strokeWidth = Shape::getStrokeWidth(Shape::StrokeMode::Thin);
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);
juce::Colour arrowColour = isEnabled ? Colours::ACCENTCOLOUR : Colours::ACCENTWEAKCOLOUR;
if (isHovered && isEnabled) {
arrowColour = Colours::ACCENTHOVER;
}
g.setColour(arrowColour);
juce::Path arrow;
auto arrowW = arrowZone.getWidth() * 0.3f;
auto arrowH = arrowZone.getHeight() * 0.15f;
auto centreX = arrowZone.getCentreX();
auto centreY = arrowZone.getCentreY();
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:
};
private: private:
}; };

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.15) 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) # JUCE einbinden (lokaler Pfad zu JUCE, z.B. als Submodul oder manuell kopiert)
add_subdirectory(juce) # <-- Pfad zu deinem JUCE-Ordner relativ zum Projekt add_subdirectory(juce) # <-- Pfad zu deinem JUCE-Ordner relativ zum Projekt

View File

@ -11499,7 +11499,7 @@ static const unsigned char temp_binary_data_14[] =
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 32\">\n" "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 32\">\n"
" <g id=\"Ebene_6\" data-name=\"Ebene 6\">\n" " <g id=\"Ebene_6\" data-name=\"Ebene 6\">\n"
" <rect x=\"0\" y=\"0\" width=\"64\" height=\"32\" fill=\"#171a1a\"/>\n" " <rect x=\"0\" y=\"0\" width=\"64\" height=\"32\" fill=\"#171a1a\"/>\n"
" <polygon points=\"0 32 2 30 32 16 62 2 64 0 64 32 0 32\" fill=\"#262b2b\"/>\n" " <polygon points=\"64 0 62 2 32 16 2 30 0 32 0 0 64 0\" fill=\"#262b2b\"/>\n"
" </g>\n" " </g>\n"
" <g id=\"Ebene_2\" data-name=\"Ebene 2\">\n" " <g id=\"Ebene_2\" data-name=\"Ebene 2\">\n"
" <rect x=\"2\" y=\"2\" width=\"60\" height=\"28\" fill=\"#3db7b7\"/>\n" " <rect x=\"2\" y=\"2\" width=\"60\" height=\"28\" fill=\"#3db7b7\"/>\n"
@ -11525,51 +11525,51 @@ static const unsigned char temp_binary_data_14[] =
" </g>\n" " </g>\n"
" </g>\n" " </g>\n"
" <g id=\"Ebene_3_Kopie\" data-name=\"Ebene 3 Kopie\">\n" " <g id=\"Ebene_3_Kopie\" data-name=\"Ebene 3 Kopie\">\n"
" <rect x=\"8\" y=\"8\" width=\"48\" height=\"16\" fill=\"#fffcfc\" opacity=\".21\"/>\n" " <rect x=\"8\" y=\"8\" width=\"48\" height=\"16\" fill=\"#fffcfc\" opacity=\".17\"/>\n"
" <polygon points=\"2 30 8 24 56 24 62 30 2 30\" fill=\"#fffcfc\" opacity=\".21\"/>\n" " <polygon points=\"2 30 8 24 56 24 62 30 2 30\" fill=\"#fffcfc\" opacity=\".17\"/>\n"
" <polygon points=\"62 2 56 8 8 8 2 2 62 2\" fill=\"#fffcfc\" opacity=\".21\"/>\n" " <polygon points=\"62 2 56 8 8 8 2 2 62 2\" fill=\"#fffcfc\" opacity=\".17\"/>\n"
" <polygon points=\"56 24 56 8 62 2 62 30 56 24\" fill=\"#fffcfc\" opacity=\".21\"/>\n" " <polygon points=\"56 24 56 8 62 2 62 30 56 24\" fill=\"#fffcfc\" opacity=\".17\"/>\n"
" <polygon points=\"8 8 8 24 2 30 2 2 8 8\" fill=\"#fffcfc\" opacity=\".21\"/>\n" " <polygon points=\"8 8 8 24 2 30 2 2 8 8\" fill=\"#fffcfc\" opacity=\".17\"/>\n"
" <polygon points=\"8 24 5 27 2 30 2 24 8 24\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"8 24 5 27 2 30 2 24 8 24\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"2 30 5 27 8 24 8 30 2 30\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"2 30 5 27 8 24 8 30 2 30\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"8 30 8 24 14 27 20 30 8 30\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"8 30 8 24 14 27 20 30 8 30\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"2 8 5 12 8 16 2 16 2 8\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"2 8 5 12 8 16 2 16 2 8\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"20 30 26 27 32 24 32 30 20 30\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"20 30 26 27 32 24 32 30 20 30\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"32 24 26 27 20 30 20 24 32 24\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"32 24 26 27 20 30 20 24 32 24\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"8 16 5 12 2 8 8 8 8 16\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"8 16 5 12 2 8 8 8 8 16\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"8 16 5 20 2 24 2 16 8 16\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"8 16 5 20 2 24 2 16 8 16\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"20 24 20 30 14 27 8 24 20 24\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"20 24 20 30 14 27 8 24 20 24\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"56 30 56 24 50 27 44 30 56 30\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"56 30 56 24 50 27 44 30 56 30\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"44 30 38 27 32 24 32 30 44 30\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"44 30 38 27 32 24 32 30 44 30\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"32 24 38 27 44 30 44 24 32 24\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"32 24 38 27 44 30 44 24 32 24\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"44 24 44 30 50 27 56 24 44 24\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"44 24 44 30 50 27 56 24 44 24\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"32 2 32 8 26 5 20 2 32 2\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"32 2 32 8 26 5 20 2 32 2\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"20 2 14 5 8 8 8 2 20 2\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"20 2 14 5 8 8 8 2 20 2\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"2 24 5 20 8 16 8 24 2 24\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"2 24 5 20 8 16 8 24 2 24\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"8 8 14 5 20 2 20 8 8 8\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"8 8 14 5 20 2 20 8 8 8\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"8 24 20 20 32 16 32 24 8 24\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"8 24 20 20 32 16 32 24 8 24\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"56 24 44 20 32 16 32 24 56 24\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"56 24 44 20 32 16 32 24 56 24\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"56 8 44 12 32 16 32 8 56 8\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"56 8 44 12 32 16 32 8 56 8\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"8 8 20 12 32 16 32 8 8 8\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"8 8 20 12 32 16 32 8 8 8\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"32 16 20 12 8 8 8 16 32 16\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"32 16 20 12 8 8 8 16 32 16\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"32 16 44 12 56 8 56 16 32 16\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"32 16 44 12 56 8 56 16 32 16\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"32 16 20 20 8 24 8 16 32 16\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"32 16 20 20 8 24 8 16 32 16\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"32 16 44 20 56 24 56 16 32 16\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"32 16 44 20 56 24 56 16 32 16\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"20 8 20 2 26 5 32 8 20 8\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"20 8 20 2 26 5 32 8 20 8\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"32 2 32 8 38 5 44 2 32 2\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"32 2 32 8 38 5 44 2 32 2\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"44 2 50 5 56 8 56 2 44 2\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"44 2 50 5 56 8 56 2 44 2\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"56 8 50 5 44 2 44 8 56 8\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"56 8 50 5 44 2 44 8 56 8\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"44 8 44 2 38 5 32 8 44 8\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"44 8 44 2 38 5 32 8 44 8\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"8 2 8 8 5 5 2 2 8 2\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"8 2 8 8 5 5 2 2 8 2\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"2 8 2 2 5 5 8 8 2 8\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"2 8 2 2 5 5 8 8 2 8\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"56 24 59 27 62 30 62 24 56 24\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"56 24 59 27 62 30 62 24 56 24\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"62 8 59 12 56 16 62 16 62 8\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"62 8 59 12 56 16 62 16 62 8\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"56 16 59 12 62 8 56 8 56 16\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"56 16 59 12 62 8 56 8 56 16\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"56 16 59 20 62 24 56 24 56 16\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"56 16 59 20 62 24 56 24 56 16\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"62 30 59 27 56 24 56 30 62 30\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"62 30 59 27 56 24 56 30 62 30\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"56 16 59 20 62 24 62 16 56 16\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"56 16 59 20 62 24 62 16 56 16\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" <polygon points=\"56 2 56 8 59 5 62 2 56 2\" fill=\"#fff\" opacity=\".21\"/>\n" " <polygon points=\"56 2 56 8 59 5 62 2 56 2\" fill=\"#fff\" opacity=\".17\"/>\n"
" <polygon points=\"62 8 62 2 59 5 56 8 62 8\" fill=\"#d1d1d1\" opacity=\".21\"/>\n" " <polygon points=\"62 8 62 2 59 5 56 8 62 8\" fill=\"#d1d1d1\" opacity=\".17\"/>\n"
" </g>\n" " </g>\n"
"</svg>"; "</svg>";
@ -11692,7 +11692,7 @@ const char* getNamedResource (const char* resourceNameUTF8, int& numBytes)
case 0x31532e06: numBytes = 353; return low_cut_icon_svg; case 0x31532e06: numBytes = 353; return low_cut_icon_svg;
case 0x7e8ca05e: numBytes = 410; return low_shelf_icon_svg; case 0x7e8ca05e: numBytes = 410; return low_shelf_icon_svg;
case 0x4df4bf1e: numBytes = 350; return preset_menu_icon_svg; case 0x4df4bf1e: numBytes = 350; return preset_menu_icon_svg;
case 0xd842aaab: numBytes = 6343; return crystalize_button_active_icon_svg; case 0xd842aaab: numBytes = 6341; return crystalize_button_active_icon_svg;
case 0x885a7642: numBytes = 6279; return crystalize_button_passive_icon_svg; case 0x885a7642: numBytes = 6279; return crystalize_button_passive_icon_svg;
case 0xe49e0f15: numBytes = 406; return bypass_icon_svg; case 0xe49e0f15: numBytes = 406; return bypass_icon_svg;
default: break; default: break;

View File

@ -51,7 +51,7 @@ namespace BinaryData
const int preset_menu_icon_svgSize = 350; const int preset_menu_icon_svgSize = 350;
extern const char* crystalize_button_active_icon_svg; extern const char* crystalize_button_active_icon_svg;
const int crystalize_button_active_icon_svgSize = 6343; const int crystalize_button_active_icon_svgSize = 6341;
extern const char* crystalize_button_passive_icon_svg; extern const char* crystalize_button_passive_icon_svg;
const int crystalize_button_passive_icon_svgSize = 6279; const int crystalize_button_passive_icon_svgSize = 6279;

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,11 @@
using AXIOM::DesignSystem; using AXIOM::DesignSystem;
using Components = DesignSystem::Components; using Components = DesignSystem::Components;
using SliderStyles = Components::SliderStyles; using SliderStyles = Components::SliderStyles;
class SpectrumAnalyzer { class SpectrumAnalyzer {
public: public:
SpectrumAnalyzer(AudioFIFO& fifoRef, CrystalizerEQAudioProcessor& processor) : audioFIFO(fifoRef), audioProcessor(processor) { SpectrumAnalyzer(AudioFIFO &fifoRef, CrystalizerEQAudioProcessor &processor) : audioFIFO(fifoRef),
audioProcessor(processor) {
fftData.resize(2 * FFTSIZE); fftData.resize(2 * FFTSIZE);
magnitudes.resize(BINS); magnitudes.resize(BINS);
magnitudesDb.resize(BINS); magnitudesDb.resize(BINS);
@ -33,9 +35,8 @@ class SpectrumAnalyzer {
peakHoldMagnitudesDb.resize(BINS); peakHoldMagnitudesDb.resize(BINS);
DELTAT = static_cast<float>(HOPSIZE) / static_cast<float>(sampleRate); DELTAT = static_cast<float>(HOPSIZE) / static_cast<float>(sampleRate);
renderValuesDb.resize(BINS); renderValuesDb.resize(BINS);
} }
~SpectrumAnalyzer() = default; ~SpectrumAnalyzer() = default;
@ -52,7 +53,7 @@ class SpectrumAnalyzer {
juce::Array<float> fftFrame; juce::Array<float> fftFrame;
//CHANGE FOR LOWER/HIGHER EMA-SMOOTHING //LOWER VALUE -> MORE SMOOTHING/SLOW RESPONSE -- HIGHER VALUE -> LESS SMOOTHING/FAST RESPONSE //MAX VALUE = 1.f //CHANGE FOR LOWER/HIGHER EMA-SMOOTHING //LOWER VALUE -> MORE SMOOTHING/SLOW RESPONSE -- HIGHER VALUE -> LESS SMOOTHING/FAST RESPONSE //MAX VALUE = 1.f
float smoothingFactor = 0.3f; float smoothingFactor = 0.5f;
void processSamples(); void processSamples();
@ -76,7 +77,6 @@ class SpectrumAnalyzer {
std::vector<float> fftData; std::vector<float> fftData;
const float MINDB = -90.f; const float MINDB = -90.f;
const float MAXDB = 0.f; const float MAXDB = 0.f;
@ -103,7 +103,7 @@ class SpectrumAnalyzer {
std::vector<double> freqPerBin; std::vector<double> freqPerBin;
//CHANGE FOR FASTER FALLOFF -- VALUE IS IN DB/S //CHANGE FOR FASTER FALLOFF -- VALUE IS IN DB/S
const float FALLOFFRATE = 120.f; const float FALLOFFRATE = 240.f;
float DELTAT = 0.f; float DELTAT = 0.f;
@ -114,23 +114,27 @@ class SpectrumAnalyzer {
const float MAXFREQ = static_cast<float>(sampleRate) / 2; const float MAXFREQ = static_cast<float>(sampleRate) / 2;
void fillFftDataFromFrame(std::vector<float> &windowedFrame); void fillFftDataFromFrame(std::vector<float> &windowedFrame);
void buildMagnitudeSpectrum();
void convertToDb();
void applySmoothing();
void applyEMA();
void applyFreqSmoothing();
void applyPeakHoldAndFalloff();
std::vector<float> getRenderValues();
void buildMagnitudeSpectrum();
void convertToDb();
void applySmoothing();
void applyEMA();
void applyFreqSmoothing();
void applyPeakHoldAndFalloff();
std::vector<float> getRenderValues();
}; };
class CrystalizerEQAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer class CrystalizerEQAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer, private juce::AudioProcessorValueTreeState::Listener {
{
public: public:
CrystalizerEQAudioProcessorEditor(CrystalizerEQAudioProcessor &); CrystalizerEQAudioProcessorEditor(CrystalizerEQAudioProcessor &);
~CrystalizerEQAudioProcessorEditor() override; ~CrystalizerEQAudioProcessorEditor() override;
void paintAnalyzer(juce::Graphics &g); void paintAnalyzer(juce::Graphics &g);
@ -141,10 +145,12 @@ public:
//=================================================================== //===================================================================
void timerCallback(); void timerCallback();
void paint(juce::Graphics &) override; void paint(juce::Graphics &) override;
void resized() override; void resized() override;
void resetAllCheckboxes(); void resetAllCheckboxes();
using SliderAttach = juce::AudioProcessorValueTreeState::SliderAttachment; using SliderAttach = juce::AudioProcessorValueTreeState::SliderAttachment;
@ -153,7 +159,7 @@ public:
using ComboBoxAttach = juce::AudioProcessorValueTreeState::ComboBoxAttachment; using ComboBoxAttach = juce::AudioProcessorValueTreeState::ComboBoxAttachment;
juce::Slider testNoiseSlider, juce::Slider
lowBandFreqSlider, lowBandSlopeSlider, lowBandGainSlider, lowBandQSlider, lowBandFreqSlider, lowBandSlopeSlider, lowBandGainSlider, lowBandQSlider,
peak1FreqSlider, peak1GainSlider, peak1QSlider, peak1FreqSlider, peak1GainSlider, peak1QSlider,
peak2FreqSlider, peak2GainSlider, peak2QSlider, peak2FreqSlider, peak2GainSlider, peak2QSlider,
@ -170,7 +176,7 @@ public:
&inputSlider, &outputSlider &inputSlider, &outputSlider
}; };
std::unique_ptr<SliderAttach> testNoiseAttach, std::unique_ptr<SliderAttach>
lowBandFreqAttach, lowBandSlopeAttach, lowBandGainAttach, lowBandQAttach, lowBandFreqAttach, lowBandSlopeAttach, lowBandGainAttach, lowBandQAttach,
peak1FreqAttach, peak1GainAttach, peak1QAttach, peak1FreqAttach, peak1GainAttach, peak1QAttach,
peak2FreqAttach, peak2GainAttach, peak2QAttach, peak2FreqAttach, peak2GainAttach, peak2QAttach,
@ -178,7 +184,11 @@ public:
highBandFreqAttach, highBandSlopeAttach, highBandGainAttach, highBandQAttach, highBandFreqAttach, highBandSlopeAttach, highBandGainAttach, highBandQAttach,
inputAttach, outputAttach; inputAttach, outputAttach;
juce::Label titleLabel, testNoiseLabel, std::unique_ptr<ButtonAttach>
peak1BypassAttach, peak2BypassAttach, peak3BypassAttach,
crystalizeAttach, masterBypassAttach;
juce::Label titleLabel,
lowBandFreqLabel, lowBandSlopeLabel, lowBandGainLabel, lowBandQLabel, lowBandModeLabel, lowBandFreqLabel, lowBandSlopeLabel, lowBandGainLabel, lowBandQLabel, lowBandModeLabel,
low12, low24, low36, low48, lowdBOctLabel, low12, low24, low36, low48, lowdBOctLabel,
peak1FreqLabel, peak1GainLabel, peak1QLabel, peak1FreqLabel, peak1GainLabel, peak1QLabel,
@ -194,7 +204,8 @@ public:
&peak1FreqLabel, &peak1GainLabel, &peak1QLabel, &peak1FreqLabel, &peak1GainLabel, &peak1QLabel,
&peak2FreqLabel, &peak2GainLabel, &peak2QLabel, &peak2FreqLabel, &peak2GainLabel, &peak2QLabel,
&peak3FreqLabel, &peak3GainLabel, &peak3QLabel, &peak3FreqLabel, &peak3GainLabel, &peak3QLabel,
&highBandFreqLabel, &highBandSlopeLabel, &highBandGainLabel, &highBandQLabel, &highBandModeLabel, &highdBOctLabel, &highBandFreqLabel, &highBandSlopeLabel, &highBandGainLabel, &highBandQLabel, &highBandModeLabel,
&highdBOctLabel,
&inputLabel, &outputLabel &inputLabel, &outputLabel
}; };
const juce::Array<juce::Label *> slopeLabels = { const juce::Array<juce::Label *> slopeLabels = {
@ -203,12 +214,14 @@ public:
}; };
juce::TextButton resetButton, savePresetButton, deletePresetButton;
juce::TextButton testNoiseButton, resetButton, savePresetButton, deletePresetButton; juce::ToggleButton masterBypassButton, crystalizeButton, peak1BypassButton, peak2BypassButton, peak3BypassButton,
presetMenuButton;
juce::ToggleButton masterBypassButton, crystalizeButton, peak1BypassButton, peak2BypassButton, peak3BypassButton, presetMenuButton; const juce::Array<juce::ToggleButton *> peakBypassButtons = {
&peak1BypassButton, &peak2BypassButton, &peak3BypassButton
const juce::Array<juce::ToggleButton*> peakBypassButtons = {&peak1BypassButton, &peak2BypassButton, &peak3BypassButton}; };
juce::ComboBox presetBox; juce::ComboBox presetBox;
@ -222,18 +235,14 @@ public:
juce::ToggleButton lowBypass, lowCut, lowBell, lowShelf, juce::ToggleButton lowBypass, lowCut, lowBell, lowShelf,
highBypass, highCut, highBell, highShelf; highBypass, highCut, highBell, highShelf;
juce::TextEditor presetNameInput; DesignSystem::ClickableTextEditor presetNameInput;
private: private:
// This reference is provided as a quick way for your editor to // This reference is provided as a quick way for your editor to
// access the processor object that created it. // access the processor object that created it.
CrystalizerEQAudioProcessor &audioProcessor; CrystalizerEQAudioProcessor &audioProcessor;
void setKnobVisibility(); void setKnobVisibility();
void animateCrystalizeButton(); void animateCrystalizeButton();
@ -253,9 +262,13 @@ private:
void setupToggleButtons(); void setupToggleButtons();
void disableLowBand(float target); void disableLowBand(float target);
void disableLowMidBand(float target); void disableLowMidBand(float target);
void disableMidBand(float target); void disableMidBand(float target);
void disableHighMidBand(float target); void disableHighMidBand(float target);
void disableHighBand(float target); void disableHighBand(float target);
void disableEverything(float target); void disableEverything(float target);
@ -270,6 +283,10 @@ private:
void initPresetSystem(); void initPresetSystem();
void updateFrequencyRanges();
void updateModeButtons();
std::unique_ptr<AXIOM::DesignSystem::Components::BaseSliderLookAndFeel> baseLookAndFeel; std::unique_ptr<AXIOM::DesignSystem::Components::BaseSliderLookAndFeel> baseLookAndFeel;
std::unique_ptr<DesignSystem::Components::GainSliderLookAndFeel> gainLookAndFeel; std::unique_ptr<DesignSystem::Components::GainSliderLookAndFeel> gainLookAndFeel;
@ -284,6 +301,7 @@ private:
std::unique_ptr<DesignSystem::PresetMenuLookAndFeel> presetMenuLookAndFeel; std::unique_ptr<DesignSystem::PresetMenuLookAndFeel> presetMenuLookAndFeel;
std::unique_ptr<DesignSystem::PresetButtonsLookAndFeel> presetButtonLookAndFeel; std::unique_ptr<DesignSystem::PresetButtonsLookAndFeel> presetButtonLookAndFeel;
std::unique_ptr<DesignSystem::PresetComboBoxLookAndFeel> presetComboBoxLookAndFeel; std::unique_ptr<DesignSystem::PresetComboBoxLookAndFeel> presetComboBoxLookAndFeel;
std::unique_ptr<DesignSystem::PresetInputLookAndFeel> presetInputLookAndFeel;
//SPECRTRUM ANALYZER //SPECRTRUM ANALYZER
@ -315,18 +333,24 @@ private:
Component globalControlArea; Component globalControlArea;
void scalePluginWindow(juce::Rectangle<int> area); void scalePluginWindow(juce::Rectangle<int> area);
void setupMainGrid(juce::Rectangle<int> area); void setupMainGrid(juce::Rectangle<int> area);
void setupLowBandLayout(); void setupLowBandLayout();
void setupLowMidBandLayout(); void setupLowMidBandLayout();
void setupMidBandLayout(); void setupMidBandLayout();
void setupHighMidBandLayout(); void setupHighMidBandLayout();
void setupHighBandLayout(); void setupHighBandLayout();
void setupHeader(); void setupHeader();
void setupBody(); void setupBody();
void setupFooter(); void setupFooter();
juce::Array<float> getReferenceCell(); juce::Array<float> getReferenceCell();
@ -344,13 +368,19 @@ private:
std::unique_ptr<juce::Drawable> logoDrawable; std::unique_ptr<juce::Drawable> logoDrawable;
DesignSystem::PresetMenu* test;
juce::Component::SafePointer<DesignSystem::PresetMenu> presetMenuSafePtr; juce::Component::SafePointer<DesignSystem::PresetMenu> presetMenuSafePtr;
bool inputFocused = false;
float mapFrequencyToX(float freq, float minFreq, float maxFreq, float width); float mapFrequencyToX(float freq, float minFreq, float maxFreq, float width);
int getClosestBinForFrequency(float freq); int getClosestBinForFrequency(float freq);
void drawFrequencyGrid(juce::Graphics &g, const float dbRange); void drawFrequencyGrid(juce::Graphics &g, const float dbRange);
float getInterpolatedDb(float exactBin); float getInterpolatedDb(float exactBin);
void parameterChanged(const juce::String& parameterID, float newValue) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CrystalizerEQAudioProcessorEditor) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CrystalizerEQAudioProcessorEditor)
}; };

View File

@ -9,25 +9,24 @@
#include "PluginProcessor.h" #include "PluginProcessor.h"
#include "PluginEditor.h" #include "PluginEditor.h"
// ==================== Parameter-Layout ====================
juce::AudioProcessorValueTreeState::ParameterLayout juce::AudioProcessorValueTreeState::ParameterLayout
CrystalizerEQAudioProcessor::createParameterLayout() { CrystalizerEQAudioProcessor::createParameterLayout() {
std::vector<std::unique_ptr<juce::RangedAudioParameter> > params; std::vector<std::unique_ptr<juce::RangedAudioParameter> > params;
params.push_back (std::make_unique<juce::AudioParameterBool>(
"TestNoiseEnabled", "Test Noise Enabled", false));
params.push_back (std::make_unique<juce::AudioParameterFloat>("TestNoiseLevel", "Test Noise Level", juce::NormalisableRange<float>(-60.f, 0.f, 0.1f), 0.f));
//LOW-BAND //LOW-BAND
params.push_back(std::make_unique<juce::AudioParameterFloat>( params.push_back(std::make_unique<juce::AudioParameterFloat>(
"LowBandFreq", "LowBand Freq", "LowBandFreq", "LowBand Freq",
juce::NormalisableRange<float>(20.f, 20000.f, 1.f, 0.5f), 30.f)); 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 "LowBandSlope", "LowBand Slope", // Anzeigename
@ -96,9 +95,13 @@ CrystalizerEQAudioProcessor::createParameterLayout() {
"HighBandFreq", "HighBand Freq", "HighBandFreq", "HighBand Freq",
juce::NormalisableRange<float>(20.f, 20000.f, 1.f, 0.5f), 17000.f)); 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 "HighBandSlope", "HighBand Slope", // Anzeigename
@ -110,16 +113,18 @@ CrystalizerEQAudioProcessor::createParameterLayout() {
juce::StringArray{"Off", "Cut", "Shelf", "Bell"}, // Einträge juce::StringArray{"Off", "Cut", "Shelf", "Bell"}, // Einträge
2)); 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()};
@ -139,20 +144,17 @@ CrystalizerEQAudioProcessor::CrystalizerEQAudioProcessor()
) )
#endif #endif
{ {
} }
CrystalizerEQAudioProcessor::~CrystalizerEQAudioProcessor() = default; CrystalizerEQAudioProcessor::~CrystalizerEQAudioProcessor() = default;
//============================================================================== //==============================================================================
const juce::String CrystalizerEQAudioProcessor::getName() const const juce::String CrystalizerEQAudioProcessor::getName() const {
{
return JucePlugin_Name; return JucePlugin_Name;
} }
bool CrystalizerEQAudioProcessor::acceptsMidi() const bool CrystalizerEQAudioProcessor::acceptsMidi() const {
{
#if JucePlugin_WantsMidiInput #if JucePlugin_WantsMidiInput
return true; return true;
#else #else
@ -160,8 +162,7 @@ bool CrystalizerEQAudioProcessor::acceptsMidi() const
#endif #endif
} }
bool CrystalizerEQAudioProcessor::producesMidi() const bool CrystalizerEQAudioProcessor::producesMidi() const {
{
#if JucePlugin_ProducesMidiOutput #if JucePlugin_ProducesMidiOutput
return true; return true;
#else #else
@ -169,8 +170,7 @@ bool CrystalizerEQAudioProcessor::producesMidi() const
#endif #endif
} }
bool CrystalizerEQAudioProcessor::isMidiEffect() const bool CrystalizerEQAudioProcessor::isMidiEffect() const {
{
#if JucePlugin_IsMidiEffect #if JucePlugin_IsMidiEffect
return true; return true;
#else #else
@ -178,49 +178,38 @@ bool CrystalizerEQAudioProcessor::isMidiEffect() const
#endif #endif
} }
double CrystalizerEQAudioProcessor::getTailLengthSeconds() const double CrystalizerEQAudioProcessor::getTailLengthSeconds() const {
{
return 0.0; 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, 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. // so this should be at least 1, even if you're not really implementing programs.
} }
int CrystalizerEQAudioProcessor::getCurrentProgram() int CrystalizerEQAudioProcessor::getCurrentProgram() {
{
return 0; 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 {}; 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() { 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 // Use this method as the place to do any pre-playback
// initialisation that you need.. // initialisation that you need..
@ -230,41 +219,55 @@ void CrystalizerEQAudioProcessor::prepareToPlay (double sampleRate, int samplesP
spec.numChannels = static_cast<juce::uint32>(getTotalNumOutputChannels()); spec.numChannels = static_cast<juce::uint32>(getTotalNumOutputChannels());
mbLowpass.prepare(spec); mbLowpassL.prepare(spec);
mbHighpass.prepare(spec); mbLowpassR.prepare(spec);
mbLowpass.reset();
mbHighpass.reset();
mbHighPeak.prepare(spec); mbHighpassL.prepare(spec);
mbHighPeak.reset(); mbHighpassR.prepare(spec);
saturator.prepare(spec); mbLowpassL.reset();
saturator.functionToUse = [] (float x) { 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); return std::tanh(x);
}; };
saturator.reset(); saturatorL.reset();
saturatorR.prepare(spec);
saturatorR.functionToUse = [](float x) {
return std::tanh(x);
};
saturatorR.reset();
spec.numChannels = static_cast<juce::uint32>(getTotalNumOutputChannels());
leftChain.prepare(spec); leftChain.prepare(spec);
rightChain.prepare(spec); rightChain.prepare(spec);
leftChain.reset(); leftChain.reset();
rightChain.reset(); rightChain.reset();
updateFilters(); // initiale Koeffizienten updateFilters();
} }
static void setCoeffs(juce::dsp::IIR::Filter<float> &f, static void setCoeffs(juce::dsp::IIR::Filter<float> &f,
juce::dsp::IIR::Coefficients<float>::Ptr c) juce::dsp::IIR::Coefficients<float>::Ptr c) {
{ f.coefficients = c;
f.coefficients = c; // ok
// oder: *f.coefficients = *c;
} }
void CrystalizerEQAudioProcessor::updateFilters() {
void CrystalizerEQAudioProcessor::updateFilters()
{
const auto sr = getSampleRate(); const auto sr = getSampleRate();
const auto lowFreq = apvts.getRawParameterValue("LowBandFreq")->load(); const auto lowFreq = apvts.getRawParameterValue("LowBandFreq")->load();
@ -312,21 +315,12 @@ void CrystalizerEQAudioProcessor::updateFilters()
const auto crystalized = static_cast<int>(apvts.getRawParameterValue("CrystalizeButton")->load()); const auto crystalized = static_cast<int>(apvts.getRawParameterValue("CrystalizeButton")->load());
juce::dsp::IIR::Coefficients<float>::Ptr lowBand; juce::dsp::IIR::Coefficients<float>::Ptr lowBand;
juce::dsp::IIR::Coefficients<float>::Ptr highBand; juce::dsp::IIR::Coefficients<float>::Ptr highBand;
// links
leftChain.get<0>().setGainDecibels(inputGdB); leftChain.get<0>().setGainDecibels(inputGdB);
rightChain.get<0>().setGainDecibels(inputGdB); rightChain.get<0>().setGainDecibels(inputGdB);
setCoeffs(leftChain.get<5>(), peak1); setCoeffs(leftChain.get<5>(), peak1);
setCoeffs(rightChain.get<5>(), peak1); setCoeffs(rightChain.get<5>(), peak1);
setCoeffs(leftChain.get<6>(), peak2); setCoeffs(leftChain.get<6>(), peak2);
@ -334,11 +328,13 @@ void CrystalizerEQAudioProcessor::updateFilters()
setCoeffs(leftChain.get<7>(), peak3); setCoeffs(leftChain.get<7>(), peak3);
setCoeffs(rightChain.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(leftChain.get<12>(), crystalizedShelf);
setCoeffs(rightChain.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(leftChain.get<13>(), crystalizedBell);
setCoeffs(rightChain.get<13>(), crystalizedBell); setCoeffs(rightChain.get<13>(), crystalizedBell);
@ -346,8 +342,6 @@ void CrystalizerEQAudioProcessor::updateFilters()
rightChain.get<14>().setGainDecibels(outputGdB); rightChain.get<14>().setGainDecibels(outputGdB);
if (peak1Bypass) { if (peak1Bypass) {
leftChain.setBypassed<5>(true); leftChain.setBypassed<5>(true);
rightChain.setBypassed<5>(true); rightChain.setBypassed<5>(true);
@ -404,7 +398,6 @@ void CrystalizerEQAudioProcessor::updateFilters()
setCoeffs(rightChain.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); rightChain.setBypassed<2>(lowSlope < 2);
leftChain.setBypassed<3>(lowSlope < 3); leftChain.setBypassed<3>(lowSlope < 3);
@ -415,7 +408,6 @@ void CrystalizerEQAudioProcessor::updateFilters()
break; break;
} }
case 2: case 2:
// Q steuert die „Güte“/Steilheit des Übergangs
lowBand = juce::dsp::IIR::Coefficients<float>::makeLowShelf(sr, lowFreq, lowQ, lowGainLin); 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); setCoeffs(rightChain.get<1>(), lowBand);
@ -423,18 +415,15 @@ void CrystalizerEQAudioProcessor::updateFilters()
rightChain.setBypassed<1>(false); rightChain.setBypassed<1>(false);
break; 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); 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); setCoeffs(rightChain.get<1>(), lowBand);
leftChain.setBypassed<1>(false); leftChain.setBypassed<1>(false);
rightChain.setBypassed<1>(false); rightChain.setBypassed<1>(false);
break; break;
} }
//HIGH-BAND //HIGH-BAND
const float highGainHin = juce::Decibels::decibelsToGain(highGdB); const float highGainHin = juce::Decibels::decibelsToGain(highGdB);
@ -467,7 +456,6 @@ void CrystalizerEQAudioProcessor::updateFilters()
setCoeffs(rightChain.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); rightChain.setBypassed<9>(highSlope < 2);
leftChain.setBypassed<10>(highSlope < 3); leftChain.setBypassed<10>(highSlope < 3);
@ -478,7 +466,6 @@ void CrystalizerEQAudioProcessor::updateFilters()
break; break;
} }
case 2: case 2:
// Q steuert die „Güte“/Steilheit des Übergangs
highBand = juce::dsp::IIR::Coefficients<float>::makeHighShelf(sr, highFreq, highQ, highGainHin); 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); setCoeffs(rightChain.get<8>(), highBand);
@ -486,7 +473,7 @@ void CrystalizerEQAudioProcessor::updateFilters()
rightChain.setBypassed<8>(false); rightChain.setBypassed<8>(false);
break; 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); 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); setCoeffs(rightChain.get<8>(), highBand);
@ -494,7 +481,6 @@ void CrystalizerEQAudioProcessor::updateFilters()
rightChain.setBypassed<8>(false); rightChain.setBypassed<8>(false);
break; break;
} }
if (!crystalized) { if (!crystalized) {
@ -509,24 +495,21 @@ void CrystalizerEQAudioProcessor::updateFilters()
rightChain.setBypassed<13>(false); rightChain.setBypassed<13>(false);
} }
lowCutActive = (lowBandModes == 1);
highCutActive = (highBandModes == 1);
} }
juce::String CrystalizerEQAudioProcessor::savePresetToFile() const { juce::String CrystalizerEQAudioProcessor::savePresetToFile() const {
const auto nameInput = getPresetName(); const auto nameInput = getPresetName();
auto appData = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory);
auto desktop = juce::File::getSpecialLocation(juce::File::userDesktopDirectory); auto presetFolder = appData.getChildFile("AXIOM")
.getChildFile("CrystalizerEQ")
.getChildFile("Presets");
// Unterordner auf dem Desktop anlegen
auto presetFolder = desktop.getChildFile("CrystalizerEQ_Presets");
presetFolder.createDirectory(); presetFolder.createDirectory();
// Datei vorbereiten
auto file = presetFolder.getNonexistentChildFile(nameInput, ".xml"); auto file = presetFolder.getNonexistentChildFile(nameInput, ".xml");
juce::ValueTree preset("Preset"); juce::ValueTree preset("Preset");
@ -535,10 +518,8 @@ juce::String CrystalizerEQAudioProcessor::savePresetToFile() const {
for (auto *p: getParameters()) { for (auto *p: getParameters()) {
if (p == nullptr) continue; if (p == nullptr) continue;
if (auto* ranged = dynamic_cast<juce::RangedAudioParameter*>(p)) if (auto *ranged = dynamic_cast<juce::RangedAudioParameter *>(p)) {
{ if (ranged->getParameterID() == "MasterBypass") { continue; }
if (ranged->getParameterID() == "MasterBypass")
{continue;}
juce::ValueTree param("Param"); juce::ValueTree param("Param");
param.setProperty("id", ranged->getParameterID(), nullptr); param.setProperty("id", ranged->getParameterID(), nullptr);
@ -555,16 +536,12 @@ juce::String CrystalizerEQAudioProcessor::savePresetToFile() const {
} }
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");
auto desktop = juce::File::getSpecialLocation(juce::File::userDesktopDirectory); auto files = presetFolder.findChildFiles(juce::File::findFiles, false, "*.xml");
auto presetFolder = desktop.getChildFile("CrystalizerEQ_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
);
for (const auto &f: files) { for (const auto &f: files) {
if (f.getFileName() != preset) { if (f.getFileName() != preset) {
@ -579,10 +556,8 @@ void CrystalizerEQAudioProcessor::loadPreset(const juce::String &preset){
if (p == nullptr) continue; if (p == nullptr) continue;
if (auto *ranged = dynamic_cast<juce::RangedAudioParameter *>(p)) { if (auto *ranged = dynamic_cast<juce::RangedAudioParameter *>(p)) {
if (auto* child = xml->getFirstChildElement()) if (auto *child = xml->getFirstChildElement()) {
{ while (child != nullptr) {
while (child != nullptr)
{
juce::String id = child->getStringAttribute("id"); juce::String id = child->getStringAttribute("id");
if (id == ranged->getParameterID()) { if (id == ranged->getParameterID()) {
@ -601,15 +576,15 @@ void CrystalizerEQAudioProcessor::loadPreset(const juce::String &preset){
} }
void CrystalizerEQAudioProcessor::deletePreset(const juce::String &preset) const { void CrystalizerEQAudioProcessor::deletePreset(const juce::String &preset) const {
auto desktop = juce::File::getSpecialLocation(juce::File::userDesktopDirectory); auto appData = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory);
auto presetFolder = appData.getChildFile("AXIOM")
// Unterordner auf dem Desktop anlegen .getChildFile("CrystalizerEQ")
auto presetFolder = desktop.getChildFile("CrystalizerEQ_Presets"); .getChildFile("Presets");
juce::Array<juce::File> files = presetFolder.findChildFiles( juce::Array<juce::File> files = presetFolder.findChildFiles(
juce::File::findFiles, // nur Dateien (nicht Ordner) juce::File::findFiles,
false, // nicht rekursiv (kein Durchsuchen von Unterordnern) false,
"*.xml" // Pattern: alle Dateien "*.xml"
); );
for (const auto &f: files) { for (const auto &f: files) {
@ -624,35 +599,29 @@ void CrystalizerEQAudioProcessor::resetAllParameters() const{
for (auto *p: getParameters()) { for (auto *p: getParameters()) {
if (p == nullptr) continue; if (p == nullptr) continue;
if (auto* ranged = dynamic_cast<juce::RangedAudioParameter*>(p)) if (auto *ranged = dynamic_cast<juce::RangedAudioParameter *>(p)) {
{ const float def = ranged->getDefaultValue();
const float def = ranged->getDefaultValue(); // normalisiert [0..1]
ranged->beginChangeGesture(); ranged->beginChangeGesture();
ranged->setValueNotifyingHost(def); ranged->setValueNotifyingHost(def);
ranged->endChangeGesture(); 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"}; juce::StringArray presetNames = {"Init"};
auto desktop = juce::File::getSpecialLocation(juce::File::userDesktopDirectory); auto appData = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory);
auto presetFolder = appData.getChildFile("AXIOM")
// Unterordner auf dem Desktop anlegen .getChildFile("CrystalizerEQ")
auto presetFolder = desktop.getChildFile("CrystalizerEQ_Presets"); .getChildFile("Presets");
juce::Array<juce::File> files = presetFolder.findChildFiles( juce::Array<juce::File> files = presetFolder.findChildFiles(
juce::File::findFiles, // nur Dateien (nicht Ordner) juce::File::findFiles,
false, // nicht rekursiv (kein Durchsuchen von Unterordnern) false,
"*.xml" // Pattern: alle Dateien "*.xml"
); );
for (const auto &f: files) { for (const auto &f: files) {
@ -663,15 +632,13 @@ juce::StringArray CrystalizerEQAudioProcessor::getPresetNamesArray() const{
return presetNames; return presetNames;
} }
void CrystalizerEQAudioProcessor::releaseResources() void CrystalizerEQAudioProcessor::releaseResources() {
{
// When playback stops, you can use this as an opportunity to free up any // When playback stops, you can use this as an opportunity to free up any
// spare memory, etc. // spare memory, etc.
} }
#ifndef JucePlugin_PreferredChannelConfigurations #ifndef JucePlugin_PreferredChannelConfigurations
bool CrystalizerEQAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const bool CrystalizerEQAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const {
{
#if JucePlugin_IsMidiEffect #if JucePlugin_IsMidiEffect
juce::ignoreUnused (layouts); juce::ignoreUnused (layouts);
return true; return true;
@ -695,66 +662,17 @@ bool CrystalizerEQAudioProcessor::isBusesLayoutSupported (const BusesLayout& lay
} }
#endif #endif
void CrystalizerEQAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) void CrystalizerEQAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages) {
{
juce::ignoreUnused(midiMessages); juce::ignoreUnused(midiMessages);
juce::ScopedNoDenormals noDenormals; juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels();
// In case we have more outputs than inputs, this code clears any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
// This is here to avoid people getting screaming feedback
// when they first compile a plugin, but obviously you don't need to keep
// this code if your algorithm always overwrites all the output channels.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear(i, 0, buffer.getNumSamples()); buffer.clear(i, 0, buffer.getNumSamples());
juce::AudioBuffer<float> lowBuf, highBuf; juce::AudioBuffer<float> lowBuf, highBuf;
// This is the place where you'd normally do the guts of your plugin's
// audio processing...
// Make sure to reset the state if your inner loop is processing
// the samples and the outer loop is handling the channels.
// Alternatively, you can process the samples with the channels
// interleaved by keeping the same state.
// (Einfacher Weg) pro Block Parameter lesen & Filter updaten
const bool testNoiseOn = apvts.getRawParameterValue("TestNoiseEnabled")->load() > 0.5f;
if (testNoiseOn)
{
buffer.clear();
const float noiseLevelDb = apvts.getRawParameterValue("TestNoiseLevel")->load();
const float gain = juce::Decibels::decibelsToGain (noiseLevelDb);
const int numSamples = buffer.getNumSamples();
const int numChannels = buffer.getNumChannels();
for (int ch = 0; ch < numChannels; ++ch)
{
auto* write = buffer.getWritePointer (ch);
auto& rng = (ch == 0 ? noiseRandL : noiseRandR);
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
}
}
}
const bool masterBypassed = apvts.getRawParameterValue("MasterBypass")->load() > 0.5f; const bool masterBypassed = apvts.getRawParameterValue("MasterBypass")->load() > 0.5f;
if (masterBypassed) if (masterBypassed)
return; return;
@ -768,18 +686,16 @@ void CrystalizerEQAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer
} }
updateFilters(); updateFilters();
juce::dsp::AudioBlock<float> block(buffer); juce::dsp::AudioBlock<float> block(buffer);
auto leftBlock = block.getSingleChannelBlock(0); auto leftBlock = block.getSingleChannelBlock(0);
auto rightBlock = block.getSingleChannelBlock(1); auto rightBlock = block.getSingleChannelBlock(1);
juce::dsp::ProcessContextReplacing<float> leftCtx(leftBlock); juce::dsp::ProcessContextReplacing<float> leftCtx(leftBlock);
juce::dsp::ProcessContextReplacing<float> rightCtx(rightBlock); juce::dsp::ProcessContextReplacing<float> rightCtx(rightBlock);
@ -787,12 +703,11 @@ void CrystalizerEQAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer
rightChain.process(rightCtx); rightChain.process(rightCtx);
audioFIFO.loadSamplesToFIFO(buffer); 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(); const auto sr = getSampleRate();
float fc = 10000.0f; float fc = 10000.0f;
fc = juce::jlimit(20.0f, 0.49f * (float) sr, fc); fc = juce::jlimit(20.0f, 0.49f * (float) sr, fc);
@ -801,12 +716,15 @@ juce::AudioBuffer<float> CrystalizerEQAudioProcessor::processMultiBand(juce::Aud
auto lp = Coeff::makeLowPass(sr, fc, 0.7071f); auto lp = Coeff::makeLowPass(sr, fc, 0.7071f);
auto hp = Coeff::makeHighPass(sr, fc, 0.7071f); auto hp = Coeff::makeHighPass(sr, fc, 0.7071f);
mbLowpass.setType(juce::dsp::LinkwitzRileyFilterType::lowpass); mbLowpassL.setType(juce::dsp::LinkwitzRileyFilterType::lowpass);
mbLowpass.setCutoffFrequency(fc); mbLowpassL.setCutoffFrequency(fc);
mbLowpassR.setType(juce::dsp::LinkwitzRileyFilterType::lowpass);
mbHighpass.setType(juce::dsp::LinkwitzRileyFilterType::highpass); mbLowpassR.setCutoffFrequency(fc);
mbHighpass.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); juce::dsp::AudioBlock<float> lowBlock(lowBuf);
@ -815,11 +733,10 @@ juce::AudioBuffer<float> CrystalizerEQAudioProcessor::processMultiBand(juce::Aud
juce::dsp::ProcessContextReplacing<float> leftLowCtx(leftLowBlock); juce::dsp::ProcessContextReplacing<float> leftLowCtx(leftLowBlock);
juce::dsp::ProcessContextReplacing<float> rightLowCtx(rightLowBlock); juce::dsp::ProcessContextReplacing<float> rightLowCtx(rightLowBlock);
mbLowpass.process(leftLowCtx); mbLowpassL.process(leftLowCtx);
mbLowpass.process(rightLowCtx); mbLowpassR.process(rightLowCtx);
}
{ } {
//HIGH-BAND PROCESSING //HIGH-BAND PROCESSING
juce::dsp::AudioBlock<float> highBlock(highBuf); juce::dsp::AudioBlock<float> highBlock(highBuf);
auto leftHighBlock = highBlock.getSingleChannelBlock(0); auto leftHighBlock = highBlock.getSingleChannelBlock(0);
@ -827,8 +744,8 @@ juce::AudioBuffer<float> CrystalizerEQAudioProcessor::processMultiBand(juce::Aud
juce::dsp::ProcessContextReplacing<float> leftHighCtx(leftHighBlock); juce::dsp::ProcessContextReplacing<float> leftHighCtx(leftHighBlock);
juce::dsp::ProcessContextReplacing<float> rightHighCtx(rightHighBlock); juce::dsp::ProcessContextReplacing<float> rightHighCtx(rightHighBlock);
mbHighpass.process(leftHighCtx); mbHighpassL.process(leftHighCtx);
mbHighpass.process(rightHighCtx); mbHighpassR.process(rightHighCtx);
//WHITE NOISE ON HIGH-BAND //WHITE NOISE ON HIGH-BAND
@ -836,25 +753,27 @@ juce::AudioBuffer<float> CrystalizerEQAudioProcessor::processMultiBand(juce::Aud
const int numSamples = highBuf.getNumSamples(); const int numSamples = highBuf.getNumSamples();
const int numChannels = highBuf.getNumChannels(); const int numChannels = highBuf.getNumChannels();
const float rms = highBuf.getRMSLevel(0, 0, numSamples); // oder Mittelwert über beide Kanäle const float rmsL = highBuf.getRMSLevel(0, 0, numSamples);
const float dyn = juce::jlimit(0.0f, 1.0f, rms * 2.0f); // einfacher Kompressor const float rmsR = highBuf.getRMSLevel(1, 0, numSamples);
const float gain = dyn * juce::Decibels::decibelsToGain(.5f); 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) { for (int ch = 0; ch < numChannels; ++ch) {
auto *write = highBuf.getWritePointer(ch); auto *write = highBuf.getWritePointer(ch);
auto &rng = (ch == 0 ? noiseRandL : noiseRandR); 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) // rng.nextFloat() ∈ [0,1) → in [-1,1)
const float white = 2.0f * rng.nextFloat() - 1.0f; 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); saturatorL.process(leftHighCtx);
saturator.process(rightHighCtx); saturatorR.process(rightHighCtx);
} }
@ -863,57 +782,47 @@ juce::AudioBuffer<float> CrystalizerEQAudioProcessor::processMultiBand(juce::Aud
const int numCh = out.getNumChannels(); const int numCh = out.getNumChannels();
const int numSm = out.getNumSamples(); const int numSm = out.getNumSamples();
const float highBandGain = juce::Decibels::decibelsToGain(3.0f);
for (int ch = 0; ch < numCh; ++ch) 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; return out;
} }
void CrystalizerEQAudioProcessor::setPresetName(const juce::String &name) {
void CrystalizerEQAudioProcessor::setPresetName (const juce::String& name)
{
presetName = 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) return true; // (change this to false if you choose to not supply an editor)
} }
juce::AudioProcessorEditor* CrystalizerEQAudioProcessor::createEditor() juce::AudioProcessorEditor *CrystalizerEQAudioProcessor::createEditor() {
{
return new CrystalizerEQAudioProcessorEditor(*this); 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 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 // 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. // 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, // You should use this method to restore your parameters from this memory block,
// whose contents will have been created by the getStateInformation() call. // whose contents will have been created by the getStateInformation() call.
} }
//============================================================================== //==============================================================================
// This creates new instances of the plugin.. // This creates new instances of the plugin..
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() juce::AudioProcessor * JUCE_CALLTYPE createPluginFilter() {
{
return new CrystalizerEQAudioProcessor(); return new CrystalizerEQAudioProcessor();
} }
void AudioFIFO::loadSamplesToFIFO(const juce::AudioBuffer<float> &samples) { void AudioFIFO::loadSamplesToFIFO(const juce::AudioBuffer<float> &samples) {
const int numSamples = samples.getNumSamples(); const int numSamples = samples.getNumSamples();
@ -922,20 +831,16 @@ void AudioFIFO::loadSamplesToFIFO(const juce::AudioBuffer<float> &samples) {
const juce::SpinLock::ScopedLockType guard(lock); // <— NEU const juce::SpinLock::ScopedLockType guard(lock); // <— NEU
sampleStack.ensureStorageAllocated(sampleStack.size() + numSamples); sampleStack.ensureStorageAllocated(sampleStack.size() + numSamples);
for (int i = 0; i < numSamples; ++i) for (int i = 0; i < numSamples; ++i) {
{
sampleStack.add(channelData[i]); sampleStack.add(channelData[i]);
} }
} }
juce::Array<float> AudioFIFO::sendSamplesToEditor() { juce::Array<float> AudioFIFO::sendSamplesToEditor() {
const juce::SpinLock::ScopedLockType guard(lock); const juce::SpinLock::ScopedLockType guard(lock);
juce::Array<float> copiedSamples = sampleStack; juce::Array<float> copiedSamples = sampleStack;
sampleStack.clear(); sampleStack.clear();
return copiedSamples; return copiedSamples;
} }

View File

@ -18,29 +18,33 @@
class AudioFIFO { class AudioFIFO {
public: public:
AudioFIFO(); AudioFIFO();
~AudioFIFO(); ~AudioFIFO();
juce::Array<float> sampleStack; juce::Array<float> sampleStack;
void loadSamplesToFIFO(const juce::AudioBuffer<float> &samples); void loadSamplesToFIFO(const juce::AudioBuffer<float> &samples);
juce::Array<float> sendSamplesToEditor(); juce::Array<float> sendSamplesToEditor();
juce::SpinLock lock; juce::SpinLock lock;
private: private:
}; };
class CrystalizerEQAudioProcessor : public juce::AudioProcessor , public juce::AudioProcessorValueTreeState::Listener class CrystalizerEQAudioProcessor : public juce::AudioProcessor, public juce::AudioProcessorValueTreeState::Listener {
{
public: public:
//============================================================================== //==============================================================================
CrystalizerEQAudioProcessor(); CrystalizerEQAudioProcessor();
~CrystalizerEQAudioProcessor() override; ~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; void releaseResources() override;
#ifndef JucePlugin_PreferredChannelConfigurations #ifndef JucePlugin_PreferredChannelConfigurations
@ -51,33 +55,44 @@ public:
//============================================================================== //==============================================================================
juce::AudioProcessorEditor *createEditor() override; juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override; bool hasEditor() const override;
//============================================================================== //==============================================================================
const juce::String getName() const override; const juce::String getName() const override;
bool acceptsMidi() const override; bool acceptsMidi() const override;
bool producesMidi() const override; bool producesMidi() const override;
bool isMidiEffect() const override; bool isMidiEffect() const override;
double getTailLengthSeconds() const override; double getTailLengthSeconds() const override;
//============================================================================== //==============================================================================
int getNumPrograms() override; int getNumPrograms() override;
int getCurrentProgram() override; int getCurrentProgram() override;
void setCurrentProgram(int index) override; void setCurrentProgram(int index) override;
const juce::String getProgramName(int index) override; const juce::String getProgramName(int index) override;
void changeProgramName(int index, const juce::String &newName) override; void changeProgramName(int index, const juce::String &newName) override;
//============================================================================== //==============================================================================
void getStateInformation(juce::MemoryBlock &destData) override; void getStateInformation(juce::MemoryBlock &destData) override;
void setStateInformation(const void *data, int sizeInBytes) override; void setStateInformation(const void *data, int sizeInBytes) override;
// --------- Parameter (APVTS) ---------- // --------- Parameter (APVTS) ----------
static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
juce::AudioProcessorValueTreeState apvts{*this, nullptr, "PARAMS", createParameterLayout()}; juce::AudioProcessorValueTreeState apvts{*this, nullptr, "PARAMS", createParameterLayout()};
void setPresetName(const juce::String &s); void setPresetName(const juce::String &s);
juce::String getPresetName() const noexcept { return presetName; } juce::String getPresetName() const noexcept { return presetName; }
juce::StringArray getPresetNamesArray() const; juce::StringArray getPresetNamesArray() const;
@ -90,22 +105,21 @@ public:
void resetAllParameters() const; void resetAllParameters() const;
bool lowCutActive = false;
bool highCutActive = false;
AudioFIFO audioFIFO; AudioFIFO audioFIFO;
private: private:
// --------- EQ-Kette ----------
using Gain = juce::dsp::Gain<float>; using Gain = juce::dsp::Gain<float>;
using Filter = juce::dsp::IIR::Filter<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; juce::Random noiseRandL, noiseRandR;
Chain leftChain, rightChain; 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);
@ -113,10 +127,13 @@ private:
using Coeff = juce::dsp::IIR::Coefficients<float>; using Coeff = juce::dsp::IIR::Coefficients<float>;
juce::dsp::LinkwitzRileyFilter<float> mbLowpass;
juce::dsp::LinkwitzRileyFilter<float> mbHighpass; juce::dsp::IIR::Filter<float> mbHighPeakL;
juce::dsp::IIR::Filter<float> mbHighPeak; juce::dsp::IIR::Filter<float> mbHighPeakR;
juce::dsp::WaveShaper<float> saturator;
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"};
@ -124,5 +141,3 @@ private:
//============================================================================== //==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CrystalizerEQAudioProcessor) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CrystalizerEQAudioProcessor)
}; };