1945 lines
75 KiB
C++
1945 lines
75 KiB
C++
/*
|
|
==============================================================================
|
|
PluginEditor.cpp
|
|
|
|
Editor implementation for CrystalizerEQ
|
|
Handles UI layout, real-time spectrum analysis visualization,
|
|
parameter attachments, and user interactions
|
|
==============================================================================
|
|
*/
|
|
|
|
|
|
|
|
#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;
|
|
using Components = DesignSystem::Components;
|
|
using SliderStyles = Components::SliderStyles;
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setupSliders() {
|
|
auto style = juce::Slider::RotaryHorizontalVerticalDrag;
|
|
for (auto *s: sliders) {
|
|
s->setSliderStyle(style);
|
|
}
|
|
gainLookAndFeel = std::make_unique<DesignSystem::Components::GainSliderLookAndFeel>();
|
|
freqQLookAndFeel = std::make_unique<DesignSystem::Components::FreqQSliderLookAndFeel>();
|
|
slopeLookAndFeel = std::make_unique<DesignSystem::Components::SlopeSliderLookAndFeel>();
|
|
globalLookAndFeel = std::make_unique<DesignSystem::Components::GlobalSliderLookAndFeel>();
|
|
|
|
lowBandGainSlider.setLookAndFeel(gainLookAndFeel.get());
|
|
peak1GainSlider.setLookAndFeel(gainLookAndFeel.get());
|
|
peak2GainSlider.setLookAndFeel(gainLookAndFeel.get());
|
|
peak3GainSlider.setLookAndFeel(gainLookAndFeel.get());
|
|
highBandGainSlider.setLookAndFeel(gainLookAndFeel.get());
|
|
|
|
lowBandSlopeSlider.setLookAndFeel(slopeLookAndFeel.get());
|
|
highBandSlopeSlider.setLookAndFeel(slopeLookAndFeel.get());
|
|
|
|
inputSlider.setLookAndFeel(globalLookAndFeel.get());
|
|
outputSlider.setLookAndFeel(globalLookAndFeel.get());
|
|
|
|
for (auto *s: {
|
|
&lowBandFreqSlider, &lowBandQSlider, &peak1FreqSlider,
|
|
&peak1QSlider, &peak2FreqSlider, &peak2QSlider,
|
|
&peak3FreqSlider, &peak3QSlider, &highBandFreqSlider, &highBandQSlider
|
|
}) {
|
|
s->setLookAndFeel(freqQLookAndFeel.get());
|
|
}
|
|
|
|
lowBandSlopeSlider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0);
|
|
highBandSlopeSlider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0);
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setupAttachments() {
|
|
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);
|
|
|
|
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);
|
|
|
|
inputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "InputGain", inputSlider);
|
|
outputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "OutputGain", outputSlider);
|
|
|
|
peak1BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak1Bypass", peak1BypassButton);
|
|
peak2BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak2Bypass", peak2BypassButton);
|
|
peak3BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak3Bypass", peak3BypassButton);
|
|
crystalizeAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "CrystalizeButton", crystalizeButton);
|
|
masterBypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "MasterBypass", masterBypassButton);
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setupDisplayNames() {
|
|
titleLabel.setText("Crystalizer", juce::dontSendNotification);
|
|
|
|
lowBypass.setName("LowBypass");
|
|
lowBell.setName("LowBell");
|
|
lowCut.setName("LowCut");
|
|
lowShelf.setName("LowShelf");
|
|
lowBandModeBox.setName("LowBandModeBox");
|
|
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");
|
|
|
|
highBypass.setName("HighBypass");
|
|
highBell.setName("HighBell");
|
|
highCut.setName("HighCut");
|
|
highShelf.setName("HighShelf");
|
|
highBandModeBox.setName("HighBandModeBox");
|
|
highBandFreqSlider.setName("HighBand Freq");
|
|
highBandSlopeSlider.setName("HighBand Slope");
|
|
highBandGainSlider.setName("HighBand Gain");
|
|
highBandQSlider.setName("HighBand Q");
|
|
|
|
inputSlider.setName("Input");
|
|
outputSlider.setName("Output");
|
|
|
|
lowBandGainSlider.setTextValueSuffix("\ndB");
|
|
|
|
peak1GainSlider.setTextValueSuffix("\ndB");
|
|
peak1BypassButton.setName("peak1Bypass");
|
|
|
|
peak2GainSlider.setTextValueSuffix("\ndB");
|
|
peak2BypassButton.setName("peak2Bypass");
|
|
|
|
peak3GainSlider.setTextValueSuffix("\ndB");
|
|
peak3BypassButton.setName("peak3Bypass");
|
|
|
|
highBandGainSlider.setTextValueSuffix("\ndB");
|
|
|
|
inputSlider.setTextValueSuffix("\nIN");
|
|
outputSlider.setTextValueSuffix("\nOUT");
|
|
|
|
crystalizeButton.setName("CrystalizeButton");
|
|
|
|
resetButton.setName("ResetButton");
|
|
resetButton.setButtonText("Reset");
|
|
|
|
masterBypassButton.setName("MasterBypass");
|
|
|
|
savePresetButton.setName("SavePresetButton");
|
|
savePresetButton.setButtonText("Save Preset");
|
|
|
|
deletePresetButton.setName("DeletePresetButton");
|
|
deletePresetButton.setButtonText("Delete Preset");
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setupToggleButtons() {
|
|
bypassButtonLookAndFeel = std::make_unique<Components::BypassButtonLookAndFeel>();
|
|
svgToggleButtonLookAndFeel = std::make_unique<Components::SvgToggleButtonLookAndFeel>();
|
|
presetMenuButtonLookAndFeel = std::make_unique<Components::PresetMenuButtonLookAndFeel>();
|
|
lowBandButtonLookAndFeel = std::make_unique<Components::lowBandButtonLookAndFeel>();
|
|
highBandButtonLookAndFeel = std::make_unique<Components::highBandButtonLookAndFeel>();
|
|
|
|
peak1BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
|
|
peak2BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
|
|
peak3BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
|
|
masterBypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
|
|
|
|
crystalizeButton.setLookAndFeel(svgToggleButtonLookAndFeel.get());
|
|
presetMenuButton.setLookAndFeel(presetMenuButtonLookAndFeel.get());
|
|
|
|
|
|
lowShelf.setToggleState(true, juce::dontSendNotification);
|
|
highShelf.setToggleState(true, juce::dontSendNotification);
|
|
|
|
lowBypass.setLookAndFeel(lowBandButtonLookAndFeel.get());
|
|
lowCut.setLookAndFeel(lowBandButtonLookAndFeel.get());
|
|
lowBell.setLookAndFeel(lowBandButtonLookAndFeel.get());
|
|
lowShelf.setLookAndFeel(lowBandButtonLookAndFeel.get());
|
|
|
|
highBypass.setLookAndFeel(highBandButtonLookAndFeel.get());
|
|
highCut.setLookAndFeel(highBandButtonLookAndFeel.get());
|
|
highBell.setLookAndFeel(highBandButtonLookAndFeel.get());
|
|
highShelf.setLookAndFeel(highBandButtonLookAndFeel.get());
|
|
|
|
for (auto *b: {
|
|
&peak1BypassButton, &peak2BypassButton, &peak3BypassButton, &crystalizeButton, &masterBypassButton
|
|
}) {
|
|
b->onClick = [this, b]() {
|
|
juce::String mode;
|
|
if (b == &peak1BypassButton) {
|
|
mode = "Low-Mid";
|
|
} else if (b == &peak2BypassButton) {
|
|
mode = "Mid";
|
|
} else if (b == &peak3BypassButton) {
|
|
mode = "High-Mid";
|
|
} else if (b == &masterBypassButton) {
|
|
mode = "Master";
|
|
}
|
|
|
|
const float target = b->getToggleState() ? 1.0f : 0.0f;
|
|
|
|
if (mode == "Low-Mid") disableLowMidBand(target);
|
|
else if (mode == "Mid") disableMidBand(target);
|
|
else if (mode == "High-Mid") disableHighMidBand(target);
|
|
else if (mode == "Master") disableEverything(target);
|
|
|
|
if (b == &crystalizeButton) {
|
|
isAnimatingCrystalize = true;
|
|
isFadingToActive = (svgToggleButtonLookAndFeel->activeIconOpacity < 1.0f);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::disableLowBand(const float target) {
|
|
bool isToggled = target <= 0.5f;
|
|
lowBandFreqSlider.setEnabled(isToggled);
|
|
lowBandFreqLabel.setEnabled(isToggled);
|
|
lowBandSlopeSlider.setEnabled(isToggled);
|
|
lowBandSlopeLabel.setEnabled(isToggled);
|
|
lowBandGainSlider.setEnabled(isToggled);
|
|
lowBandGainLabel.setEnabled(isToggled);
|
|
lowBandQSlider.setEnabled(isToggled);
|
|
lowBandQLabel.setEnabled(isToggled);
|
|
};
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::disableLowMidBand(const float target) {
|
|
bool isToggled = target <= 0.5f;
|
|
peak1GainSlider.setEnabled(isToggled);
|
|
peak1QSlider.setEnabled(isToggled);
|
|
peak1FreqSlider.setEnabled(isToggled);
|
|
peak1GainLabel.setEnabled(isToggled);
|
|
peak1QLabel.setEnabled(isToggled);
|
|
peak1FreqLabel.setEnabled(isToggled);
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::disableMidBand(const float target) {
|
|
bool isToggled = target <= 0.5f;
|
|
peak2GainSlider.setEnabled(isToggled);
|
|
peak2QSlider.setEnabled(isToggled);
|
|
peak2FreqSlider.setEnabled(isToggled);
|
|
peak2GainLabel.setEnabled(isToggled);
|
|
peak2QLabel.setEnabled(isToggled);
|
|
peak2FreqLabel.setEnabled(isToggled);
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::disableHighMidBand(const float target) {
|
|
bool isToggled = target <= 0.5f;
|
|
peak3GainSlider.setEnabled(isToggled);
|
|
peak3QSlider.setEnabled(isToggled);
|
|
peak3FreqSlider.setEnabled(isToggled);
|
|
peak3GainLabel.setEnabled(isToggled);
|
|
peak3QLabel.setEnabled(isToggled);
|
|
peak3FreqLabel.setEnabled(isToggled);
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::disableHighBand(const float target) {
|
|
bool isToggled = target <= 0.5f;
|
|
highBandFreqSlider.setEnabled(isToggled);
|
|
highBandFreqLabel.setEnabled(isToggled);
|
|
highBandSlopeSlider.setEnabled(isToggled);
|
|
highBandSlopeLabel.setEnabled(isToggled);
|
|
highBandGainSlider.setEnabled(isToggled);
|
|
highBandGainLabel.setEnabled(isToggled);
|
|
highBandQSlider.setEnabled(isToggled);
|
|
highBandQLabel.setEnabled(isToggled);
|
|
};
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::disableEverything(const float target) {
|
|
bool isToggled = target <= 0.5f;
|
|
|
|
for (auto *s: sliders) {
|
|
s->setEnabled(isToggled);
|
|
}
|
|
for (auto *l: sliderLabels) {
|
|
l->setEnabled(isToggled);
|
|
}
|
|
|
|
for (auto *b: peakBypassButtons) {
|
|
b->setEnabled(isToggled);
|
|
const auto bypassTarget = b->getToggleState() ? 1.0f : 0.0f;
|
|
if (b == &peak1BypassButton && isToggled) {
|
|
disableLowMidBand(bypassTarget);
|
|
} else if (b == &peak2BypassButton && isToggled) {
|
|
disableMidBand(bypassTarget);
|
|
} else if (b == &peak3BypassButton && isToggled) {
|
|
disableHighMidBand(bypassTarget);
|
|
}
|
|
}
|
|
|
|
resetButton.setEnabled(isToggled);
|
|
|
|
crystalizeButton.setEnabled(isToggled);
|
|
lowBandModeBox.setEnabled(isToggled);
|
|
highBandModeBox.setEnabled(isToggled);
|
|
|
|
if (!isToggled) {
|
|
if (svgToggleButtonLookAndFeel->activeIconOpacity == 1.0f) {
|
|
svgToggleButtonLookAndFeel->activeIconOpacity = 0.7f;
|
|
crystalizeButton.repaint();
|
|
} else if (svgToggleButtonLookAndFeel->passiveIconOpacity == 1.0f) {
|
|
svgToggleButtonLookAndFeel->passiveIconOpacity = 0.7f;
|
|
crystalizeButton.repaint();
|
|
}
|
|
} else {
|
|
if (svgToggleButtonLookAndFeel->activeIconOpacity == 0.7f) {
|
|
svgToggleButtonLookAndFeel->activeIconOpacity = 1.0f;
|
|
crystalizeButton.repaint();
|
|
} else if (svgToggleButtonLookAndFeel->passiveIconOpacity == 0.7f) {
|
|
svgToggleButtonLookAndFeel->passiveIconOpacity = 1.0f;
|
|
crystalizeButton.repaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
|
addAndMakeVisible(mainPanel);
|
|
mainPanel.addAndMakeVisible(analyzerArea);
|
|
mainPanel.addAndMakeVisible(crystalizeButton);
|
|
|
|
//FILTERAREA
|
|
{
|
|
mainPanel.addAndMakeVisible(filterArea); {
|
|
filterArea.addAndMakeVisible(lowFilterArea);
|
|
|
|
lowBandModeBox.addAndMakeVisible(lowBandModeLabel);
|
|
lowBandModeBox.addAndMakeVisible(lowBypass);
|
|
lowBandModeBox.addAndMakeVisible(lowCut);
|
|
lowBandModeBox.addAndMakeVisible(lowBell);
|
|
lowBandModeBox.addAndMakeVisible(lowShelf);
|
|
lowFilterArea.addAndMakeVisible(lowBandModeBox);
|
|
lowBandModeButtons.add(&lowBypass, &lowCut, &lowShelf, &lowBell);
|
|
lowBandBools = {false, false, true, false};
|
|
|
|
|
|
lowFilterArea.addAndMakeVisible(lowBandSlopeLabel);
|
|
lowFilterArea.addAndMakeVisible(lowBandGainLabel);
|
|
lowFilterArea.addAndMakeVisible(lowBandQLabel);
|
|
lowFilterArea.addAndMakeVisible(lowBandFreqLabel);
|
|
|
|
lowBandSlopeSlider.addAndMakeVisible(low12);
|
|
lowBandSlopeSlider.addAndMakeVisible(low24);
|
|
lowBandSlopeSlider.addAndMakeVisible(low36);
|
|
lowBandSlopeSlider.addAndMakeVisible(low48);
|
|
|
|
lowFilterArea.addAndMakeVisible(lowBandSlopeSlider);
|
|
lowFilterArea.addAndMakeVisible(lowdBOctLabel);
|
|
|
|
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);
|
|
|
|
highBandModeBox.addAndMakeVisible(highBandModeLabel);
|
|
highBandModeBox.addAndMakeVisible(highBypass);
|
|
highBandModeBox.addAndMakeVisible(highCut);
|
|
highBandModeBox.addAndMakeVisible(highBell);
|
|
highBandModeBox.addAndMakeVisible(highShelf);
|
|
highFilterArea.addAndMakeVisible(highBandModeBox);
|
|
highBandModeButtons.add(&highBypass, &highCut, &highShelf, &highBell);
|
|
highBandBools = {false, false, true, false};
|
|
|
|
highFilterArea.addAndMakeVisible(highBandSlopeLabel);
|
|
highFilterArea.addAndMakeVisible(highBandGainLabel);
|
|
highFilterArea.addAndMakeVisible(highBandQLabel);
|
|
highFilterArea.addAndMakeVisible(highBandFreqLabel);
|
|
|
|
highBandSlopeSlider.addAndMakeVisible(high12);
|
|
highBandSlopeSlider.addAndMakeVisible(high24);
|
|
highBandSlopeSlider.addAndMakeVisible(high36);
|
|
highBandSlopeSlider.addAndMakeVisible(high48);
|
|
highFilterArea.addAndMakeVisible(highBandSlopeSlider);
|
|
highFilterArea.addAndMakeVisible(highdBOctLabel);
|
|
|
|
|
|
highFilterArea.addAndMakeVisible(highBandGainSlider);
|
|
highFilterArea.addAndMakeVisible(highBandQSlider);
|
|
highFilterArea.addAndMakeVisible(highBandFreqSlider);
|
|
}
|
|
}
|
|
|
|
|
|
globalControlArea.addAndMakeVisible(inputSlider);
|
|
globalControlArea.addAndMakeVisible(outputSlider);
|
|
globalControlArea.addAndMakeVisible(masterBypassButton);
|
|
footerBar.addAndMakeVisible(globalControlArea);
|
|
addAndMakeVisible(footerBar);
|
|
}
|
|
|
|
|
|
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
|
|
}; {
|
|
//LOW-BAND
|
|
setupLabel(lowBandFreqLabel, "Low\nHz");
|
|
setupLabel(lowBandSlopeLabel, "Slope");
|
|
setupLabel(lowBandGainLabel, "Low\nGain");
|
|
setupLabel(lowBandQLabel, "Low\nQ");
|
|
setupLabel(lowBandModeLabel, "Band Mode");
|
|
setupLabel(low12, "12");
|
|
setupLabel(low24, "24");
|
|
setupLabel(low36, "36");
|
|
setupLabel(low48, "48");
|
|
setupLabel(lowdBOctLabel, "dB/Oct");
|
|
|
|
//PEAK 1
|
|
setupLabel(peak1FreqLabel, "Low-Mid\nHz");
|
|
setupLabel(peak1GainLabel, "Low-Mid\nGain");
|
|
setupLabel(peak1QLabel, "Low-Mid\nQ");
|
|
|
|
//PEAK 2
|
|
setupLabel(peak2FreqLabel, "Mid\nHz");
|
|
setupLabel(peak2GainLabel, "Mid\nGain");
|
|
setupLabel(peak2QLabel, "Mid\nQ");
|
|
|
|
//PEAK 3
|
|
setupLabel(peak3FreqLabel, "High-Mid\nHz");
|
|
setupLabel(peak3GainLabel, "High-Mid\nGain");
|
|
setupLabel(peak3QLabel, "High-Mid\nQ");
|
|
|
|
//HIGH-BAND
|
|
setupLabel(highBandFreqLabel, "High\nHz");
|
|
setupLabel(highBandSlopeLabel, "Slope");
|
|
setupLabel(highBandGainLabel, "High\nGain");
|
|
setupLabel(highBandQLabel, "High\nQ");
|
|
setupLabel(highBandModeLabel, "Band Mode");
|
|
setupLabel(high12, "12");
|
|
setupLabel(high24, "24");
|
|
setupLabel(high36, "36");
|
|
setupLabel(high48, "48");
|
|
setupLabel(highdBOctLabel, "dB/Oct");
|
|
|
|
|
|
setupLabel(presetBoxLabel, "Presets");
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setupFontsWithColours() {
|
|
Typography::applyToLabel(titleLabel, Typography::Style::Display, 1.f);
|
|
titleLabel.setColour(juce::Label::textColourId, Colours::ACCENTCOLOUR);
|
|
|
|
Typography::applyToLabel(presetBoxLabel, Typography::Style::H3, 1.f);
|
|
presetBoxLabel.setColour(juce::Label::textColourId, Colours::FOREGROUNDCOLOUR);
|
|
|
|
for (auto *l: sliderLabels) {
|
|
l->setColour(juce::Label::textColourId, Colours::FOREGROUNDCOLOUR);
|
|
Typography::applyToLabel(*l, Typography::Style::Mono, 1.f);
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setupSliderTextBoxes() {
|
|
baseLookAndFeel = std::make_unique<AXIOM::DesignSystem::Components::BaseSliderLookAndFeel>();
|
|
for (auto *s: sliders) {
|
|
s->setLookAndFeel(baseLookAndFeel.get());
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::handleLowBandModes() {
|
|
for (int i = 0; i < lowBandModeButtons.size(); ++i) {
|
|
lowBandModeButtons[i]->onClick = [this, i] {
|
|
if (!lowBandBools[i]) {
|
|
if (auto *param = audioProcessor.apvts.getParameter("LowBandModes")) {
|
|
param->beginChangeGesture();
|
|
|
|
for (int j = 0; j < lowBandBools.size(); ++j) {
|
|
if (j != i) {
|
|
lowBandBools[j] = false;
|
|
param->setValueNotifyingHost(param->convertTo0to1(0.0f));
|
|
lowBandModeButtons[j]->setToggleState(false, juce::dontSendNotification);
|
|
}
|
|
}
|
|
lowBandBools[i] = true;
|
|
param->setValueNotifyingHost(param->convertTo0to1((float) i));
|
|
|
|
param->endChangeGesture();
|
|
|
|
updateFrequencyRanges();
|
|
}
|
|
} else if (lowBandBools[i]) {
|
|
lowBandModeButtons[i]->setToggleState(true, juce::dontSendNotification);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::handleHighBandModes() {
|
|
for (int i = 0; i < highBandModeButtons.size(); ++i) {
|
|
highBandModeButtons[i]->onClick = [this, i] {
|
|
if (!highBandBools[i]) {
|
|
if (auto *param = audioProcessor.apvts.getParameter("HighBandModes")) {
|
|
param->beginChangeGesture();
|
|
|
|
for (int j = 0; j < highBandBools.size(); ++j) {
|
|
if (j != i) {
|
|
highBandBools[j] = false;
|
|
param->setValueNotifyingHost(param->convertTo0to1(0.0f));
|
|
highBandModeButtons[j]->setToggleState(false, juce::dontSendNotification);
|
|
}
|
|
}
|
|
highBandBools[i] = true;
|
|
param->setValueNotifyingHost(param->convertTo0to1((float) i));
|
|
|
|
param->endChangeGesture();
|
|
updateFrequencyRanges();
|
|
}
|
|
} else if (highBandBools[i]) {
|
|
highBandModeButtons[i]->setToggleState(true, juce::dontSendNotification);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
|
|
presetMenuButton.onClick = [this]() {
|
|
auto *menu = new DesignSystem::PresetMenu(&presetNameInput, &savePresetButton, &deletePresetButton);
|
|
const auto presetMenuBounds = presetMenu.getLocalBounds();
|
|
menu->setBounds(presetMenuBounds);
|
|
|
|
presetMenuLookAndFeel = std::make_unique<DesignSystem::PresetMenuLookAndFeel>();
|
|
|
|
juce::Rectangle<int> areaToPointTo = presetMenuButton.getScreenBounds();
|
|
|
|
auto *menuRaw = menu;
|
|
presetMenuSafePtr = menuRaw;
|
|
|
|
auto &box = juce::CallOutBox::launchAsynchronously(
|
|
std::unique_ptr<juce::Component>(menu),
|
|
areaToPointTo,
|
|
nullptr
|
|
);
|
|
box.setLookAndFeel(presetMenuLookAndFeel.get());
|
|
};
|
|
|
|
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();
|
|
updateModeButtons();
|
|
return;
|
|
}
|
|
const auto selectedPresetWithExt = selectedPreset + ".xml";
|
|
audioProcessor.loadPreset(selectedPresetWithExt);
|
|
updateModeButtons(); // ← NEU!
|
|
updateFrequencyRanges();
|
|
};
|
|
|
|
resetButton.onClick = [this]() {
|
|
audioProcessor.resetAllParameters();
|
|
if (crystalizeButton.getToggleState()) {
|
|
isAnimatingCrystalize = true;
|
|
isFadingToActive = (svgToggleButtonLookAndFeel->activeIconOpacity < 1.0f);
|
|
}
|
|
resetAllCheckboxes();
|
|
for (auto *s: sliders) {
|
|
s->setEnabled(true);
|
|
}
|
|
for (auto *l: sliderLabels) {
|
|
l->setEnabled(true);
|
|
}
|
|
presetBox.setSelectedId(1, juce::dontSendNotification);
|
|
updateFrequencyRanges();
|
|
};
|
|
|
|
handleLowBandModes();
|
|
|
|
handleHighBandModes();
|
|
|
|
presetNameInput.onMouseDown = [this]() {
|
|
inputFocused = true;
|
|
};
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::updateModeButtons() {
|
|
int lowMode = static_cast<int>(audioProcessor.apvts.getRawParameterValue("LowBandModes")->load());
|
|
|
|
for (int i = 0; i < lowBandModeButtons.size(); ++i) {
|
|
lowBandBools[i] = (i == lowMode);
|
|
lowBandModeButtons[i]->setToggleState(lowBandBools[i], juce::dontSendNotification);
|
|
}
|
|
|
|
int highMode = static_cast<int>(audioProcessor.apvts.getRawParameterValue("HighBandModes")->load());
|
|
|
|
for (int i = 0; i < highBandModeButtons.size(); ++i) {
|
|
highBandBools[i] = (i == highMode);
|
|
highBandModeButtons[i]->setToggleState(highBandBools[i], juce::dontSendNotification);
|
|
}
|
|
}
|
|
|
|
void CrystalizerEQAudioProcessorEditor::parameterChanged(const juce::String& parameterID, float newValue) {
|
|
if (parameterID == "LowBandModes" || parameterID == "HighBandModes") {
|
|
juce::MessageManager::callAsync([this]() {
|
|
updateModeButtons();
|
|
repaint();
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::initPresetSystem() {
|
|
auto presets = audioProcessor.getPresetNamesArray();
|
|
presetBox.addItemList(presets, 1);
|
|
presetBox.setSelectedId(1, juce::dontSendNotification);
|
|
|
|
presetMenuButton.setName("PresetMenuButton");
|
|
presetMenuButton.setColour(juce::ToggleButton::textColourId, juce::Colours::black);
|
|
presetMenuButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
|
|
presetMenuButton.setToggleState(false, juce::dontSendNotification);
|
|
|
|
presetNameInput.setJustification(juce::Justification::centred);
|
|
|
|
|
|
presetButtonLookAndFeel = std::make_unique<DesignSystem::PresetButtonsLookAndFeel>();
|
|
presetComboBoxLookAndFeel = std::make_unique<DesignSystem::PresetComboBoxLookAndFeel>();
|
|
presetInputLookAndFeel = std::make_unique<DesignSystem::PresetInputLookAndFeel>();
|
|
|
|
savePresetButton.setLookAndFeel(presetButtonLookAndFeel.get());
|
|
deletePresetButton.setLookAndFeel(presetButtonLookAndFeel.get());
|
|
resetButton.setLookAndFeel(presetButtonLookAndFeel.get());
|
|
presetNameInput.setLookAndFeel(presetInputLookAndFeel.get());
|
|
presetBox.setLookAndFeel(presetComboBoxLookAndFeel.get());
|
|
}
|
|
|
|
void CrystalizerEQAudioProcessorEditor::syncPresetBox() {
|
|
const auto currentPresetName = audioProcessor.getPresetName();
|
|
|
|
// Durch alle Items in der ComboBox gehen
|
|
for (int i = 0; i < presetBox.getNumItems(); ++i) {
|
|
if (presetBox.getItemText(i) == currentPresetName) {
|
|
presetBox.setSelectedId(i + 1, juce::dontSendNotification);
|
|
return;
|
|
}
|
|
}
|
|
|
|
presetBox.setSelectedId(1, juce::dontSendNotification);
|
|
}
|
|
|
|
void CrystalizerEQAudioProcessorEditor::syncAllButtonStates() {
|
|
// Peak Bypass Buttons
|
|
const bool peak1Bypassed = audioProcessor.apvts.getRawParameterValue("Peak1Bypass")->load() > 0.5f;
|
|
if (peak1Bypassed) {
|
|
disableLowMidBand(1.0f);
|
|
}
|
|
|
|
const bool peak2Bypassed = audioProcessor.apvts.getRawParameterValue("Peak2Bypass")->load() > 0.5f;
|
|
if (peak2Bypassed) {
|
|
disableMidBand(1.0f);
|
|
}
|
|
|
|
const bool peak3Bypassed = audioProcessor.apvts.getRawParameterValue("Peak3Bypass")->load() > 0.5f;
|
|
if (peak3Bypassed) {
|
|
disableHighMidBand(1.0f);
|
|
}
|
|
|
|
// Master Bypass
|
|
const bool masterBypassed = audioProcessor.apvts.getRawParameterValue("MasterBypass")->load() > 0.5f;
|
|
if (masterBypassed) {
|
|
disableEverything(1.0f);
|
|
}
|
|
|
|
// Crystalize Button - Icon-Opacity setzen
|
|
const bool crystalized = audioProcessor.apvts.getRawParameterValue("CrystalizeButton")->load() > 0.5f;
|
|
if (crystalized) {
|
|
svgToggleButtonLookAndFeel->activeIconOpacity = 1.0f;
|
|
svgToggleButtonLookAndFeel->passiveIconOpacity = 0.0f;
|
|
} else {
|
|
svgToggleButtonLookAndFeel->activeIconOpacity = 0.0f;
|
|
svgToggleButtonLookAndFeel->passiveIconOpacity = 1.0f;
|
|
}
|
|
crystalizeButton.repaint();
|
|
|
|
syncPresetBox();
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor(CrystalizerEQAudioProcessor &p)
|
|
: AudioProcessorEditor(&p), audioProcessor(p), spectrumAnalyzer(p.audioFIFO, p) {
|
|
setSize(1280, 720);
|
|
startTimerHz(60);
|
|
addComponentsToLayout();
|
|
setupLabels();
|
|
|
|
setupFontsWithColours();
|
|
setupAttachments();
|
|
setupDisplayNames();
|
|
setupToggleButtons();
|
|
setupEventListeners();
|
|
setupSliderTextBoxes();
|
|
setupSliders();
|
|
initPresetSystem();
|
|
updateFrequencyRanges();
|
|
|
|
audioProcessor.apvts.addParameterListener("LowBandModes", this);
|
|
audioProcessor.apvts.addParameterListener("HighBandModes", this);
|
|
|
|
updateModeButtons();
|
|
syncAllButtonStates();
|
|
}
|
|
|
|
CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() {
|
|
stopTimer();
|
|
for (auto *s: sliders) {
|
|
s->setLookAndFeel(nullptr);
|
|
}
|
|
peak1BypassButton.setLookAndFeel(nullptr);
|
|
peak2BypassButton.setLookAndFeel(nullptr);
|
|
peak3BypassButton.setLookAndFeel(nullptr);
|
|
masterBypassButton.setLookAndFeel(nullptr);
|
|
crystalizeButton.setLookAndFeel(nullptr);
|
|
presetMenuButton.setLookAndFeel(nullptr);
|
|
presetNameInput.setLookAndFeel(nullptr);
|
|
presetBox.setLookAndFeel(nullptr);
|
|
|
|
audioProcessor.apvts.removeParameterListener("LowBandModes", this);
|
|
audioProcessor.apvts.removeParameterListener("HighBandModes", this);
|
|
for (auto *b: lowBandModeButtons) {
|
|
b->setLookAndFeel(nullptr);
|
|
}
|
|
for (auto *b: highBandModeButtons) {
|
|
b->setLookAndFeel(nullptr);
|
|
}
|
|
};
|
|
|
|
float CrystalizerEQAudioProcessorEditor::getInterpolatedDb(float exactBin) {
|
|
int bin1 = static_cast<int>(std::floor(exactBin));
|
|
int bin2 = static_cast<int>(std::ceil(exactBin));
|
|
|
|
const int maxBin = static_cast<int>(spectrumAnalyzer.renderValuesDb.size()) - 1;
|
|
bin1 = juce::jlimit(0, maxBin, bin1);
|
|
bin2 = juce::jlimit(0, maxBin, bin2);
|
|
|
|
if (bin1 == bin2) return spectrumAnalyzer.renderValuesDb[bin1];
|
|
|
|
float y1 = juce::jlimit(spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB, spectrumAnalyzer.renderValuesDb[bin1]);
|
|
float y2 = juce::jlimit(spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB, spectrumAnalyzer.renderValuesDb[bin2]);
|
|
|
|
float fraction = exactBin - bin1;
|
|
return y1 + fraction * (y2 - y1);
|
|
}
|
|
|
|
void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
|
|
analyzerRect = getLocalArea(&analyzerArea, analyzerArea.getLocalBounds());
|
|
juce::Graphics::ScopedSaveState guard(g);
|
|
g.reduceClipRegion(analyzerRect);
|
|
|
|
auto r = analyzerRect.toFloat();
|
|
|
|
// BACKGROUND
|
|
g.setColour(juce::Colours::black);
|
|
g.fillRect(r);
|
|
|
|
// BORDER
|
|
g.setColour(juce::Colours::grey);
|
|
g.drawRect(analyzerRect);
|
|
|
|
// SPECTRUM
|
|
g.setColour(Colours::ACCENTCOLOUR);
|
|
|
|
const float analyzerWidth = analyzerRect.getWidth();
|
|
const float analyzerHeight = analyzerRect.getHeight();
|
|
const float analyzerBottom = analyzerRect.getBottom();
|
|
|
|
const float minDb = spectrumAnalyzer.MINDB;
|
|
const float maxDb = spectrumAnalyzer.MAXDB;
|
|
const float dbRange = maxDb - minDb; // = 96 dB
|
|
|
|
const float minFreq = spectrumAnalyzer.MINFREQ; // 20 Hz
|
|
const float maxFreq = spectrumAnalyzer.MAXFREQ; // 20 kHz
|
|
|
|
float deltaF = spectrumAnalyzer.deltaF;
|
|
|
|
juce::Path spectrumPath;
|
|
bool pathStarted = false;
|
|
|
|
const int resolutionMultiplier = 4;
|
|
const int numPoints = static_cast<int>(analyzerWidth) * resolutionMultiplier;
|
|
|
|
for (int i = 0; i < numPoints; ++i) {
|
|
float x = (i / static_cast<float>(numPoints)) * analyzerWidth;
|
|
|
|
float normalizedX = i / static_cast<float>(numPoints);
|
|
float logMin = std::log10(minFreq);
|
|
float logMax = std::log10(maxFreq);
|
|
float logFreq = logMin + normalizedX * (logMax - logMin);
|
|
float freq = std::pow(10.0f, logFreq);
|
|
|
|
float exactBin = freq / deltaF;
|
|
|
|
float dB = getInterpolatedDb(exactBin);
|
|
|
|
float normalized = (dB - minDb) / dbRange;
|
|
normalized = juce::jlimit(0.0f, 1.0f, normalized);
|
|
|
|
float pixelHeight = normalized * analyzerHeight;
|
|
float y = analyzerBottom - pixelHeight;
|
|
|
|
if (!pathStarted) {
|
|
spectrumPath.startNewSubPath(x + analyzerRect.getX(), y);
|
|
pathStarted = true;
|
|
} else {
|
|
spectrumPath.lineTo(x + analyzerRect.getX(), y);
|
|
}
|
|
}
|
|
|
|
if (pathStarted) {
|
|
juce::PathStrokeType stroke(2.0f);
|
|
stroke.setJointStyle(juce::PathStrokeType::curved); // Runde Ecken
|
|
g.strokePath(spectrumPath, stroke);
|
|
|
|
spectrumPath.lineTo(analyzerRect.getRight(), analyzerBottom);
|
|
spectrumPath.lineTo(analyzerRect.getX(), analyzerBottom);
|
|
spectrumPath.closeSubPath();
|
|
|
|
g.setColour(Colours::ACCENTCOLOUR.withAlpha(0.3f));
|
|
g.fillPath(spectrumPath);
|
|
}
|
|
|
|
drawFrequencyGrid(g, dbRange);
|
|
}
|
|
|
|
|
|
float CrystalizerEQAudioProcessorEditor::mapFrequencyToX(float freq, float minFreq, float maxFreq, float width) {
|
|
float logMin = std::log10(minFreq);
|
|
float logMax = std::log10(maxFreq);
|
|
float logFreq = std::log10(freq);
|
|
|
|
float normalized = (logFreq - logMin) / (logMax - logMin);
|
|
return normalized * width;
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::drawFrequencyGrid(juce::Graphics &g, const float dbRange) {
|
|
g.setColour(Colours::FOREGROUNDCOLOUR.withAlpha(0.3f));
|
|
|
|
const float minFreq = spectrumAnalyzer.MINFREQ;
|
|
const float maxFreq = spectrumAnalyzer.MAXFREQ;
|
|
const float analyzerWidth = analyzerRect.getWidth();
|
|
|
|
const float minDB = spectrumAnalyzer.MINDB;
|
|
const float maxDB = spectrumAnalyzer.MAXDB;
|
|
const float analyzerHeight = analyzerRect.getHeight();
|
|
const float analyzerBottom = analyzerRect.getBottom();
|
|
|
|
std::vector<float> gridFreqs = {50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000};
|
|
std::vector<float> gridDB = {0, -12, -24, -36, -48, -60, -72};
|
|
|
|
|
|
for (int i = 0; i < gridDB.size(); ++i) {
|
|
float db = gridDB[i];
|
|
|
|
float normalized = (db - minDB) / dbRange;
|
|
normalized = juce::jlimit(0.0f, 1.0f, normalized);
|
|
|
|
float pixelHeight = normalized * analyzerHeight;
|
|
float y = analyzerBottom - pixelHeight;
|
|
|
|
g.drawHorizontalLine(static_cast<int>(y), static_cast<float>(analyzerRect.getX()),
|
|
static_cast<float>(analyzerRect.getRight()));
|
|
g.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
|
|
juce::String label = juce::String(gridDB[i]);
|
|
g.drawText(label, analyzerRect.getX(), y, 30, 15,
|
|
juce::Justification::centred);
|
|
}
|
|
|
|
for (float freq: gridFreqs) {
|
|
float x = mapFrequencyToX(freq, minFreq, maxFreq, analyzerWidth);
|
|
x += analyzerRect.getX();
|
|
|
|
g.drawVerticalLine(static_cast<int>(x),
|
|
static_cast<float>(analyzerRect.getY()),
|
|
static_cast<float>(analyzerRect.getBottom()));
|
|
|
|
if (freq == 20) continue;
|
|
g.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
|
|
juce::String label = (freq >= 1000) ? juce::String(freq / 1000) + "k" : juce::String(freq);
|
|
g.drawText(label, static_cast<int>(x) - 15, analyzerRect.getY(), 30, 15,
|
|
juce::Justification::centred);
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::paintModeBoxBorders(juce::Graphics &g) {
|
|
for (auto *b: lowBandModeButtons) {
|
|
if (b->getToggleState()) {
|
|
auto r = getLocalArea(&lowBandModeBox, b->getBounds());
|
|
int side = juce::jmin(r.getWidth(), r.getHeight());
|
|
r = r.withSizeKeepingCentre(side, side);
|
|
|
|
auto rf = r.toFloat();
|
|
auto rfFilled = rf.toNearestInt().toFloat();
|
|
const float stroke = 1.0f;
|
|
auto rfStroke = rfFilled.reduced(stroke * 0.5f);
|
|
|
|
g.setColour(juce::Colours::white.withAlpha(0.3f));
|
|
g.fillRoundedRectangle(r.toFloat(), 5);
|
|
g.setColour(juce::Colours::white.withAlpha(0.9f));
|
|
g.drawRoundedRectangle(rfStroke, 5, stroke);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::paint(juce::Graphics &g) {
|
|
g.fillAll(Colours::BACKGROUNDCOLOUR);
|
|
g.setColour(AXIOM::DesignSystem::Colours::FOREGROUNDCOLOUR);
|
|
|
|
|
|
const auto mainPanelArea = getLocalArea(&mainPanel, mainPanel.getLocalBounds());
|
|
auto mainPanelY = mainPanelArea.getY();
|
|
|
|
paintAnalyzer(g);
|
|
g.setColour(Colours::SURFACEBYPASS);
|
|
const auto headerBarArea = getLocalArea(&headerBar, headerBar.getLocalBounds());
|
|
auto headerBarAreaX = headerBarArea.getX();
|
|
auto headerBarAreaY = headerBarArea.getY();
|
|
auto headerBarAreaW = headerBarArea.getWidth();
|
|
auto headerBarAreaH = headerBarArea.getHeight();
|
|
auto headerBarAreaPad = ((headerBarAreaY + mainPanelY) - headerBarAreaH) / 2;
|
|
g.fillRect(headerBarAreaX, headerBarAreaY, headerBarAreaW, mainPanelY - headerBarAreaPad);
|
|
|
|
|
|
g.setColour(Colours::SURFACECOLOUR);
|
|
const auto presetLocalArea = getLocalArea(&presetArea, presetArea.getLocalBounds());
|
|
auto presetLocalAreaX = presetLocalArea.getX();
|
|
auto presetLocalAreaY = presetLocalArea.getY();
|
|
auto presetLocalAreaWidth = presetLocalArea.getWidth();
|
|
auto presetLocalAreaHeight = presetLocalArea.getHeight();
|
|
g.fillRoundedRectangle(presetLocalAreaX, presetLocalAreaY - 10.f, presetLocalAreaWidth, presetLocalAreaHeight + 10.f, 10.0f);
|
|
|
|
|
|
const auto filterLocalArea = getLocalArea(&filterArea, filterArea.getLocalBounds());
|
|
const int filterLocalAreaBorderWidth = 3;
|
|
auto filterLocalAreaBorder = filterLocalArea.withSizeKeepingCentre(filterLocalArea.getWidth() + filterLocalAreaBorderWidth, filterLocalArea.getHeight() + filterLocalAreaBorderWidth);
|
|
filterLocalAreaBorder = filterLocalAreaBorder.withX(filterLocalAreaBorder.getX() + filterLocalAreaBorderWidth).withY(filterLocalAreaBorder.getY() + filterLocalAreaBorderWidth);
|
|
|
|
|
|
g.setColour(Colours::SURFACECOLOUR);
|
|
g.fillRect(filterLocalAreaBorder);
|
|
|
|
filterLocalAreaBorder = filterLocalAreaBorder.withX(filterLocalAreaBorder.getX() - 2 * filterLocalAreaBorderWidth).withY(filterLocalAreaBorder.getY() - 2 * filterLocalAreaBorderWidth);
|
|
g.fillRect(filterLocalAreaBorder);
|
|
|
|
g.setColour(Colours::BACKGROUNDBYPASS);
|
|
g.fillRect(filterLocalArea);
|
|
|
|
g.setColour(Colours::SURFACEBYPASS);
|
|
const auto footerBarArea = getLocalArea(&footerBar, footerBar.getLocalBounds());
|
|
g.fillRect(footerBarArea);
|
|
paintBorderLines(g);
|
|
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::paintBorderLines(juce::Graphics &g) {
|
|
g.setColour(DesignSystem::Colours::BACKGROUNDCOLOUR);
|
|
auto prevRight = (float) lowFilterArea.getRight();
|
|
|
|
for (auto *c: filterAreas) {
|
|
if (c == &lowFilterArea) continue;
|
|
|
|
const auto area = getLocalArea(c, c->getLocalBounds());
|
|
const float xAvg = ((float) area.getX() - prevRight) / 2;
|
|
const int x = area.getX() - xAvg + (filterAreaMargin / 2);
|
|
prevRight = (float) c->getRight();
|
|
|
|
const auto top = (float) area.getY();
|
|
const auto bot = (float) area.getBottom();
|
|
|
|
|
|
const float center = (top + bot) * 0.5f;
|
|
const float halfLen = (bot - top) * 0.375f;
|
|
g.drawVerticalLine(x, center - halfLen, center + halfLen);
|
|
}
|
|
g.setColour(DesignSystem::Colours::SURFACECOLOUR);
|
|
auto prevRightGlobal = (float) globalControlArea.getRight();
|
|
for (auto *c: globalControlArea.getChildren()) {
|
|
if (c == &inputSlider) continue;
|
|
if (c == &masterBypassButton) continue;
|
|
|
|
const auto area = getLocalArea(c, c->getLocalBounds());
|
|
const float xAvg = ((float) area.getX() - prevRightGlobal) / 2;
|
|
const int x = area.getX() - xAvg;
|
|
prevRightGlobal = (float) c->getRight();
|
|
|
|
const auto top = (float) area.getY();
|
|
const auto bot = (float) area.getBottom();
|
|
|
|
|
|
const float center = (top + bot) * 0.5f;
|
|
const float halfLen = (bot - top) * 0.375f;
|
|
g.drawVerticalLine(x, center - halfLen, center + halfLen);
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
|
|
int lowMode = (int) audioProcessor.apvts
|
|
.getRawParameterValue("LowBandModes")->load();
|
|
const bool masterIsToggled = masterBypassButton.getToggleState();
|
|
|
|
const float target = masterIsToggled ? 1.0f : 0.0f;
|
|
if (masterIsToggled) {
|
|
lowMode = 0;
|
|
disableLowBand(target);
|
|
}
|
|
|
|
lowBandFreqSlider.setEnabled(lowMode >= 1);
|
|
lowBandFreqLabel.setEnabled(lowMode >= 1);
|
|
lowBandSlopeSlider.setEnabled(lowMode == 1);
|
|
lowBandSlopeLabel.setEnabled(lowMode == 1);
|
|
lowdBOctLabel.setEnabled(lowMode == 1);
|
|
lowBandGainSlider.setEnabled(lowMode >= 2);
|
|
lowBandGainLabel.setEnabled(lowMode >= 2);
|
|
lowBandQSlider.setEnabled(lowMode >= 1);
|
|
lowBandQLabel.setEnabled(lowMode >= 1);
|
|
lowBandModeLabel.setEnabled(lowMode >= 1);
|
|
|
|
int highMode = (int) audioProcessor.apvts
|
|
.getRawParameterValue("HighBandModes")->load();
|
|
|
|
if (masterIsToggled) {
|
|
highMode = 0;
|
|
disableHighBand(target);
|
|
}
|
|
highBandFreqSlider.setEnabled(highMode >= 1);
|
|
highBandFreqLabel.setEnabled(highMode >= 1);
|
|
highBandSlopeSlider.setEnabled(highMode == 1);
|
|
highBandSlopeLabel.setEnabled(highMode == 1);
|
|
highdBOctLabel.setEnabled(highMode == 1);
|
|
highBandGainSlider.setEnabled(highMode >= 2);
|
|
highBandGainLabel.setEnabled(highMode >= 2);
|
|
highBandQSlider.setEnabled(highMode >= 1);
|
|
highBandQLabel.setEnabled(highMode >= 1);
|
|
highBandModeLabel.setEnabled(highMode >= 1);
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::updateFrequencyRanges() {
|
|
int lowMode = (int) audioProcessor.apvts.getRawParameterValue("LowBandModes")->load();
|
|
|
|
if (lowMode == 3) {
|
|
lowBandFreqSlider.setRange(20.0, 500.0, 1.0);
|
|
|
|
float currentFreq = audioProcessor.apvts.getRawParameterValue("LowBandFreq")->load();
|
|
if (currentFreq > 500.0f) {
|
|
lowBandFreqSlider.setValue(500.0, juce::sendNotificationSync);
|
|
}
|
|
} else if (lowMode >= 1) {
|
|
lowBandFreqSlider.setRange(20.0, 20000.0, 1.0);
|
|
}
|
|
|
|
lowBandFreqSlider.repaint();
|
|
int highMode = (int) audioProcessor.apvts.getRawParameterValue("HighBandModes")->load();
|
|
|
|
if (highMode == 3) {
|
|
highBandFreqSlider.setRange(8000.0, 20000.0, 1.0);
|
|
|
|
float currentFreq = audioProcessor.apvts.getRawParameterValue("HighBandFreq")->load();
|
|
if (currentFreq < 8000.0f) {
|
|
highBandFreqSlider.setValue(8000.0, juce::sendNotificationSync);
|
|
}
|
|
} else if (highMode >= 1) {
|
|
highBandFreqSlider.setRange(20.0, 20000.0, 1.0);
|
|
}
|
|
highBandFreqSlider.repaint();
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::animateCrystalizeButton() {
|
|
if (isAnimatingCrystalize) {
|
|
const float step = 0.1f;
|
|
if (isFadingToActive) {
|
|
svgToggleButtonLookAndFeel->activeIconOpacity += step;
|
|
svgToggleButtonLookAndFeel->passiveIconOpacity -= step;
|
|
|
|
if (svgToggleButtonLookAndFeel->activeIconOpacity >= 1.0f) {
|
|
svgToggleButtonLookAndFeel->activeIconOpacity = 1.0f;
|
|
svgToggleButtonLookAndFeel->passiveIconOpacity = 0.0f;
|
|
isAnimatingCrystalize = false;
|
|
}
|
|
} else {
|
|
svgToggleButtonLookAndFeel->activeIconOpacity -= step;
|
|
svgToggleButtonLookAndFeel->passiveIconOpacity += step;
|
|
|
|
if (svgToggleButtonLookAndFeel->passiveIconOpacity >= 1.0f) {
|
|
svgToggleButtonLookAndFeel->activeIconOpacity = 0.0f;
|
|
svgToggleButtonLookAndFeel->passiveIconOpacity = 1.0f;
|
|
isAnimatingCrystalize = false;
|
|
}
|
|
}
|
|
crystalizeButton.repaint();
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::timerCallback() {
|
|
setKnobVisibility();
|
|
spectrumAnalyzer.processSamples();
|
|
repaint(analyzerRect);
|
|
resized();
|
|
animateCrystalizeButton();
|
|
updateFrequencyRanges();
|
|
|
|
if (presetMenuSafePtr == nullptr) {
|
|
presetMenuButton.setToggleState(false, juce::dontSendNotification);
|
|
inputFocused = false;
|
|
} else if (!inputFocused) {
|
|
presetNameInput.giveAwayKeyboardFocus();
|
|
}
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::resized() {
|
|
auto pluginArea = getLocalBounds();
|
|
scalePluginWindow(pluginArea);
|
|
setupMainGrid(pluginArea);
|
|
setupHeader();
|
|
setupBody();
|
|
setupFooter();
|
|
}
|
|
|
|
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);
|
|
|
|
lowBypass.setToggleState(false, notify);
|
|
lowCut.setToggleState(false, notify);
|
|
lowBell.setToggleState(false, notify);
|
|
lowShelf.setToggleState(true, notify);
|
|
|
|
highBypass.setToggleState(false, notify);
|
|
highCut.setToggleState(false, notify);
|
|
highBell.setToggleState(false, notify);
|
|
highShelf.setToggleState(true, notify);
|
|
}
|
|
|
|
|
|
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)},
|
|
/* 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;
|
|
|
|
const auto titleWidthCentre = titleLabel.getFont().getStringWidth("Crystalizer") / 4;
|
|
|
|
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(presetArea, 1, 2, 2, 3),
|
|
Layout::area(titleLabel, 1, 1, 2, 2)
|
|
.withMargin(juce::GridItem::Margin(0, 0, 0, titleWidthCentre)),
|
|
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());
|
|
const float rowHeight = presetBoxHeight * 0.25f;
|
|
const float iconSize = rowHeight;
|
|
|
|
Layout::GridSpec presetSpec{
|
|
/* cols */ {Layout::fr(1), Layout::pxTrack(presetBoxWidth * 0.5f), Layout::fr(1)},
|
|
/* rows */ {Layout::pxTrack(rowHeight), Layout::pxTrack(rowHeight)},
|
|
/* colGap */ Spacing::SizeMode::XS,
|
|
/* rowGap */ Spacing::SizeMode::XS,
|
|
/* pad */ Layout::padding(Spacing::SizeMode::S)
|
|
};
|
|
Layout::grid(presetAreaBounds, presetSpec, {
|
|
Layout::area(presetBoxLabel, 1, 2, 2, 3),
|
|
Layout::area(presetBox, 2, 2, 3, 3),
|
|
Layout::area(resetButton, 2, 1, 2, 2),
|
|
Layout::area(presetMenuButton, 2, 3, 3, 4)
|
|
.withWidth(iconSize)
|
|
.withHeight(iconSize)
|
|
|
|
});
|
|
}
|
|
|
|
|
|
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;
|
|
const auto crystalizeIconSize = 140.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)
|
|
.withMargin(juce::GridItem::Margin(filterAreaMargin, 0, filterAreaMargin, 0)),
|
|
Layout::area(crystalizeButton, 1, 5, 2, 6)
|
|
.withWidth(crystalizeIconSize)
|
|
.withHeight(crystalizeIconSize * 0.5f)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(filterArea, 2, 1, 3, 6)
|
|
.withMargin(juce::GridItem::Margin(0, filterAreaMargin, 0, filterAreaMargin))
|
|
|
|
});
|
|
|
|
const auto filterAreaBounds = filterArea.getLocalBounds();
|
|
const auto filterAreaWidth = static_cast<float>(filterArea.getWidth());
|
|
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;
|
|
|
|
const auto refW = getReferenceCell()[0];
|
|
const auto refH = getReferenceCell()[1];
|
|
const auto gainSize = refH * gainMod;
|
|
const auto offSetToGainTop = gainSize;
|
|
const float labelPadding = Components::SlopeSliderLookAndFeel::labelPadding * 2;
|
|
|
|
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(lowBandFreqSlider, 2, 1, 3, 2)
|
|
.withWidth(refW * freqMod * 0.8f)
|
|
.withHeight(refH * freqMod * 0.8f)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(lowBandGainSlider, 2, 2, 3, 3)
|
|
.withWidth(refW * gainMod * 0.8f)
|
|
.withHeight(refH * gainMod * 0.8f)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(lowBandQSlider, 2, 3, 3, 4)
|
|
.withWidth(refW * qMod * 0.8f)
|
|
.withHeight(refH * qMod * 0.8f)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(lowBandSlopeSlider, 3, 1, 4, 2)
|
|
.withWidth(refW * slopeMod + labelPadding)
|
|
.withHeight(refH * slopeMod + labelPadding)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(lowBandModeBox, 3, 2, 4, 4),
|
|
|
|
|
|
Layout::area(lowBandFreqLabel, 2, 1, 3, 2)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
Layout::area(lowBandGainLabel, 1, 2, 2, 3),
|
|
Layout::area(lowBandQLabel, 2, 3, 3, 4)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
Layout::area(lowBandSlopeLabel, 3, 1, 4, 2)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop / 1.5f, 0, 0, 0)),
|
|
|
|
Layout::area(lowdBOctLabel, 3, 1, 4, 2)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
|
|
});
|
|
|
|
const auto modeBoxBounds = lowBandModeBox.getLocalBounds();
|
|
const auto slopeH = lowBandSlopeSlider.getY() - lowBandModeBox.getY() + lowBandModeLabel.getFont().getHeight() / 2;
|
|
|
|
Layout::GridSpec lowBandModeBoxSpec{
|
|
/* cols */ {Layout::fr(1), Layout::fr(1), 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(modeBoxBounds, lowBandModeBoxSpec, {
|
|
Layout::area(lowBandModeLabel, 1, 2, 2, 4)
|
|
.withMargin(juce::GridItem::Margin(slopeH, 0, 0, 0)),
|
|
Layout::area(lowBypass, 1, 1, 3, 2),
|
|
Layout::area(lowCut, 1, 2, 3, 3),
|
|
Layout::area(lowBell, 1, 3, 3, 4),
|
|
Layout::area(lowShelf, 1, 4, 3, 5)
|
|
});
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
const auto refW = getReferenceCell()[0];
|
|
const auto refH = getReferenceCell()[1];
|
|
const auto gainSize = refH * gainMod;
|
|
const auto offSetToGainTop = gainSize;
|
|
|
|
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(peak1FreqSlider, 2, 1, 3, 2)
|
|
.withWidth(refW * freqMod)
|
|
.withHeight(refH * freqMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(peak1GainSlider, 2, 2, 3, 3)
|
|
.withWidth(refW * gainMod)
|
|
.withHeight(refH * gainMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(peak1QSlider, 2, 3, 3, 4)
|
|
.withWidth(refW * qMod)
|
|
.withHeight(refH * qMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(peak1FreqLabel, 2, 1, 3, 2)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
Layout::area(peak1GainLabel, 1, 2, 2, 3),
|
|
Layout::area(peak1QLabel, 2, 3, 3, 4)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
|
|
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;
|
|
|
|
const auto refW = getReferenceCell()[0];
|
|
const auto refH = getReferenceCell()[1];
|
|
const auto gainSize = refH * gainMod;
|
|
const auto offSetToGainTop = gainSize;
|
|
|
|
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(peak2FreqSlider, 2, 1, 3, 2)
|
|
.withWidth(refW * freqMod)
|
|
.withHeight(refH * freqMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(peak2GainSlider, 2, 2, 3, 3)
|
|
.withWidth(refW * gainMod)
|
|
.withHeight(refH * gainMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(peak2QSlider, 2, 3, 3, 4)
|
|
.withWidth(refW * qMod)
|
|
.withHeight(refH * qMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(peak2FreqLabel, 2, 1, 3, 2)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
Layout::area(peak2GainLabel, 1, 2, 2, 3),
|
|
|
|
Layout::area(peak2QLabel, 2, 3, 3, 4)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
|
|
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;
|
|
|
|
const auto refW = getReferenceCell()[0];
|
|
const auto refH = getReferenceCell()[1];
|
|
const auto gainSize = refH * gainMod;
|
|
const auto offSetToGainTop = gainSize;
|
|
|
|
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(peak3FreqSlider, 2, 1, 3, 2)
|
|
.withWidth(refW * freqMod)
|
|
.withHeight(refH * freqMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(peak3GainSlider, 2, 2, 3, 3)
|
|
.withWidth(refW * gainMod)
|
|
.withHeight(refH * gainMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(peak3QSlider, 2, 3, 3, 4)
|
|
.withWidth(refW * qMod)
|
|
.withHeight(refH * qMod)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(peak3FreqLabel, 2, 1, 3, 2)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
Layout::area(peak3GainLabel, 1, 2, 2, 3),
|
|
Layout::area(peak3QLabel, 2, 3, 3, 4)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
|
|
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;
|
|
|
|
const auto refW = getReferenceCell()[0];
|
|
const auto refH = getReferenceCell()[1];
|
|
const auto gainSize = refH * gainMod;
|
|
const auto offSetToGainTop = gainSize;
|
|
const float labelPadding = Components::SlopeSliderLookAndFeel::labelPadding * 2;
|
|
|
|
|
|
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(highBandFreqSlider, 2, 1, 3, 2)
|
|
.withWidth(refW * freqMod * 0.8f)
|
|
.withHeight(refH * freqMod * 0.8f)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(highBandGainSlider, 2, 2, 3, 3)
|
|
.withWidth(refW * gainMod * 0.8f)
|
|
.withHeight(refH * gainMod * 0.8f)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
Layout::area(highBandQSlider, 2, 3, 3, 4)
|
|
.withWidth(refW * qMod * 0.8f)
|
|
.withHeight(refH * qMod * 0.8f)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(highBandModeBox, 3, 1, 4, 3),
|
|
|
|
|
|
Layout::area(highBandSlopeSlider, 3, 3, 4, 4)
|
|
.withWidth(refW * slopeMod + labelPadding)
|
|
.withHeight(refH * slopeMod + labelPadding)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(highBandFreqLabel, 2, 1, 3, 2)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
Layout::area(highBandGainLabel, 1, 2, 2, 3),
|
|
Layout::area(highBandQLabel, 2, 3, 3, 4)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
|
|
Layout::area(highBandSlopeLabel, 3, 3, 4, 4)
|
|
.withMargin(juce::GridItem::Margin(-offSetToGainTop / 1.5f, 0, 0, 0)),
|
|
|
|
Layout::area(highdBOctLabel, 3, 3, 4, 4)
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
});
|
|
|
|
const auto modeBoxBounds = lowBandModeBox.getLocalBounds();
|
|
const auto slopeH = highBandSlopeSlider.getY() - highBandModeBox.getY() + highBandModeLabel.getFont().getHeight() /
|
|
2;
|
|
|
|
Layout::GridSpec highBandModeBoxSpec{
|
|
/* cols */ {Layout::fr(1), Layout::fr(1), 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(modeBoxBounds, highBandModeBoxSpec, {
|
|
Layout::area(highBandModeLabel, 1, 2, 2, 4)
|
|
.withMargin(juce::GridItem::Margin(slopeH, 0, 0, 0)),
|
|
Layout::area(highBypass, 1, 4, 3, 5),
|
|
Layout::area(highCut, 1, 3, 3, 4),
|
|
Layout::area(highBell, 1, 2, 3, 3),
|
|
Layout::area(highShelf, 1, 1, 3, 2)
|
|
});
|
|
}
|
|
|
|
|
|
void CrystalizerEQAudioProcessorEditor::setupFooter() {
|
|
const auto bounds = footerBar.getLocalBounds();
|
|
|
|
const auto refW = getReferenceCell()[0];
|
|
const auto refH = getReferenceCell()[1];
|
|
|
|
Layout::GridSpec footerSpec{
|
|
/* cols */ {Layout::fr(1), Layout::fr(1), Layout::fr(1)},
|
|
/* 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 sliderRadius = outputSlider.getWidth() / 2.0f;
|
|
const auto sliderWidth = outputSlider.getWidth();
|
|
|
|
|
|
Layout::GridSpec globalControlAreaSpec{
|
|
/* cols */ {Layout::fr(1), Layout::fr(1), Layout::fr(1)},
|
|
/* rows */ {Layout::fr(1)},
|
|
/* colGap */ Spacing::SizeMode::XS,
|
|
/* rowGap */ Spacing::SizeMode::XS,
|
|
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
|
};
|
|
Layout::grid(globalControlAreaBounds, globalControlAreaSpec, {
|
|
|
|
Layout::area(outputSlider, 1, 2, 3, 3)
|
|
.withWidth(refW * globalMod)
|
|
.withHeight(refH * globalMod)
|
|
.withMargin(juce::GridItem::Margin(0, 0, 0, sliderRadius * 6))
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
Layout::area(inputSlider, 1, 1, 3, 2)
|
|
.withWidth(refW * globalMod)
|
|
.withHeight(refH * globalMod)
|
|
.withMargin(juce::GridItem::Margin(0, 0, 0, sliderRadius * 10))
|
|
.withAlignSelf(juce::GridItem::AlignSelf::center)
|
|
.withJustifySelf(juce::GridItem::JustifySelf::center),
|
|
|
|
|
|
Layout::area(masterBypassButton, 1, 3, 3, 4)
|
|
.withWidth(sliderWidth)
|
|
|
|
.withMargin(juce::GridItem::Margin(0, 0, 0, sliderRadius * 2)),
|
|
});
|
|
}
|
|
|
|
|
|
juce::Array<float> CrystalizerEQAudioProcessorEditor::getReferenceCell() {
|
|
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;
|
|
|
|
const auto gainCellW = knobColWidth;
|
|
const auto gainCellH = knobRowHeight * 1.25f;
|
|
const juce::Array<float> refCell = {gainCellW, gainCellH};
|
|
return refCell;
|
|
}
|
|
|
|
|
|
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.performFrequencyOnlyForwardTransform(fftData.data(), true);
|
|
buildMagnitudeSpectrum();
|
|
convertToDb();
|
|
applySmoothing();
|
|
renderValuesDb = getRenderValues();
|
|
}
|
|
|
|
|
|
void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
|
|
for (int n = 0; n < FFTSIZE; ++n) {
|
|
fftData[n] = windowedFrame[n];
|
|
}
|
|
for (int n = FFTSIZE; n < fftData.size(); ++n) {
|
|
fftData[n] = 0.0f;
|
|
}
|
|
}
|
|
|
|
|
|
void SpectrumAnalyzer::buildMagnitudeSpectrum() {
|
|
for (int k = 0; k < BINS; ++k) {
|
|
float mag = fftData[k];
|
|
mag /= FFTSIZE;
|
|
|
|
mag = std::max(mag, 1e-24f);
|
|
magnitudes[k] = mag;
|
|
}
|
|
}
|
|
|
|
float getFrequencyWeighting(float freq) {
|
|
// Pre-Emphasis: +3dB pro Oktave (kompensiert Pink Noise Charakteristik)
|
|
// Basis: 1kHz = 0dB
|
|
const float referenceFreq = 1000.0f;
|
|
float octaves = std::log2(freq / referenceFreq);
|
|
float weightingDb = octaves * 3.0f; // 3dB pro Oktave
|
|
|
|
// Begrenzen auf sinnvolle Werte
|
|
weightingDb = juce::jlimit(-12.0f, 12.0f, weightingDb);
|
|
|
|
return weightingDb;
|
|
}
|
|
|
|
void SpectrumAnalyzer::convertToDb() {
|
|
for (int k = 0; k < magnitudes.size(); ++k) {
|
|
float mag = std::max(magnitudes[k], 1e-9f);
|
|
float dB = juce::Decibels::gainToDecibels(mag);
|
|
|
|
float freq = k * deltaF;
|
|
float weighting = getFrequencyWeighting(freq);
|
|
dB += weighting;
|
|
if (dB < -60.0f) {
|
|
dB = MINDB;
|
|
}
|
|
|
|
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;
|
|
}
|