1589 lines
58 KiB
C++

/*
==============================================================================
This file contains the basic framework code for a JUCE plugin editor.
==============================================================================
*/
#include "PluginProcessor.h"
#include "PluginEditor.h"
#include "AXIOMDesignSystem.h"
#include "JuceLibraryCode/BinaryData.h"
using AXIOM::DesignSystem;
using Colours = DesignSystem::Colours;
using Typography = DesignSystem::Typography;
using Spacing = DesignSystem::Spacing;
using Shape = DesignSystem::Shape;
using Opacity = DesignSystem::Opacity;
using Layout = DesignSystem::Layout;
using Components = DesignSystem::Components;
using SliderStyles = Components::SliderStyles;
//region setupModeBoxes
void CrystalizerEQAudioProcessorEditor::setupModeBoxes() {
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
audioProcessor.apvts.getParameter("LowBandModes")))
{
lowBandModeBox.addItemList (choice->choices, 1);
lowBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification);
lowBandModeAttach = std::make_unique<ComboBoxAttach>(
audioProcessor.apvts, "LowBandModes", lowBandModeBox);
}
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
audioProcessor.apvts.getParameter("HighBandModes")))
{
highBandModeBox.addItemList (choice->choices, 1);
highBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification);
highBandModeAttach = std::make_unique<ComboBoxAttach>(
audioProcessor.apvts, "HighBandModes", highBandModeBox);
}
}
//endregion setupModeBoxes
//region setupSliders
void CrystalizerEQAudioProcessorEditor::setupSliders() {
auto style = juce::Slider::RotaryHorizontalVerticalDrag;
for (auto* s : sliders)
{
s->setSliderStyle (style);
}
gainLookAndFeel = std::make_unique<DesignSystem::Components::GainSliderLookAndFeel>();
freqQLookAndFeel = std::make_unique<DesignSystem::Components::FreqQSliderLookAndFeel>();
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());
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());
}
}
//endregion setupSliders
//region setupAttachments
void CrystalizerEQAudioProcessorEditor::setupAttachments() {
testNoiseAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "TestNoiseLevel", testNoiseSlider);
lowBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandFreq", lowBandFreqSlider);
lowBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandSlope", lowBandSlopeSlider);
lowBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandGain", lowBandGainSlider);
lowBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandQ", lowBandQSlider);
lowBandModeAttach = std::make_unique<ComboBoxAttach>(audioProcessor.apvts, "LowBandModes", lowBandModeBox);
peak1FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Freq", peak1FreqSlider);
peak1GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Gain", peak1GainSlider);
peak1QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Q", peak1QSlider);
peak2FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Freq", peak2FreqSlider);
peak2GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Gain", peak2GainSlider);
peak2QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Q", peak2QSlider);
peak3FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Freq", peak3FreqSlider);
peak3GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Gain", peak3GainSlider);
peak3QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Q", peak3QSlider);
highBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandFreq", highBandFreqSlider);
highBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandSlope", highBandSlopeSlider);
highBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandGain", highBandGainSlider);
highBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandQ", highBandQSlider);
highBandModeAttach = std::make_unique<ComboBoxAttach>(audioProcessor.apvts, "HighBandModes", highBandModeBox);
inputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "InputGain", inputSlider);
outputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "OutputGain", outputSlider);
}
//endregion setupAttachments
//region setupDisplayNames
void CrystalizerEQAudioProcessorEditor::setupDisplayNames() {
titleLabel.setText("Crystalizer", juce::dontSendNotification);
testNoiseSlider.setName("TestNoise");
lowBandFreqSlider.setName ("LowBand Freq");
lowBandSlopeSlider.setName ("LowBand Slope");
lowBandGainSlider.setName ("LowBand Gain");
lowBandQSlider.setName ("LowBand Q");
peak1FreqSlider.setName("Peak1 Freq");
peak1GainSlider.setName("Peak1 Gain");
peak1QSlider.setName ("Peak1 Q");
peak2FreqSlider.setName("Peak2 Freq");
peak2GainSlider.setName("Peak2 Gain");
peak2QSlider.setName ("Peak2 Q");
peak3FreqSlider.setName("Peak3 Freq");
peak3GainSlider.setName("Peak3 Gain");
peak3QSlider.setName ("Peak3 Q");
highBandFreqSlider.setName ("HighBand Freq");
highBandSlopeSlider.setName ("HighBand Slope");
highBandGainSlider.setName ("HighBand Gain");
highBandQSlider.setName ("HighBand Q");
inputSlider.setName ("Input");
outputSlider.setName ("Output");
testNoiseSlider.setTextValueSuffix(" Gain");
//lowBandFreqSlider.setTextValueSuffix ("\nHz");
lowBandSlopeSlider.setTextValueSuffix ("\ndB/Oct");
lowBandGainSlider.setTextValueSuffix("\ndB");
//lowBandQSlider.setTextValueSuffix ("\nQ");
//peak1FreqSlider.setTextValueSuffix("\nHz");
peak1GainSlider.setTextValueSuffix("\ndB");
//peak1QSlider.setTextValueSuffix("\nQ");
peak1BypassButton.setName("peak1Bypass");
//peak2FreqSlider.setTextValueSuffix("\nHz");
peak2GainSlider.setTextValueSuffix("\ndB");
//peak2QSlider.setTextValueSuffix("\nQ");
peak2BypassButton.setName("peak2Bypass");
//peak3FreqSlider.setTextValueSuffix("\nHz");
peak3GainSlider.setTextValueSuffix("\ndB");
//peak3QSlider.setTextValueSuffix("\nQ");
peak3BypassButton.setName("peak3Bypass");
//highBandFreqSlider.setTextValueSuffix ("\nHz");
highBandSlopeSlider.setTextValueSuffix ("\ndB/Oct");
highBandGainSlider.setTextValueSuffix("\ndB");
// highBandQSlider.setTextValueSuffix ("\nQ");
inputSlider.setTextValueSuffix("\nIN");
outputSlider.setTextValueSuffix("\nOUT");
testNoiseButton.setName("TestNoise");
testNoiseButton.setButtonText("Test Noise");
crystalizeButton.setName("CrystalizeButton");
crystalizeButton.setButtonText("Crystalize");
resetButton.setName("ResetButton");
resetButton.setButtonText("Reset");
masterBypassButton.setName("MasterBypass");
savePresetButton.setName("SavePresetButton");
savePresetButton.setButtonText("Save Preset");
deletePresetButton.setName("DeletePresetButton");
deletePresetButton.setButtonText("Delete Preset");
}
//endregion setupDisplayNames
//region setupToggleButtons
void CrystalizerEQAudioProcessorEditor::setupToggleButtons() {
for (auto* b : {&peak1BypassButton, &peak2BypassButton, &peak3BypassButton, &crystalizeButton, &masterBypassButton}) {
b->onClick = [this, b]() {
juce::String paramID; // nimm juce::String statt std::string
juce::String mode;
if (b == &peak1BypassButton) {
paramID = "Peak1Bypass";
mode = "Low-Mid";
}
else if (b == &peak2BypassButton) {
paramID = "Peak2Bypass";
mode = "Mid";
}
else if (b == &peak3BypassButton) {
paramID = "Peak3Bypass";
mode = "High-Mid";
}
else if (b == &crystalizeButton) {
paramID = "CrystalizeButton";
}
else if (b == &masterBypassButton) {
paramID = "MasterBypass";
mode = "Master";
}
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();
}
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);
}
};
}
bypassButtonLookAndFeel = std::make_unique<Components::BypassButtonLookAndFeel>();
peak1BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
peak2BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
peak3BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
masterBypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
}
//endregion setupToggleButtons
//region disableLowBand
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);
};
//endregion disableLowBand
//region disableLowMidBand
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);
}
//endregion disableLowMidBand
//region disableMidBand
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);
}
//endregion disableMidBand
//region disableHighMidBand
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);
}
//endregion disableHighMidBand
//region disableHighBand
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);
};
//endregion disableHighBand
//region disableEverything
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);
}
//TODO: If band bypass is active prior to master bypass, upon reactivating master the bypassed band is getting active.
peak1BypassButton.setEnabled(isToggled);
peak2BypassButton.setEnabled(isToggled);
peak3BypassButton.setEnabled(isToggled);
resetButton.setEnabled(isToggled);
crystalizeButton.setEnabled(isToggled);
lowBandModeBox.setEnabled(isToggled);
highBandModeBox.setEnabled(isToggled);
}
//endregion disableEverything
//region addComponentsToLayout
void CrystalizerEQAudioProcessorEditor::addComponentsToLayout() {
addAndMakeVisible(headerBar);
headerBar.addAndMakeVisible(titleLabel);
headerBar.addAndMakeVisible(presetArea);
presetArea.addAndMakeVisible(presetBoxLabel);
presetArea.addAndMakeVisible(resetButton);
presetArea.addAndMakeVisible(presetBox);
presetArea.addAndMakeVisible(presetMenuButton);
headerBar.addAndMakeVisible(presetMenu);
presetMenu.addAndMakeVisible(presetNameInput);
presetMenu.addAndMakeVisible(savePresetButton);
presetMenu.addAndMakeVisible(deletePresetButton);
presetMenu.toFront(true);
presetMenu.setVisible(false);
addAndMakeVisible(mainPanel);
mainPanel.addAndMakeVisible(analyzerArea);
mainPanel.addAndMakeVisible(crystalizeButton);
//FILTERAREA
{
mainPanel.addAndMakeVisible(filterArea);
{
filterArea.addAndMakeVisible(lowFilterArea);
lowFilterArea.addAndMakeVisible(lowBandModeBox);
lowFilterArea.addAndMakeVisible(lowBandSlopeLabel);
lowFilterArea.addAndMakeVisible(lowBandGainLabel);
lowFilterArea.addAndMakeVisible(lowBandQLabel);
lowFilterArea.addAndMakeVisible(lowBandFreqLabel);
lowFilterArea.addAndMakeVisible(lowBandSlopeSlider);
lowFilterArea.addAndMakeVisible(lowBandGainSlider);
lowFilterArea.addAndMakeVisible(lowBandQSlider);
lowFilterArea.addAndMakeVisible(lowBandFreqSlider);
}
{
filterArea.addAndMakeVisible(lowMidFilterArea);
lowMidFilterArea.addAndMakeVisible(peak1FreqLabel);
lowMidFilterArea.addAndMakeVisible(peak1GainLabel);
lowMidFilterArea.addAndMakeVisible(peak1QLabel);
lowMidFilterArea.addAndMakeVisible(peak1FreqSlider);
lowMidFilterArea.addAndMakeVisible(peak1GainSlider);
lowMidFilterArea.addAndMakeVisible(peak1QSlider);
lowMidFilterArea.addAndMakeVisible(peak1BypassButton);
}
{
filterArea.addAndMakeVisible(midFilterArea);
midFilterArea.addAndMakeVisible(peak2FreqLabel);
midFilterArea.addAndMakeVisible(peak2GainLabel);
midFilterArea.addAndMakeVisible(peak2QLabel);
midFilterArea.addAndMakeVisible(peak2FreqSlider);
midFilterArea.addAndMakeVisible(peak2GainSlider);
midFilterArea.addAndMakeVisible(peak2QSlider);
midFilterArea.addAndMakeVisible(peak2BypassButton);
}
{
filterArea.addAndMakeVisible(highMidFilterArea);
highMidFilterArea.addAndMakeVisible(peak3FreqLabel);
highMidFilterArea.addAndMakeVisible(peak3GainLabel);
highMidFilterArea.addAndMakeVisible(peak3QLabel);
highMidFilterArea.addAndMakeVisible(peak3FreqSlider);
highMidFilterArea.addAndMakeVisible(peak3GainSlider);
highMidFilterArea.addAndMakeVisible(peak3QSlider);
highMidFilterArea.addAndMakeVisible(peak3BypassButton);
}
{
filterArea.addAndMakeVisible(highFilterArea);
highFilterArea.addAndMakeVisible(highBandModeBox);
highFilterArea.addAndMakeVisible(highBandSlopeLabel);
highFilterArea.addAndMakeVisible(highBandGainLabel);
highFilterArea.addAndMakeVisible(highBandQLabel);
highFilterArea.addAndMakeVisible(highBandFreqLabel);
highFilterArea.addAndMakeVisible(highBandSlopeSlider);
highFilterArea.addAndMakeVisible(highBandGainSlider);
highFilterArea.addAndMakeVisible(highBandQSlider);
highFilterArea.addAndMakeVisible(highBandFreqSlider);
}
}
addAndMakeVisible(footerBar);
footerBar.addAndMakeVisible(globalControlArea);
globalControlArea.addAndMakeVisible(inputLabel);
globalControlArea.addAndMakeVisible(outputLabel);
globalControlArea.addAndMakeVisible(inputSlider);
globalControlArea.addAndMakeVisible(outputSlider);
globalControlArea.addAndMakeVisible(masterBypassButton);
}
//endregion addComponentsToLayout
//region setupLabels
void CrystalizerEQAudioProcessorEditor::setupLabels() {
auto setupLabel = [] (juce::Label& label, const juce::String& text)
{
label.setText (text, juce::dontSendNotification);
label.setJustificationType (juce::Justification::centred);
label.setColour (juce::Label::textColourId, juce::Colours::black);
label.setInterceptsMouseClicks (false, false); // Klicks gehen an den Slider
};
//SetupLabels
{
//LOW-BAND
setupLabel (lowBandFreqLabel, "Low\nHz");
setupLabel (lowBandSlopeLabel, "Slope");
setupLabel (lowBandGainLabel, "Low\nGain");
setupLabel(lowBandQLabel, "Low\nQ");
//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(presetBoxLabel, "Presets");
/*setupLabel(inputLabel, "IN");
setupLabel(outputLabel, "OUT");*/
}
}
//endregion setupLabels
void CrystalizerEQAudioProcessorEditor::setupFontsWithColours() {
Typography::applyToLabel(titleLabel, Typography::Style::Display, 1.f);
titleLabel.setColour(juce::Label::textColourId, Colours::ACCENTCOLOUR);
for (auto* l : sliderLabels) {
l->setColour(juce::Label::textColourId, Colours::FOREGROUNDCOLOUR);
Typography::applyToLabel(*l, Typography::Style::Mono, 1.f);
}
}
//region setupSliderTextBoxes
void CrystalizerEQAudioProcessorEditor::setupSliderTextBoxes() {
baseLookAndFeel = std::make_unique<AXIOM::DesignSystem::Components::BaseSliderLookAndFeel>();
for (auto* s : sliders) {
s->setLookAndFeel(baseLookAndFeel.get());
}
}
//endregion setupSliderTextBoxes
//region setupEventListeners
void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
presetMenuButton.onClick = [this]() {
bool menuOpened = presetMenuButton.getToggleState();
presetMenu.setVisible(menuOpened);
};
savePresetButton.onClick = [this]() {
if (presetNameInput.getText() != "")
audioProcessor.setPresetName (presetNameInput.getText());
else
audioProcessor.setPresetName ("Preset");
const auto presetName = audioProcessor.savePresetToFile();
presetBox.clear();
const auto updatedPresets = audioProcessor.getPresetNamesArray();
presetBox.addItemList(updatedPresets, 1);
for (int i = 0; i < presetBox.getNumItems(); i++)
{
if (presetName != presetBox.getItemText(i)) continue;
presetBox.setSelectedId(i + 1, juce::dontSendNotification);
}
presetNameInput.setText("");
};
deletePresetButton.onClick = [this]() {
auto selectedPreset = presetBox.getText();
if (selectedPreset == "Init") return;
const auto selectedPresetWithExt = selectedPreset + ".xml";
audioProcessor.deletePreset(selectedPresetWithExt);
audioProcessor.resetAllParameters();
presetBox.clear();
const auto updatedPresets = audioProcessor.getPresetNamesArray();
presetBox.addItemList(updatedPresets, 1);
presetBox.setSelectedId(1, juce::dontSendNotification);
};
presetBox.onChange = [this]() {
auto selectedPreset = presetBox.getText();
if (selectedPreset == "Init") {
audioProcessor.resetAllParameters();
return;
}
const auto selectedPresetWithExt = selectedPreset + ".xml";
audioProcessor.loadPreset(selectedPresetWithExt);
};
resetButton.onClick = [this]()
{
audioProcessor.resetAllParameters();
resetAllCheckboxes();
presetBox.setSelectedId(1, juce::dontSendNotification);
};
}
//endregion setupEventListeners
//region initPresetSystem
void CrystalizerEQAudioProcessorEditor::initPresetSystem() {
auto presets = audioProcessor.getPresetNamesArray();
presetBox.addItemList(presets, 1);
presetBox.setSelectedId(1, juce::dontSendNotification);
presetMenuButton.setName("PresetMenuButton");
presetMenuButton.setButtonText("Preset Menu");
presetMenuButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black);
presetMenuButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
presetMenuButton.setToggleState(false, juce::dontSendNotification);
presetNameInput.setTextToShowWhenEmpty("Preset Name", juce::Colours::grey);
presetNameInput.setJustification(juce::Justification::centred);
presetNameInput.setColour(juce::TextEditor::backgroundColourId, juce::Colours::black);
presetNameInput.setColour(juce::TextEditor::textColourId, juce::Colours::white);
}
//endregion initPresetSystem
//==============================================================================
CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor (CrystalizerEQAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p), spectrumAnalyzer(p.audioFIFO, p) {
setSize (1280, 720);
startTimerHz(60);
addComponentsToLayout();
setupLabels();
setupModeBoxes();
setupFontsWithColours();
setupAttachments();
setupDisplayNames();
setupToggleButtons();
setupEventListeners();
setupSliderTextBoxes();
setupSliders();
initPresetSystem();
addAndMakeVisible (testNoiseButton);
testNoiseButton.onClick = [this]() {
if (auto* param = audioProcessor.apvts.getParameter("TestNoiseEnabled"))
{
if (param->getValue() == false) {
param->beginChangeGesture();
param->setValueNotifyingHost (1.0f);
param->endChangeGesture();
} else {
param->beginChangeGesture();
param->setValueNotifyingHost (0.0f); // -> true
param->endChangeGesture();
}
}
};
}
CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() {
stopTimer();
for (auto* s : sliders) {
s->setLookAndFeel(nullptr);
}
peak1BypassButton.setLookAndFeel(nullptr);
peak2BypassButton.setLookAndFeel(nullptr);
peak3BypassButton.setLookAndFeel(nullptr);
masterBypassButton.setLookAndFeel(nullptr);
};
//region paintAnalyzer
void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
analyzerRect = getLocalArea(&analyzerArea, analyzerArea.getLocalBounds());
juce::Graphics::ScopedSaveState guard(g);
g.reduceClipRegion(analyzerRect);
auto r = analyzerRect.toFloat();
// Hintergrund des Analyzer-Bereichs
g.setColour(juce::Colours::black);
g.fillRect(r);
// Rahmen (optional)
g.setColour(juce::Colours::grey);
g.drawRect(analyzerRect);
}
//endregion paintAnalyzer
//==============================================================================
void CrystalizerEQAudioProcessorEditor::paint (juce::Graphics& g)
{
g.fillAll (Colours::BACKGROUNDCOLOUR);
g.setColour (AXIOM::DesignSystem::Colours::FOREGROUNDCOLOUR);
if constexpr (false) // -> auf false setzen, wenn nicht gebraucht
{
for (auto* c : getChildren()) // headerBar, mainPanel, footerBar
{
auto r = c->getBounds();
g.setColour(juce::Colours::magenta.withAlpha(0.9f));
g.drawRect(r, 1.0f); // nur Rahmen, kein fillRect
g.setColour(juce::Colours::white);
g.drawText(c->getName(), r, juce::Justification::centred, false);
}
for (auto* c : headerBar.getChildren())
{
auto r = c->getBounds(); // <-- lokal zu headerBar
r = r.translated(headerBar.getX(), headerBar.getY()); // <-- in Editor-Koordinaten bringen!
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : presetArea.getChildren())
{
auto r = c->getBounds(); // lokal in presetArea
r = r.translated(headerBar.getX() + presetArea.getX(),
headerBar.getY() + presetArea.getY()); // beide Offsets addieren
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : mainPanel.getChildren())
{
auto r = c->getBounds(); // <-- lokal zu headerBar
r = r.translated(mainPanel.getX(), mainPanel.getY()); // <-- in Editor-Koordinaten bringen!
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : filterArea.getChildren())
{
auto r = c->getBounds(); // lokal in presetArea
r = r.translated(mainPanel.getX() + filterArea.getX(),
mainPanel.getY() + filterArea.getY()); // beide Offsets addieren
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
{
for (auto* c : lowFilterArea.getChildren())
{
auto r = getLocalArea(&lowFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : lowMidFilterArea.getChildren())
{
auto r = getLocalArea(&lowMidFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : midFilterArea.getChildren())
{
auto r = getLocalArea(&midFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : highMidFilterArea.getChildren())
{
auto r = getLocalArea(&highMidFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : highFilterArea.getChildren())
{
auto r = getLocalArea(&highFilterArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
}
for (auto* c : footerBar.getChildren())
{
auto r = c->getBounds(); // <-- lokal zu headerBar
r = r.translated(footerBar.getX(), footerBar.getY()); // <-- in Editor-Koordinaten bringen!
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
for (auto* c : globalControlArea.getChildren())
{
auto r = getLocalArea(&globalControlArea, c->getBounds()); // lokal in presetArea
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
g.fillRect(r);
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
g.drawRect(r, 1.0f);
}
}
const auto mP = getLocalArea(&mainPanel, mainPanel.getLocalBounds());
auto mPX = mP.getX();
auto mPY = mP.getY();
auto mPW = mP.getWidth();
auto mPH = mP.getHeight();
paintAnalyzer(g);
g.setColour(Colours::SURFACEBYPASS);
const auto hB = getLocalArea(&headerBar, headerBar.getLocalBounds());
auto hBX = hB.getX();
auto hBY = hB.getY();
auto hBW = hB.getWidth();
auto hBH = hB.getHeight();
auto hBPad = ((hBY + mPY) - hBH) / 2;
g.fillRect(hBX, hBY, hBW, mPY - hBPad);
paintBorderLines(g);
g.setColour(Colours::SURFACECOLOUR);
const auto pA = getLocalArea(&presetArea, presetArea.getLocalBounds());
auto pAX = pA.getX();
auto pAY = pA.getY();
auto pAWidth = pA.getWidth();
auto pAHeight = pA.getHeight();
g.fillRoundedRectangle(pAX, pAY - 10.f, pAWidth, pAHeight + 10.f, 10.0f);
paintBorderLines(g);
g.setColour(Colours::BACKGROUNDBYPASS);
const auto fA = getLocalArea(&filterArea, filterArea.getLocalBounds());
g.fillRect(fA);
paintBorderLines(g);
}
//region paintBorderLines
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;
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);
}
}
//endregion paintBorderLines
//region setKnobVisibility
void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
int lowMode = (int) audioProcessor.apvts
.getRawParameterValue("LowBandModes")->load(); // 0..3
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);
lowBandGainSlider.setEnabled(lowMode >= 2);
lowBandGainLabel .setEnabled(lowMode >= 2);
lowBandQSlider .setEnabled(lowMode >= 1);
lowBandQLabel .setEnabled(lowMode >= 1);
int highMode = (int) audioProcessor.apvts
.getRawParameterValue("HighBandModes")->load(); // 0..3
if (masterIsToggled) {
highMode = 0;
disableHighBand(target);
}
highBandFreqSlider.setEnabled (highMode >= 1);
highBandFreqLabel.setEnabled (highMode >= 1);
highBandSlopeSlider.setEnabled (highMode == 1);
highBandSlopeLabel.setEnabled (highMode == 1);
highBandGainSlider.setEnabled(highMode >= 2);
highBandGainLabel .setEnabled(highMode >= 2);
highBandQSlider .setEnabled(highMode >= 1);
highBandQLabel .setEnabled(highMode >= 1);
}
//endregion setKnobVisibility
//region timerCallback
void CrystalizerEQAudioProcessorEditor::timerCallback()
{
setKnobVisibility();
spectrumAnalyzer.processSamples();
repaint(analyzerRect);
resized();
}
//endregion timerCallback
void CrystalizerEQAudioProcessorEditor::resized()
{
auto pluginArea = getLocalBounds();
scalePluginWindow(pluginArea);
setupMainGrid(pluginArea);
setupHeader();
setupBody();
setupFooter();
//setSliderSizes();
const auto testBounds = mainPanel.getLocalBounds();
const auto testWidth = testBounds.getWidth();
const auto testHeight = testBounds.getHeight();
testNoiseButton.setBounds(50, testHeight / 2, testWidth / 8, testHeight / 8);
}
//region resetAllCheckboxes
void CrystalizerEQAudioProcessorEditor::resetAllCheckboxes() {
const auto notify = juce::dontSendNotification;
masterBypassButton.setToggleState(false, notify);
crystalizeButton.setToggleState(false, notify);
peak1BypassButton.setToggleState(false, notify);
peak2BypassButton.setToggleState(false, notify);
peak3BypassButton.setToggleState(false, notify);
}
//endregion resetAllCheckboxes
//region scalePluginWindow
void CrystalizerEQAudioProcessorEditor::scalePluginWindow(juce::Rectangle<int> area) {
if (area.getWidth() < 520) Spacing::setUiScale(Spacing::uiScaleMode::XS);
else if (area.getWidth() < 720) Spacing::setUiScale(Spacing::uiScaleMode::S);
else if (area.getWidth() < 1024) Spacing::setUiScale(Spacing::uiScaleMode::M);
else if (area.getWidth() < 1366) Spacing::setUiScale(Spacing::uiScaleMode::L);
else Spacing::setUiScale(Spacing::uiScaleMode::XL);
}
//endregion scalePluginWindow
//region setupMainGrid
void CrystalizerEQAudioProcessorEditor::setupMainGrid(juce::Rectangle<int> area) {
const float headerHeight = static_cast<float>(getHeight()) * 0.1f;
const float footerHeight = static_cast<float>(getHeight()) * 0.1f;
Layout::GridSpec spec {
/* cols */ { Layout::fr(1) }, // eine Spalte, füllt Breite
/* rows */ { Layout::pxTrack(headerHeight), Layout::fr(1), Layout::pxTrack(footerHeight) }, // Header / Body / Footer
/* colGap */ Spacing::SizeMode::S,
/* rowGap */ Spacing::SizeMode::S,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(area, spec, { &headerBar, &mainPanel, &footerBar });
}
//endregion setupMainGrid
//region setupHeader
void CrystalizerEQAudioProcessorEditor::setupHeader() {
const auto bounds = headerBar.getLocalBounds();
const float presetAreaWidth = static_cast<float>(bounds.getWidth()) * 0.5f;
Layout::GridSpec headerSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(presetAreaWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1) },
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, headerSpec, {
Layout::area(titleLabel, 1, 1, 2, 2),
Layout::area(presetArea, 1, 2, 2, 3),
Layout::area(presetMenu, 1, 3, 2, 4),
});
const auto presetMenuBounds = presetMenu.getLocalBounds();
Layout::GridSpec presetMenuSpec{
/* cols */ { Layout::fr(1), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::fr(1) },
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(presetMenuBounds, presetMenuSpec, {
Layout::area(presetNameInput, 1, 1, 2, 3),
Layout::area(savePresetButton, 2, 1, 3, 2),
Layout::area(deletePresetButton, 2, 2, 3, 3),
});
const auto presetAreaBounds = presetArea.getLocalBounds();
const auto presetBoxWidth = static_cast<float>(presetArea.getWidth());
const auto presetBoxHeight = static_cast<float>(presetArea.getHeight());
Layout::GridSpec presetSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(presetBoxWidth * 0.5f), Layout::fr(1)},
/* rows */ { Layout::pxTrack(presetBoxHeight * 0.25f), Layout::pxTrack(presetBoxHeight * 0.25f)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::S)
};
Layout::grid(presetAreaBounds, presetSpec, {
// Label über beide Spalten (row1, col1..2)
Layout::area(presetBoxLabel, 1, 2, 2, 3),
// Box unten links (row2, col1)
Layout::area(presetBox, 2, 2, 3, 3),
Layout::area(resetButton, 2, 1, 2, 2),
// Menütaste unten rechts (row2, col2)
Layout::area(presetMenuButton, 2, 3, 3, 4),
});
}
//endregion setupHeader
//region setupBody
void CrystalizerEQAudioProcessorEditor::setupBody() {
const auto bounds = mainPanel.getLocalBounds();
const auto bodyHeight = static_cast<float>(bounds.getHeight());
const auto bodyWidth = static_cast<float>(bounds.getWidth());
const auto bodyColWidth = bodyWidth / 5.0f;
Layout::GridSpec bodySpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(bodyColWidth), Layout::pxTrack(bodyColWidth), Layout::pxTrack(bodyColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1), Layout::fr(1) },
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, bodySpec, {
Layout::area(analyzerArea, 1, 2, 2, 5),
Layout::area(crystalizeButton, 1, 5, 2, 6),
Layout::area(filterArea, 2, 1, 3, 6)
});
const auto analyzerAreaBounds = analyzerArea.getLocalBounds();
const auto analyzerAreaWidth = static_cast<float>(analyzerArea.getWidth());
const auto analyzerAreaHeight = static_cast<float>(analyzerArea.getHeight());
const auto filterAreaBounds = filterArea.getLocalBounds();
const auto filterAreaWidth = static_cast<float>(filterArea.getWidth());
const auto filterAreaHeight = static_cast<float>(filterArea.getHeight());
const auto filterColWidth = filterAreaWidth * 0.2f;
Layout::GridSpec filterSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(filterColWidth), Layout::pxTrack(filterColWidth), Layout::pxTrack(filterColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::S)
};
Layout::grid(filterAreaBounds, filterSpec, {
Layout::area(lowFilterArea, 1, 1, 2, 2),
Layout::area(lowMidFilterArea, 1, 2, 2, 3),
Layout::area(midFilterArea, 1, 3, 2, 4),
Layout::area(highMidFilterArea, 1, 4, 2, 5),
Layout::area(highFilterArea, 1, 5, 2, 6),
});
setupLowBandLayout();
setupLowMidBandLayout();
setupMidBandLayout();
setupHighMidBandLayout();
setupHighBandLayout();
}
//endregion setupBody
//region setupLowBandLayout
void CrystalizerEQAudioProcessorEditor::setupLowBandLayout() {
const auto bounds = lowFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
const auto refW = getReferenceCell()[0];
const auto refH = getReferenceCell()[1];
const auto gainSize = refH * gainMod;
const auto offSetToGainTop = gainSize;
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, 3, 4, 4)
.withWidth(refW * slopeMod)
.withHeight(refH * slopeMod)
.withAlignSelf(juce::GridItem::AlignSelf::center)
.withJustifySelf(juce::GridItem::JustifySelf::center),
Layout::area(lowBandModeBox, 3, 1, 4, 2),
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, 3, 4, 4)
.withMargin(juce::GridItem::Margin(-offSetToGainTop / 2, 0, 0, 0)),
});
}
//endregion setupLowBandLayout
//region setupLowMidBandLayout
void CrystalizerEQAudioProcessorEditor::setupLowMidBandLayout() {
const auto bounds = lowMidFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
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)
});
}
//endregion setupMidBandLayout
//region setupHighMidBandLayout
void CrystalizerEQAudioProcessorEditor::setupHighMidBandLayout() {
const auto bounds = highMidFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
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)
});
}
//endregion setupHighMidBandLayout
//region setupHighBandLayout
void CrystalizerEQAudioProcessorEditor::setupHighBandLayout() {
const auto bounds = highFilterArea.getLocalBounds();
const auto areaWidth = static_cast<float>(bounds.getWidth());
const auto areaHeight = static_cast<float>(bounds.getHeight());
const auto knobColWidth = areaWidth / 3.0f;
const auto knobRowHeight = areaHeight / 3.0f;
const auto refW = getReferenceCell()[0];
const auto refH = getReferenceCell()[1];
const auto gainSize = refH * gainMod;
const auto offSetToGainTop = gainSize;
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, 3, 4, 4),
Layout::area(highBandSlopeSlider, 3, 1, 4, 2)
.withWidth(refW * slopeMod)
.withHeight(refH * slopeMod)
.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, 1, 4, 2)
.withMargin(juce::GridItem::Margin(-offSetToGainTop / 2, 0, 0, 0)),
});
}
//endregion setupHighBandLayout
//region setupFooter
void CrystalizerEQAudioProcessorEditor::setupFooter() {
const auto bounds = footerBar.getLocalBounds();
const auto footerWidth = static_cast<float>(bounds.getWidth());
const auto footerHeight = static_cast<float>(bounds.getHeight());
const auto refW = getReferenceCell()[0];
const auto refH = getReferenceCell()[1];
Layout::GridSpec footerSpec{
/* cols */ { Layout::fr(1), Layout::fr(1), Layout::pxTrack(footerWidth / 3.0f) },
/* rows */ { Layout::fr(1) },
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(bounds, footerSpec, {
Layout::area(globalControlArea, 1, 3, 2, 4)
});
const auto globalControlAreaBounds = globalControlArea.getLocalBounds();
const auto globalControlAreaWidth = static_cast<float>(globalControlArea.getWidth());
const auto globalControlAreaHeight = static_cast<float>(globalControlArea.getHeight());
const auto globalControlColWidth = globalControlAreaWidth / 3.0f;
Layout::GridSpec globalControlAreaSpec{
/* cols */ { Layout::fr(1), Layout::pxTrack(globalControlColWidth), Layout::fr(1) },
/* rows */ { Layout::fr(1)},
/* colGap */ Spacing::SizeMode::XS,
/* rowGap */ Spacing::SizeMode::XS,
/* pad */ Layout::padding(Spacing::SizeMode::XS)
};
Layout::grid(globalControlAreaBounds, globalControlAreaSpec, {
//TODO: Bring components closer together
Layout::area(inputSlider, 1, 2, 3, 3)
.withWidth(refW * globalMod * 0.8f)
.withHeight(refH * globalMod * 0.8f)
.withAlignSelf(juce::GridItem::AlignSelf::center)
.withJustifySelf(juce::GridItem::JustifySelf::center),
Layout::area(outputSlider, 1, 3, 3, 4)
.withWidth(refW * globalMod * 0.8f)
.withHeight(refH * globalMod * 0.8f)
.withAlignSelf(juce::GridItem::AlignSelf::center)
.withJustifySelf(juce::GridItem::JustifySelf::center),
Layout::area(masterBypassButton, 1, 1, 3, 2),
});
}
//endregion setupFooter
//region getReferenceCell
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;
}
//endregion getReferenceCell
//region processSamples
void SpectrumAnalyzer::processSamples() {
auto samples = audioFIFO.sendSamplesToEditor();
getFftFrame(samples);
}
//endregion processSamples
//region getFftFrame
void SpectrumAnalyzer::getFftFrame(juce::Array<float>& samples) {
const int needed = FFTSIZE - fftFrame.size();
if (needed <= 0)
return;
const int available = samples.size();
if (available <= 0)
return;
const int take = std::min(needed, available);
for (int i = 0; i < take; ++i)
fftFrame.add(samples.getUnchecked(i));
samples.removeRange(0, take);
if (fftFrame.size() == FFTSIZE)
{
std::vector<float> fullFrame(fftFrame.begin(), fftFrame.end());
fftFrame.removeRange(0, HOPSIZE);
applyWindowOnFftFrame(fullFrame);
}
}
//endregion getFftFrame
//region applyWindowOnFftFrame
void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector<float> &fullFrame) {
if (fullFrame.size()!= FFTSIZE) return;
window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE);
processWindowedFrame(fullFrame);
}
//endregion applyWindowOnFftFrame
//region processWindowedFrame
void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return;
fillFftDataFromFrame(windowedFrame);
fft.performRealOnlyForwardTransform(fftData.data());
buildMagnitudeSpectrum();
convertToDb();
applySmoothing();
renderValuesDb = getRenderValues();
}
//endregion processWindowedFrame
//region fillFftDataFromFrame
void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
for (int n = 0; n < FFTSIZE; ++n) {
fftData[2*n] = windowedFrame[n];
fftData[2*n + 1] = 0.0f;
}
}
//endregion fillFftDataFromFrame
//region buildMagnitudeSpectrum
void SpectrumAnalyzer::buildMagnitudeSpectrum() {
for (int k = 0; k < BINS; ++k) {
float re = 0.f;
float im = 0.f;
if (k < BINS / 2) {
re = fftData[k];
} else {
im = fftData[k];
}
float mag = sqrt(re * re + im * im);
mag /= (FFTSIZE * 0.5f);
mag = std::max(mag, 1e-12f);
magnitudes[k] = mag;
}
}
//endregion buildMagnitudeSpectrum
//region convertToDb
void SpectrumAnalyzer::convertToDb() {
for (int k = 0; k < magnitudes.size(); ++k) {
float mag = magnitudes[k];
float dB = juce::Decibels::gainToDecibels(mag);
dB = juce::jlimit(MINDB, MAXDB, dB);
magnitudesDb[k] = dB;
}
}
//endregion convertToDb
//region applySmoothing
void SpectrumAnalyzer::applySmoothing() {
applyEMA();
applyFreqSmoothing();
applyPeakHoldAndFalloff();
}
//endregion applySmoothing
//region applyEMA
void SpectrumAnalyzer::applyEMA() {
for (int k = 0; k < magnitudesDb.size(); ++k) {
float smoothedVal = smoothingFactor * magnitudesDb[k] + (1 - smoothingFactor) * emaSmoothedMagnitudesDb[k];
emaSmoothedMagnitudesDb[k] = smoothedVal;
}
}
//endregion applyEMA
//region applyFreqSmoothing
void SpectrumAnalyzer::applyFreqSmoothing() {
for (int k = 0; k < BINS; ++k) {
double freq = k * deltaF;
double lowestFreq = freq * pow(2.0, -OCTAVERADIUS);
double highestFreq = freq * pow(2.0, OCTAVERADIUS);
int lowestBin = std::max(0, static_cast<int>(floor(lowestFreq / deltaF)));
int highestBin = std::min(BINS - 1, static_cast<int>(ceil(highestFreq / deltaF)));
if (lowestBin > highestBin) {
lowestBin = k;
highestBin = k;
};
if (k == 0) {lowestBin = 0; highestBin = std::min(1, BINS - 1);}
float sum = 0.f;
for (int bin = lowestBin; bin <= highestBin; ++bin) {
sum += emaSmoothedMagnitudesDb[bin];
}
float avg = sum / static_cast<float>(highestBin - lowestBin + 1);
freqSmoothedMagnitudesDb[k] = avg;
}
}
//endregion applyFreqSmoothing
//region applyPeakHoldAndFalloff
void SpectrumAnalyzer::applyPeakHoldAndFalloff() {
std::vector<float> prevPeak = peakHoldMagnitudesDb;
for (int k = 0; k < BINS; ++k) {
float current = freqSmoothedMagnitudesDb[k];
float prev = prevPeak[k];
peakHoldMagnitudesDb[k] = std::max(current, prev - FALLOFFRATE * DELTAT);
}
}
//endregion applyPeakHoldAndFalloff
//region getRenderValues
std::vector<float> SpectrumAnalyzer::getRenderValues() {
std::vector<float> renderValues = peakHoldMagnitudesDb;
return renderValues;
}
//endregion getRenderValues