877 lines
30 KiB
C++
877 lines
30 KiB
C++
/*
|
||
==============================================================================
|
||
|
||
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);
|
||
|
||
|
||
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<juce::AudioParameterChoice*>(
|
||
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<ComboBoxAttach>(
|
||
audioProcessor.apvts, "LowBandModes", lowBandModeBox);
|
||
}
|
||
|
||
if (auto* choice = dynamic_cast<juce::AudioParameterChoice*>(
|
||
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<ComboBoxAttach>(
|
||
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<SliderAttach>(audioProcessor.apvts, "TestNoiseLevel", testNoiseSlider);
|
||
|
||
lowBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandFreq", lowBandFreqSlider);
|
||
lowBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandSlope", lowBandSlopeSlider);
|
||
lowBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandGain", lowBandGainSlider);
|
||
lowBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandQ", lowBandQSlider);
|
||
lowBandModeAttach = std::make_unique<ComboBoxAttach>(audioProcessor.apvts, "LowBandModes", lowBandModeBox);
|
||
|
||
|
||
peak1FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Freq", peak1FreqSlider);
|
||
peak1GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Gain", peak1GainSlider);
|
||
peak1QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Q", peak1QSlider);
|
||
|
||
peak2FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Freq", peak2FreqSlider);
|
||
peak2GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Gain", peak2GainSlider);
|
||
peak2QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Q", peak2QSlider);
|
||
|
||
peak3FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Freq", peak3FreqSlider);
|
||
peak3GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Gain", peak3GainSlider);
|
||
peak3QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Q", peak3QSlider);
|
||
|
||
|
||
highBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandFreq", highBandFreqSlider);
|
||
highBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandSlope", highBandSlopeSlider);
|
||
highBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandGain", highBandGainSlider);
|
||
highBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandQ", highBandQSlider);
|
||
highBandModeAttach = std::make_unique<ComboBoxAttach>(audioProcessor.apvts, "HighBandModes", highBandModeBox);
|
||
|
||
|
||
inputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "InputGain", inputSlider);
|
||
outputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "OutputGain", outputSlider);
|
||
|
||
|
||
// 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<float>& samples) {
|
||
|
||
const int needed = FFTSIZE - fftFrame.size();
|
||
if (needed <= 0)
|
||
return;
|
||
|
||
const int available = samples.size();
|
||
if (available <= 0)
|
||
return;
|
||
|
||
const int take = std::min(needed, available);
|
||
|
||
for (int i = 0; i < take; ++i)
|
||
fftFrame.add(samples.getUnchecked(i));
|
||
|
||
samples.removeRange(0, take);
|
||
|
||
if (fftFrame.size() == FFTSIZE)
|
||
{
|
||
std::vector<float> fullFrame(fftFrame.begin(), fftFrame.end());
|
||
fftFrame.removeRange(0, HOPSIZE);
|
||
applyWindowOnFftFrame(fullFrame);
|
||
}
|
||
}
|
||
|
||
void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector<float> &fullFrame) {
|
||
if (fullFrame.size()!= FFTSIZE) return;
|
||
window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE);
|
||
processWindowedFrame(fullFrame);
|
||
}
|
||
|
||
void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
|
||
if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return;
|
||
fillFftDataFromFrame(windowedFrame);
|
||
fft.performRealOnlyForwardTransform(fftData.data());
|
||
buildMagnitudeSpectrum();
|
||
convertToDb();
|
||
applySmoothing();
|
||
renderValuesDb = getRenderValues();
|
||
|
||
}
|
||
|
||
void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
|
||
for (int n = 0; n < FFTSIZE; ++n) {
|
||
fftData[2*n] = windowedFrame[n];
|
||
fftData[2*n + 1] = 0.0f;
|
||
}
|
||
}
|
||
|
||
|
||
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<int>(floor(lowestFreq / deltaF)));
|
||
int highestBin = std::min(BINS - 1, static_cast<int>(ceil(highestFreq / deltaF)));
|
||
|
||
if (lowestBin > highestBin) {
|
||
lowestBin = k;
|
||
highestBin = k;
|
||
};
|
||
|
||
if (k == 0) {lowestBin = 0; highestBin = std::min(1, BINS - 1);}
|
||
|
||
float sum = 0.f;
|
||
|
||
for (int bin = lowestBin; bin <= highestBin; ++bin) {
|
||
sum += emaSmoothedMagnitudesDb[bin];
|
||
}
|
||
float avg = sum / static_cast<float>(highestBin - lowestBin + 1);
|
||
freqSmoothedMagnitudesDb[k] = avg;
|
||
|
||
}
|
||
}
|
||
|
||
|
||
void SpectrumAnalyzer::applyPeakHoldAndFalloff() {
|
||
std::vector<float> prevPeak = peakHoldMagnitudesDb;
|
||
for (int k = 0; k < BINS; ++k) {
|
||
float current = freqSmoothedMagnitudesDb[k];
|
||
float prev = prevPeak[k];
|
||
peakHoldMagnitudesDb[k] = std::max(current, prev - FALLOFFRATE * DELTAT);
|
||
}
|
||
}
|
||
|
||
std::vector<float> SpectrumAnalyzer::getRenderValues() {
|
||
std::vector<float> renderValues = peakHoldMagnitudesDb;
|
||
return renderValues;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|