2025-11-02 15:07:57 +01:00

1287 lines
47 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;
//region setupModeBoxes
void CrystalizerEQAudioProcessorEditor::setupModeBoxes() {
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
audioProcessor.apvts.getParameter("LowBandModes")))
{
lowBandModeBox.addItemList (choice->choices, 1);
lowBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification);
lowBandModeAttach = std::make_unique<ComboBoxAttach>(
audioProcessor.apvts, "LowBandModes", lowBandModeBox);
}
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
audioProcessor.apvts.getParameter("HighBandModes")))
{
highBandModeBox.addItemList (choice->choices, 1);
highBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification);
highBandModeAttach = std::make_unique<ComboBoxAttach>(
audioProcessor.apvts, "HighBandModes", highBandModeBox);
}
}
//endregion setupModeBoxes
//region setupSliders
void CrystalizerEQAudioProcessorEditor::setupSliders() {
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);
}
}
//endregion setupSliders
//region setupAttachments
void CrystalizerEQAudioProcessorEditor::setupAttachments() {
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);
}
//endregion setupAttachments
//region setupDisplayNames
void CrystalizerEQAudioProcessorEditor::setupDisplayNames() {
titleLabel.setText("Crystalizer", juce::dontSendNotification);
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");
}
//endregion setupDisplayNames
//region setupToggleButtons
void CrystalizerEQAudioProcessorEditor::setupToggleButtons() {
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();
}
}
};
}
}
//endregion setupToggleButtons
//region addComponentsToLayout
void CrystalizerEQAudioProcessorEditor::addComponentsToLayout() {
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);
}
//endregion addComponentsToLayout
//region setupLabels
void CrystalizerEQAudioProcessorEditor::setupLabels() {
auto setupLabel = [] (juce::Label& label, const juce::String& text)
{
label.setText (text, juce::dontSendNotification);
label.setJustificationType (juce::Justification::centred);
label.setColour (juce::Label::textColourId, juce::Colours::black);
label.setInterceptsMouseClicks (false, false); // Klicks gehen an den Slider
};
//SetupLabels
{
//LOW-BAND
setupLabel (lowBandFreqLabel, "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");
}
}
//endregion setupLabels
//region setupEventListeners
void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
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);
};
}
//endregion setupEventListeners
//region initPresetSystem
void CrystalizerEQAudioProcessorEditor::initPresetSystem() {
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);
}
//endregion initPresetSystem
//==============================================================================
CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor (CrystalizerEQAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p), spectrumAnalyzer(p.audioFIFO, p) {
setSize (1280, 720);
startTimerHz(60);
addComponentsToLayout();
setupLabels();
setupModeBoxes();
setupSliders();
setupAttachments();
setupDisplayNames();
setupToggleButtons();
setupEventListeners();
initPresetSystem();
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();
}
}
};
}
CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() {
stopTimer();
};
//region paintAnalyzer
void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
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);
}
//endregion paintAnalyzer
//==============================================================================
void CrystalizerEQAudioProcessorEditor::paint (juce::Graphics& g)
{
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);
}
}
paintAnalyzer(g);
}
//region setKnobVisibility
void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
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);
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);
highBandGainSlider.setVisible(highMode >= 2);
highBandGainLabel .setVisible(highMode >= 2);
highBandQSlider .setVisible(highMode >= 1);
highBandQLabel .setVisible(highMode >= 1);
}
//endregion setKnobVisibility
//region timerCallback
void CrystalizerEQAudioProcessorEditor::timerCallback()
{
setKnobVisibility();
spectrumAnalyzer.processSamples();
repaint(analyzerRect);
resized();
}
//endregion timerCallback
void CrystalizerEQAudioProcessorEditor::resized()
{
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);
}
//region resetAllCheckboxes
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);
}
//endregion resetAllCheckboxes
//region scalePluginWindow
void CrystalizerEQAudioProcessorEditor::scalePluginWindow(juce::Rectangle<int> area) {
if (area.getWidth() < 520) Spacing::setUiScale(Spacing::uiScaleMode::XS);
else if (area.getWidth() < 720) Spacing::setUiScale(Spacing::uiScaleMode::S);
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);
}
//endregion scalePluginWindow
//region setupMainGrid
void CrystalizerEQAudioProcessorEditor::setupMainGrid(juce::Rectangle<int> area) {
const float headerHeight = static_cast<float>(getHeight()) * 0.1f;
const float footerHeight = static_cast<float>(getHeight()) * 0.1f;
Layout::GridSpec spec {
/* cols */ { Layout::fr(1) }, // eine Spalte, füllt Breite
/* 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 });
}
//endregion setupMainGrid
//region setupHeader
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),
});
}
//endregion setupHeader
//region setupBody
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();
}
//endregion setupBody
//region setupLowBandLayout
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)
});
}
//endregion setupLowBandLayout
//region setupLowMidBandLayout
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)
});
}
//endregion setupLowMidBandLayout
//region setupMidBandLayout
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)
});
}
//endregion setupMidBandLayout
//region setupHighMidBandLayout
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)
});
}
//endregion setupHighMidBandLayout
//region setupHighBandLayout
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)
});
}
//endregion setupHighBandLayout
//region setupFooter
void CrystalizerEQAudioProcessorEditor::setupFooter() {
const auto bounds = footerBar.getLocalBounds();
const auto footerWidth = static_cast<float>(bounds.getWidth());
const auto footerHeight = static_cast<float>(bounds.getHeight());
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),
});
}
//endregion setupFooter
//region processSamples
void SpectrumAnalyzer::processSamples() {
auto samples = audioFIFO.sendSamplesToEditor();
getFftFrame(samples);
}
//endregion processSamples
//region getFftFrame
void SpectrumAnalyzer::getFftFrame(juce::Array<float>& samples) {
const int needed = FFTSIZE - fftFrame.size();
if (needed <= 0)
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);
}
}
//endregion getFftFrame
//region applyWindowOnFftFrame
void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector<float> &fullFrame) {
if (fullFrame.size()!= FFTSIZE) return;
window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE);
processWindowedFrame(fullFrame);
}
//endregion applyWindowOnFftFrame
//region processWindowedFrame
void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return;
fillFftDataFromFrame(windowedFrame);
fft.performRealOnlyForwardTransform(fftData.data());
buildMagnitudeSpectrum();
convertToDb();
applySmoothing();
renderValuesDb = getRenderValues();
}
//endregion processWindowedFrame
//region fillFftDataFromFrame
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;
}
}
//endregion fillFftDataFromFrame
//region buildMagnitudeSpectrum
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;
}
}
//endregion buildMagnitudeSpectrum
//region convertToDb
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;
}
}
//endregion convertToDb
//region applySmoothing
void SpectrumAnalyzer::applySmoothing() {
applyEMA();
applyFreqSmoothing();
applyPeakHoldAndFalloff();
}
//endregion applySmoothing
//region applyEMA
void SpectrumAnalyzer::applyEMA() {
for (int k = 0; k < magnitudesDb.size(); ++k) {
float smoothedVal = smoothingFactor * magnitudesDb[k] + (1 - smoothingFactor) * emaSmoothedMagnitudesDb[k];
emaSmoothedMagnitudesDb[k] = smoothedVal;
}
}
//endregion applyEMA
//region applyFreqSmoothing
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;
}
}
//endregion applyFreqSmoothing
//region applyPeakHoldAndFalloff
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);
}
}
//endregion applyPeakHoldAndFalloff
//region getRenderValues
std::vector<float> SpectrumAnalyzer::getRenderValues() {
std::vector<float> renderValues = peakHoldMagnitudesDb;
return renderValues;
}
//endregion getRenderValues