/* ============================================================================== 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 setupSliders void CrystalizerEQAudioProcessorEditor::setupSliders() { auto style = juce::Slider::RotaryHorizontalVerticalDrag; for (auto* s : sliders) { s->setSliderStyle (style); } gainLookAndFeel = std::make_unique(); freqQLookAndFeel = std::make_unique(); slopeLookAndFeel = std::make_unique(); globalLookAndFeel = std::make_unique(); lowBandGainSlider.setLookAndFeel(gainLookAndFeel.get()); peak1GainSlider.setLookAndFeel(gainLookAndFeel.get()); peak2GainSlider.setLookAndFeel(gainLookAndFeel.get()); peak3GainSlider.setLookAndFeel(gainLookAndFeel.get()); highBandGainSlider.setLookAndFeel(gainLookAndFeel.get()); lowBandSlopeSlider.setLookAndFeel(slopeLookAndFeel.get()); highBandSlopeSlider.setLookAndFeel(slopeLookAndFeel.get()); inputSlider.setLookAndFeel(globalLookAndFeel.get()); outputSlider.setLookAndFeel(globalLookAndFeel.get()); for (auto* s : {&lowBandFreqSlider, &lowBandQSlider, &peak1FreqSlider, &peak1QSlider, &peak2FreqSlider, &peak2QSlider, &peak3FreqSlider, &peak3QSlider, &highBandFreqSlider, &highBandQSlider}) { s->setLookAndFeel(freqQLookAndFeel.get()); } } //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); 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); 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"); lowBypass.setName("LowBypass"); lowBell.setName("LowBell"); lowCut.setName("LowCut"); lowShelf.setName("LowShelf"); lowBandModeBox.setName("LowBandModeBox"); lowBandFreqSlider.setName ("LowBand Freq"); lowBandSlopeSlider.setName ("LowBand Slope"); lowBandGainSlider.setName ("LowBand Gain"); lowBandQSlider.setName ("LowBand Q"); peak1FreqSlider.setName("Peak1 Freq"); peak1GainSlider.setName("Peak1 Gain"); peak1QSlider.setName ("Peak1 Q"); peak2FreqSlider.setName("Peak2 Freq"); peak2GainSlider.setName("Peak2 Gain"); peak2QSlider.setName ("Peak2 Q"); peak3FreqSlider.setName("Peak3 Freq"); peak3GainSlider.setName("Peak3 Gain"); peak3QSlider.setName ("Peak3 Q"); highBypass.setName("HighBypass"); highBell.setName("HighBell"); highCut.setName("HighCut"); highShelf.setName("HighShelf"); highBandModeBox.setName("HighBandModeBox"); highBandFreqSlider.setName ("HighBand Freq"); highBandSlopeSlider.setName ("HighBand Slope"); highBandGainSlider.setName ("HighBand Gain"); highBandQSlider.setName ("HighBand Q"); inputSlider.setName ("Input"); outputSlider.setName ("Output"); testNoiseSlider.setTextValueSuffix(" Gain"); //lowBandFreqSlider.setTextValueSuffix ("\nHz"); lowBandSlopeSlider.setTextValueSuffix ("\ndB/Oct"); lowBandGainSlider.setTextValueSuffix("\ndB"); //lowBandQSlider.setTextValueSuffix ("\nQ"); //peak1FreqSlider.setTextValueSuffix("\nHz"); peak1GainSlider.setTextValueSuffix("\ndB"); //peak1QSlider.setTextValueSuffix("\nQ"); peak1BypassButton.setName("peak1Bypass"); //peak2FreqSlider.setTextValueSuffix("\nHz"); peak2GainSlider.setTextValueSuffix("\ndB"); //peak2QSlider.setTextValueSuffix("\nQ"); peak2BypassButton.setName("peak2Bypass"); //peak3FreqSlider.setTextValueSuffix("\nHz"); peak3GainSlider.setTextValueSuffix("\ndB"); //peak3QSlider.setTextValueSuffix("\nQ"); peak3BypassButton.setName("peak3Bypass"); //highBandFreqSlider.setTextValueSuffix ("\nHz"); highBandSlopeSlider.setTextValueSuffix ("\ndB/Oct"); highBandGainSlider.setTextValueSuffix("\ndB"); // highBandQSlider.setTextValueSuffix ("\nQ"); inputSlider.setTextValueSuffix("\nIN"); outputSlider.setTextValueSuffix("\nOUT"); testNoiseButton.setName("TestNoise"); testNoiseButton.setButtonText("Test Noise"); crystalizeButton.setName("CrystalizeButton"); resetButton.setName("ResetButton"); resetButton.setButtonText("Reset"); masterBypassButton.setName("MasterBypass"); savePresetButton.setName("SavePresetButton"); savePresetButton.setButtonText("Save Preset"); deletePresetButton.setName("DeletePresetButton"); deletePresetButton.setButtonText("Delete Preset"); } //endregion setupDisplayNames //region setupToggleButtons void CrystalizerEQAudioProcessorEditor::setupToggleButtons() { bypassButtonLookAndFeel = std::make_unique(); svgToggleButtonLookAndFeel = std::make_unique(); presetMenuButtonLookAndFeel = std::make_unique(); lowBandButtonLookAndFeel = std::make_unique(); highBandButtonLookAndFeel = std::make_unique(); peak1BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get()); peak2BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get()); peak3BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get()); masterBypassButton.setLookAndFeel(bypassButtonLookAndFeel.get()); crystalizeButton.setLookAndFeel(svgToggleButtonLookAndFeel.get()); presetMenuButton.setLookAndFeel(presetMenuButtonLookAndFeel.get()); lowShelf.setToggleState(true, juce::dontSendNotification); highShelf.setToggleState(true, juce::dontSendNotification); lowBypass.setLookAndFeel(lowBandButtonLookAndFeel.get()); lowCut.setLookAndFeel(lowBandButtonLookAndFeel.get()); lowBell.setLookAndFeel(lowBandButtonLookAndFeel.get()); lowShelf.setLookAndFeel(lowBandButtonLookAndFeel.get()); highBypass.setLookAndFeel(highBandButtonLookAndFeel.get()); highCut.setLookAndFeel(highBandButtonLookAndFeel.get()); highBell.setLookAndFeel(highBandButtonLookAndFeel.get()); highShelf.setLookAndFeel(highBandButtonLookAndFeel.get()); for (auto* b : {&peak1BypassButton, &peak2BypassButton, &peak3BypassButton, &crystalizeButton, &masterBypassButton}) { b->onClick = [this, b]() { juce::String paramID; // nimm juce::String statt std::string juce::String mode; if (b == &peak1BypassButton) { paramID = "Peak1Bypass"; mode = "Low-Mid"; } else if (b == &peak2BypassButton) { paramID = "Peak2Bypass"; mode = "Mid"; } else if (b == &peak3BypassButton) { paramID = "Peak3Bypass"; mode = "High-Mid"; } else if (b == &crystalizeButton) { paramID = "CrystalizeButton"; } else if (b == &masterBypassButton) { paramID = "MasterBypass"; mode = "Master"; } if (auto* param = audioProcessor.apvts.getParameter(paramID)) { const bool isToggled = b->getToggleState(); const float target = isToggled ? 1.0f : 0.0f; // Nur senden, wenn sich wirklich etwas ändert (Schwelle 0.5f) if ((param->getValue() >= 0.5f) != isToggled) { param->beginChangeGesture(); param->setValueNotifyingHost(target); param->endChangeGesture(); } if (mode == "Low-Mid") disableLowMidBand(target); else if (mode == "Mid") disableMidBand(target); else if (mode == "High-Mid") disableHighMidBand(target); else if (mode == "Master") disableEverything(target); } if (b == &crystalizeButton) { isAnimatingCrystalize = true; isFadingToActive = (svgToggleButtonLookAndFeel->activeIconOpacity < 1.0f); } }; } } //endregion setupToggleButtons //region disableLowBand void CrystalizerEQAudioProcessorEditor::disableLowBand(const float target) { bool isToggled = target <= 0.5f; lowBandFreqSlider.setEnabled (isToggled); lowBandFreqLabel.setEnabled (isToggled); lowBandSlopeSlider.setEnabled (isToggled); lowBandSlopeLabel.setEnabled (isToggled); lowBandGainSlider.setEnabled(isToggled); lowBandGainLabel .setEnabled(isToggled); lowBandQSlider .setEnabled(isToggled); lowBandQLabel .setEnabled(isToggled); }; //endregion disableLowBand //region disableLowMidBand void CrystalizerEQAudioProcessorEditor::disableLowMidBand(const float target) { bool isToggled = target <= 0.5f; peak1GainSlider.setEnabled(isToggled); peak1QSlider.setEnabled(isToggled); peak1FreqSlider.setEnabled(isToggled); peak1GainLabel.setEnabled(isToggled); peak1QLabel.setEnabled(isToggled); peak1FreqLabel.setEnabled(isToggled); } //endregion disableLowMidBand //region disableMidBand void CrystalizerEQAudioProcessorEditor::disableMidBand(const float target) { bool isToggled = target <= 0.5f; peak2GainSlider.setEnabled(isToggled); peak2QSlider.setEnabled(isToggled); peak2FreqSlider.setEnabled(isToggled); peak2GainLabel.setEnabled(isToggled); peak2QLabel.setEnabled(isToggled); peak2FreqLabel.setEnabled(isToggled); } //endregion disableMidBand //region disableHighMidBand void CrystalizerEQAudioProcessorEditor::disableHighMidBand(const float target) { bool isToggled = target <= 0.5f; peak3GainSlider.setEnabled(isToggled); peak3QSlider.setEnabled(isToggled); peak3FreqSlider.setEnabled(isToggled); peak3GainLabel.setEnabled(isToggled); peak3QLabel.setEnabled(isToggled); peak3FreqLabel.setEnabled(isToggled); } //endregion disableHighMidBand //region disableHighBand void CrystalizerEQAudioProcessorEditor::disableHighBand(const float target) { bool isToggled = target <= 0.5f; highBandFreqSlider.setEnabled (isToggled); highBandFreqLabel.setEnabled (isToggled); highBandSlopeSlider.setEnabled (isToggled); highBandSlopeLabel.setEnabled (isToggled); highBandGainSlider.setEnabled(isToggled); highBandGainLabel .setEnabled(isToggled); highBandQSlider .setEnabled(isToggled); highBandQLabel .setEnabled(isToggled); }; //endregion disableHighBand //region disableEverything void CrystalizerEQAudioProcessorEditor::disableEverything(const float target) { bool isToggled = target <= 0.5f; for (auto* s : sliders) { s->setEnabled(isToggled); } for (auto* l : sliderLabels) { l->setEnabled(isToggled); } //TODO: If band bypass is active prior to master bypass, upon reactivating master the bypassed band is getting active. peak1BypassButton.setEnabled(isToggled); peak2BypassButton.setEnabled(isToggled); peak3BypassButton.setEnabled(isToggled); resetButton.setEnabled(isToggled); crystalizeButton.setEnabled(isToggled); lowBandModeBox.setEnabled(isToggled); highBandModeBox.setEnabled(isToggled); if (!isToggled) { if (svgToggleButtonLookAndFeel->activeIconOpacity == 1.0f) { svgToggleButtonLookAndFeel->activeIconOpacity = 0.7f; crystalizeButton.repaint(); }else if (svgToggleButtonLookAndFeel->passiveIconOpacity == 1.0f) { svgToggleButtonLookAndFeel->passiveIconOpacity = 0.7f; crystalizeButton.repaint(); } } else { if (svgToggleButtonLookAndFeel->activeIconOpacity == 0.7f) { svgToggleButtonLookAndFeel->activeIconOpacity = 1.0f; crystalizeButton.repaint(); }else if (svgToggleButtonLookAndFeel->passiveIconOpacity == 0.7f) { svgToggleButtonLookAndFeel->passiveIconOpacity = 1.0f; crystalizeButton.repaint(); } } } //endregion disableEverything //region addComponentsToLayout void CrystalizerEQAudioProcessorEditor::addComponentsToLayout() { addAndMakeVisible(headerBar); headerBar.addAndMakeVisible(titleLabel); headerBar.addAndMakeVisible(presetArea); presetArea.addAndMakeVisible(presetBoxLabel); presetArea.addAndMakeVisible(resetButton); presetArea.addAndMakeVisible(presetBox); presetArea.addAndMakeVisible(presetMenuButton); headerBar.addAndMakeVisible(presetMenu); presetMenu.addAndMakeVisible(presetNameInput); presetMenu.addAndMakeVisible(savePresetButton); presetMenu.addAndMakeVisible(deletePresetButton); presetMenu.toFront(true); presetMenu.setVisible(false); addAndMakeVisible(mainPanel); mainPanel.addAndMakeVisible(analyzerArea); mainPanel.addAndMakeVisible(crystalizeButton); //FILTERAREA { mainPanel.addAndMakeVisible(filterArea); { filterArea.addAndMakeVisible(lowFilterArea); lowBandModeBox.addAndMakeVisible(lowBandModeLabel); lowBandModeBox.addAndMakeVisible(lowBypass); lowBandModeBox.addAndMakeVisible(lowCut); lowBandModeBox.addAndMakeVisible(lowBell); lowBandModeBox.addAndMakeVisible(lowShelf); lowFilterArea.addAndMakeVisible(lowBandModeBox); lowBandModeButtons.add(&lowBypass, &lowCut, &lowBell, &lowShelf); lowBandBools = {false, false, false, true}; lowFilterArea.addAndMakeVisible(lowBandSlopeLabel); lowFilterArea.addAndMakeVisible(lowBandGainLabel); lowFilterArea.addAndMakeVisible(lowBandQLabel); lowFilterArea.addAndMakeVisible(lowBandFreqLabel); lowBandSlopeSlider.addAndMakeVisible(low12); lowBandSlopeSlider.addAndMakeVisible(low24); lowBandSlopeSlider.addAndMakeVisible(low36); lowBandSlopeSlider.addAndMakeVisible(low48); lowFilterArea.addAndMakeVisible(lowBandSlopeSlider); lowFilterArea.addAndMakeVisible(lowBandGainSlider); lowFilterArea.addAndMakeVisible(lowBandQSlider); lowFilterArea.addAndMakeVisible(lowBandFreqSlider); } { filterArea.addAndMakeVisible(lowMidFilterArea); lowMidFilterArea.addAndMakeVisible(peak1FreqLabel); lowMidFilterArea.addAndMakeVisible(peak1GainLabel); lowMidFilterArea.addAndMakeVisible(peak1QLabel); lowMidFilterArea.addAndMakeVisible(peak1FreqSlider); lowMidFilterArea.addAndMakeVisible(peak1GainSlider); lowMidFilterArea.addAndMakeVisible(peak1QSlider); lowMidFilterArea.addAndMakeVisible(peak1BypassButton); } { filterArea.addAndMakeVisible(midFilterArea); midFilterArea.addAndMakeVisible(peak2FreqLabel); midFilterArea.addAndMakeVisible(peak2GainLabel); midFilterArea.addAndMakeVisible(peak2QLabel); midFilterArea.addAndMakeVisible(peak2FreqSlider); midFilterArea.addAndMakeVisible(peak2GainSlider); midFilterArea.addAndMakeVisible(peak2QSlider); midFilterArea.addAndMakeVisible(peak2BypassButton); } { filterArea.addAndMakeVisible(highMidFilterArea); highMidFilterArea.addAndMakeVisible(peak3FreqLabel); highMidFilterArea.addAndMakeVisible(peak3GainLabel); highMidFilterArea.addAndMakeVisible(peak3QLabel); highMidFilterArea.addAndMakeVisible(peak3FreqSlider); highMidFilterArea.addAndMakeVisible(peak3GainSlider); highMidFilterArea.addAndMakeVisible(peak3QSlider); highMidFilterArea.addAndMakeVisible(peak3BypassButton); } { filterArea.addAndMakeVisible(highFilterArea); highBandModeBox.addAndMakeVisible(highBandModeLabel); highBandModeBox.addAndMakeVisible(highBypass); highBandModeBox.addAndMakeVisible(highCut); highBandModeBox.addAndMakeVisible(highBell); highBandModeBox.addAndMakeVisible(highShelf); highFilterArea.addAndMakeVisible(highBandModeBox); highBandModeButtons.add(&highBypass, &highCut, &highBell, &highShelf); highBandBools = {false, false, false, true}; highFilterArea.addAndMakeVisible(highBandSlopeLabel); highFilterArea.addAndMakeVisible(highBandGainLabel); highFilterArea.addAndMakeVisible(highBandQLabel); highFilterArea.addAndMakeVisible(highBandFreqLabel); highBandSlopeSlider.addAndMakeVisible(high12); highBandSlopeSlider.addAndMakeVisible(high24); highBandSlopeSlider.addAndMakeVisible(high36); highBandSlopeSlider.addAndMakeVisible(high48); highFilterArea.addAndMakeVisible(highBandSlopeSlider); highFilterArea.addAndMakeVisible(highBandGainSlider); highFilterArea.addAndMakeVisible(highBandQSlider); highFilterArea.addAndMakeVisible(highBandFreqSlider); } } addAndMakeVisible(footerBar); footerBar.addAndMakeVisible(globalControlArea); globalControlArea.addAndMakeVisible(inputLabel); globalControlArea.addAndMakeVisible(outputLabel); globalControlArea.addAndMakeVisible(inputSlider); globalControlArea.addAndMakeVisible(outputSlider); globalControlArea.addAndMakeVisible(masterBypassButton); } //endregion addComponentsToLayout //region setupLabels void CrystalizerEQAudioProcessorEditor::setupLabels() { auto setupLabel = [] (juce::Label& label, const juce::String& text) { label.setText (text, juce::dontSendNotification); label.setJustificationType (juce::Justification::centred); label.setColour (juce::Label::textColourId, juce::Colours::black); label.setInterceptsMouseClicks (false, false); // Klicks gehen an den Slider }; //SetupLabels { //LOW-BAND setupLabel (lowBandFreqLabel, "Low\nHz"); setupLabel (lowBandSlopeLabel, "Slope"); setupLabel (lowBandGainLabel, "Low\nGain"); setupLabel(lowBandQLabel, "Low\nQ"); setupLabel(lowBandModeLabel, "Band Mode"); setupLabel(low12, "12"); setupLabel(low24, "24"); setupLabel(low36, "36"); setupLabel(low48, "48"); //PEAK 1 setupLabel (peak1FreqLabel,"Low-Mid\nHz"); setupLabel (peak1GainLabel,"Low-Mid\nGain"); setupLabel (peak1QLabel, "Low-Mid\nQ"); //PEAK 2 setupLabel (peak2FreqLabel,"Mid\nHz"); setupLabel (peak2GainLabel,"Mid\nGain"); setupLabel (peak2QLabel, "Mid\nQ"); //PEAK 3 setupLabel (peak3FreqLabel,"High-Mid\nHz"); setupLabel (peak3GainLabel,"High-Mid\nGain"); setupLabel (peak3QLabel, "High-Mid\nQ"); //HIGH-BAND setupLabel (highBandFreqLabel, "High\nHz"); setupLabel (highBandSlopeLabel, "Slope"); setupLabel (highBandGainLabel, "High\nGain"); setupLabel(highBandQLabel, "High\nQ"); setupLabel(highBandModeLabel, "Band Mode"); setupLabel(high12, "12"); setupLabel(high24, "24"); setupLabel(high36, "36"); setupLabel(high48, "48"); setupLabel(presetBoxLabel, "Presets"); /*setupLabel(inputLabel, "IN"); setupLabel(outputLabel, "OUT");*/ } } //endregion setupLabels void CrystalizerEQAudioProcessorEditor::setupFontsWithColours() { Typography::applyToLabel(titleLabel, Typography::Style::Display, 1.f); titleLabel.setColour(juce::Label::textColourId, Colours::ACCENTCOLOUR); for (auto* l : sliderLabels) { l->setColour(juce::Label::textColourId, Colours::FOREGROUNDCOLOUR); Typography::applyToLabel(*l, Typography::Style::Mono, 1.f); } } //region setupSliderTextBoxes void CrystalizerEQAudioProcessorEditor::setupSliderTextBoxes() { baseLookAndFeel = std::make_unique(); for (auto* s : sliders) { s->setLookAndFeel(baseLookAndFeel.get()); } } //endregion setupSliderTextBoxes //region handleLowBandModes void CrystalizerEQAudioProcessorEditor::handleLowBandModes() { for (int i = 0; i < lowBandModeButtons.size(); ++i) { lowBandModeButtons[i]->onClick = [this, i] { if (!lowBandBools[i]) { if (auto* param = audioProcessor.apvts.getParameter("LowBandModes")) { param->beginChangeGesture(); for (int j = 0; j < lowBandBools.size(); ++j) { if (j != i) { lowBandBools[j] = false; param->setValueNotifyingHost(param->convertTo0to1(0.0f)); lowBandModeButtons[j]->setToggleState(false, juce::dontSendNotification); } } // Aktuellen aktivieren lowBandBools[i] = true; param->setValueNotifyingHost(param->convertTo0to1((float)i)); param->endChangeGesture(); } } else if (lowBandBools[i]) { lowBandModeButtons[i]->setToggleState(true, juce::dontSendNotification); } }; } } //endregion handleLowBandModes //region handleHighBandModes void CrystalizerEQAudioProcessorEditor::handleHighBandModes() { for (int i = 0; i < highBandModeButtons.size(); ++i) { highBandModeButtons[i]->onClick = [this, i] { if (!highBandBools[i]) { if (auto* param = audioProcessor.apvts.getParameter("HighBandModes")) { param->beginChangeGesture(); for (int j = 0; j < highBandBools.size(); ++j) { if (j != i) { highBandBools[j] = false; param->setValueNotifyingHost(param->convertTo0to1(0.0f)); highBandModeButtons[j]->setToggleState(false, juce::dontSendNotification); } } highBandBools[i] = true; param->setValueNotifyingHost(param->convertTo0to1((float)i)); param->endChangeGesture(); } } else if (highBandBools[i]) { highBandModeButtons[i]->setToggleState(true, juce::dontSendNotification); } }; } } //endregion handleHighBandModes //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); }; handleLowBandModes(); handleHighBandModes(); } //endregion setupEventListeners //region initPresetSystem void CrystalizerEQAudioProcessorEditor::initPresetSystem() { auto presets = audioProcessor.getPresetNamesArray(); presetBox.addItemList(presets, 1); presetBox.setSelectedId(1, juce::dontSendNotification); presetMenuButton.setName("PresetMenuButton"); presetMenuButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black); presetMenuButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black); presetMenuButton.setToggleState(false, juce::dontSendNotification); presetNameInput.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(); setupAttachments(); setupDisplayNames(); setupToggleButtons(); setupEventListeners(); setupSliderTextBoxes(); setupSliders(); initPresetSystem(); addAndMakeVisible (testNoiseButton); testNoiseButton.onClick = [this]() { if (auto* param = audioProcessor.apvts.getParameter("TestNoiseEnabled")) { if (param->getValue() == false) { param->beginChangeGesture(); param->setValueNotifyingHost (1.0f); param->endChangeGesture(); } else { param->beginChangeGesture(); param->setValueNotifyingHost (0.0f); // -> true param->endChangeGesture(); } } }; } CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() { stopTimer(); for (auto* s : sliders) { s->setLookAndFeel(nullptr); } peak1BypassButton.setLookAndFeel(nullptr); peak2BypassButton.setLookAndFeel(nullptr); peak3BypassButton.setLookAndFeel(nullptr); masterBypassButton.setLookAndFeel(nullptr); crystalizeButton.setLookAndFeel(nullptr); presetMenuButton.setLookAndFeel(nullptr); for (auto* b : lowBandModeButtons) { b->setLookAndFeel(nullptr); } for (auto* b : highBandModeButtons) { b->setLookAndFeel(nullptr); } }; //region paintAnalyzer void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) { analyzerRect = getLocalArea(&analyzerArea, analyzerArea.getLocalBounds()); juce::Graphics::ScopedSaveState guard(g); g.reduceClipRegion(analyzerRect); auto r = analyzerRect.toFloat(); // Hintergrund des Analyzer-Bereichs g.setColour(juce::Colours::black); g.fillRect(r); // Rahmen (optional) g.setColour(juce::Colours::grey); g.drawRect(analyzerRect); } //endregion paintAnalyzer void CrystalizerEQAudioProcessorEditor::paintModeBoxBorders(juce::Graphics &g) { for (auto* b : lowBandModeButtons) { if (b->getToggleState()) { auto r = getLocalArea(&lowBandModeBox, b->getBounds()); int side = juce::jmin(r.getWidth(), r.getHeight()); r = r.withSizeKeepingCentre(side, side); auto rf = r.toFloat(); auto rfFilled = rf.toNearestInt().toFloat(); const float stroke = 1.0f; auto rfStroke = rfFilled.reduced(stroke * 0.5f); g.setColour(juce::Colours::white.withAlpha(0.3f)); g.fillRoundedRectangle(r.toFloat(), 5); g.setColour(juce::Colours::white.withAlpha(0.9f)); g.drawRoundedRectangle(rfStroke, 5, stroke); } } } //============================================================================== void CrystalizerEQAudioProcessorEditor::paint (juce::Graphics& g) { g.fillAll (Colours::BACKGROUNDCOLOUR); g.setColour (AXIOM::DesignSystem::Colours::FOREGROUNDCOLOUR); const auto mP = getLocalArea(&mainPanel, mainPanel.getLocalBounds()); auto mPX = mP.getX(); auto mPY = mP.getY(); auto mPW = mP.getWidth(); auto mPH = mP.getHeight(); paintAnalyzer(g); g.setColour(Colours::SURFACEBYPASS); const auto hB = getLocalArea(&headerBar, headerBar.getLocalBounds()); auto hBX = hB.getX(); auto hBY = hB.getY(); auto hBW = hB.getWidth(); auto hBH = hB.getHeight(); auto hBPad = ((hBY + mPY) - hBH) / 2; g.fillRect(hBX, hBY, hBW, mPY - hBPad); //paintBorderLines(g); g.setColour(Colours::SURFACECOLOUR); const auto pA = getLocalArea(&presetArea, presetArea.getLocalBounds()); auto pAX = pA.getX(); auto pAY = pA.getY(); auto pAWidth = pA.getWidth(); auto pAHeight = pA.getHeight(); g.fillRoundedRectangle(pAX, pAY - 10.f, pAWidth, pAHeight + 10.f, 10.0f); //paintBorderLines(g); const auto fA = getLocalArea(&filterArea, filterArea.getLocalBounds()); const int fABorderWidth = 3; auto fABorder = fA.withSizeKeepingCentre(fA.getWidth() + fABorderWidth, fA.getHeight() + fABorderWidth); fABorder = fABorder.withX(fABorder.getX() + fABorderWidth).withY(fABorder.getY() + fABorderWidth); g.setColour(Colours::SURFACECOLOUR); g.fillRect(fABorder); fABorder = fABorder.withX(fABorder.getX() - 2* fABorderWidth).withY(fABorder.getY() - 2 * fABorderWidth); //g.setColour(Colours::FOREGROUNDBYPASS); g.fillRect(fABorder); g.setColour(Colours::BACKGROUNDBYPASS); g.fillRect(fA); paintBorderLines(g); //paintModeBoxBorders(g); g.setColour(Colours::SURFACEBYPASS); const auto fB = getLocalArea(&footerBar, footerBar.getLocalBounds()); g.fillRect(fB); 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 : lowBandModeBox.getChildren()) { auto r = getLocalArea(&lowBandModeBox, 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); } 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); } } } //region paintBorderLines void CrystalizerEQAudioProcessorEditor::paintBorderLines(juce::Graphics &g) { g.setColour(DesignSystem::Colours::BACKGROUNDCOLOUR); auto prevRight = (float) lowFilterArea.getRight(); for (auto* c : filterAreas) { if (c == &lowFilterArea) continue; const auto area = getLocalArea(c, c->getLocalBounds()); const float xAvg = ((float) area.getX() - prevRight) / 2; const int x = area.getX() - xAvg + (filterAreaMargin / 2); prevRight = (float) c->getRight(); const auto top = (float) area.getY(); const auto bot = (float) area.getBottom(); const float center = (top + bot) * 0.5f; const float halfLen = (bot - top) * 0.375f; g.drawVerticalLine(x,center - halfLen, center + halfLen); } } //endregion paintBorderLines //region setKnobVisibility void CrystalizerEQAudioProcessorEditor::setKnobVisibility() { int lowMode = (int) audioProcessor.apvts .getRawParameterValue("LowBandModes")->load(); // 0..3 const bool masterIsToggled = masterBypassButton.getToggleState(); const float target = masterIsToggled ? 1.0f : 0.0f; if (masterIsToggled) { lowMode = 0; disableLowBand(target); } lowBandFreqSlider.setEnabled (lowMode >= 1); lowBandFreqLabel.setEnabled (lowMode >= 1); lowBandSlopeSlider.setEnabled (lowMode == 1); lowBandSlopeLabel.setEnabled (lowMode == 1); lowBandGainSlider.setEnabled(lowMode >= 2); lowBandGainLabel .setEnabled(lowMode >= 2); lowBandQSlider .setEnabled(lowMode >= 1); lowBandQLabel .setEnabled(lowMode >= 1); lowBandModeLabel.setEnabled (lowMode >= 1); int highMode = (int) audioProcessor.apvts .getRawParameterValue("HighBandModes")->load(); // 0..3 if (masterIsToggled) { highMode = 0; disableHighBand(target); } highBandFreqSlider.setEnabled (highMode >= 1); highBandFreqLabel.setEnabled (highMode >= 1); highBandSlopeSlider.setEnabled (highMode == 1); highBandSlopeLabel.setEnabled (highMode == 1); highBandGainSlider.setEnabled(highMode >= 2); highBandGainLabel .setEnabled(highMode >= 2); highBandQSlider .setEnabled(highMode >= 1); highBandQLabel .setEnabled(highMode >= 1); highBandModeLabel.setEnabled (highMode >= 1); } //endregion setKnobVisibility //region animateCrystalizeButton void CrystalizerEQAudioProcessorEditor::animateCrystalizeButton() { if (isAnimatingCrystalize) { const float step = 0.1f; if (isFadingToActive) { svgToggleButtonLookAndFeel->activeIconOpacity += step; svgToggleButtonLookAndFeel->passiveIconOpacity -= step; if (svgToggleButtonLookAndFeel->activeIconOpacity >= 1.0f) { svgToggleButtonLookAndFeel->activeIconOpacity = 1.0f; svgToggleButtonLookAndFeel->passiveIconOpacity = 0.0f; isAnimatingCrystalize = false; } } else { svgToggleButtonLookAndFeel->activeIconOpacity -= step; svgToggleButtonLookAndFeel->passiveIconOpacity += step; if (svgToggleButtonLookAndFeel->passiveIconOpacity >= 1.0f) { svgToggleButtonLookAndFeel->activeIconOpacity = 0.0f; svgToggleButtonLookAndFeel->passiveIconOpacity = 1.0f; isAnimatingCrystalize = false; } } crystalizeButton.repaint(); } } //endregion animateCrystalizeButton //region timerCallback void CrystalizerEQAudioProcessorEditor::timerCallback() { setKnobVisibility(); spectrumAnalyzer.processSamples(); repaint(analyzerRect); resized(); animateCrystalizeButton(); } //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); lowBypass.setToggleState(false, notify); lowCut.setToggleState(false, notify); lowBell.setToggleState(false, notify); lowShelf.setToggleState(true, notify); highBypass.setToggleState(false, notify); highCut.setToggleState(false, notify); highBell.setToggleState(false, notify); highShelf.setToggleState(true, notify); } //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) .withMargin(juce::GridItem::Margin(filterAreaMargin, 0, filterAreaMargin, 0)), Layout::area(crystalizeButton, 1, 5, 2, 6), Layout::area(filterArea, 2, 1, 3, 6) .withMargin(juce::GridItem::Margin(0, filterAreaMargin, 0, filterAreaMargin)) }); 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; const float labelPadding = Components::SlopeSliderLookAndFeel::labelPadding * 2; Layout::GridSpec lowBandSpec{ /* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) }, /* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)}, /* colGap */ Spacing::SizeMode::XS, /* rowGap */ Spacing::SizeMode::XS, /* pad */ Layout::padding(Spacing::SizeMode::XS) }; Layout::grid(bounds, lowBandSpec, { Layout::area(lowBandFreqSlider, 2, 1, 3, 2) .withWidth(refW * freqMod * 0.8f) .withHeight(refH * freqMod * 0.8f) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(lowBandGainSlider, 2, 2, 3, 3) .withWidth(refW * gainMod * 0.8f) .withHeight(refH * gainMod * 0.8f) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(lowBandQSlider, 2, 3, 3, 4) .withWidth(refW * qMod * 0.8f) .withHeight(refH * qMod * 0.8f) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(lowBandSlopeSlider, 3, 1, 4, 2) .withWidth(refW * slopeMod + labelPadding) .withHeight(refH * slopeMod + labelPadding) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(lowBandModeBox, 3, 2, 4, 4), Layout::area(lowBandFreqLabel, 2, 1, 3, 2) .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)), Layout::area(lowBandGainLabel, 1, 2, 2, 3), Layout::area(lowBandQLabel, 2, 3, 3, 4) .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)), Layout::area(lowBandSlopeLabel, 3, 1, 4, 2) .withMargin(juce::GridItem::Margin(-offSetToGainTop / 1.5f, 0, 0, 0)), }); const auto modeBoxBounds = lowBandModeBox.getLocalBounds(); const auto modeBoxAreaWidth = static_cast(modeBoxBounds.getWidth()); const auto modeBoxAreaHeight = static_cast(modeBoxBounds.getHeight()); const auto modeBoxButtonColWidth = modeBoxAreaWidth / 4.0f; const auto slopeH = lowBandSlopeSlider.getY() - lowBandModeBox.getY() + lowBandModeLabel.getFont().getHeight() / 2; Layout::GridSpec lowBandModeBoxSpec{ /* cols */ { Layout::fr(1), Layout::fr(1), Layout::fr(1), Layout::fr(1) }, /* rows */ { Layout::fr(1), Layout::fr(1)}, /* colGap */ Spacing::SizeMode::XS, /* rowGap */ Spacing::SizeMode::XS, /* pad */ Layout::padding(Spacing::SizeMode::XS) }; Layout::grid(modeBoxBounds, lowBandModeBoxSpec, { Layout::area(lowBandModeLabel, 1, 2, 2, 4) .withMargin(juce::GridItem::Margin(slopeH , 0, 0, 0)), Layout::area(lowBypass, 1, 1, 3, 2), Layout::area(lowCut, 1, 2, 3, 3), Layout::area(lowBell, 1, 3, 3, 4), Layout::area(lowShelf, 1, 4, 3, 5) }); } //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; const float labelPadding = Components::SlopeSliderLookAndFeel::labelPadding * 2; Layout::GridSpec highBandSpec{ /* cols */ { Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1) }, /* rows */ { Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)}, /* colGap */ Spacing::SizeMode::XS, /* rowGap */ Spacing::SizeMode::XS, /* pad */ Layout::padding(Spacing::SizeMode::XS) }; Layout::grid(bounds, highBandSpec, { Layout::area(highBandFreqSlider, 2, 1, 3, 2) .withWidth(refW * freqMod * 0.8f) .withHeight(refH * freqMod * 0.8f) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(highBandGainSlider, 2, 2, 3, 3) .withWidth(refW * gainMod * 0.8f) .withHeight(refH * gainMod * 0.8f) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(highBandQSlider, 2, 3, 3, 4) .withWidth(refW * qMod * 0.8f) .withHeight(refH * qMod * 0.8f) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(highBandModeBox, 3, 1, 4, 3), Layout::area(highBandSlopeSlider, 3, 3, 4, 4) .withWidth(refW * slopeMod + labelPadding) .withHeight(refH * slopeMod + labelPadding) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(highBandFreqLabel, 2, 1, 3, 2) .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)), Layout::area(highBandGainLabel, 1, 2, 2, 3), Layout::area(highBandQLabel, 2, 3, 3, 4) .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)), Layout::area(highBandSlopeLabel, 3, 3, 4, 4) .withMargin(juce::GridItem::Margin(-offSetToGainTop / 1.5f, 0, 0, 0)), }); const auto modeBoxBounds = lowBandModeBox.getLocalBounds(); const auto modeBoxAreaWidth = static_cast(modeBoxBounds.getWidth()); const auto modeBoxAreaHeight = static_cast(modeBoxBounds.getHeight()); const auto modeBoxButtonColWidth = modeBoxAreaWidth / 4.0f; const auto slopeH = highBandSlopeSlider.getY() - highBandModeBox.getY() + highBandModeLabel.getFont().getHeight() / 2; Layout::GridSpec highBandModeBoxSpec{ /* cols */ { Layout::fr(1), Layout::fr(1), Layout::fr(1), Layout::fr(1) }, /* rows */ { Layout::fr(1), Layout::fr(1)}, /* colGap */ Spacing::SizeMode::XS, /* rowGap */ Spacing::SizeMode::XS, /* pad */ Layout::padding(Spacing::SizeMode::XS) }; Layout::grid(modeBoxBounds, highBandModeBoxSpec, { Layout::area(highBandModeLabel, 1, 2, 2, 4) .withMargin(juce::GridItem::Margin(slopeH , 0, 0, 0)), Layout::area(highBypass, 1, 4, 3, 5), Layout::area(highCut, 1, 3, 3, 4), Layout::area(highBell, 1, 2, 3, 3), Layout::area(highShelf, 1, 1, 3, 2) }); } //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; Layout::GridSpec globalControlAreaSpec{ /* cols */ { Layout::fr(1), Layout::pxTrack(globalControlColWidth), Layout::fr(1) }, /* rows */ { Layout::fr(1)}, /* colGap */ Spacing::SizeMode::XS, /* rowGap */ Spacing::SizeMode::XS, /* pad */ Layout::padding(Spacing::SizeMode::XS) }; Layout::grid(globalControlAreaBounds, globalControlAreaSpec, { //TODO: Bring components closer together Layout::area(inputSlider, 1, 2, 3, 3) .withWidth(refW * globalMod * 0.8f) .withHeight(refH * globalMod * 0.8f) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(outputSlider, 1, 3, 3, 4) .withWidth(refW * globalMod * 0.8f) .withHeight(refH * globalMod * 0.8f) .withAlignSelf(juce::GridItem::AlignSelf::center) .withJustifySelf(juce::GridItem::JustifySelf::center), Layout::area(masterBypassButton, 1, 1, 3, 2), }); } //endregion setupFooter //region getReferenceCell juce::Array 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