1287 lines
47 KiB
C++
1287 lines
47 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;
|
||
|
||
//region setupModeBoxes
|
||
void CrystalizerEQAudioProcessorEditor::setupModeBoxes() {
|
||
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
|
||
audioProcessor.apvts.getParameter("LowBandModes")))
|
||
{
|
||
lowBandModeBox.addItemList (choice->choices, 1);
|
||
|
||
lowBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification);
|
||
|
||
lowBandModeAttach = std::make_unique<ComboBoxAttach>(
|
||
audioProcessor.apvts, "LowBandModes", lowBandModeBox);
|
||
}
|
||
|
||
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
|
||
audioProcessor.apvts.getParameter("HighBandModes")))
|
||
{
|
||
highBandModeBox.addItemList (choice->choices, 1);
|
||
|
||
highBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification);
|
||
|
||
highBandModeAttach = std::make_unique<ComboBoxAttach>(
|
||
audioProcessor.apvts, "HighBandModes", highBandModeBox);
|
||
}
|
||
}
|
||
//endregion setupModeBoxes
|
||
|
||
//region setupSliders
|
||
void CrystalizerEQAudioProcessorEditor::setupSliders() {
|
||
auto style = juce::Slider::RotaryHorizontalVerticalDrag;
|
||
|
||
for (auto* s : {
|
||
&testNoiseSlider, &lowBandFreqSlider, &lowBandSlopeSlider, &lowBandGainSlider, &lowBandQSlider,
|
||
|
||
&peak1FreqSlider, &peak1GainSlider, &peak1QSlider,
|
||
&peak2FreqSlider, &peak2GainSlider, &peak2QSlider,
|
||
&peak3FreqSlider, &peak3GainSlider, &peak3QSlider,
|
||
|
||
&highBandFreqSlider, &highBandSlopeSlider, &highBandGainSlider, &highBandQSlider,
|
||
|
||
&inputSlider, &outputSlider })
|
||
{
|
||
s->setSliderStyle (style);
|
||
s->setTextBoxStyle (juce::Slider::TextBoxBelow, true, 70, 18);
|
||
s->setColour (juce::Slider::textBoxTextColourId, juce::Colours::black);
|
||
|
||
}
|
||
}
|
||
//endregion setupSliders
|
||
|
||
//region setupAttachments
|
||
void CrystalizerEQAudioProcessorEditor::setupAttachments() {
|
||
testNoiseAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "TestNoiseLevel", testNoiseSlider);
|
||
|
||
lowBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandFreq", lowBandFreqSlider);
|
||
lowBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandSlope", lowBandSlopeSlider);
|
||
lowBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandGain", lowBandGainSlider);
|
||
lowBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandQ", lowBandQSlider);
|
||
lowBandModeAttach = std::make_unique<ComboBoxAttach>(audioProcessor.apvts, "LowBandModes", lowBandModeBox);
|
||
|
||
peak1FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Freq", peak1FreqSlider);
|
||
peak1GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Gain", peak1GainSlider);
|
||
peak1QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Q", peak1QSlider);
|
||
|
||
peak2FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Freq", peak2FreqSlider);
|
||
peak2GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Gain", peak2GainSlider);
|
||
peak2QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Q", peak2QSlider);
|
||
|
||
peak3FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Freq", peak3FreqSlider);
|
||
peak3GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Gain", peak3GainSlider);
|
||
peak3QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Q", peak3QSlider);
|
||
|
||
highBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandFreq", highBandFreqSlider);
|
||
highBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandSlope", highBandSlopeSlider);
|
||
highBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandGain", highBandGainSlider);
|
||
highBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandQ", highBandQSlider);
|
||
highBandModeAttach = std::make_unique<ComboBoxAttach>(audioProcessor.apvts, "HighBandModes", highBandModeBox);
|
||
|
||
inputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "InputGain", inputSlider);
|
||
outputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "OutputGain", outputSlider);
|
||
}
|
||
//endregion setupAttachments
|
||
|
||
//region setupDisplayNames
|
||
void CrystalizerEQAudioProcessorEditor::setupDisplayNames() {
|
||
titleLabel.setText("Crystalizer", juce::dontSendNotification);
|
||
|
||
testNoiseSlider.setName("TestNoise");
|
||
|
||
lowBandFreqSlider.setName ("LowBand Freq");
|
||
lowBandSlopeSlider.setName ("LowBand Slope");
|
||
lowBandGainSlider.setName ("LowBand Gain");
|
||
lowBandQSlider.setName ("LowBand Q");
|
||
|
||
peak1FreqSlider.setName("Peak1 Freq");
|
||
peak1GainSlider.setName("Peak1 Gain");
|
||
peak1QSlider.setName ("Peak1 Q");
|
||
|
||
peak2FreqSlider.setName("Peak2 Freq");
|
||
peak2GainSlider.setName("Peak2 Gain");
|
||
peak2QSlider.setName ("Peak2 Q");
|
||
|
||
peak3FreqSlider.setName("Peak3 Freq");
|
||
peak3GainSlider.setName("Peak3 Gain");
|
||
peak3QSlider.setName ("Peak3 Q");
|
||
|
||
highBandFreqSlider.setName ("HighBand Freq");
|
||
highBandSlopeSlider.setName ("HighBand Slope");
|
||
highBandGainSlider.setName ("HighBand Gain");
|
||
highBandQSlider.setName ("HighBand Q");
|
||
|
||
inputSlider.setName ("Input");
|
||
outputSlider.setName ("Output");
|
||
|
||
testNoiseSlider.setTextValueSuffix(" Gain");
|
||
|
||
lowBandFreqSlider.setTextValueSuffix (" Hz");
|
||
lowBandSlopeSlider.setTextValueSuffix (" dB/Oct");
|
||
lowBandGainSlider.setTextValueSuffix(" Gain");
|
||
lowBandQSlider.setTextValueSuffix (" Q");
|
||
|
||
peak1FreqSlider.setTextValueSuffix(" Hz");
|
||
peak1GainSlider.setTextValueSuffix(" dB");
|
||
peak1QSlider.setTextValueSuffix(" Q");
|
||
peak1BypassButton.setName("peak1Bypass");
|
||
peak1BypassButton.setButtonText("Low-Mid Bypass");
|
||
|
||
peak2FreqSlider.setTextValueSuffix(" Hz");
|
||
peak2GainSlider.setTextValueSuffix(" dB");
|
||
peak2QSlider.setTextValueSuffix(" Q");
|
||
peak2BypassButton.setName("peak2Bypass");
|
||
peak2BypassButton.setButtonText("Mid Bypass");
|
||
|
||
peak3FreqSlider.setTextValueSuffix(" Hz");
|
||
peak3GainSlider.setTextValueSuffix(" dB");
|
||
peak3QSlider.setTextValueSuffix(" Q");
|
||
peak3BypassButton.setName("peak3Bypass");
|
||
peak3BypassButton.setButtonText("High-Mid Bypass");
|
||
|
||
highBandFreqSlider.setTextValueSuffix (" Hz");
|
||
highBandSlopeSlider.setTextValueSuffix (" dB/Oct");
|
||
highBandGainSlider.setTextValueSuffix(" Gain");
|
||
highBandQSlider.setTextValueSuffix (" Q");
|
||
|
||
inputSlider.setTextValueSuffix(" IN");
|
||
outputSlider.setTextValueSuffix(" OUT");
|
||
|
||
testNoiseButton.setName("TestNoise");
|
||
testNoiseButton.setButtonText("Test Noise");
|
||
|
||
crystalizeButton.setName("CrystalizeButton");
|
||
crystalizeButton.setButtonText("Crystalize");
|
||
|
||
resetButton.setName("ResetButton");
|
||
resetButton.setButtonText("Reset");
|
||
|
||
masterBypassButton.setName("MasterBypass");
|
||
masterBypassButton.setButtonText("Master Bypass");
|
||
|
||
savePresetButton.setName("SavePresetButton");
|
||
savePresetButton.setButtonText("Save Preset");
|
||
|
||
deletePresetButton.setName("DeletePresetButton");
|
||
deletePresetButton.setButtonText("Delete Preset");
|
||
}
|
||
//endregion setupDisplayNames
|
||
|
||
//region setupToggleButtons
|
||
void CrystalizerEQAudioProcessorEditor::setupToggleButtons() {
|
||
for (auto* b : {&peak1BypassButton, &peak2BypassButton, &peak3BypassButton, &crystalizeButton, &masterBypassButton}) {
|
||
b->onClick = [this, b]() {
|
||
juce::String paramID; // nimm juce::String statt std::string
|
||
|
||
if (b == &peak1BypassButton) paramID = "Peak1Bypass";
|
||
else if (b == &peak2BypassButton) paramID = "Peak2Bypass";
|
||
else if (b == &peak3BypassButton) paramID = "Peak3Bypass";
|
||
else if (b == &crystalizeButton) paramID = "CrystalizeButton";
|
||
else if (b == &masterBypassButton) paramID = "MasterBypass";
|
||
|
||
else {return; }
|
||
|
||
b->setColour(juce::ToggleButton::textColourId, juce::Colours::black);
|
||
b->setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
|
||
|
||
if (auto* param = audioProcessor.apvts.getParameter(paramID))
|
||
{
|
||
const bool isToggled = b->getToggleState();
|
||
|
||
const float target = isToggled ? 1.0f : 0.0f;
|
||
|
||
// Nur senden, wenn sich wirklich etwas ändert (Schwelle 0.5f)
|
||
if ((param->getValue() >= 0.5f) != isToggled)
|
||
{
|
||
param->beginChangeGesture();
|
||
param->setValueNotifyingHost(target);
|
||
param->endChangeGesture();
|
||
}
|
||
}
|
||
};
|
||
}
|
||
}
|
||
//endregion setupToggleButtons
|
||
|
||
//region addComponentsToLayout
|
||
void CrystalizerEQAudioProcessorEditor::addComponentsToLayout() {
|
||
addAndMakeVisible(headerBar);
|
||
headerBar.addAndMakeVisible(titleLabel);
|
||
|
||
headerBar.addAndMakeVisible(presetArea);
|
||
presetArea.addAndMakeVisible(presetBoxLabel);
|
||
presetArea.addAndMakeVisible(resetButton);
|
||
presetArea.addAndMakeVisible(presetBox);
|
||
presetArea.addAndMakeVisible(presetMenuButton);
|
||
|
||
headerBar.addAndMakeVisible(presetMenu);
|
||
presetMenu.addAndMakeVisible(presetNameInput);
|
||
presetMenu.addAndMakeVisible(savePresetButton);
|
||
presetMenu.addAndMakeVisible(deletePresetButton);
|
||
presetMenu.toFront(true);
|
||
presetMenu.setVisible(false);
|
||
|
||
|
||
addAndMakeVisible(mainPanel);
|
||
mainPanel.addAndMakeVisible(analyzerArea);
|
||
mainPanel.addAndMakeVisible(crystalizeButton);
|
||
|
||
//FILTERAREA
|
||
{
|
||
mainPanel.addAndMakeVisible(filterArea);
|
||
|
||
{
|
||
filterArea.addAndMakeVisible(lowFilterArea);
|
||
lowFilterArea.addAndMakeVisible(lowBandModeBox);
|
||
lowFilterArea.addAndMakeVisible(lowBandSlopeLabel);
|
||
lowFilterArea.addAndMakeVisible(lowBandGainLabel);
|
||
lowFilterArea.addAndMakeVisible(lowBandQLabel);
|
||
lowFilterArea.addAndMakeVisible(lowBandFreqLabel);
|
||
lowFilterArea.addAndMakeVisible(lowBandSlopeSlider);
|
||
lowFilterArea.addAndMakeVisible(lowBandGainSlider);
|
||
lowFilterArea.addAndMakeVisible(lowBandQSlider);
|
||
lowFilterArea.addAndMakeVisible(lowBandFreqSlider);
|
||
}
|
||
|
||
{
|
||
filterArea.addAndMakeVisible(lowMidFilterArea);
|
||
lowMidFilterArea.addAndMakeVisible(peak1FreqLabel);
|
||
lowMidFilterArea.addAndMakeVisible(peak1GainLabel);
|
||
lowMidFilterArea.addAndMakeVisible(peak1QLabel);
|
||
lowMidFilterArea.addAndMakeVisible(peak1FreqSlider);
|
||
lowMidFilterArea.addAndMakeVisible(peak1GainSlider);
|
||
lowMidFilterArea.addAndMakeVisible(peak1QSlider);
|
||
lowMidFilterArea.addAndMakeVisible(peak1BypassButton);
|
||
}
|
||
|
||
{
|
||
filterArea.addAndMakeVisible(midFilterArea);
|
||
midFilterArea.addAndMakeVisible(peak2FreqLabel);
|
||
midFilterArea.addAndMakeVisible(peak2GainLabel);
|
||
midFilterArea.addAndMakeVisible(peak2QLabel);
|
||
midFilterArea.addAndMakeVisible(peak2FreqSlider);
|
||
midFilterArea.addAndMakeVisible(peak2GainSlider);
|
||
midFilterArea.addAndMakeVisible(peak2QSlider);
|
||
midFilterArea.addAndMakeVisible(peak2BypassButton);
|
||
}
|
||
|
||
{
|
||
filterArea.addAndMakeVisible(highMidFilterArea);
|
||
highMidFilterArea.addAndMakeVisible(peak3FreqLabel);
|
||
highMidFilterArea.addAndMakeVisible(peak3GainLabel);
|
||
highMidFilterArea.addAndMakeVisible(peak3QLabel);
|
||
highMidFilterArea.addAndMakeVisible(peak3FreqSlider);
|
||
highMidFilterArea.addAndMakeVisible(peak3GainSlider);
|
||
highMidFilterArea.addAndMakeVisible(peak3QSlider);
|
||
highMidFilterArea.addAndMakeVisible(peak3BypassButton);
|
||
}
|
||
|
||
{
|
||
filterArea.addAndMakeVisible(highFilterArea);
|
||
highFilterArea.addAndMakeVisible(highBandModeBox);
|
||
highFilterArea.addAndMakeVisible(highBandSlopeLabel);
|
||
highFilterArea.addAndMakeVisible(highBandGainLabel);
|
||
highFilterArea.addAndMakeVisible(highBandQLabel);
|
||
highFilterArea.addAndMakeVisible(highBandFreqLabel);
|
||
highFilterArea.addAndMakeVisible(highBandSlopeSlider);
|
||
highFilterArea.addAndMakeVisible(highBandGainSlider);
|
||
highFilterArea.addAndMakeVisible(highBandQSlider);
|
||
highFilterArea.addAndMakeVisible(highBandFreqSlider);
|
||
}
|
||
|
||
}
|
||
|
||
addAndMakeVisible(footerBar);
|
||
footerBar.addAndMakeVisible(globalControlArea);
|
||
globalControlArea.addAndMakeVisible(inputLabel);
|
||
globalControlArea.addAndMakeVisible(outputLabel);
|
||
globalControlArea.addAndMakeVisible(inputSlider);
|
||
globalControlArea.addAndMakeVisible(outputSlider);
|
||
globalControlArea.addAndMakeVisible(masterBypassButton);
|
||
}
|
||
//endregion addComponentsToLayout
|
||
|
||
//region setupLabels
|
||
void CrystalizerEQAudioProcessorEditor::setupLabels() {
|
||
auto setupLabel = [] (juce::Label& label, const juce::String& text)
|
||
{
|
||
label.setText (text, juce::dontSendNotification);
|
||
label.setJustificationType (juce::Justification::centred);
|
||
label.setColour (juce::Label::textColourId, juce::Colours::black);
|
||
label.setInterceptsMouseClicks (false, false); // Klicks gehen an den Slider
|
||
};
|
||
|
||
//SetupLabels
|
||
{
|
||
//LOW-BAND
|
||
setupLabel (lowBandFreqLabel, "LowBand Freq");
|
||
setupLabel (lowBandSlopeLabel, "LowBand Slope");
|
||
setupLabel (lowBandGainLabel, "LowBand Gain");
|
||
setupLabel(lowBandQLabel, "LowBand Q");
|
||
|
||
//PEAK 1
|
||
setupLabel (peak1FreqLabel,"Low-Mid Freq");
|
||
setupLabel (peak1GainLabel,"Low-Mid Gain");
|
||
setupLabel (peak1QLabel, "Low-Mid Q");
|
||
|
||
//PEAK 2
|
||
setupLabel (peak2FreqLabel,"Mid Freq");
|
||
setupLabel (peak2GainLabel,"Mid Gain");
|
||
setupLabel (peak2QLabel, "Mid Q");
|
||
|
||
//PEAK 3
|
||
setupLabel (peak3FreqLabel,"High-Mid Freq");
|
||
setupLabel (peak3GainLabel,"High-Mid Gain");
|
||
setupLabel (peak3QLabel, "High-Mid Q");
|
||
|
||
//HIGH-BAND
|
||
setupLabel (highBandFreqLabel, "HighBand Freq");
|
||
setupLabel (highBandSlopeLabel, "HighBand Slope");
|
||
setupLabel (highBandGainLabel, "HighBand Gain");
|
||
setupLabel(highBandQLabel, "HighBand Q");
|
||
|
||
setupLabel(presetBoxLabel, "Presets");
|
||
|
||
setupLabel(inputLabel, "Input");
|
||
setupLabel(outputLabel, "Output");
|
||
}
|
||
}
|
||
//endregion setupLabels
|
||
|
||
//region setupEventListeners
|
||
void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
|
||
presetMenuButton.onClick = [this]() {
|
||
bool menuOpened = presetMenuButton.getToggleState();
|
||
presetMenu.setVisible(menuOpened);
|
||
};
|
||
|
||
savePresetButton.onClick = [this]() {
|
||
if (presetNameInput.getText() != "")
|
||
audioProcessor.setPresetName (presetNameInput.getText());
|
||
else
|
||
audioProcessor.setPresetName ("Preset");
|
||
|
||
const auto presetName = audioProcessor.savePresetToFile();
|
||
|
||
|
||
presetBox.clear();
|
||
|
||
const auto updatedPresets = audioProcessor.getPresetNamesArray();
|
||
presetBox.addItemList(updatedPresets, 1);
|
||
|
||
for (int i = 0; i < presetBox.getNumItems(); i++)
|
||
{
|
||
if (presetName != presetBox.getItemText(i)) continue;
|
||
presetBox.setSelectedId(i + 1, juce::dontSendNotification);
|
||
}
|
||
|
||
presetNameInput.setText("");
|
||
|
||
|
||
};
|
||
|
||
deletePresetButton.onClick = [this]() {
|
||
auto selectedPreset = presetBox.getText();
|
||
if (selectedPreset == "Init") return;
|
||
const auto selectedPresetWithExt = selectedPreset + ".xml";
|
||
audioProcessor.deletePreset(selectedPresetWithExt);
|
||
audioProcessor.resetAllParameters();
|
||
|
||
presetBox.clear();
|
||
const auto updatedPresets = audioProcessor.getPresetNamesArray();
|
||
presetBox.addItemList(updatedPresets, 1);
|
||
presetBox.setSelectedId(1, juce::dontSendNotification);
|
||
|
||
};
|
||
|
||
presetBox.onChange = [this]() {
|
||
auto selectedPreset = presetBox.getText();
|
||
if (selectedPreset == "Init") {
|
||
audioProcessor.resetAllParameters();
|
||
return;
|
||
}
|
||
const auto selectedPresetWithExt = selectedPreset + ".xml";
|
||
audioProcessor.loadPreset(selectedPresetWithExt);
|
||
};
|
||
|
||
resetButton.onClick = [this]()
|
||
{
|
||
audioProcessor.resetAllParameters();
|
||
resetAllCheckboxes();
|
||
presetBox.setSelectedId(1, juce::dontSendNotification);
|
||
};
|
||
}
|
||
//endregion setupEventListeners
|
||
|
||
//region initPresetSystem
|
||
void CrystalizerEQAudioProcessorEditor::initPresetSystem() {
|
||
auto presets = audioProcessor.getPresetNamesArray();
|
||
presetBox.addItemList(presets, 1);
|
||
presetBox.setSelectedId(1, juce::dontSendNotification);
|
||
|
||
presetMenuButton.setName("PresetMenuButton");
|
||
presetMenuButton.setButtonText("Preset Menu");
|
||
presetMenuButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black);
|
||
presetMenuButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
|
||
presetMenuButton.setToggleState(false, juce::dontSendNotification);
|
||
|
||
presetNameInput.setTextToShowWhenEmpty("Preset Name", juce::Colours::grey);
|
||
presetNameInput.setJustification(juce::Justification::centred);
|
||
presetNameInput.setColour(juce::TextEditor::backgroundColourId, juce::Colours::black);
|
||
presetNameInput.setColour(juce::TextEditor::textColourId, juce::Colours::white);
|
||
}
|
||
//endregion initPresetSystem
|
||
|
||
//==============================================================================
|
||
CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor (CrystalizerEQAudioProcessor& p)
|
||
: AudioProcessorEditor (&p), audioProcessor (p), spectrumAnalyzer(p.audioFIFO, p) {
|
||
|
||
setSize (1280, 720);
|
||
startTimerHz(60);
|
||
addComponentsToLayout();
|
||
setupLabels();
|
||
setupModeBoxes();
|
||
setupSliders();
|
||
setupAttachments();
|
||
setupDisplayNames();
|
||
setupToggleButtons();
|
||
setupEventListeners();
|
||
initPresetSystem();
|
||
|
||
|
||
addAndMakeVisible (testNoiseButton);
|
||
|
||
testNoiseButton.onClick = [this]() {
|
||
if (auto* param = audioProcessor.apvts.getParameter("TestNoiseEnabled"))
|
||
{
|
||
if (param->getValue() == false) {
|
||
param->beginChangeGesture();
|
||
|
||
param->setValueNotifyingHost (1.0f);
|
||
|
||
param->endChangeGesture();
|
||
} else {
|
||
param->beginChangeGesture();
|
||
|
||
param->setValueNotifyingHost (0.0f); // -> true
|
||
|
||
param->endChangeGesture();
|
||
}
|
||
}
|
||
};
|
||
|
||
}
|
||
|
||
CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() {
|
||
stopTimer();
|
||
};
|
||
|
||
//region paintAnalyzer
|
||
void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
|
||
analyzerRect = getLocalArea(&analyzerArea, analyzerArea.getLocalBounds());
|
||
juce::Graphics::ScopedSaveState guard(g);
|
||
g.reduceClipRegion(analyzerRect);
|
||
|
||
auto r = analyzerRect.toFloat();
|
||
|
||
// Hintergrund des Analyzer-Bereichs
|
||
g.setColour(juce::Colours::black);
|
||
g.fillRect(r);
|
||
|
||
// Grid
|
||
g.setColour(juce::Colours::darkgrey.withAlpha(0.6f));
|
||
for (float db = -120.f; db <= 6.f; db += 12.f)
|
||
{
|
||
const float y = juce::jmap(db,
|
||
spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB,
|
||
r.getBottom(), r.getY());
|
||
g.drawLine(r.getX(), y, r.getRight(), y, 1.f);
|
||
}
|
||
|
||
// Bars
|
||
if (!spectrumAnalyzer.renderValuesDb.empty())
|
||
{
|
||
const int n = (int) spectrumAnalyzer.renderValuesDb.size();
|
||
|
||
g.setColour(AXIOM::DesignSystem::Colours::ACCENTCOLOUR);
|
||
|
||
juce::Path eqCurve;
|
||
|
||
bool hasStart = false;
|
||
|
||
const float visibleFloor = spectrumAnalyzer.MINDB;
|
||
|
||
for (int k = 0; k < n; ++k)
|
||
{
|
||
|
||
const float dB = juce::jlimit(spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB,
|
||
spectrumAnalyzer.renderValuesDb[(size_t) k]);
|
||
float yTop = juce::jmap(dB,
|
||
spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB,
|
||
r.getBottom(), r.getY());
|
||
|
||
if (dB <= visibleFloor) {
|
||
yTop = r.getBottom();
|
||
DBG ("Test");
|
||
|
||
}
|
||
|
||
|
||
float freq = k * spectrumAnalyzer.deltaF; // Frequenz des aktuellen Bins
|
||
|
||
if (k == 0) {
|
||
freq = 1e-12;
|
||
}
|
||
|
||
// Frequenz auf log-X-Koordinate (0.0–1.0) mappen
|
||
const float normX = std::log(freq / spectrumAnalyzer.MINFREQ) / std::log(spectrumAnalyzer.MAXFREQ / spectrumAnalyzer.MINFREQ);
|
||
|
||
// ...und in Pixel umrechnen:
|
||
float x = r.getX() + normX * r.getWidth();
|
||
|
||
//TODO: ANSTATT FFTSIZE VERDOPPELN, NUR DIE LINIEN VERDOPPELN -> HIGHER RESOLUTION
|
||
|
||
if (!hasStart) { eqCurve.startNewSubPath(x, yTop); hasStart = true; }
|
||
else {
|
||
eqCurve.lineTo(x, yTop);
|
||
}
|
||
|
||
|
||
}
|
||
g.strokePath(eqCurve, juce::PathStrokeType(1.5f));
|
||
juce::Path area = eqCurve;
|
||
area.lineTo(r.getRight(), r.getBottom());
|
||
area.lineTo(r.getX(), r.getBottom());
|
||
area.closeSubPath();
|
||
|
||
g.setColour(AXIOM::DesignSystem::Colours::ACCENTCOLOUR.withAlpha(0.2f));;
|
||
g.fillPath(area);
|
||
}
|
||
|
||
// Rahmen (optional)
|
||
g.setColour(juce::Colours::grey);
|
||
g.drawRect(analyzerRect);
|
||
}
|
||
//endregion paintAnalyzer
|
||
|
||
//==============================================================================
|
||
void CrystalizerEQAudioProcessorEditor::paint (juce::Graphics& g)
|
||
{
|
||
|
||
g.fillAll (Colours::BACKGROUNDCOLOUR);
|
||
g.setColour (AXIOM::DesignSystem::Colours::FOREGROUNDCOLOUR);
|
||
|
||
if constexpr (false) // -> auf false setzen, wenn nicht gebraucht
|
||
{
|
||
for (auto* c : getChildren()) // headerBar, mainPanel, footerBar
|
||
{
|
||
auto r = c->getBounds();
|
||
g.setColour(juce::Colours::magenta.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f); // nur Rahmen, kein fillRect
|
||
g.setColour(juce::Colours::white);
|
||
g.drawText(c->getName(), r, juce::Justification::centred, false);
|
||
}
|
||
for (auto* c : headerBar.getChildren())
|
||
{
|
||
|
||
auto r = c->getBounds(); // <-- lokal zu headerBar
|
||
r = r.translated(headerBar.getX(), headerBar.getY()); // <-- in Editor-Koordinaten bringen!
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
for (auto* c : presetArea.getChildren())
|
||
{
|
||
auto r = c->getBounds(); // lokal in presetArea
|
||
r = r.translated(headerBar.getX() + presetArea.getX(),
|
||
headerBar.getY() + presetArea.getY()); // beide Offsets addieren
|
||
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
|
||
for (auto* c : mainPanel.getChildren())
|
||
{
|
||
|
||
auto r = c->getBounds(); // <-- lokal zu headerBar
|
||
r = r.translated(mainPanel.getX(), mainPanel.getY()); // <-- in Editor-Koordinaten bringen!
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
|
||
|
||
|
||
for (auto* c : filterArea.getChildren())
|
||
{
|
||
auto r = c->getBounds(); // lokal in presetArea
|
||
r = r.translated(mainPanel.getX() + filterArea.getX(),
|
||
mainPanel.getY() + filterArea.getY()); // beide Offsets addieren
|
||
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
{
|
||
for (auto* c : lowFilterArea.getChildren())
|
||
{
|
||
auto r = getLocalArea(&lowFilterArea, c->getBounds()); // lokal in presetArea
|
||
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
for (auto* c : lowMidFilterArea.getChildren())
|
||
{
|
||
auto r = getLocalArea(&lowMidFilterArea, c->getBounds()); // lokal in presetArea
|
||
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
for (auto* c : midFilterArea.getChildren())
|
||
{
|
||
auto r = getLocalArea(&midFilterArea, c->getBounds()); // lokal in presetArea
|
||
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
for (auto* c : highMidFilterArea.getChildren())
|
||
{
|
||
auto r = getLocalArea(&highMidFilterArea, c->getBounds()); // lokal in presetArea
|
||
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
for (auto* c : highFilterArea.getChildren())
|
||
{
|
||
auto r = getLocalArea(&highFilterArea, c->getBounds()); // lokal in presetArea
|
||
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
}
|
||
|
||
for (auto* c : footerBar.getChildren())
|
||
{
|
||
|
||
auto r = c->getBounds(); // <-- lokal zu headerBar
|
||
r = r.translated(footerBar.getX(), footerBar.getY()); // <-- in Editor-Koordinaten bringen!
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::cyan.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
|
||
for (auto* c : globalControlArea.getChildren())
|
||
{
|
||
auto r = getLocalArea(&globalControlArea, c->getBounds()); // lokal in presetArea
|
||
|
||
g.setColour(juce::Colours::yellow.withAlpha(0.3f));
|
||
g.fillRect(r);
|
||
g.setColour(juce::Colours::yellow.withAlpha(0.9f));
|
||
g.drawRect(r, 1.0f);
|
||
}
|
||
|
||
}
|
||
|
||
paintAnalyzer(g);
|
||
}
|
||
|
||
//region setKnobVisibility
|
||
void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
|
||
const int lowMode = (int) audioProcessor.apvts
|
||
.getRawParameterValue("LowBandModes")->load(); // 0..3
|
||
|
||
lowBandFreqSlider.setVisible (lowMode >= 1);
|
||
lowBandFreqLabel.setVisible (lowMode >= 1);
|
||
lowBandSlopeSlider.setVisible (lowMode == 1);
|
||
lowBandSlopeLabel.setVisible (lowMode == 1);
|
||
lowBandGainSlider.setVisible(lowMode >= 2);
|
||
lowBandGainLabel .setVisible(lowMode >= 2);
|
||
lowBandQSlider .setVisible(lowMode >= 1);
|
||
lowBandQLabel .setVisible(lowMode >= 1);
|
||
|
||
const int highMode = (int) audioProcessor.apvts
|
||
.getRawParameterValue("HighBandModes")->load(); // 0..3
|
||
|
||
highBandFreqSlider.setVisible (highMode >= 1);
|
||
highBandFreqLabel.setVisible (highMode >= 1);
|
||
highBandSlopeSlider.setVisible (highMode == 1);
|
||
highBandSlopeLabel.setVisible (highMode == 1);
|
||
highBandGainSlider.setVisible(highMode >= 2);
|
||
highBandGainLabel .setVisible(highMode >= 2);
|
||
highBandQSlider .setVisible(highMode >= 1);
|
||
highBandQLabel .setVisible(highMode >= 1);
|
||
}
|
||
//endregion setKnobVisibility
|
||
|
||
//region timerCallback
|
||
void CrystalizerEQAudioProcessorEditor::timerCallback()
|
||
{
|
||
setKnobVisibility();
|
||
spectrumAnalyzer.processSamples();
|
||
repaint(analyzerRect);
|
||
resized();
|
||
}
|
||
//endregion timerCallback
|
||
|
||
void CrystalizerEQAudioProcessorEditor::resized()
|
||
{
|
||
|
||
Typography::applyToLabel(titleLabel, Typography::Style::Display, 1.f);
|
||
titleLabel.setColour(juce::Label::textColourId, Colours::ACCENTCOLOUR);
|
||
|
||
auto pluginArea = getLocalBounds();
|
||
scalePluginWindow(pluginArea);
|
||
setupMainGrid(pluginArea);
|
||
setupHeader();
|
||
setupBody();
|
||
setupFooter();
|
||
|
||
const auto testBounds = mainPanel.getLocalBounds();
|
||
const auto testWidth = testBounds.getWidth();
|
||
const auto testHeight = testBounds.getHeight();
|
||
testNoiseButton.setBounds(50, testHeight / 2, testWidth / 8, testHeight / 8);
|
||
|
||
}
|
||
|
||
//region resetAllCheckboxes
|
||
void CrystalizerEQAudioProcessorEditor::resetAllCheckboxes() {
|
||
const auto notify = juce::dontSendNotification;
|
||
masterBypassButton.setToggleState(false, notify);
|
||
crystalizeButton.setToggleState(false, notify);
|
||
peak1BypassButton.setToggleState(false, notify);
|
||
peak2BypassButton.setToggleState(false, notify);
|
||
peak3BypassButton.setToggleState(false, notify);
|
||
}
|
||
//endregion resetAllCheckboxes
|
||
|
||
//region scalePluginWindow
|
||
void CrystalizerEQAudioProcessorEditor::scalePluginWindow(juce::Rectangle<int> area) {
|
||
|
||
if (area.getWidth() < 520) Spacing::setUiScale(Spacing::uiScaleMode::XS);
|
||
else if (area.getWidth() < 720) Spacing::setUiScale(Spacing::uiScaleMode::S);
|
||
else if (area.getWidth() < 1024) Spacing::setUiScale(Spacing::uiScaleMode::M);
|
||
else if (area.getWidth() < 1366) Spacing::setUiScale(Spacing::uiScaleMode::L);
|
||
else Spacing::setUiScale(Spacing::uiScaleMode::XL);
|
||
}
|
||
//endregion scalePluginWindow
|
||
|
||
//region setupMainGrid
|
||
void CrystalizerEQAudioProcessorEditor::setupMainGrid(juce::Rectangle<int> area) {
|
||
const float headerHeight = static_cast<float>(getHeight()) * 0.1f;
|
||
const float footerHeight = static_cast<float>(getHeight()) * 0.1f;
|
||
|
||
Layout::GridSpec spec {
|
||
/* cols */ { Layout::fr(1) }, // eine Spalte, füllt Breite
|
||
/* rows */ { Layout::pxTrack(headerHeight), Layout::fr(1), Layout::pxTrack(footerHeight) }, // Header / Body / Footer
|
||
/* colGap */ Spacing::SizeMode::S,
|
||
/* rowGap */ Spacing::SizeMode::S,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(area, spec, { &headerBar, &mainPanel, &footerBar });
|
||
|
||
}
|
||
//endregion setupMainGrid
|
||
|
||
//region setupHeader
|
||
void CrystalizerEQAudioProcessorEditor::setupHeader() {
|
||
|
||
const auto bounds = headerBar.getLocalBounds();
|
||
|
||
const float presetAreaWidth = static_cast<float>(bounds.getWidth()) * 0.5f;
|
||
|
||
Layout::GridSpec headerSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(presetAreaWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1) },
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(bounds, headerSpec, {
|
||
Layout::area(titleLabel, 1, 1, 2, 2),
|
||
Layout::area(presetArea, 1, 2, 2, 3),
|
||
Layout::area(presetMenu, 1, 3, 2, 4),
|
||
});
|
||
|
||
const auto presetMenuBounds = presetMenu.getLocalBounds();
|
||
|
||
Layout::GridSpec presetMenuSpec{
|
||
/* cols */ { Layout::fr(1), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1), Layout::fr(1) },
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(presetMenuBounds, presetMenuSpec, {
|
||
Layout::area(presetNameInput, 1, 1, 2, 3),
|
||
Layout::area(savePresetButton, 2, 1, 3, 2),
|
||
Layout::area(deletePresetButton, 2, 2, 3, 3),
|
||
});
|
||
|
||
const auto presetAreaBounds = presetArea.getLocalBounds();
|
||
const auto presetBoxWidth = static_cast<float>(presetArea.getWidth());
|
||
const auto presetBoxHeight = static_cast<float>(presetArea.getHeight());
|
||
|
||
|
||
Layout::GridSpec presetSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(presetBoxWidth * 0.5f), Layout::fr(1)},
|
||
/* rows */ { Layout::pxTrack(presetBoxHeight * 0.25f), Layout::pxTrack(presetBoxHeight * 0.25f)},
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::S)
|
||
};
|
||
Layout::grid(presetAreaBounds, presetSpec, {
|
||
// Label über beide Spalten (row1, col1..2)
|
||
Layout::area(presetBoxLabel, 1, 2, 2, 3),
|
||
// Box unten links (row2, col1)
|
||
Layout::area(presetBox, 2, 2, 3, 3),
|
||
Layout::area(resetButton, 2, 1, 2, 2),
|
||
// Menütaste unten rechts (row2, col2)
|
||
Layout::area(presetMenuButton, 2, 3, 3, 4),
|
||
|
||
});
|
||
|
||
|
||
}
|
||
//endregion setupHeader
|
||
|
||
//region setupBody
|
||
void CrystalizerEQAudioProcessorEditor::setupBody() {
|
||
const auto bounds = mainPanel.getLocalBounds();
|
||
|
||
const auto bodyHeight = static_cast<float>(bounds.getHeight());
|
||
const auto bodyWidth = static_cast<float>(bounds.getWidth());
|
||
const auto bodyColWidth = bodyWidth / 5.0f;
|
||
|
||
Layout::GridSpec bodySpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(bodyColWidth), Layout::pxTrack(bodyColWidth), Layout::pxTrack(bodyColWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1), Layout::fr(1) },
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(bounds, bodySpec, {
|
||
Layout::area(analyzerArea, 1, 2, 2, 5),
|
||
Layout::area(crystalizeButton, 1, 5, 2, 6),
|
||
Layout::area(filterArea, 2, 1, 3, 6)
|
||
});
|
||
|
||
const auto analyzerAreaBounds = analyzerArea.getLocalBounds();
|
||
const auto analyzerAreaWidth = static_cast<float>(analyzerArea.getWidth());
|
||
const auto analyzerAreaHeight = static_cast<float>(analyzerArea.getHeight());
|
||
|
||
const auto filterAreaBounds = filterArea.getLocalBounds();
|
||
const auto filterAreaWidth = static_cast<float>(filterArea.getWidth());
|
||
const auto filterAreaHeight = static_cast<float>(filterArea.getHeight());
|
||
const auto filterColWidth = filterAreaWidth * 0.2f;
|
||
|
||
|
||
|
||
Layout::GridSpec filterSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(filterColWidth), Layout::pxTrack(filterColWidth), Layout::pxTrack(filterColWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1)},
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::S)
|
||
};
|
||
Layout::grid(filterAreaBounds, filterSpec, {
|
||
Layout::area(lowFilterArea, 1, 1, 2, 2),
|
||
Layout::area(lowMidFilterArea, 1, 2, 2, 3),
|
||
Layout::area(midFilterArea, 1, 3, 2, 4),
|
||
Layout::area(highMidFilterArea, 1, 4, 2, 5),
|
||
Layout::area(highFilterArea, 1, 5, 2, 6),
|
||
|
||
});
|
||
|
||
setupLowBandLayout();
|
||
setupLowMidBandLayout();
|
||
setupMidBandLayout();
|
||
setupHighMidBandLayout();
|
||
setupHighBandLayout();
|
||
|
||
}
|
||
//endregion setupBody
|
||
|
||
//region setupLowBandLayout
|
||
void CrystalizerEQAudioProcessorEditor::setupLowBandLayout() {
|
||
const auto bounds = lowFilterArea.getLocalBounds();
|
||
const auto areaWidth = static_cast<float>(bounds.getWidth());
|
||
const auto areaHeight = static_cast<float>(bounds.getHeight());
|
||
const auto knobColWidth = areaWidth / 3.0f;
|
||
const auto knobRowHeight = areaHeight / 3.0f;
|
||
|
||
Layout::GridSpec lowBandSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(bounds, lowBandSpec, {
|
||
Layout::area(lowBandFreqLabel, 1, 1, 2, 2),
|
||
Layout::area(lowBandGainLabel, 1, 2, 2, 3),
|
||
Layout::area(lowBandQLabel, 1, 3, 2, 4),
|
||
Layout::area(lowBandFreqSlider, 2, 1, 3, 2),
|
||
Layout::area(lowBandGainSlider, 2, 2, 3, 3),
|
||
Layout::area(lowBandQSlider, 2, 3, 3, 4),
|
||
Layout::area(lowBandModeBox, 3, 1, 4, 2),
|
||
Layout::area(lowBandSlopeLabel, 3, 2, 4, 3),
|
||
Layout::area(lowBandSlopeSlider, 3, 3, 4, 4)
|
||
|
||
});
|
||
}
|
||
//endregion setupLowBandLayout
|
||
|
||
//region setupLowMidBandLayout
|
||
void CrystalizerEQAudioProcessorEditor::setupLowMidBandLayout() {
|
||
const auto bounds = lowMidFilterArea.getLocalBounds();
|
||
const auto areaWidth = static_cast<float>(bounds.getWidth());
|
||
const auto areaHeight = static_cast<float>(bounds.getHeight());
|
||
const auto knobColWidth = areaWidth / 3.0f;
|
||
const auto knobRowHeight = areaHeight / 3.0f;
|
||
|
||
Layout::GridSpec lowMidBandSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(bounds, lowMidBandSpec, {
|
||
Layout::area(peak1FreqLabel, 1, 1, 2, 2),
|
||
Layout::area(peak1GainLabel, 1, 2, 2, 3),
|
||
Layout::area(peak1QLabel, 1, 3, 2, 4),
|
||
Layout::area(peak1FreqSlider, 2, 1, 3, 2),
|
||
Layout::area(peak1GainSlider, 2, 2, 3, 3),
|
||
Layout::area(peak1QSlider, 2, 3, 3, 4),
|
||
Layout::area(peak1BypassButton, 3, 2, 4, 3)
|
||
});
|
||
}
|
||
//endregion setupLowMidBandLayout
|
||
|
||
//region setupMidBandLayout
|
||
void CrystalizerEQAudioProcessorEditor::setupMidBandLayout() {
|
||
const auto bounds = midFilterArea.getLocalBounds();
|
||
const auto areaWidth = static_cast<float>(bounds.getWidth());
|
||
const auto areaHeight = static_cast<float>(bounds.getHeight());
|
||
const auto knobColWidth = areaWidth / 3.0f;
|
||
const auto knobRowHeight = areaHeight / 3.0f;
|
||
|
||
Layout::GridSpec midBandSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(bounds, midBandSpec, {
|
||
Layout::area(peak2FreqLabel, 1, 1, 2, 2),
|
||
Layout::area(peak2GainLabel, 1, 2, 2, 3),
|
||
Layout::area(peak2QLabel, 1, 3, 2, 4),
|
||
Layout::area(peak2FreqSlider, 2, 1, 3, 2),
|
||
Layout::area(peak2GainSlider, 2, 2, 3, 3),
|
||
Layout::area(peak2QSlider, 2, 3, 3, 4),
|
||
Layout::area(peak2BypassButton, 3, 2, 4, 3)
|
||
});
|
||
}
|
||
//endregion setupMidBandLayout
|
||
|
||
//region setupHighMidBandLayout
|
||
void CrystalizerEQAudioProcessorEditor::setupHighMidBandLayout() {
|
||
const auto bounds = highMidFilterArea.getLocalBounds();
|
||
const auto areaWidth = static_cast<float>(bounds.getWidth());
|
||
const auto areaHeight = static_cast<float>(bounds.getHeight());
|
||
const auto knobColWidth = areaWidth / 3.0f;
|
||
const auto knobRowHeight = areaHeight / 3.0f;
|
||
|
||
Layout::GridSpec highMidBandSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(bounds, highMidBandSpec, {
|
||
Layout::area(peak3FreqLabel, 1, 1, 2, 2),
|
||
Layout::area(peak3GainLabel, 1, 2, 2, 3),
|
||
Layout::area(peak3QLabel, 1, 3, 2, 4),
|
||
Layout::area(peak3FreqSlider, 2, 1, 3, 2),
|
||
Layout::area(peak3GainSlider, 2, 2, 3, 3),
|
||
Layout::area(peak3QSlider, 2, 3, 3, 4),
|
||
Layout::area(peak3BypassButton, 3, 2, 4, 3)
|
||
});
|
||
}
|
||
//endregion setupHighMidBandLayout
|
||
|
||
//region setupHighBandLayout
|
||
void CrystalizerEQAudioProcessorEditor::setupHighBandLayout() {
|
||
const auto bounds = highFilterArea.getLocalBounds();
|
||
const auto areaWidth = static_cast<float>(bounds.getWidth());
|
||
const auto areaHeight = static_cast<float>(bounds.getHeight());
|
||
const auto knobColWidth = areaWidth / 3.0f;
|
||
const auto knobRowHeight = areaHeight / 3.0f;
|
||
|
||
Layout::GridSpec highBandSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(bounds, highBandSpec, {
|
||
Layout::area(highBandFreqLabel, 1, 1, 2, 2),
|
||
Layout::area(highBandGainLabel, 1, 2, 2, 3),
|
||
Layout::area(highBandQLabel, 1, 3, 2, 4),
|
||
Layout::area(highBandFreqSlider, 2, 1, 3, 2),
|
||
Layout::area(highBandGainSlider, 2, 2, 3, 3),
|
||
Layout::area(highBandQSlider, 2, 3, 3, 4),
|
||
Layout::area(highBandModeBox, 3, 1, 4, 2),
|
||
Layout::area(highBandSlopeLabel, 3, 2, 4, 3),
|
||
Layout::area(highBandSlopeSlider, 3, 3, 4, 4)
|
||
});
|
||
}
|
||
//endregion setupHighBandLayout
|
||
|
||
//region setupFooter
|
||
void CrystalizerEQAudioProcessorEditor::setupFooter() {
|
||
const auto bounds = footerBar.getLocalBounds();
|
||
|
||
const auto footerWidth = static_cast<float>(bounds.getWidth());
|
||
const auto footerHeight = static_cast<float>(bounds.getHeight());
|
||
|
||
Layout::GridSpec footerSpec{
|
||
/* cols */ { Layout::fr(1), Layout::fr(1), Layout::pxTrack(footerWidth / 3.0f) },
|
||
/* rows */ { Layout::fr(1) },
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(bounds, footerSpec, {
|
||
Layout::area(globalControlArea, 1, 3, 2, 4)
|
||
});
|
||
|
||
const auto globalControlAreaBounds = globalControlArea.getLocalBounds();
|
||
const auto globalControlAreaWidth = static_cast<float>(globalControlArea.getWidth());
|
||
const auto globalControlAreaHeight = static_cast<float>(globalControlArea.getHeight());
|
||
const auto globalControlColWidth = globalControlAreaWidth / 3.0f;
|
||
|
||
Layout::GridSpec globalControlAreaSpec{
|
||
/* cols */ { Layout::fr(1), Layout::pxTrack(globalControlColWidth), Layout::fr(1) },
|
||
/* rows */ { Layout::pxTrack(globalControlAreaHeight * 0.25f),Layout::fr(1)},
|
||
/* colGap */ Spacing::SizeMode::XS,
|
||
/* rowGap */ Spacing::SizeMode::XS,
|
||
/* pad */ Layout::padding(Spacing::SizeMode::XS)
|
||
};
|
||
Layout::grid(globalControlAreaBounds, globalControlAreaSpec, {
|
||
Layout::area(inputLabel, 1, 2, 2, 3),
|
||
Layout::area(outputLabel, 1, 3, 2, 4),
|
||
Layout::area(inputSlider, 2, 2, 3, 3),
|
||
Layout::area(outputSlider, 2, 3, 3, 4),
|
||
Layout::area(masterBypassButton, 2, 1, 3, 2),
|
||
});
|
||
|
||
}
|
||
//endregion setupFooter
|
||
|
||
|
||
//region processSamples
|
||
void SpectrumAnalyzer::processSamples() {
|
||
auto samples = audioFIFO.sendSamplesToEditor();
|
||
|
||
getFftFrame(samples);
|
||
}
|
||
//endregion processSamples
|
||
|
||
//region getFftFrame
|
||
void SpectrumAnalyzer::getFftFrame(juce::Array<float>& samples) {
|
||
|
||
const int needed = FFTSIZE - fftFrame.size();
|
||
if (needed <= 0)
|
||
return;
|
||
|
||
const int available = samples.size();
|
||
if (available <= 0)
|
||
return;
|
||
|
||
const int take = std::min(needed, available);
|
||
|
||
for (int i = 0; i < take; ++i)
|
||
fftFrame.add(samples.getUnchecked(i));
|
||
|
||
samples.removeRange(0, take);
|
||
|
||
if (fftFrame.size() == FFTSIZE)
|
||
{
|
||
std::vector<float> fullFrame(fftFrame.begin(), fftFrame.end());
|
||
fftFrame.removeRange(0, HOPSIZE);
|
||
applyWindowOnFftFrame(fullFrame);
|
||
}
|
||
}
|
||
//endregion getFftFrame
|
||
|
||
//region applyWindowOnFftFrame
|
||
void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector<float> &fullFrame) {
|
||
if (fullFrame.size()!= FFTSIZE) return;
|
||
window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE);
|
||
processWindowedFrame(fullFrame);
|
||
}
|
||
//endregion applyWindowOnFftFrame
|
||
|
||
//region processWindowedFrame
|
||
void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
|
||
if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return;
|
||
fillFftDataFromFrame(windowedFrame);
|
||
fft.performRealOnlyForwardTransform(fftData.data());
|
||
buildMagnitudeSpectrum();
|
||
convertToDb();
|
||
applySmoothing();
|
||
renderValuesDb = getRenderValues();
|
||
|
||
}
|
||
//endregion processWindowedFrame
|
||
|
||
//region fillFftDataFromFrame
|
||
void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
|
||
for (int n = 0; n < FFTSIZE; ++n) {
|
||
fftData[2*n] = windowedFrame[n];
|
||
fftData[2*n + 1] = 0.0f;
|
||
}
|
||
}
|
||
//endregion fillFftDataFromFrame
|
||
|
||
//region buildMagnitudeSpectrum
|
||
void SpectrumAnalyzer::buildMagnitudeSpectrum() {
|
||
for (int k = 0; k < BINS; ++k) {
|
||
float re = 0.f;
|
||
float im = 0.f;
|
||
if (k < BINS / 2) {
|
||
re = fftData[k];
|
||
} else {
|
||
im = fftData[k];
|
||
}
|
||
|
||
float mag = sqrt(re * re + im * im);
|
||
mag /= (FFTSIZE * 0.5f);
|
||
mag = std::max(mag, 1e-12f);
|
||
magnitudes[k] = mag;
|
||
}
|
||
}
|
||
//endregion buildMagnitudeSpectrum
|
||
|
||
//region convertToDb
|
||
void SpectrumAnalyzer::convertToDb() {
|
||
for (int k = 0; k < magnitudes.size(); ++k) {
|
||
float mag = magnitudes[k];
|
||
float dB = juce::Decibels::gainToDecibels(mag);
|
||
dB = juce::jlimit(MINDB, MAXDB, dB);
|
||
magnitudesDb[k] = dB;
|
||
}
|
||
}
|
||
//endregion convertToDb
|
||
|
||
//region applySmoothing
|
||
void SpectrumAnalyzer::applySmoothing() {
|
||
applyEMA();
|
||
applyFreqSmoothing();
|
||
applyPeakHoldAndFalloff();
|
||
}
|
||
//endregion applySmoothing
|
||
|
||
//region applyEMA
|
||
void SpectrumAnalyzer::applyEMA() {
|
||
for (int k = 0; k < magnitudesDb.size(); ++k) {
|
||
float smoothedVal = smoothingFactor * magnitudesDb[k] + (1 - smoothingFactor) * emaSmoothedMagnitudesDb[k];
|
||
emaSmoothedMagnitudesDb[k] = smoothedVal;
|
||
}
|
||
|
||
}
|
||
//endregion applyEMA
|
||
|
||
//region applyFreqSmoothing
|
||
void SpectrumAnalyzer::applyFreqSmoothing() {
|
||
|
||
for (int k = 0; k < BINS; ++k) {
|
||
double freq = k * deltaF;
|
||
|
||
double lowestFreq = freq * pow(2.0, -OCTAVERADIUS);
|
||
double highestFreq = freq * pow(2.0, OCTAVERADIUS);
|
||
|
||
int lowestBin = std::max(0, static_cast<int>(floor(lowestFreq / deltaF)));
|
||
int highestBin = std::min(BINS - 1, static_cast<int>(ceil(highestFreq / deltaF)));
|
||
|
||
if (lowestBin > highestBin) {
|
||
lowestBin = k;
|
||
highestBin = k;
|
||
};
|
||
|
||
if (k == 0) {lowestBin = 0; highestBin = std::min(1, BINS - 1);}
|
||
|
||
float sum = 0.f;
|
||
|
||
for (int bin = lowestBin; bin <= highestBin; ++bin) {
|
||
sum += emaSmoothedMagnitudesDb[bin];
|
||
}
|
||
float avg = sum / static_cast<float>(highestBin - lowestBin + 1);
|
||
freqSmoothedMagnitudesDb[k] = avg;
|
||
|
||
}
|
||
}
|
||
//endregion applyFreqSmoothing
|
||
|
||
//region applyPeakHoldAndFalloff
|
||
void SpectrumAnalyzer::applyPeakHoldAndFalloff() {
|
||
std::vector<float> prevPeak = peakHoldMagnitudesDb;
|
||
for (int k = 0; k < BINS; ++k) {
|
||
float current = freqSmoothedMagnitudesDb[k];
|
||
float prev = prevPeak[k];
|
||
peakHoldMagnitudesDb[k] = std::max(current, prev - FALLOFFRATE * DELTAT);
|
||
}
|
||
}
|
||
//endregion applyPeakHoldAndFalloff
|
||
|
||
//region getRenderValues
|
||
std::vector<float> SpectrumAnalyzer::getRenderValues() {
|
||
std::vector<float> renderValues = peakHoldMagnitudesDb;
|
||
return renderValues;
|
||
}
|
||
//endregion getRenderValues
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|