/* ============================================================================== This file contains the basic framework code for a JUCE plugin editor. ============================================================================== */ #include "PluginProcessor.h" #include "PluginEditor.h" //============================================================================== CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor (CrystalizerEQAudioProcessor& p) : AudioProcessorEditor (&p), audioProcessor (p), spectrumAnalyzer(p.audioFIFO, p) { // Make sure that before the constructor has finished, you've set the // editor's size to whatever you need it to be. setSize (1280, 480); // these define the parameters of our slider object startTimerHz(60); //AXIOM WAR HIER 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 }; setupLabel(testNoiseLabel, "Test Noise"); addAndMakeVisible (testNoiseLabel); //LOW-BAND setupLabel (lowBandFreqLabel, "LowBand Freq"); setupLabel (lowBandSlopeLabel, "LowBand Slope"); setupLabel (lowBandGainLabel, "LowBand Gain"); setupLabel(lowBandQLabel, "LowBand Q"); addAndMakeVisible (lowBandFreqLabel); addAndMakeVisible (lowBandSlopeLabel); addAndMakeVisible(lowBandGainLabel); addAndMakeVisible(lowBandQLabel); lowBandModeBox.setJustificationType (juce::Justification::centred); addAndMakeVisible (lowBandModeBox); //PEAK 1 setupLabel (peak1FreqLabel,"Low-Mid Freq"); setupLabel (peak1GainLabel,"Low-Mid Gain"); setupLabel (peak1QLabel, "Low-Mid Q"); addAndMakeVisible (peak1FreqLabel); addAndMakeVisible (peak1GainLabel); addAndMakeVisible (peak1QLabel); //PEAK 2 setupLabel (peak2FreqLabel,"Mid Freq"); setupLabel (peak2GainLabel,"Mid Gain"); setupLabel (peak2QLabel, "Mid Q"); addAndMakeVisible (peak2FreqLabel); addAndMakeVisible (peak2GainLabel); addAndMakeVisible (peak2QLabel); //PEAK 3 setupLabel (peak3FreqLabel,"High-Mid Freq"); setupLabel (peak3GainLabel,"High-Mid Gain"); setupLabel (peak3QLabel, "High-Mid Q"); addAndMakeVisible (peak3FreqLabel); addAndMakeVisible (peak3GainLabel); addAndMakeVisible (peak3QLabel); //HIGH-BAND setupLabel (highBandFreqLabel, "HighBand Freq"); setupLabel (highBandSlopeLabel, "HighBand Slope"); setupLabel (highBandGainLabel, "HighBand Gain"); setupLabel(highBandQLabel, "HighBand Q"); addAndMakeVisible (highBandFreqLabel); addAndMakeVisible (highBandSlopeLabel); addAndMakeVisible(highBandGainLabel); addAndMakeVisible(highBandQLabel); highBandModeBox.setJustificationType (juce::Justification::centred); addAndMakeVisible (highBandModeBox); setupLabel(inputLabel, "Input"); setupLabel(outputLabel, "Output"); addAndMakeVisible (inputLabel); addAndMakeVisible (outputLabel); setupLabel(presetBoxLabel, "Presets"); presetBox.setJustificationType (juce::Justification::centred); auto presets = audioProcessor.getPresetNamesArray(); presetBox.addItemList(presets, 1); presetBox.setSelectedId(1, juce::dontSendNotification); addAndMakeVisible(presetBox); 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); addAndMakeVisible(presetMenuButton); addAndMakeVisible(savePresetButton); addAndMakeVisible(deletePresetButton); 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); addAndMakeVisible(presetNameInput); if (auto* choice = dynamic_cast( audioProcessor.apvts.getParameter("LowBandModes"))) { // Items 1-basiert hinzufügen lowBandModeBox.addItemList (choice->choices, 1); // aktuellen Index spiegeln (0-basiert -> setSelectedItemIndex) lowBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification); // Attachment erst, wenn Items existieren lowBandModeAttach = std::make_unique( audioProcessor.apvts, "LowBandModes", lowBandModeBox); } if (auto* choice = dynamic_cast( audioProcessor.apvts.getParameter("HighBandModes"))) { // Items 1-basiert hinzufügen highBandModeBox.addItemList (choice->choices, 1); // aktuellen Index spiegeln (0-basiert -> setSelectedItemIndex) highBandModeBox.setSelectedItemIndex (choice->getIndex(), juce::dontSendNotification); // Attachment erst, wenn Items existieren highBandModeAttach = std::make_unique( audioProcessor.apvts, "HighBandModes", highBandModeBox); } auto style = juce::Slider::RotaryHorizontalVerticalDrag; for (auto* s : { &testNoiseSlider, &lowBandFreqSlider, &lowBandSlopeSlider, &lowBandGainSlider, &lowBandQSlider, &peak1FreqSlider, &peak1GainSlider, &peak1QSlider, &peak2FreqSlider, &peak2GainSlider, &peak2QSlider, &peak3FreqSlider, &peak3GainSlider, &peak3QSlider, &highBandFreqSlider, &highBandSlopeSlider, &highBandGainSlider, &highBandQSlider, &inputSlider, &outputSlider }) { s->setSliderStyle (style); s->setTextBoxStyle (juce::Slider::TextBoxBelow, true, 70, 18); addAndMakeVisible (*s); s->setColour (juce::Slider::textBoxTextColourId, juce::Colours::black); } // Attachments verbinden GUI <-> apvts Parameter-IDs 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); // Display-Namen 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"); addAndMakeVisible(peak1BypassButton); peak1BypassButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black); peak1BypassButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black); peak2FreqSlider.setTextValueSuffix(" Hz"); peak2GainSlider.setTextValueSuffix(" dB"); peak2QSlider.setTextValueSuffix(" Q"); peak2BypassButton.setName("peak2Bypass"); peak2BypassButton.setButtonText("Mid Bypass"); addAndMakeVisible(peak2BypassButton); peak2BypassButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black); peak2BypassButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black); peak3FreqSlider.setTextValueSuffix(" Hz"); peak3GainSlider.setTextValueSuffix(" dB"); peak3QSlider.setTextValueSuffix(" Q"); peak3BypassButton.setName("peak3Bypass"); peak3BypassButton.setButtonText("High-Mid Bypass"); addAndMakeVisible(peak3BypassButton); peak3BypassButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black); peak3BypassButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black); 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"); addAndMakeVisible (testNoiseButton); addAndMakeVisible (crystalizeButton); addAndMakeVisible (resetButton); addAndMakeVisible (masterBypassButton); savePresetButton.setName("SavePresetButton"); savePresetButton.setButtonText("Save Preset"); deletePresetButton.setName("DeletePresetButton"); deletePresetButton.setButtonText("Delete Preset"); presetNameInput.setVisible(false); savePresetButton.setVisible(false); deletePresetButton.setVisible(false); 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(); } } }; 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; } 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(); } } }; } presetMenuButton.onClick = [this]() { bool menuOpened = presetMenuButton.getToggleState(); if (!menuOpened) { presetNameInput.setVisible(false); savePresetButton.setVisible(false); deletePresetButton.setVisible(false); } else { presetNameInput.setVisible(true); savePresetButton.setVisible(true); deletePresetButton.setVisible(true); } }; 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(); presetBox.setSelectedId(1, juce::dontSendNotification); }; } CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() { stopTimer(); }; //============================================================================== void CrystalizerEQAudioProcessorEditor::paint (juce::Graphics& g) { // (Our component is opaque, so we must completely fill the background with a solid colour) // fill the whole window white g.fillAll (juce::Colour::fromString("#333333")); g.setColour (juce::Colour::fromString("#fcfaf9")); g.setFont (15.0f); g.drawFittedText ("CrystalizerEQ", getLocalBounds().removeFromTop(20), juce::Justification::centred, 1); //ANALYZER // VERSTEHEN!!!!!! if (! analyzerArea.isEmpty()) { juce::Graphics::ScopedSaveState guard(g); // Clip/Rückbau automatisch g.reduceClipRegion(analyzerArea); // auf Sub-Rect beschneiden auto r = analyzerArea.toFloat(); // Hintergrund des Analyzer-Bereichs g.setColour(juce::Colours::black); g.fillRect(r); // Grid g.setColour(juce::Colours::darkgrey.withAlpha(0.6f)); for (float db = -120.f; db <= 6.f; db += 12.f) { const float y = juce::jmap(db, spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB, r.getBottom(), r.getY()); g.drawLine(r.getX(), y, r.getRight(), y, 1.f); } // Bars if (!spectrumAnalyzer.renderValuesDb.empty()) { const int n = (int) spectrumAnalyzer.renderValuesDb.size(); g.setColour(juce::Colour::fromString("#56FEFF")); juce::Path eqCurve; bool hasStart = false; const float visibleFloor = spectrumAnalyzer.MINDB; for (int k = 0; k < n; ++k) { const float dB = juce::jlimit(spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB, spectrumAnalyzer.renderValuesDb[(size_t) k]); float yTop = juce::jmap(dB, spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB, r.getBottom(), r.getY()); if (dB <= visibleFloor) { yTop = r.getBottom(); DBG ("Test"); } float freq = k * spectrumAnalyzer.deltaF; // Frequenz des aktuellen Bins if (k == 0) { freq = 1e-12; } // Frequenz auf log-X-Koordinate (0.0–1.0) mappen const float normX = std::log(freq / spectrumAnalyzer.MINFREQ) / std::log(spectrumAnalyzer.MAXFREQ / spectrumAnalyzer.MINFREQ); // ...und in Pixel umrechnen: float x = r.getX() + normX * r.getWidth(); //TODO: ANSTATT FFTSIZE VERDOPPELN, NUR DIE LINIEN VERDOPPELN -> HIGHER RESOLUTION if (!hasStart) { eqCurve.startNewSubPath(x, yTop); hasStart = true; } else { eqCurve.lineTo(x, yTop); } } g.strokePath(eqCurve, juce::PathStrokeType(1.5f)); juce::Path area = eqCurve; area.lineTo(r.getRight(), r.getBottom()); area.lineTo(r.getX(), r.getBottom()); area.closeSubPath(); g.setColour(juce::Colour::fromString("#56FEFF").withAlpha(0.2f)); g.fillPath(area); } // Rahmen (optional) g.setColour(juce::Colours::grey); g.drawRect(analyzerArea); } } void CrystalizerEQAudioProcessorEditor::timerCallback() { 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); //HIER SLOPE BUTTON IMPLEMENTIEREN 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); //HIER SLOPE BUTTON IMPLEMENTIEREN highBandGainSlider.setVisible(highMode >= 2); highBandGainLabel .setVisible(highMode >= 2); highBandQSlider .setVisible(highMode >= 1); highBandQLabel .setVisible(highMode >= 1); // optional: Layout neu berechnen, damit keine Lücke bleibt spectrumAnalyzer.processSamples(); repaint(analyzerArea); resized(); } void CrystalizerEQAudioProcessorEditor::resized() { // This is generally where you'll want to lay out the positions of any // subcomponents in your editor. // sets the position and size of the slider with arguments (x, y, width, height) auto content = getLocalBounds().reduced(10); // untere Leiste für Buttons/Combos auto bottom = content.removeFromBottom(56); // Grid-Parameter (gerne anpassen) const int cellW = 120; // Zielbreite pro „Knopf“ (Label+Slider) const int cellH = 130; // Zielhöhe pro „Knopf“ const int labelH = 22; // Labelhöhe const int cols = std::max(1, content.getWidth() / cellW); // dynamisch je nach Fensterbreite analyzerArea = content.removeFromTop(160); // Helper zum Platzieren (wrappt automatisch) auto place = [&](int idx, juce::Label* label, juce::Slider* slider, juce::Button* button = nullptr, juce::ComboBox *combobox = nullptr, juce::TextEditor *editor = nullptr) { const bool anyVisible = (label && label ->isVisible()) || (slider && slider->isVisible()) || (button && button->isVisible()) || (combobox && combobox->isVisible()) || (editor && editor->isVisible()); if (!anyVisible) return idx; const int col = idx % cols; const int row = idx / cols; const int x = content.getX() + col * cellW; const int y = content.getY() + row * cellH; // Label (optional) if (label) label->setBounds(x + 6, y, cellW - 12, labelH); const int topOffset = (label ? labelH : 0); // Slider (optional) if (slider) slider->setBounds(x + 6, y + topOffset, cellW - 12, cellH - topOffset - 8); // Button (optional) if (button) { const int btnY = y + topOffset; const int btnH = slider ? labelH : (cellH - topOffset - 8); button->setBounds(x + 6, btnY, cellW - 12, btnH); } // ComboBox (optional) if (combobox) { const int cbY = y + topOffset; const int cbH = labelH; // oder wie Button behandeln combobox->setBounds(x + 6, cbY, cellW - 12, cbH); } if (editor) { const int cbY = y + topOffset; const int cbH = labelH; // oder wie Button behandeln editor->setBounds(x + 6, cbY, cellW - 12, cbH); } return idx + 1; }; int i = 0; i = place(i, &testNoiseLabel, &testNoiseSlider); i = place(i, nullptr, nullptr, nullptr, &lowBandModeBox); i = place(i, &lowBandFreqLabel, &lowBandFreqSlider); i = place(i, &lowBandSlopeLabel,&lowBandSlopeSlider); i = place(i, &lowBandGainLabel, &lowBandGainSlider); i = place(i, &lowBandQLabel, &lowBandQSlider); i = place(i, &peak1FreqLabel, &peak1FreqSlider); i = place(i, &peak1GainLabel, &peak1GainSlider); i = place(i, &peak1QLabel, &peak1QSlider); i = place(i, nullptr, nullptr, &peak1BypassButton); i = place(i, &peak2FreqLabel, &peak2FreqSlider); i = place(i, &peak2GainLabel, &peak2GainSlider); i = place(i, &peak2QLabel, &peak2QSlider); i = place(i, nullptr, nullptr, &peak2BypassButton); i = place(i, &peak3FreqLabel, &peak3FreqSlider); i = place(i, &peak3GainLabel, &peak3GainSlider); i = place(i, &peak3QLabel, &peak3QSlider); i = place(i, nullptr, nullptr, &peak3BypassButton); i = place(i, nullptr, nullptr, nullptr, &highBandModeBox); i = place(i, &highBandFreqLabel, &highBandFreqSlider); i = place(i, &highBandSlopeLabel,&highBandSlopeSlider); i = place(i, &highBandGainLabel, &highBandGainSlider); i = place(i, &highBandQLabel, &highBandQSlider); i = place(i, &inputLabel, &inputSlider); i = place(i, &outputLabel, &outputSlider); i = place(i, nullptr, nullptr, nullptr, &presetBox); i = place(i, nullptr, nullptr, nullptr, nullptr, &presetNameInput); // Untere Leiste: Buttons/Combos ordentlich platzieren auto leftBar = bottom.removeFromLeft(bottom.getWidth()/2); auto rightBar = bottom; testNoiseButton.setBounds(leftBar.removeFromLeft(160).reduced(8, 8)); crystalizeButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black); crystalizeButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black); resetButton.setBounds(leftBar.removeFromLeft(200).reduced(8, 8)); const int n = 4; // z.B. 4 Buttons rechts int btnW = rightBar.getWidth() / n; crystalizeButton .setBounds(rightBar.removeFromLeft(btnW).reduced(8, 8)); masterBypassButton .setBounds(rightBar.removeFromLeft(btnW).reduced(8, 8)); savePresetButton .setBounds(rightBar.removeFromLeft(btnW).reduced(8, 8)); deletePresetButton .setBounds(rightBar.removeFromLeft(btnW).reduced(8, 8)); // Wenn du den PresetMenu-Button auch rechts willst, erhöhe n und füge ihn hinzu, // oder pack ihn auf die linke Seite: presetMenuButton.setBounds(leftBar.removeFromLeft(180).reduced(8, 8)); masterBypassButton.setColour (juce::ToggleButton::textColourId, juce::Colours::black); masterBypassButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black); } void SpectrumAnalyzer::processSamples() { auto samples = audioFIFO.sendSamplesToEditor(); getFftFrame(samples); } 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); } } void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector &fullFrame) { if (fullFrame.size()!= FFTSIZE) return; window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE); processWindowedFrame(fullFrame); } 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(); } void SpectrumAnalyzer::fillFftDataFromFrame(std::vector &windowedFrame) { for (int n = 0; n < FFTSIZE; ++n) { fftData[2*n] = windowedFrame[n]; fftData[2*n + 1] = 0.0f; } } 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; } } 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; } } void SpectrumAnalyzer::applySmoothing() { applyEMA(); applyFreqSmoothing(); applyPeakHoldAndFalloff(); } void SpectrumAnalyzer::applyEMA() { for (int k = 0; k < magnitudesDb.size(); ++k) { float smoothedVal = smoothingFactor * magnitudesDb[k] + (1 - smoothingFactor) * emaSmoothedMagnitudesDb[k]; emaSmoothedMagnitudesDb[k] = smoothedVal; } } void SpectrumAnalyzer::applyFreqSmoothing() { for (int k = 0; k < BINS; ++k) { double freq = k * deltaF; double lowestFreq = freq * pow(2.0, -OCTAVERADIUS); double highestFreq = freq * pow(2.0, OCTAVERADIUS); int lowestBin = std::max(0, static_cast(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; } } 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); } } std::vector SpectrumAnalyzer::getRenderValues() { std::vector renderValues = peakHoldMagnitudesDb; return renderValues; }