Legaeli 51ec8e00cf Completed most of Typography struct.
Added BinaryData for Fonts and .jucer file for importing resources and assets.

TODO: Complete new Design System Class and test it in the Editor.
2025-10-23 13:34:26 +02:00

811 lines
28 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;
//==============================================================================
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);
titleLabel.setText("CrystalizerEQ", juce::dontSendNotification);
addAndMakeVisible(titleLabel);
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);
peak2FreqSlider.setTextValueSuffix(" Hz");
peak2GainSlider.setTextValueSuffix(" dB");
peak2QSlider.setTextValueSuffix(" Q");
peak2BypassButton.setName("peak2Bypass");
peak2BypassButton.setButtonText("Mid Bypass");
addAndMakeVisible(peak2BypassButton);
peak3FreqSlider.setTextValueSuffix(" Hz");
peak3GainSlider.setTextValueSuffix(" dB");
peak3QSlider.setTextValueSuffix(" Q");
peak3BypassButton.setName("peak3Bypass");
peak3BypassButton.setButtonText("High-Mid Bypass");
addAndMakeVisible(peak3BypassButton);
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();
}
}
};
//TODO: DESIGN CHECKBOXES
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();
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 (Colours::BACKGROUNDCOLOUR);
g.setColour (AXIOM::DesignSystem::Colours::FOREGROUNDCOLOUR);
//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(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(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.
Typography::applyToLabel(titleLabel, Typography::Style::Display, 1.f);
titleLabel.setColour(juce::Label::textColourId, Colours::ACCENTCOLOUR);
titleLabel.setJustificationType(juce::Justification::centred);
titleLabel.setBounds (getLocalBounds().removeFromTop(20));
//TODO: PLACE ALL KNOBS AND BUILD FUNCTIONS FOR THEIR DESIGN
/*i = place(i, &testNoiseLabel, &testNoiseSlider);
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, &peak2FreqLabel, &peak2FreqSlider);
i = place(i, &peak2GainLabel, &peak2GainSlider);
i = place(i, &peak2QLabel, &peak2QSlider);
i = place(i, &peak3FreqLabel, &peak3FreqSlider);
i = place(i, &peak3GainLabel, &peak3GainSlider);
i = place(i, &peak3QLabel, &peak3QSlider);
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);
//TODO: PLACE ALL BUTTONS AND BUILD FUNCTIONS FOR THEIR DESIGN
i = place(i, nullptr, nullptr, &peak1BypassButton);
i = place(i, nullptr, nullptr, &peak2BypassButton);
i = place(i, nullptr, nullptr, &peak3BypassButton);
resetButton.setBounds(leftBar.removeFromLeft(200).reduced(8, 8));
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));
presetMenuButton.setBounds(leftBar.removeFromLeft(180).reduced(8, 8));
//TODO: PLACE ALLS COMBOBOXES AND BUILD FUNCTIONS FOR THEIR DESIGN
i = place(i, nullptr, nullptr, nullptr, &lowBandModeBox);
i = place(i, nullptr, nullptr, nullptr, &highBandModeBox);
i = place(i, nullptr, nullptr, nullptr, &presetBox);
i = place(i, nullptr, nullptr, nullptr, nullptr, &presetNameInput);*/
/*highBandGainLabel.setBounds (getLocalBounds().removeFromTop (60));
highBandFreqLabel.setBounds (getLocalBounds().removeFromTop (100));
highBandSlopeLabel.setBounds (getLocalBounds().removeFromTop (80));*/
}
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;
}