/* ============================================================================== 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( audioProcessor.apvts.getParameter("LowBandModes"))) { lowBandModeBox.addItemList (choice->choices, 1); lowBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification); lowBandModeAttach = std::make_unique( audioProcessor.apvts, "LowBandModes", lowBandModeBox); } if (auto* choice = dynamic_cast( audioProcessor.apvts.getParameter("HighBandModes"))) { highBandModeBox.addItemList (choice->choices, 1); highBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification); highBandModeAttach = std::make_unique( audioProcessor.apvts, "HighBandModes", highBandModeBox); } } //endregion setupModeBoxes //region setupSliders void CrystalizerEQAudioProcessorEditor::setupSliders() { auto style = juce::Slider::RotaryHorizontalVerticalDrag; for (auto* s : sliders) { 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(audioProcessor.apvts, "TestNoiseLevel", testNoiseSlider); lowBandFreqAttach = std::make_unique(audioProcessor.apvts, "LowBandFreq", lowBandFreqSlider); lowBandSlopeAttach = std::make_unique(audioProcessor.apvts, "LowBandSlope", lowBandSlopeSlider); lowBandGainAttach = std::make_unique(audioProcessor.apvts, "LowBandGain", lowBandGainSlider); lowBandQAttach = std::make_unique(audioProcessor.apvts, "LowBandQ", lowBandQSlider); lowBandModeAttach = std::make_unique(audioProcessor.apvts, "LowBandModes", lowBandModeBox); peak1FreqAttach = std::make_unique(audioProcessor.apvts, "Peak1Freq", peak1FreqSlider); peak1GainAttach = std::make_unique(audioProcessor.apvts, "Peak1Gain", peak1GainSlider); peak1QAttach = std::make_unique(audioProcessor.apvts, "Peak1Q", peak1QSlider); peak2FreqAttach = std::make_unique(audioProcessor.apvts, "Peak2Freq", peak2FreqSlider); peak2GainAttach = std::make_unique(audioProcessor.apvts, "Peak2Gain", peak2GainSlider); peak2QAttach = std::make_unique(audioProcessor.apvts, "Peak2Q", peak2QSlider); peak3FreqAttach = std::make_unique(audioProcessor.apvts, "Peak3Freq", peak3FreqSlider); peak3GainAttach = std::make_unique(audioProcessor.apvts, "Peak3Gain", peak3GainSlider); peak3QAttach = std::make_unique(audioProcessor.apvts, "Peak3Q", peak3QSlider); highBandFreqAttach = std::make_unique(audioProcessor.apvts, "HighBandFreq", highBandFreqSlider); highBandSlopeAttach = std::make_unique(audioProcessor.apvts, "HighBandSlope", highBandSlopeSlider); highBandGainAttach = std::make_unique(audioProcessor.apvts, "HighBandGain", highBandGainSlider); highBandQAttach = std::make_unique(audioProcessor.apvts, "HighBandQ", highBandQSlider); highBandModeAttach = std::make_unique(audioProcessor.apvts, "HighBandModes", highBandModeBox); inputAttach = std::make_unique(audioProcessor.apvts, "InputGain", inputSlider); outputAttach = std::make_unique(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, "Low\nFreq"); setupLabel (lowBandSlopeLabel, "Low\nSlope"); setupLabel (lowBandGainLabel, "Low\nGain"); setupLabel(lowBandQLabel, "Low\nQ"); //PEAK 1 setupLabel (peak1FreqLabel,"Low-Mid\nFreq"); setupLabel (peak1GainLabel,"Low-Mid\nGain"); setupLabel (peak1QLabel, "Low-Mid\nQ"); //PEAK 2 setupLabel (peak2FreqLabel,"Mid\nFreq"); setupLabel (peak2GainLabel,"Mid\nGain"); setupLabel (peak2QLabel, "Mid\nQ"); //PEAK 3 setupLabel (peak3FreqLabel,"High-Mid\nFreq"); setupLabel (peak3GainLabel,"High-Mid\nGain"); setupLabel (peak3QLabel, "High-Mid\nQ"); //HIGH-BAND setupLabel (highBandFreqLabel, "High\nFreq"); setupLabel (highBandSlopeLabel, "High\nSlope"); 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::Body, 1.f); } //TODO: Set font of textboxes to mono and place them inside of the slider. auto styleSliderTextBox = [](juce::Slider& s) { for (int i = 0; i < s.getNumChildComponents(); ++i) { DBG (s.getNumChildComponents()); if (auto* label = dynamic_cast(s.getChildComponent(i))) { label->setColour(juce::Label::backgroundColourId, juce::Colours::transparentBlack); label->setColour(juce::Label::outlineColourId, juce::Colours::transparentBlack); label->setColour(juce::Label::textColourId, juce::Colours::white); label->setJustificationType(juce::Justification::centred); //Typography::applyToLabel(*label, Typography::Style::Mono, 1.f); } } }; for (auto* s : sliders) { styleSliderTextBox(*s); } } //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(); setupFontsWithColours(); 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); // 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); paintBorderLines(g); } //region paintBorderLines void CrystalizerEQAudioProcessorEditor::paintBorderLines(juce::Graphics &g) { g.setColour(DesignSystem::Colours::BACKGROUNDHOVER); 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() { 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() { 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 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 area) { const float headerHeight = static_cast(getHeight()) * 0.1f; const float footerHeight = static_cast(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(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(presetArea.getWidth()); const auto presetBoxHeight = static_cast(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(bounds.getHeight()); const auto bodyWidth = static_cast(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(analyzerArea.getWidth()); const auto analyzerAreaHeight = static_cast(analyzerArea.getHeight()); const auto filterAreaBounds = filterArea.getLocalBounds(); const auto filterAreaWidth = static_cast(filterArea.getWidth()); const auto filterAreaHeight = static_cast(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(bounds.getWidth()); const auto areaHeight = static_cast(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) .withHeight(refH * freqMod) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(lowBandGainSlider, 2, 2, 3, 3) .withWidth(refW * gainMod) .withHeight(refH * gainMod) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(lowBandQSlider, 2, 3, 3, 4) .withWidth(refW * qMod) .withHeight(refH * qMod) .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, 2, 4, 3), }); } //endregion setupLowBandLayout //region setupLowMidBandLayout void CrystalizerEQAudioProcessorEditor::setupLowMidBandLayout() { const auto bounds = lowMidFilterArea.getLocalBounds(); const auto areaWidth = static_cast(bounds.getWidth()); const auto areaHeight = static_cast(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(bounds.getWidth()); const auto areaHeight = static_cast(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(bounds.getWidth()); const auto areaHeight = static_cast(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(bounds.getWidth()); const auto areaHeight = static_cast(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) .withHeight(refH * freqMod) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(highBandGainSlider, 2, 2, 3, 3) .withWidth(refW * gainMod) .withHeight(refH * gainMod) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(highBandQSlider, 2, 3, 3, 4) .withWidth(refW * qMod) .withHeight(refH * qMod) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(highBandModeBox, 3, 1, 4, 2), Layout::area(highBandSlopeSlider, 3, 3, 4, 4) .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, 2, 4, 3), }); } //endregion setupHighBandLayout //region setupFooter void CrystalizerEQAudioProcessorEditor::setupFooter() { const auto bounds = footerBar.getLocalBounds(); const auto footerWidth = static_cast(bounds.getWidth()); const auto footerHeight = static_cast(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(globalControlArea.getWidth()); const auto globalControlAreaHeight = static_cast(globalControlArea.getHeight()); const auto globalControlColWidth = globalControlAreaWidth / 3.0f; const auto inLabelH = inputLabel.getFont().getHeight(); const auto outLabelH = outputLabel.getFont().getHeight(); 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(inputSlider, 2, 2, 3, 3) .withWidth(refW * globalMod) .withHeight(refH * globalMod) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(outputSlider, 2, 3, 3, 4) .withWidth(refW * globalMod) .withHeight(refH * globalMod) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(inputLabel, 2, 2, 3, 3) .withWidth(refW * globalMod) .withHeight(inLabelH) .withAlignSelf(juce::GridItem::AlignSelf::stretch) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(outputLabel, 2, 3, 3, 4) .withWidth(refW * globalMod) .withHeight(outLabelH) .withAlignSelf(juce::GridItem::AlignSelf::stretch) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(masterBypassButton, 2, 1, 3, 2), }); } //endregion setupFooter //region getReferenceCell juce::Array CrystalizerEQAudioProcessorEditor::getReferenceCell() { const auto bounds = midFilterArea.getLocalBounds(); const auto areaWidth = static_cast(bounds.getWidth()); const auto areaHeight = static_cast(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 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& 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 fullFrame(fftFrame.begin(), fftFrame.end()); fftFrame.removeRange(0, HOPSIZE); applyWindowOnFftFrame(fullFrame); } } //endregion getFftFrame //region applyWindowOnFftFrame void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector &fullFrame) { if (fullFrame.size()!= FFTSIZE) return; window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE); processWindowedFrame(fullFrame); } //endregion applyWindowOnFftFrame //region processWindowedFrame void SpectrumAnalyzer::processWindowedFrame(std::vector &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 &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(floor(lowestFreq / deltaF))); int highestBin = std::min(BINS - 1, static_cast(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(highestBin - lowestBin + 1); freqSmoothedMagnitudesDb[k] = avg; } } //endregion applyFreqSmoothing //region applyPeakHoldAndFalloff void SpectrumAnalyzer::applyPeakHoldAndFalloff() { std::vector 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 SpectrumAnalyzer::getRenderValues() { std::vector renderValues = peakHoldMagnitudesDb; return renderValues; } //endregion getRenderValues