2025-10-31 14:34:42 +01:00

1239 lines
46 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"
#include "AXIOMDesignSystem.h"
#include "JuceLibraryCode/BinaryData.h"
using AXIOM::DesignSystem;
using Colours = DesignSystem::Colours;
using Typography = DesignSystem::Typography;
using Spacing = DesignSystem::Spacing;
using Shape = DesignSystem::Shape;
using Opacity = DesignSystem::Opacity;
using Layout = DesignSystem::Layout;
//==============================================================================
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, 720);
// these define the parameters of our slider object
startTimerHz(60);
addAndMakeVisible(headerBar);
headerBar.addAndMakeVisible(titleLabel);
headerBar.addAndMakeVisible(presetArea);
presetArea.addAndMakeVisible(presetBoxLabel);
presetArea.addAndMakeVisible(resetButton);
presetArea.addAndMakeVisible(presetBox);
presetArea.addAndMakeVisible(presetMenuButton);
headerBar.addAndMakeVisible(presetMenu);
presetMenu.addAndMakeVisible(presetNameInput);
presetMenu.addAndMakeVisible(savePresetButton);
presetMenu.addAndMakeVisible(deletePresetButton);
presetMenu.toFront(true);
presetMenu.setVisible(false);
addAndMakeVisible(mainPanel);
mainPanel.addAndMakeVisible(analyzerArea);
mainPanel.addAndMakeVisible(crystalizeButton);
//FILTERAREA
{
mainPanel.addAndMakeVisible(filterArea);
{
filterArea.addAndMakeVisible(lowFilterArea);
lowFilterArea.addAndMakeVisible(lowBandModeBox);
lowFilterArea.addAndMakeVisible(lowBandSlopeLabel);
lowFilterArea.addAndMakeVisible(lowBandGainLabel);
lowFilterArea.addAndMakeVisible(lowBandQLabel);
lowFilterArea.addAndMakeVisible(lowBandFreqLabel);
lowFilterArea.addAndMakeVisible(lowBandSlopeSlider);
lowFilterArea.addAndMakeVisible(lowBandGainSlider);
lowFilterArea.addAndMakeVisible(lowBandQSlider);
lowFilterArea.addAndMakeVisible(lowBandFreqSlider);
}
{
filterArea.addAndMakeVisible(lowMidFilterArea);
lowMidFilterArea.addAndMakeVisible(peak1FreqLabel);
lowMidFilterArea.addAndMakeVisible(peak1GainLabel);
lowMidFilterArea.addAndMakeVisible(peak1QLabel);
lowMidFilterArea.addAndMakeVisible(peak1FreqSlider);
lowMidFilterArea.addAndMakeVisible(peak1GainSlider);
lowMidFilterArea.addAndMakeVisible(peak1QSlider);
lowMidFilterArea.addAndMakeVisible(peak1BypassButton);
}
{
filterArea.addAndMakeVisible(midFilterArea);
midFilterArea.addAndMakeVisible(peak2FreqLabel);
midFilterArea.addAndMakeVisible(peak2GainLabel);
midFilterArea.addAndMakeVisible(peak2QLabel);
midFilterArea.addAndMakeVisible(peak2FreqSlider);
midFilterArea.addAndMakeVisible(peak2GainSlider);
midFilterArea.addAndMakeVisible(peak2QSlider);
midFilterArea.addAndMakeVisible(peak2BypassButton);
}
{
filterArea.addAndMakeVisible(highMidFilterArea);
highMidFilterArea.addAndMakeVisible(peak3FreqLabel);
highMidFilterArea.addAndMakeVisible(peak3GainLabel);
highMidFilterArea.addAndMakeVisible(peak3QLabel);
highMidFilterArea.addAndMakeVisible(peak3FreqSlider);
highMidFilterArea.addAndMakeVisible(peak3GainSlider);
highMidFilterArea.addAndMakeVisible(peak3QSlider);
highMidFilterArea.addAndMakeVisible(peak3BypassButton);
}
{
filterArea.addAndMakeVisible(highFilterArea);
highFilterArea.addAndMakeVisible(highBandModeBox);
highFilterArea.addAndMakeVisible(highBandSlopeLabel);
highFilterArea.addAndMakeVisible(highBandGainLabel);
highFilterArea.addAndMakeVisible(highBandQLabel);
highFilterArea.addAndMakeVisible(highBandFreqLabel);
highFilterArea.addAndMakeVisible(highBandSlopeSlider);
highFilterArea.addAndMakeVisible(highBandGainSlider);
highFilterArea.addAndMakeVisible(highBandQSlider);
highFilterArea.addAndMakeVisible(highBandFreqSlider);
}
}
addAndMakeVisible(footerBar);
footerBar.addAndMakeVisible(globalControlArea);
globalControlArea.addAndMakeVisible(inputLabel);
globalControlArea.addAndMakeVisible(outputLabel);
globalControlArea.addAndMakeVisible(inputSlider);
globalControlArea.addAndMakeVisible(outputSlider);
globalControlArea.addAndMakeVisible(masterBypassButton);
Spacing::setDensity(Spacing::DensityMode::Wide);
titleLabel.setText("Crystalizer", juce::dontSendNotification);
auto setupLabel = [] (juce::Label& label, const juce::String& text)
{
label.setText (text, juce::dontSendNotification);
label.setJustificationType (juce::Justification::centred);
label.setColour (juce::Label::textColourId, juce::Colours::black);
label.setInterceptsMouseClicks (false, false); // Klicks gehen an den Slider
};
//SetupLabels
{
//LOW-BAND
setupLabel (lowBandFreqLabel, "LowBand Freq");
setupLabel (lowBandSlopeLabel, "LowBand Slope");
setupLabel (lowBandGainLabel, "LowBand Gain");
setupLabel(lowBandQLabel, "LowBand Q");
//PEAK 1
setupLabel (peak1FreqLabel,"Low-Mid Freq");
setupLabel (peak1GainLabel,"Low-Mid Gain");
setupLabel (peak1QLabel, "Low-Mid Q");
//PEAK 2
setupLabel (peak2FreqLabel,"Mid Freq");
setupLabel (peak2GainLabel,"Mid Gain");
setupLabel (peak2QLabel, "Mid Q");
//PEAK 3
setupLabel (peak3FreqLabel,"High-Mid Freq");
setupLabel (peak3GainLabel,"High-Mid Gain");
setupLabel (peak3QLabel, "High-Mid Q");
//HIGH-BAND
setupLabel (highBandFreqLabel, "HighBand Freq");
setupLabel (highBandSlopeLabel, "HighBand Slope");
setupLabel (highBandGainLabel, "HighBand Gain");
setupLabel(highBandQLabel, "HighBand Q");
setupLabel(presetBoxLabel, "Presets");
setupLabel(inputLabel, "Input");
setupLabel(outputLabel, "Output");
}
auto presets = audioProcessor.getPresetNamesArray();
presetBox.addItemList(presets, 1);
presetBox.setSelectedId(1, juce::dontSendNotification);
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);
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);
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);
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");
peak2FreqSlider.setTextValueSuffix(" Hz");
peak2GainSlider.setTextValueSuffix(" dB");
peak2QSlider.setTextValueSuffix(" Q");
peak2BypassButton.setName("peak2Bypass");
peak2BypassButton.setButtonText("Mid Bypass");
peak3FreqSlider.setTextValueSuffix(" Hz");
peak3GainSlider.setTextValueSuffix(" dB");
peak3QSlider.setTextValueSuffix(" Q");
peak3BypassButton.setName("peak3Bypass");
peak3BypassButton.setButtonText("High-Mid Bypass");
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");
savePresetButton.setName("SavePresetButton");
savePresetButton.setButtonText("Save Preset");
deletePresetButton.setName("DeletePresetButton");
deletePresetButton.setButtonText("Delete Preset");
}
addAndMakeVisible (testNoiseButton);
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; }
b->setColour(juce::ToggleButton::textColourId, juce::Colours::black);
b->setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
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();
presetMenu.setVisible(menuOpened);
};
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();
resetAllCheckboxes();
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 (Colours::BACKGROUNDCOLOUR);
g.setColour (AXIOM::DesignSystem::Colours::FOREGROUNDCOLOUR);
if constexpr (false) // -> auf false setzen, wenn nicht gebraucht
{
for (auto* c : getChildren()) // headerBar, mainPanel, footerBar
{
auto r = c->getBounds();
g.setColour(juce::Colours::magenta.withAlpha(0.9f));
g.drawRect(r, 1.0f); // nur Rahmen, kein fillRect
g.setColour(juce::Colours::white);
g.drawText(c->getName(), r, juce::Justification::centred, false);
}
for (auto* c : headerBar.getChildren())
{
auto r = c->getBounds(); // <-- lokal zu headerBar
r = r.translated(headerBar.getX(), headerBar.getY()); // <-- in Editor-Koordinaten bringen!
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : presetArea.getChildren())
{
auto r = c->getBounds(); // lokal in presetArea
r = r.translated(headerBar.getX() + presetArea.getX(),
headerBar.getY() + presetArea.getY()); // beide Offsets addieren
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : mainPanel.getChildren())
{
auto r = c->getBounds(); // <-- lokal zu headerBar
r = r.translated(mainPanel.getX(), mainPanel.getY()); // <-- in Editor-Koordinaten bringen!
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : filterArea.getChildren())
{
auto r = c->getBounds(); // lokal in presetArea
r = r.translated(mainPanel.getX() + filterArea.getX(),
mainPanel.getY() + filterArea.getY()); // beide Offsets addieren
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
{
for (auto* c : lowFilterArea.getChildren())
{
auto r = getLocalArea(&lowFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : lowMidFilterArea.getChildren())
{
auto r = getLocalArea(&lowMidFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : midFilterArea.getChildren())
{
auto r = getLocalArea(&midFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : highMidFilterArea.getChildren())
{
auto r = getLocalArea(&highMidFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : highFilterArea.getChildren())
{
auto r = getLocalArea(&highFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
}
for (auto* c : footerBar.getChildren())
{
auto r = c->getBounds(); // <-- lokal zu headerBar
r = r.translated(footerBar.getX(), footerBar.getY()); // <-- in Editor-Koordinaten bringen!
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : globalControlArea.getChildren())
{
auto r = getLocalArea(&globalControlArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
}
//ANALYZER // VERSTEHEN!!!!!!
analyzerRect = getLocalArea(&analyzerArea, analyzerArea.getLocalBounds());
juce::Graphics::ScopedSaveState guard(g);
g.reduceClipRegion(analyzerRect);
auto r = analyzerRect.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(AXIOM::DesignSystem::Colours::ACCENTCOLOUR);
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(AXIOM::DesignSystem::Colours::ACCENTCOLOUR.withAlpha(0.2f));;
g.fillPath(area);
}
// Rahmen (optional)
g.setColour(juce::Colours::grey);
g.drawRect(analyzerRect);
}
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(analyzerRect);
resized();
}
void CrystalizerEQAudioProcessorEditor::resized()
{
// This is generally where you'll want to lay out the positions of any
// subcomponents in your editor.
Typography::applyToLabel(titleLabel, Typography::Style::Display, 1.f);
titleLabel.setColour(juce::Label::textColourId, Colours::ACCENTCOLOUR);
auto pluginArea = getLocalBounds();
scalePluginWindow(pluginArea);
setupMainGrid(pluginArea);
setupHeader();
setupBody();
setupFooter();
const auto testBounds = mainPanel.getLocalBounds();
const auto testWidth = testBounds.getWidth();
const auto testHeight = testBounds.getHeight();
testNoiseButton.setBounds(50, testHeight / 2, testWidth / 8, testHeight / 8);
}
void CrystalizerEQAudioProcessorEditor::resetAllCheckboxes() {
const auto notify = juce::dontSendNotification;
masterBypassButton.setToggleState(false, notify);
crystalizeButton.setToggleState(false, notify);
peak1BypassButton.setToggleState(false, notify);
peak2BypassButton.setToggleState(false, notify);
peak3BypassButton.setToggleState(false, notify);
presetMenuButton.setToggleState(false, notify);
presetMenu.setVisible(false);
}
void CrystalizerEQAudioProcessorEditor::scalePluginWindow(juce::Rectangle<int> area) {
if (area.getWidth() < 520) Spacing::setUiScale(Spacing::uiScaleMode::XS);
else if (area.getWidth() < 720) Spacing::setUiScale(Spacing::uiScaleMode::S);
else if (area.getWidth() < 1024) Spacing::setUiScale(Spacing::uiScaleMode::M);
else if (area.getWidth() < 1366) Spacing::setUiScale(Spacing::uiScaleMode::L);
else Spacing::setUiScale(Spacing::uiScaleMode::XL);
}
void CrystalizerEQAudioProcessorEditor::setupMainGrid(juce::Rectangle<int> area) {
const float headerHeight = static_cast<float>(getHeight()) * 0.1f;
const float footerHeight = static_cast<float>(getHeight()) * 0.1f;
Layout::GridSpec spec {
/* cols */ { Layout::fr(1) }, // eine Spalte, füllt Breite
/* rows */ { Layout::pxTrack(headerHeight), Layout::fr(1), Layout::pxTrack(footerHeight) }, // Header / Body / Footer
/* colGap */ Spacing::SizeMode::S,
/* rowGap */ Spacing::SizeMode::S,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(area, spec, { &headerBar, &mainPanel, &footerBar });
}
void CrystalizerEQAudioProcessorEditor::setupHeader() {
const auto bounds = headerBar.getLocalBounds();
const float presetAreaWidth = static_cast<float>(bounds.getWidth()) * 0.5f;
Layout::GridSpec headerSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(presetAreaWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1) },
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, headerSpec, {
Layout::area(titleLabel, 1, 1, 2, 2),
Layout::area(presetArea, 1, 2, 2, 3),
Layout::area(presetMenu, 1, 3, 2, 4),
});
const auto presetMenuBounds = presetMenu.getLocalBounds();
Layout::GridSpec presetMenuSpec{
/* cols */ { Layout::fr(1), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::fr(1) },
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(presetMenuBounds, presetMenuSpec, {
Layout::area(presetNameInput, 1, 1, 2, 3),
Layout::area(savePresetButton, 2, 1, 3, 2),
Layout::area(deletePresetButton, 2, 2, 3, 3),
});
const auto presetAreaBounds = presetArea.getLocalBounds();
const auto presetBoxWidth = static_cast<float>(presetArea.getWidth());
const auto presetBoxHeight = static_cast<float>(presetArea.getHeight());
Layout::GridSpec presetSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(presetBoxWidth * 0.5f), Layout::fr(1)},
/* rows */ { Layout::pxTrack(presetBoxHeight * 0.25f), Layout::pxTrack(presetBoxHeight * 0.25f)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::S)
};
Layout::grid(presetAreaBounds, presetSpec, {
// Label über beide Spalten (row1, col1..2)
Layout::area(presetBoxLabel, 1, 2, 2, 3),
// Box unten links (row2, col1)
Layout::area(presetBox, 2, 2, 3, 3),
Layout::area(resetButton, 2, 1, 2, 2),
// Menütaste unten rechts (row2, col2)
Layout::area(presetMenuButton, 2, 3, 3, 4),
});
}
void CrystalizerEQAudioProcessorEditor::setupBody() {
const auto bounds = mainPanel.getLocalBounds();
const auto bodyHeight = static_cast<float>(bounds.getHeight());
const auto bodyWidth = static_cast<float>(bounds.getWidth());
const auto bodyColWidth = bodyWidth / 5.0f;
Layout::GridSpec bodySpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(bodyColWidth), Layout::pxTrack(bodyColWidth), Layout::pxTrack(bodyColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::fr(1) },
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, bodySpec, {
Layout::area(analyzerArea, 1, 2, 2, 5),
Layout::area(crystalizeButton, 1, 5, 2, 6),
Layout::area(filterArea, 2, 1, 3, 6)
});
const auto analyzerAreaBounds = analyzerArea.getLocalBounds();
const auto analyzerAreaWidth = static_cast<float>(analyzerArea.getWidth());
const auto analyzerAreaHeight = static_cast<float>(analyzerArea.getHeight());
const auto filterAreaBounds = filterArea.getLocalBounds();
const auto filterAreaWidth = static_cast<float>(filterArea.getWidth());
const auto filterAreaHeight = static_cast<float>(filterArea.getHeight());
const auto filterColWidth = filterAreaWidth * 0.2f;
Layout::GridSpec filterSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(filterColWidth), Layout::pxTrack(filterColWidth), Layout::pxTrack(filterColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::S)
};
Layout::grid(filterAreaBounds, filterSpec, {
Layout::area(lowFilterArea, 1, 1, 2, 2),
Layout::area(lowMidFilterArea, 1, 2, 2, 3),
Layout::area(midFilterArea, 1, 3, 2, 4),
Layout::area(highMidFilterArea, 1, 4, 2, 5),
Layout::area(highFilterArea, 1, 5, 2, 6),
});
setupLowBandLayout();
setupLowMidBandLayout();
setupMidBandLayout();
setupHighMidBandLayout();
setupHighBandLayout();
}
void CrystalizerEQAudioProcessorEditor::setupLowBandLayout() {
const auto bounds = lowFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
Layout::GridSpec lowBandSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, lowBandSpec, {
Layout::area(lowBandFreqLabel, 1, 1, 2, 2),
Layout::area(lowBandGainLabel, 1, 2, 2, 3),
Layout::area(lowBandQLabel, 1, 3, 2, 4),
Layout::area(lowBandFreqSlider, 2, 1, 3, 2),
Layout::area(lowBandGainSlider, 2, 2, 3, 3),
Layout::area(lowBandQSlider, 2, 3, 3, 4),
Layout::area(lowBandModeBox, 3, 1, 4, 2),
Layout::area(lowBandSlopeLabel, 3, 2, 4, 3),
Layout::area(lowBandSlopeSlider, 3, 3, 4, 4)
});
}
void CrystalizerEQAudioProcessorEditor::setupLowMidBandLayout() {
const auto bounds = lowMidFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
Layout::GridSpec lowMidBandSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, lowMidBandSpec, {
Layout::area(peak1FreqLabel, 1, 1, 2, 2),
Layout::area(peak1GainLabel, 1, 2, 2, 3),
Layout::area(peak1QLabel, 1, 3, 2, 4),
Layout::area(peak1FreqSlider, 2, 1, 3, 2),
Layout::area(peak1GainSlider, 2, 2, 3, 3),
Layout::area(peak1QSlider, 2, 3, 3, 4),
Layout::area(peak1BypassButton, 3, 2, 4, 3)
});
}
void CrystalizerEQAudioProcessorEditor::setupMidBandLayout() {
const auto bounds = midFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
Layout::GridSpec midBandSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, midBandSpec, {
Layout::area(peak2FreqLabel, 1, 1, 2, 2),
Layout::area(peak2GainLabel, 1, 2, 2, 3),
Layout::area(peak2QLabel, 1, 3, 2, 4),
Layout::area(peak2FreqSlider, 2, 1, 3, 2),
Layout::area(peak2GainSlider, 2, 2, 3, 3),
Layout::area(peak2QSlider, 2, 3, 3, 4),
Layout::area(peak2BypassButton, 3, 2, 4, 3)
});
}
void CrystalizerEQAudioProcessorEditor::setupHighMidBandLayout() {
const auto bounds = highMidFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
Layout::GridSpec highMidBandSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, highMidBandSpec, {
Layout::area(peak3FreqLabel, 1, 1, 2, 2),
Layout::area(peak3GainLabel, 1, 2, 2, 3),
Layout::area(peak3QLabel, 1, 3, 2, 4),
Layout::area(peak3FreqSlider, 2, 1, 3, 2),
Layout::area(peak3GainSlider, 2, 2, 3, 3),
Layout::area(peak3QSlider, 2, 3, 3, 4),
Layout::area(peak3BypassButton, 3, 2, 4, 3)
});
}
void CrystalizerEQAudioProcessorEditor::setupHighBandLayout() {
const auto bounds = highFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
Layout::GridSpec highBandSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, highBandSpec, {
Layout::area(highBandFreqLabel, 1, 1, 2, 2),
Layout::area(highBandGainLabel, 1, 2, 2, 3),
Layout::area(highBandQLabel, 1, 3, 2, 4),
Layout::area(highBandFreqSlider, 2, 1, 3, 2),
Layout::area(highBandGainSlider, 2, 2, 3, 3),
Layout::area(highBandQSlider, 2, 3, 3, 4),
Layout::area(highBandModeBox, 3, 1, 4, 2),
Layout::area(highBandSlopeLabel, 3, 2, 4, 3),
Layout::area(highBandSlopeSlider, 3, 3, 4, 4)
});
}
void CrystalizerEQAudioProcessorEditor::setupFooter() {
const auto bounds = footerBar.getLocalBounds();
const auto footerWidth = static_cast<float>(bounds.getWidth());
const auto footerHeight = static_cast<float>(bounds.getHeight());
Layout::GridSpec footerSpec{
/* cols */ { Layout::fr(1), Layout::fr(1), Layout::pxTrack(footerWidth / 3.0f) },
/* rows */ { Layout::fr(1) },
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, footerSpec, {
Layout::area(globalControlArea, 1, 3, 2, 4)
});
const auto globalControlAreaBounds = globalControlArea.getLocalBounds();
const auto globalControlAreaWidth = static_cast<float>(globalControlArea.getWidth());
const auto globalControlAreaHeight = static_cast<float>(globalControlArea.getHeight());
const auto globalControlColWidth = globalControlAreaWidth / 3.0f;
Layout::GridSpec globalControlAreaSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(globalControlColWidth), Layout::fr(1) },
/* rows */ { Layout::pxTrack(globalControlAreaHeight * 0.25f),Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(globalControlAreaBounds, globalControlAreaSpec, {
Layout::area(inputLabel, 1, 2, 2, 3),
Layout::area(outputLabel, 1, 3, 2, 4),
Layout::area(inputSlider, 2, 2, 3, 3),
Layout::area(outputSlider, 2, 3, 3, 4),
Layout::area(masterBypassButton, 2, 1, 3, 2),
});
}
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;
}