mirror of
https://codeberg.org/armin/gelyk.git
synced 2026-10-11 17:01:53 +02:00
Add per-band frequency control and static EQ curve overlay
This commit is contained in:
parent
10feb7a7fe
commit
8e9eb733cb
5 changed files with 172 additions and 11 deletions
|
|
@ -231,10 +231,42 @@ GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor&
|
|||
bands[i].qValue->setText(juce::String(bands[i].q->getValue(), 2), juce::dontSendNotification);
|
||||
};
|
||||
|
||||
w.freqCaption = std::make_unique<juce::Label>("fcap", "FREQ");
|
||||
w.freqCaption->setFont(monoFont(10.0f));
|
||||
w.freqCaption->setColour(juce::Label::textColourId, lookAndFeel.textDim);
|
||||
w.freqCaption->setJustificationType(juce::Justification::centred);
|
||||
|
||||
w.freqValue = std::make_unique<juce::Label>("fval", "160");
|
||||
w.freqValue->setFont(monoFont(10.0f));
|
||||
w.freqValue->setColour(juce::Label::textColourId, lookAndFeel.cyan);
|
||||
w.freqValue->setJustificationType(juce::Justification::centred);
|
||||
|
||||
w.freq = std::make_unique<juce::Slider>(juce::Slider::RotaryVerticalDrag, juce::Slider::NoTextBox);
|
||||
w.freq->setLookAndFeel(&lookAndFeel);
|
||||
w.freq->setSliderStyle(juce::Slider::RotaryVerticalDrag);
|
||||
w.freq->setRotaryParameters(2.4f, 6.9f, true); // ~270° sweep
|
||||
auto freqRange = juce::NormalisableRange<double>(20.0, 20000.0, 0.5);
|
||||
freqRange.setSkewForCentre(1000.0);
|
||||
w.freq->setNormalisableRange(freqRange);
|
||||
w.freq->setDoubleClickReturnValue(true, 1000.0f);
|
||||
w.freq->onValueChange = [this, i]
|
||||
{
|
||||
float f = bands[i].freq->getValue();
|
||||
juce::String txt;
|
||||
if (f >= 1000.0f)
|
||||
txt = juce::String(f / 1000.0f, 1) + "K";
|
||||
else
|
||||
txt = juce::String((int) f);
|
||||
bands[i].freqValue->setText(txt, juce::dontSendNotification);
|
||||
};
|
||||
|
||||
addAndMakeVisible(*w.freqLabel);
|
||||
addAndMakeVisible(*w.gainCaption);
|
||||
addAndMakeVisible(*w.gainValue);
|
||||
addAndMakeVisible(*w.gain);
|
||||
addAndMakeVisible(*w.freqCaption);
|
||||
addAndMakeVisible(*w.freqValue);
|
||||
addAndMakeVisible(*w.freq);
|
||||
addAndMakeVisible(*w.qCaption);
|
||||
addAndMakeVisible(*w.q);
|
||||
addAndMakeVisible(*w.qValue);
|
||||
|
|
@ -243,6 +275,8 @@ GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor&
|
|||
processor.getAPVTS(), "gain" + juce::String(i), *w.gain);
|
||||
qAttachments[i] = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processor.getAPVTS(), "q" + juce::String(i), *w.q);
|
||||
freqAttachments[i] = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processor.getAPVTS(), "freq" + juce::String(i), *w.freq);
|
||||
}
|
||||
|
||||
// Status bar.
|
||||
|
|
@ -261,6 +295,7 @@ GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor&
|
|||
|
||||
spectrum.ensureStorageAllocated(128);
|
||||
freqBins.ensureStorageAllocated(128);
|
||||
eqCurve.resize(128);
|
||||
|
||||
startTimerHz(30);
|
||||
scaleUI(100.0f);
|
||||
|
|
@ -275,6 +310,21 @@ GelykEQAudioProcessorEditor::~GelykEQAudioProcessorEditor()
|
|||
void GelykEQAudioProcessorEditor::timerCallback()
|
||||
{
|
||||
processor.getAnalyser().copySpectrum(spectrum, freqBins, 128);
|
||||
|
||||
const int n = 128;
|
||||
FilterBand::Type types[numBands];
|
||||
float gains[numBands], freqs[numBands], qs[numBands];
|
||||
auto& params = processor.getAPVTS();
|
||||
for (int i = 0; i < numBands; ++i)
|
||||
{
|
||||
types[i] = (FilterBand::Type)(i == 0 ? 0 : (i == numBands - 1 ? 2 : 1));
|
||||
gains[i] = params.getRawParameterValue("gain" + juce::String(i))->load();
|
||||
freqs[i] = params.getRawParameterValue("freq" + juce::String(i))->load();
|
||||
qs[i] = params.getRawParameterValue("q" + juce::String(i))->load();
|
||||
}
|
||||
computeEQResponse(types, gains, freqs, qs, numBands,
|
||||
(double) processor.getSampleRate(), eqCurve.getRawDataPointer(), n);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
|
@ -330,24 +380,31 @@ void GelykEQAudioProcessorEditor::resized()
|
|||
const int captionH = (int) (18 * s);
|
||||
const int valueH = (int) (15 * s);
|
||||
const int freqH = (int) (20 * s);
|
||||
const int qValueH = (int) (14 * s);
|
||||
const int qKnobH = (int) (58 * s);
|
||||
const int qCapH = (int) (14 * s);
|
||||
const int knobStackH = (int) (14 * s) + (int)(58 * s) + (int)(12 * s); // value + knob + caption
|
||||
|
||||
w.gainCaption->setBounds(col.removeFromTop(captionH));
|
||||
w.gainValue->setBounds(col.removeFromTop(valueH));
|
||||
|
||||
// The gain fader fills the large analyser area (spectrum drawn behind it).
|
||||
w.gain->setBounds(col.removeFromTop(col.getHeight()
|
||||
- freqH - qValueH - qKnobH - qCapH - (int)(6*s)));
|
||||
w.gain->setBounds(col.removeFromTop(col.getHeight() - freqH - knobStackH - (int)(6*s)));
|
||||
|
||||
w.freqLabel->setBounds(col.removeFromTop(freqH));
|
||||
|
||||
// Q rotary control: value label, knob, caption.
|
||||
w.qValue->setBounds(col.removeFromTop(qValueH));
|
||||
juce::Rectangle<int> knobArea = col.removeFromTop(qKnobH);
|
||||
w.q->setBounds(knobArea.reduced((int)(colW * 0.22f), 0));
|
||||
w.qCaption->setBounds(col.removeFromTop(qCapH));
|
||||
// Bottom control row: FREQ and Q knobs side by side.
|
||||
juce::Rectangle<int> knobRow = col;
|
||||
int rowW = knobRow.getWidth();
|
||||
int halfW = rowW / 2;
|
||||
|
||||
// FREQ (left half).
|
||||
juce::Rectangle<int> freqCol = knobRow.removeFromLeft(halfW);
|
||||
w.freqValue->setBounds(freqCol.removeFromTop((int)(14*s)));
|
||||
w.freq->setBounds(freqCol.removeFromTop((int)(58*s)).reduced((int)(colW*0.08f), 0));
|
||||
w.freqCaption->setBounds(freqCol.removeFromTop((int)(12*s)));
|
||||
|
||||
// Q (right half).
|
||||
w.qValue->setBounds(knobRow.removeFromTop((int)(14*s)));
|
||||
w.q->setBounds(knobRow.removeFromTop((int)(58*s)).reduced((int)(colW*0.08f), 0));
|
||||
w.qCaption->setBounds(knobRow.removeFromTop((int)(12*s)));
|
||||
}
|
||||
|
||||
// Master column.
|
||||
|
|
@ -418,6 +475,32 @@ void GelykEQAudioProcessorEditor::paint(juce::Graphics& g)
|
|||
g.setColour(lookAndFeel.cyan.withAlpha(0.9f));
|
||||
g.strokePath(path, juce::PathStrokeType(1.6f * s));
|
||||
}
|
||||
|
||||
// Static EQ curve overlay (the actual summed filter response).
|
||||
if (eqCurve.size() > 2)
|
||||
{
|
||||
const float x0 = (float) mainArea.getX();
|
||||
const float width = (float) mainArea.getWidth();
|
||||
const float centreY = topY + (botY - topY) * 0.5f;
|
||||
const float halfH = (botY - topY) * 0.42f; // ±24 dB maps here
|
||||
|
||||
juce::Path curve;
|
||||
for (int i = 0; i < eqCurve.size(); ++i)
|
||||
{
|
||||
float db = juce::jlimit(-24.0f, 24.0f, eqCurve[i]);
|
||||
float yy = centreY - (db / 24.0f) * halfH;
|
||||
float xx = x0 + (float) i / (float) (eqCurve.size() - 1) * width;
|
||||
if (i == 0) curve.startNewSubPath(xx, yy);
|
||||
else curve.lineTo(xx, yy);
|
||||
}
|
||||
|
||||
g.setColour(lookAndFeel.violet.withAlpha(0.20f));
|
||||
g.strokePath(curve, juce::PathStrokeType(7.0f * s, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
g.setColour(lookAndFeel.violet);
|
||||
g.strokePath(curve, juce::PathStrokeType(1.8f * s, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gelyk
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue