2025-10-22 09:25:31 +02:00

879 lines
30 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
==============================================================================
This file contains the basic framework code for a JUCE plugin editor.
==============================================================================
*/
#include "PluginProcessor.h"
#include "PluginEditor.h"
//==============================================================================
CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor (CrystalizerEQAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p), spectrumAnalyzer(p.audioFIFO, p)
{
// Make sure that before the constructor has finished, you've set the
// editor's size to whatever you need it to be.
setSize (1280, 480);
// these define the parameters of our slider object
startTimerHz(60);
//AXIOM WAR HIER
auto setupLabel = [] (juce::Label& label, const juce::String& text)
{
label.setText (text, juce::dontSendNotification);
label.setJustificationType (juce::Justification::centred);
label.setColour (juce::Label::textColourId, juce::Colours::black);
label.setInterceptsMouseClicks (false, false); // Klicks gehen an den Slider
};
setupLabel(testNoiseLabel, "Test Noise");
addAndMakeVisible (testNoiseLabel);
//LOW-BAND
setupLabel (lowBandFreqLabel, "LowBand Freq");
setupLabel (lowBandSlopeLabel, "LowBand Slope");
setupLabel (lowBandGainLabel, "LowBand Gain");
setupLabel(lowBandQLabel, "LowBand Q");
addAndMakeVisible (lowBandFreqLabel);
addAndMakeVisible (lowBandSlopeLabel);
addAndMakeVisible(lowBandGainLabel);
addAndMakeVisible(lowBandQLabel);
lowBandModeBox.setJustificationType (juce::Justification::centred);
addAndMakeVisible (lowBandModeBox);
//PEAK 1
setupLabel (peak1FreqLabel,"Low-Mid Freq");
setupLabel (peak1GainLabel,"Low-Mid Gain");
setupLabel (peak1QLabel, "Low-Mid Q");
addAndMakeVisible (peak1FreqLabel);
addAndMakeVisible (peak1GainLabel);
addAndMakeVisible (peak1QLabel);
//PEAK 2
setupLabel (peak2FreqLabel,"Mid Freq");
setupLabel (peak2GainLabel,"Mid Gain");
setupLabel (peak2QLabel, "Mid Q");
addAndMakeVisible (peak2FreqLabel);
addAndMakeVisible (peak2GainLabel);
addAndMakeVisible (peak2QLabel);
//PEAK 3
setupLabel (peak3FreqLabel,"High-Mid Freq");
setupLabel (peak3GainLabel,"High-Mid Gain");
setupLabel (peak3QLabel, "High-Mid Q");
addAndMakeVisible (peak3FreqLabel);
addAndMakeVisible (peak3GainLabel);
addAndMakeVisible (peak3QLabel);
//HIGH-BAND
setupLabel (highBandFreqLabel, "HighBand Freq");
setupLabel (highBandSlopeLabel, "HighBand Slope");
setupLabel (highBandGainLabel, "HighBand Gain");
setupLabel(highBandQLabel, "HighBand Q");
addAndMakeVisible (highBandFreqLabel);
addAndMakeVisible (highBandSlopeLabel);
addAndMakeVisible(highBandGainLabel);
addAndMakeVisible(highBandQLabel);
highBandModeBox.setJustificationType (juce::Justification::centred);
addAndMakeVisible (highBandModeBox);
setupLabel(inputLabel, "Input");
setupLabel(outputLabel, "Output");
addAndMakeVisible (inputLabel);
addAndMakeVisible (outputLabel);
setupLabel(presetBoxLabel, "Presets");
presetBox.setJustificationType (juce::Justification::centred);
auto presets = audioProcessor.getPresetNamesArray();
presetBox.addItemList(presets, 1);
presetBox.setSelectedId(1, juce::dontSendNotification);
addAndMakeVisible(presetBox);
presetMenuButton.setName("PresetMenuButton");
presetMenuButton.setButtonText("Preset Menu");
presetMenuButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black);
presetMenuButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
presetMenuButton.setToggleState(false, juce::dontSendNotification);
addAndMakeVisible(presetMenuButton);
addAndMakeVisible(savePresetButton);
addAndMakeVisible(deletePresetButton);
presetNameInput.setTextToShowWhenEmpty("Preset Name", juce::Colours::grey);
presetNameInput.setJustification(juce::Justification::centred);
presetNameInput.setColour(juce::TextEditor::backgroundColourId, juce::Colours::black);
presetNameInput.setColour(juce::TextEditor::textColourId, juce::Colours::white);
addAndMakeVisible(presetNameInput);
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
audioProcessor.apvts.getParameter("LowBandModes")))
{
// Items 1-basiert hinzufügen
lowBandModeBox.addItemList (choice->choices, 1);
// aktuellen Index spiegeln (0-basiert -> setSelectedItemIndex)
lowBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification);
// Attachment erst, wenn Items existieren
lowBandModeAttach = std::make_unique<ComboBoxAttach>(
audioProcessor.apvts, "LowBandModes", lowBandModeBox);
}
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
audioProcessor.apvts.getParameter("HighBandModes")))
{
// Items 1-basiert hinzufügen
highBandModeBox.addItemList (choice->choices, 1);
// aktuellen Index spiegeln (0-basiert -> setSelectedItemIndex)
highBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification);
// Attachment erst, wenn Items existieren
highBandModeAttach = std::make_unique<ComboBoxAttach>(
audioProcessor.apvts, "HighBandModes", highBandModeBox);
}
auto style = juce::Slider::RotaryHorizontalVerticalDrag;
for (auto* s : {
&testNoiseSlider, &lowBandFreqSlider, &lowBandSlopeSlider, &lowBandGainSlider, &lowBandQSlider,
&peak1FreqSlider, &peak1GainSlider, &peak1QSlider,
&peak2FreqSlider, &peak2GainSlider, &peak2QSlider,
&peak3FreqSlider, &peak3GainSlider, &peak3QSlider,
&highBandFreqSlider, &highBandSlopeSlider, &highBandGainSlider, &highBandQSlider,
&inputSlider, &outputSlider })
{
s->setSliderStyle (style);
s->setTextBoxStyle (juce::Slider::TextBoxBelow, true, 70, 18);
addAndMakeVisible (*s);
s->setColour (juce::Slider::textBoxTextColourId, juce::Colours::black);
}
// Attachments verbinden GUI <-> apvts Parameter-IDs
testNoiseAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "TestNoiseLevel", testNoiseSlider);
lowBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandFreq", lowBandFreqSlider);
lowBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandSlope", lowBandSlopeSlider);
lowBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandGain", lowBandGainSlider);
lowBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandQ", lowBandQSlider);
lowBandModeAttach = std::make_unique<ComboBoxAttach>(audioProcessor.apvts, "LowBandModes", lowBandModeBox);
peak1FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Freq", peak1FreqSlider);
peak1GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Gain", peak1GainSlider);
peak1QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Q", peak1QSlider);
peak2FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Freq", peak2FreqSlider);
peak2GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Gain", peak2GainSlider);
peak2QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Q", peak2QSlider);
peak3FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Freq", peak3FreqSlider);
peak3GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Gain", peak3GainSlider);
peak3QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Q", peak3QSlider);
highBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandFreq", highBandFreqSlider);
highBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandSlope", highBandSlopeSlider);
highBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandGain", highBandGainSlider);
highBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandQ", highBandQSlider);
highBandModeAttach = std::make_unique<ComboBoxAttach>(audioProcessor.apvts, "HighBandModes", highBandModeBox);
inputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "InputGain", inputSlider);
outputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "OutputGain", outputSlider);
// Display-Namen
testNoiseSlider.setName("TestNoise");
lowBandFreqSlider.setName ("LowBand Freq");
lowBandSlopeSlider.setName ("LowBand Slope");
lowBandGainSlider.setName ("LowBand Gain");
lowBandQSlider.setName ("LowBand Q");
peak1FreqSlider.setName("Peak1 Freq");
peak1GainSlider.setName("Peak1 Gain");
peak1QSlider.setName ("Peak1 Q");
peak2FreqSlider.setName("Peak2 Freq");
peak2GainSlider.setName("Peak2 Gain");
peak2QSlider.setName ("Peak2 Q");
peak3FreqSlider.setName("Peak3 Freq");
peak3GainSlider.setName("Peak3 Gain");
peak3QSlider.setName ("Peak3 Q");
highBandFreqSlider.setName ("HighBand Freq");
highBandSlopeSlider.setName ("HighBand Slope");
highBandGainSlider.setName ("HighBand Gain");
highBandQSlider.setName ("HighBand Q");
inputSlider.setName ("Input");
outputSlider.setName ("Output");
testNoiseSlider.setTextValueSuffix(" Gain");
lowBandFreqSlider.setTextValueSuffix (" Hz");
lowBandSlopeSlider.setTextValueSuffix (" dB/Oct");
lowBandGainSlider.setTextValueSuffix(" Gain");
lowBandQSlider.setTextValueSuffix (" Q");
peak1FreqSlider.setTextValueSuffix(" Hz");
peak1GainSlider.setTextValueSuffix(" dB");
peak1QSlider.setTextValueSuffix(" Q");
peak1BypassButton.setName("peak1Bypass");
peak1BypassButton.setButtonText("Low-Mid Bypass");
addAndMakeVisible(peak1BypassButton);
peak1BypassButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black);
peak1BypassButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
peak2FreqSlider.setTextValueSuffix(" Hz");
peak2GainSlider.setTextValueSuffix(" dB");
peak2QSlider.setTextValueSuffix(" Q");
peak2BypassButton.setName("peak2Bypass");
peak2BypassButton.setButtonText("Mid Bypass");
addAndMakeVisible(peak2BypassButton);
peak2BypassButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black);
peak2BypassButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
peak3FreqSlider.setTextValueSuffix(" Hz");
peak3GainSlider.setTextValueSuffix(" dB");
peak3QSlider.setTextValueSuffix(" Q");
peak3BypassButton.setName("peak3Bypass");
peak3BypassButton.setButtonText("High-Mid Bypass");
addAndMakeVisible(peak3BypassButton);
peak3BypassButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black);
peak3BypassButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
highBandFreqSlider.setTextValueSuffix (" Hz");
highBandSlopeSlider.setTextValueSuffix (" dB/Oct");
highBandGainSlider.setTextValueSuffix(" Gain");
highBandQSlider.setTextValueSuffix (" Q");
inputSlider.setTextValueSuffix(" IN");
outputSlider.setTextValueSuffix(" OUT");
testNoiseButton.setName("TestNoise");
testNoiseButton.setButtonText("Test Noise");
crystalizeButton.setName("CrystalizeButton");
crystalizeButton.setButtonText("Crystalize");
resetButton.setName("ResetButton");
resetButton.setButtonText("Reset");
masterBypassButton.setName("MasterBypass");
masterBypassButton.setButtonText("Master Bypass");
addAndMakeVisible (testNoiseButton);
addAndMakeVisible (crystalizeButton);
addAndMakeVisible (resetButton);
addAndMakeVisible (masterBypassButton);
savePresetButton.setName("SavePresetButton");
savePresetButton.setButtonText("Save Preset");
deletePresetButton.setName("DeletePresetButton");
deletePresetButton.setButtonText("Delete Preset");
presetNameInput.setVisible(false);
savePresetButton.setVisible(false);
deletePresetButton.setVisible(false);
testNoiseButton.onClick = [this]() {
if (auto* param = audioProcessor.apvts.getParameter("TestNoiseEnabled"))
{
if (param->getValue() == false) {
param->beginChangeGesture();
param->setValueNotifyingHost (1.0f);
param->endChangeGesture();
} else {
param->beginChangeGesture();
param->setValueNotifyingHost (0.0f); // -> true
param->endChangeGesture();
}
}
};
for (auto* b : {&peak1BypassButton, &peak2BypassButton, &peak3BypassButton, &crystalizeButton, &masterBypassButton}) {
b->onClick = [this, b]() {
juce::String paramID; // nimm juce::String statt std::string
if (b == &peak1BypassButton) paramID = "Peak1Bypass";
else if (b == &peak2BypassButton) paramID = "Peak2Bypass";
else if (b == &peak3BypassButton) paramID = "Peak3Bypass";
else if (b == &crystalizeButton) paramID = "CrystalizeButton";
else if (b == &masterBypassButton) paramID = "MasterBypass";
else {return; }
if (auto* param = audioProcessor.apvts.getParameter(paramID))
{
const bool isToggled = b->getToggleState();
const float target = isToggled ? 1.0f : 0.0f;
// Nur senden, wenn sich wirklich etwas ändert (Schwelle 0.5f)
if ((param->getValue() >= 0.5f) != isToggled)
{
param->beginChangeGesture();
param->setValueNotifyingHost(target);
param->endChangeGesture();
}
}
};
}
presetMenuButton.onClick = [this]() {
bool menuOpened = presetMenuButton.getToggleState();
if (!menuOpened) {
presetNameInput.setVisible(false);
savePresetButton.setVisible(false);
deletePresetButton.setVisible(false);
} else {
presetNameInput.setVisible(true);
savePresetButton.setVisible(true);
deletePresetButton.setVisible(true);
}
};
savePresetButton.onClick = [this]() {
if (presetNameInput.getText() != "")
audioProcessor.setPresetName (presetNameInput.getText());
else
audioProcessor.setPresetName ("Preset");
const auto presetName = audioProcessor.savePresetToFile();
presetBox.clear();
const auto updatedPresets = audioProcessor.getPresetNamesArray();
presetBox.addItemList(updatedPresets, 1);
for (int i = 0; i < presetBox.getNumItems(); i++)
{
if (presetName != presetBox.getItemText(i)) continue;
presetBox.setSelectedId(i + 1, juce::dontSendNotification);
}
presetNameInput.setText("");
};
deletePresetButton.onClick = [this]() {
auto selectedPreset = presetBox.getText();
if (selectedPreset == "Init") return;
const auto selectedPresetWithExt = selectedPreset + ".xml";
audioProcessor.deletePreset(selectedPresetWithExt);
audioProcessor.resetAllParameters();
presetBox.clear();
const auto updatedPresets = audioProcessor.getPresetNamesArray();
presetBox.addItemList(updatedPresets, 1);
presetBox.setSelectedId(1, juce::dontSendNotification);
};
presetBox.onChange = [this]() {
auto selectedPreset = presetBox.getText();
if (selectedPreset == "Init") {
audioProcessor.resetAllParameters();
return;
}
const auto selectedPresetWithExt = selectedPreset + ".xml";
audioProcessor.loadPreset(selectedPresetWithExt);
};
resetButton.onClick = [this]()
{
audioProcessor.resetAllParameters();
presetBox.setSelectedId(1, juce::dontSendNotification);
};
}
CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() {
stopTimer();
};
//==============================================================================
void CrystalizerEQAudioProcessorEditor::paint (juce::Graphics& g)
{
// (Our component is opaque, so we must completely fill the background with a solid colour)
// fill the whole window white
g.fillAll (juce::Colour::fromString("#333333"));
g.setColour (juce::Colour::fromString("#fcfaf9"));
g.setFont (15.0f);
g.drawFittedText ("CrystalizerEQ", getLocalBounds().removeFromTop(20),
juce::Justification::centred, 1);
//ANALYZER // VERSTEHEN!!!!!!
if (! analyzerArea.isEmpty())
{
juce::Graphics::ScopedSaveState guard(g); // Clip/Rückbau automatisch
g.reduceClipRegion(analyzerArea); // auf Sub-Rect beschneiden
auto r = analyzerArea.toFloat();
// Hintergrund des Analyzer-Bereichs
g.setColour(juce::Colours::black);
g.fillRect(r);
// Grid
g.setColour(juce::Colours::darkgrey.withAlpha(0.6f));
for (float db = -120.f; db <= 6.f; db += 12.f)
{
const float y = juce::jmap(db,
spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB,
r.getBottom(), r.getY());
g.drawLine(r.getX(), y, r.getRight(), y, 1.f);
}
// Bars
if (!spectrumAnalyzer.renderValuesDb.empty())
{
const int n = (int) spectrumAnalyzer.renderValuesDb.size();
g.setColour(juce::Colour::fromString("#56FEFF"));
juce::Path eqCurve;
bool hasStart = false;
const float visibleFloor = spectrumAnalyzer.MINDB;
for (int k = 0; k < n; ++k)
{
const float dB = juce::jlimit(spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB,
spectrumAnalyzer.renderValuesDb[(size_t) k]);
float yTop = juce::jmap(dB,
spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB,
r.getBottom(), r.getY());
if (dB <= visibleFloor) {
yTop = r.getBottom();
DBG ("Test");
}
float freq = k * spectrumAnalyzer.deltaF; // Frequenz des aktuellen Bins
if (k == 0) {
freq = 1e-12;
}
// Frequenz auf log-X-Koordinate (0.01.0) mappen
const float normX = std::log(freq / spectrumAnalyzer.MINFREQ) / std::log(spectrumAnalyzer.MAXFREQ / spectrumAnalyzer.MINFREQ);
// ...und in Pixel umrechnen:
float x = r.getX() + normX * r.getWidth();
//TODO: ANSTATT FFTSIZE VERDOPPELN, NUR DIE LINIEN VERDOPPELN -> HIGHER RESOLUTION
if (!hasStart) { eqCurve.startNewSubPath(x, yTop); hasStart = true; }
else {
eqCurve.lineTo(x, yTop);
}
}
g.strokePath(eqCurve, juce::PathStrokeType(1.5f));
juce::Path area = eqCurve;
area.lineTo(r.getRight(), r.getBottom());
area.lineTo(r.getX(), r.getBottom());
area.closeSubPath();
g.setColour(juce::Colour::fromString("#56FEFF").withAlpha(0.2f));
g.fillPath(area);
}
// Rahmen (optional)
g.setColour(juce::Colours::grey);
g.drawRect(analyzerArea);
}
}
void CrystalizerEQAudioProcessorEditor::timerCallback()
{
const int lowMode = (int) audioProcessor.apvts
.getRawParameterValue("LowBandModes")->load(); // 0..3
lowBandFreqSlider.setVisible (lowMode >= 1);
lowBandFreqLabel.setVisible (lowMode >= 1);
lowBandSlopeSlider.setVisible (lowMode == 1);
lowBandSlopeLabel.setVisible (lowMode == 1);
//HIER SLOPE BUTTON IMPLEMENTIEREN
lowBandGainSlider.setVisible(lowMode >= 2);
lowBandGainLabel .setVisible(lowMode >= 2);
lowBandQSlider .setVisible(lowMode >= 1);
lowBandQLabel .setVisible(lowMode >= 1);
const int highMode = (int) audioProcessor.apvts
.getRawParameterValue("HighBandModes")->load(); // 0..3
highBandFreqSlider.setVisible (highMode >= 1);
highBandFreqLabel.setVisible (highMode >= 1);
highBandSlopeSlider.setVisible (highMode == 1);
highBandSlopeLabel.setVisible (highMode == 1);
//HIER SLOPE BUTTON IMPLEMENTIEREN
highBandGainSlider.setVisible(highMode >= 2);
highBandGainLabel .setVisible(highMode >= 2);
highBandQSlider .setVisible(highMode >= 1);
highBandQLabel .setVisible(highMode >= 1);
// optional: Layout neu berechnen, damit keine Lücke bleibt
spectrumAnalyzer.processSamples();
repaint(analyzerArea);
resized();
}
void CrystalizerEQAudioProcessorEditor::resized()
{
// This is generally where you'll want to lay out the positions of any
// subcomponents in your editor.
// sets the position and size of the slider with arguments (x, y, width, height)
auto content = getLocalBounds().reduced(10);
// untere Leiste für Buttons/Combos
auto bottom = content.removeFromBottom(56);
// Grid-Parameter (gerne anpassen)
const int cellW = 120; // Zielbreite pro „Knopf“ (Label+Slider)
const int cellH = 130; // Zielhöhe pro „Knopf“
const int labelH = 22; // Labelhöhe
const int cols = std::max(1, content.getWidth() / cellW); // dynamisch je nach Fensterbreite
analyzerArea = content.removeFromTop(160);
// Helper zum Platzieren (wrappt automatisch)
auto place = [&](int idx, juce::Label* label, juce::Slider* slider, juce::Button* button = nullptr, juce::ComboBox *combobox = nullptr, juce::TextEditor *editor = nullptr)
{
const bool anyVisible =
(label && label ->isVisible()) ||
(slider && slider->isVisible()) ||
(button && button->isVisible()) ||
(combobox && combobox->isVisible()) ||
(editor && editor->isVisible());
if (!anyVisible)
return idx;
const int col = idx % cols;
const int row = idx / cols;
const int x = content.getX() + col * cellW;
const int y = content.getY() + row * cellH;
// Label (optional)
if (label)
label->setBounds(x + 6, y, cellW - 12, labelH);
const int topOffset = (label ? labelH : 0);
// Slider (optional)
if (slider)
slider->setBounds(x + 6, y + topOffset, cellW - 12, cellH - topOffset - 8);
// Button (optional)
if (button)
{
const int btnY = y + topOffset;
const int btnH = slider ? labelH : (cellH - topOffset - 8);
button->setBounds(x + 6, btnY, cellW - 12, btnH);
}
// ComboBox (optional)
if (combobox)
{
const int cbY = y + topOffset;
const int cbH = labelH; // oder wie Button behandeln
combobox->setBounds(x + 6, cbY, cellW - 12, cbH);
}
if (editor)
{
const int cbY = y + topOffset;
const int cbH = labelH; // oder wie Button behandeln
editor->setBounds(x + 6, cbY, cellW - 12, cbH);
}
return idx + 1;
};
int i = 0;
i = place(i, &testNoiseLabel, &testNoiseSlider);
i = place(i, nullptr, nullptr, nullptr, &lowBandModeBox);
i = place(i, &lowBandFreqLabel, &lowBandFreqSlider);
i = place(i, &lowBandSlopeLabel,&lowBandSlopeSlider);
i = place(i, &lowBandGainLabel, &lowBandGainSlider);
i = place(i, &lowBandQLabel, &lowBandQSlider);
i = place(i, &peak1FreqLabel, &peak1FreqSlider);
i = place(i, &peak1GainLabel, &peak1GainSlider);
i = place(i, &peak1QLabel, &peak1QSlider);
i = place(i, nullptr, nullptr, &peak1BypassButton);
i = place(i, &peak2FreqLabel, &peak2FreqSlider);
i = place(i, &peak2GainLabel, &peak2GainSlider);
i = place(i, &peak2QLabel, &peak2QSlider);
i = place(i, nullptr, nullptr, &peak2BypassButton);
i = place(i, &peak3FreqLabel, &peak3FreqSlider);
i = place(i, &peak3GainLabel, &peak3GainSlider);
i = place(i, &peak3QLabel, &peak3QSlider);
i = place(i, nullptr, nullptr, &peak3BypassButton);
i = place(i, nullptr, nullptr, nullptr, &highBandModeBox);
i = place(i, &highBandFreqLabel, &highBandFreqSlider);
i = place(i, &highBandSlopeLabel,&highBandSlopeSlider);
i = place(i, &highBandGainLabel, &highBandGainSlider);
i = place(i, &highBandQLabel, &highBandQSlider);
i = place(i, &inputLabel, &inputSlider);
i = place(i, &outputLabel, &outputSlider);
i = place(i, nullptr, nullptr, nullptr, &presetBox);
i = place(i, nullptr, nullptr, nullptr, nullptr, &presetNameInput);
// Untere Leiste: Buttons/Combos ordentlich platzieren
auto leftBar = bottom.removeFromLeft(bottom.getWidth()/2);
auto rightBar = bottom;
testNoiseButton.setBounds(leftBar.removeFromLeft(160).reduced(8, 8));
crystalizeButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black);
crystalizeButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
resetButton.setBounds(leftBar.removeFromLeft(200).reduced(8, 8));
const int n = 4; // z.B. 4 Buttons rechts
int btnW = rightBar.getWidth() / n;
crystalizeButton .setBounds(rightBar.removeFromLeft(btnW).reduced(8, 8));
masterBypassButton .setBounds(rightBar.removeFromLeft(btnW).reduced(8, 8));
savePresetButton .setBounds(rightBar.removeFromLeft(btnW).reduced(8, 8));
deletePresetButton .setBounds(rightBar.removeFromLeft(btnW).reduced(8, 8));
// Wenn du den PresetMenu-Button auch rechts willst, erhöhe n und füge ihn hinzu,
// oder pack ihn auf die linke Seite:
presetMenuButton.setBounds(leftBar.removeFromLeft(180).reduced(8, 8));
masterBypassButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black);
masterBypassButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
}
void SpectrumAnalyzer::processSamples() {
auto samples = audioFIFO.sendSamplesToEditor();
getFftFrame(samples);
}
void SpectrumAnalyzer::getFftFrame(juce::Array<float>& samples) {
const int needed = FFTSIZE - fftFrame.size();
if (needed <= 0)
return;
const int available = samples.size();
if (available <= 0)
return;
const int take = std::min(needed, available);
for (int i = 0; i < take; ++i)
fftFrame.add(samples.getUnchecked(i));
samples.removeRange(0, take);
if (fftFrame.size() == FFTSIZE)
{
std::vector<float> fullFrame(fftFrame.begin(), fftFrame.end());
fftFrame.removeRange(0, HOPSIZE);
applyWindowOnFftFrame(fullFrame);
}
}
void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector<float> &fullFrame) {
if (fullFrame.size()!= FFTSIZE) return;
window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE);
processWindowedFrame(fullFrame);
}
void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return;
fillFftDataFromFrame(windowedFrame);
fft.performRealOnlyForwardTransform(fftData.data());
buildMagnitudeSpectrum();
convertToDb();
applySmoothing();
renderValuesDb = getRenderValues();
}
void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
for (int n = 0; n < FFTSIZE; ++n) {
fftData[2*n] = windowedFrame[n];
fftData[2*n + 1] = 0.0f;
}
}
void SpectrumAnalyzer::buildMagnitudeSpectrum() {
for (int k = 0; k < BINS; ++k) {
float re = 0.f;
float im = 0.f;
if (k < BINS / 2) {
re = fftData[k];
} else {
im = fftData[k];
}
float mag = sqrt(re * re + im * im);
mag /= (FFTSIZE * 0.5f);
mag = std::max(mag, 1e-12f);
magnitudes[k] = mag;
}
}
void SpectrumAnalyzer::convertToDb() {
for (int k = 0; k < magnitudes.size(); ++k) {
float mag = magnitudes[k];
float dB = juce::Decibels::gainToDecibels(mag);
dB = juce::jlimit(MINDB, MAXDB, dB);
magnitudesDb[k] = dB;
}
}
void SpectrumAnalyzer::applySmoothing() {
applyEMA();
applyFreqSmoothing();
applyPeakHoldAndFalloff();
}
void SpectrumAnalyzer::applyEMA() {
for (int k = 0; k < magnitudesDb.size(); ++k) {
float smoothedVal = smoothingFactor * magnitudesDb[k] + (1 - smoothingFactor) * emaSmoothedMagnitudesDb[k];
emaSmoothedMagnitudesDb[k] = smoothedVal;
}
}
void SpectrumAnalyzer::applyFreqSmoothing() {
for (int k = 0; k < BINS; ++k) {
double freq = k * deltaF;
double lowestFreq = freq * pow(2.0, -OCTAVERADIUS);
double highestFreq = freq * pow(2.0, OCTAVERADIUS);
int lowestBin = std::max(0, static_cast<int>(floor(lowestFreq / deltaF)));
int highestBin = std::min(BINS - 1, static_cast<int>(ceil(highestFreq / deltaF)));
if (lowestBin > highestBin) {
lowestBin = k;
highestBin = k;
};
if (k == 0) {lowestBin = 0; highestBin = std::min(1, BINS - 1);}
float sum = 0.f;
for (int bin = lowestBin; bin <= highestBin; ++bin) {
sum += emaSmoothedMagnitudesDb[bin];
}
float avg = sum / static_cast<float>(highestBin - lowestBin + 1);
freqSmoothedMagnitudesDb[k] = avg;
}
}
void SpectrumAnalyzer::applyPeakHoldAndFalloff() {
std::vector<float> prevPeak = peakHoldMagnitudesDb;
for (int k = 0; k < BINS; ++k) {
float current = freqSmoothedMagnitudesDb[k];
float prev = prevPeak[k];
peakHoldMagnitudesDb[k] = std::max(current, prev - FALLOFFRATE * DELTAT);
}
}
std::vector<float> SpectrumAnalyzer::getRenderValues() {
std::vector<float> renderValues = peakHoldMagnitudesDb;
return renderValues;
}