mirror of
https://codeberg.org/armin/gelyk.git
synced 2026-10-11 17:01:53 +02:00
Complete Gelyk EQ plugin (dark/cyber/neon, scaling UI, git status bar)
This commit is contained in:
parent
13863f4522
commit
580fcf7fec
7 changed files with 145 additions and 113 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
.DS_Store
|
||||
|
|
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.15)
|
|||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
project(GelykEQ VERSION 1.0.0 LANGUAGES CXX)
|
||||
project(GelykEQ VERSION 1.0.0 LANGUAGES C CXX)
|
||||
|
||||
if(NOT DEFINED JUCE_ROOT)
|
||||
include(FetchContent)
|
||||
|
|
@ -98,6 +98,10 @@ target_include_directories(GelykEQ
|
|||
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/Source
|
||||
)
|
||||
|
||||
target_link_libraries(GelykEQ PRIVATE juce::juce_dsp)
|
||||
|
||||
target_compile_definitions(GelykEQ PRIVATE JUCE_MODULE_AVAILABLE_juce_dsp=1)
|
||||
|
||||
target_compile_definitions(GelykEQ PRIVATE
|
||||
JucePlugin_Build_VST=1
|
||||
JucePlugin_Build_AU=1
|
||||
|
|
|
|||
35
Makefile
35
Makefile
|
|
@ -1,21 +1,42 @@
|
|||
BUILD_DIR := build
|
||||
|
||||
# Standard macOS user plugin folders.
|
||||
AU_DIR := $(HOME)/Library/Audio/Plug-Ins/Components
|
||||
VST3_DIR := $(HOME)/Library/Audio/Plug-Ins/VST3
|
||||
|
||||
PLUGIN := "$(BUILD_DIR)/GelykEQ_artefacts/Release/AU/Gelyk EQ.component"
|
||||
VST3PLUG := "$(BUILD_DIR)/GelykEQ_artefacts/Release/VST3/Gelyk EQ.vst3"
|
||||
|
||||
J = $(shell sysctl -n hw.ncpu)
|
||||
|
||||
all: build
|
||||
|
||||
# Configure + build with CMake (default make).
|
||||
build: cmake-configure
|
||||
cmake --build $(BUILD_DIR) --config Release -j$(shell sysctl -n hw.ncpu)
|
||||
cmake --build $(BUILD_DIR) --config Release -j$(J)
|
||||
|
||||
cmake-configure:
|
||||
cmake -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
# Fully install the plugin into the macOS user plugin folders, then
|
||||
# refresh the system so the host (AudioUnit/VST3.scan) picks it up.
|
||||
install: build
|
||||
cmake --install $(BUILD_DIR)
|
||||
|
||||
# Install directly to the standard macOS user plugin folders (AU + VST3).
|
||||
install-local: build
|
||||
cmake --install $(BUILD_DIR) --prefix "$(HOME)"
|
||||
rm -rf "$(AU_DIR)/Gelyk EQ.component" "$(VST3_DIR)/Gelyk EQ.vst3"
|
||||
mkdir -p "$(AU_DIR)" "$(VST3_DIR)"
|
||||
cp -R $(PLUGIN) "$(AU_DIR)/"
|
||||
cp -R $(VST3PLUG) "$(VST3_DIR)/"
|
||||
@# Ad-hoc codesign the plugins so macOS/DAWs accept them.
|
||||
@if command -v codesign >/dev/null; then \
|
||||
(codesign --force --deep --sign - "$(AU_DIR)/Gelyk EQ.component" 2>/dev/null || true); \
|
||||
(codesign --force --deep --sign - "$(VST3_DIR)/Gelyk EQ.vst3" 2>/dev/null || true); \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "Installed:"
|
||||
@echo " $(AU_DIR)/Gelyk EQ.component"
|
||||
@echo " $(VST3_DIR)/Gelyk EQ.vst3"
|
||||
@touch ~/Library/Preferences/com.apple.audio.InfoHelper 2>/dev/null || true
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
.PHONY: all build cmake-configure install install-local clean
|
||||
.PHONY: all build cmake-configure install clean
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ void FilterBand::prepare(double sampleRate, int blockSize)
|
|||
switch (type)
|
||||
{
|
||||
case Type::lowShelf:
|
||||
c = new Coeffs(Coeffs::makeLowShelf(sampleRate, freq, q, juce::Decibels::decibelsToGain(db)));
|
||||
c = Coeffs::makeLowShelf(sampleRate, freq, q, juce::Decibels::decibelsToGain(db));
|
||||
break;
|
||||
case Type::highShelf:
|
||||
c = new Coeffs(Coeffs::makeHighShelf(sampleRate, freq, q, juce::Decibels::decibelsToGain(db)));
|
||||
c = Coeffs::makeHighShelf(sampleRate, freq, q, juce::Decibels::decibelsToGain(db));
|
||||
break;
|
||||
case Type::peak:
|
||||
default:
|
||||
c = new Coeffs(Coeffs::makePeakFilter(sampleRate, freq, q, juce::Decibels::decibelsToGain(db)));
|
||||
c = Coeffs::makePeakFilter(sampleRate, freq, q, juce::Decibels::decibelsToGain(db));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +60,10 @@ void FilterBand::process(juce::dsp::AudioBlock<float>& block)
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
SpectrumAnalyser::SpectrumAnalyser()
|
||||
: fft(12) // 4096 point FFT
|
||||
{
|
||||
const int size = fft.getSize();
|
||||
fftBuffer.resize((size_t) (size * 2), 0.0f);
|
||||
// The timer drives periodic background analysis.
|
||||
startTimerHz(30);
|
||||
}
|
||||
|
|
@ -68,6 +71,7 @@ SpectrumAnalyser::SpectrumAnalyser()
|
|||
void SpectrumAnalyser::prepare(double sr, int maxBlockSize)
|
||||
{
|
||||
const int size = 4096;
|
||||
(void) maxBlockSize;
|
||||
sampleRate = sr;
|
||||
ring.setSize(2, size);
|
||||
ring.clear();
|
||||
|
|
@ -115,31 +119,29 @@ void SpectrumAnalyser::runAnalysis()
|
|||
if (n == 0)
|
||||
return;
|
||||
|
||||
juce::AudioBuffer<float> block(1, n);
|
||||
// Mesa of the two channels and window with a Hann.
|
||||
block.copyFrom(0, 0, ring, 0, 0, n);
|
||||
if (ringChannels > 1)
|
||||
block.addFrom(0, 0, ring, 1, 0, n);
|
||||
|
||||
juce::dsp::FFT fft(12); // 4096 point
|
||||
juce::HeapBlock<std::complex<float>> fftData;
|
||||
fftData.calloc((size_t) n);
|
||||
for (int i = 0; i < n; ++i)
|
||||
// Mesa of the two channels, Hann-windowed into the FFT buffer.
|
||||
const auto fftSize = fft.getSize();
|
||||
for (int i = 0; i < fftSize; ++i)
|
||||
{
|
||||
float v = block.getSample(0, i) * 0.5f;
|
||||
const float win = 0.5f * (1.0f - std::cos(2.0f * juce::MathConstants<float>::pi * i / (n - 1)));
|
||||
fftData[i] = std::complex<float>(v * win, 0.0f);
|
||||
const int src = (ringFill - fftSize + i + n) % n; // contiguously oldest of last fftSize
|
||||
float v = ring.getSample(0, src);
|
||||
if (ringChannels > 1)
|
||||
v = 0.5f * (v + ring.getSample(1, src));
|
||||
const float win = 0.5f * (1.0f - std::cos(2.0f * juce::MathConstants<float>::pi * i / (fftSize - 1)));
|
||||
fftBuffer[(size_t) i] = v * win;
|
||||
}
|
||||
fft.perform((juce::dsp::Complex<float>*) fftData.getData(), false);
|
||||
for (size_t i = (size_t) fftSize; i < fftBuffer.size(); ++i)
|
||||
fftBuffer[i] = 0.0f;
|
||||
|
||||
const int numBins = n / 2;
|
||||
fft.performFrequencyOnlyForwardTransform(fftBuffer.data());
|
||||
|
||||
// Magnitude values (non-negative frequencies) are in the first size/2+1 entries.
|
||||
const int numBins = juce::jmin((int) fftBuffer.size(), fftSize / 2 + 1);
|
||||
juce::Array<float> mags;
|
||||
mags.ensureStorageAllocated(numBins);
|
||||
for (int i = 1; i < numBins; ++i)
|
||||
for (int i = 0; i < numBins; ++i)
|
||||
{
|
||||
float re = fftData[i].real();
|
||||
float im = fftData[i].imag();
|
||||
float mag = std::sqrt(re * re + im * im);
|
||||
float mag = fftBuffer[(size_t) i];
|
||||
float db = 20.0f * std::log10(mag + 1.0e-9f);
|
||||
mags.add(db);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class SpectrumAnalyser : public juce::Timer
|
|||
{
|
||||
public:
|
||||
SpectrumAnalyser();
|
||||
~SpectrumAnalyser() override = default;
|
||||
|
||||
void prepare(double sampleRate, int maxBlockSize);
|
||||
void push(const float* const* channels, int numChannels, int numSamples);
|
||||
|
|
@ -57,7 +58,10 @@ private:
|
|||
void runAnalysis();
|
||||
|
||||
mutable juce::CriticalSection spectrumLock;
|
||||
juce::Array<float> readySpectrum; // dB magnitudes per bin
|
||||
juce::Array<float> readySpectrum; // magnitude per bin
|
||||
|
||||
juce::dsp::FFT fft;
|
||||
std::vector<float> fftBuffer;
|
||||
|
||||
juce::AudioBuffer<float> ring;
|
||||
int ringFill = 0;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@
|
|||
namespace gelyk
|
||||
{
|
||||
|
||||
static juce::Font monoFont(float height)
|
||||
{
|
||||
return juce::Font(juce::FontOptions(juce::Font::getDefaultMonospacedFontName(), height,
|
||||
juce::Font::plain));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// NeonLookAndFeel
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -26,23 +32,15 @@ void NeonLookAndFeel::drawLinearSlider(juce::Graphics& g, int x, int y, int widt
|
|||
const float cx = x + width * 0.5f;
|
||||
|
||||
// Track
|
||||
juce::Path track;
|
||||
if (vertical)
|
||||
{
|
||||
const float tw = 2.0f;
|
||||
juce::Path track;
|
||||
track.addRoundedRectangle(cx - tw * 0.5f, (float) y, tw, (float) height, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float th = 2.0f;
|
||||
track.addRoundedRectangle((float) x, y + height * 0.5f - th * 0.5f, (float) width, th, 1.0f);
|
||||
}
|
||||
g.setColour(line);
|
||||
g.fillPath(track);
|
||||
g.setColour(line);
|
||||
g.fillPath(track);
|
||||
|
||||
// Filled region from centre (0 dB) down to the handle, glowing.
|
||||
if (vertical)
|
||||
{
|
||||
// Filled region from centre (0 dB) down to the handle, glowing.
|
||||
const float midY = y + height * 0.5f;
|
||||
const float top = juce::jmin(midY, sliderPos);
|
||||
const float bottom = juce::jmax(midY, sliderPos);
|
||||
|
|
@ -52,15 +50,20 @@ void NeonLookAndFeel::drawLinearSlider(juce::Graphics& g, int x, int y, int widt
|
|||
g.fillPath(fill);
|
||||
|
||||
// Handle.
|
||||
g.setColour(slider.isEnabled() && isMouseOverOrDragging(true) ? violet : cyan);
|
||||
g.setColour(slider.isMouseOverOrDragging(true) ? violet : cyan);
|
||||
g.fillEllipse(cx - 5.0f, sliderPos - 5.0f, 10.0f, 10.0f);
|
||||
g.setColour(bg);
|
||||
g.fillEllipse(cx - 2.0f, sliderPos - 2.0f, 4.0f, 4.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float th = 2.0f;
|
||||
juce::Path track;
|
||||
track.addRoundedRectangle((float) x, y + height * 0.5f - th * 0.5f, (float) width, th, 1.0f);
|
||||
g.setColour(line);
|
||||
g.fillPath(track);
|
||||
g.setColour(cyan);
|
||||
g.fillRect(x, y + height * 0.5f - 3.0f, (float) width, 6.0f);
|
||||
g.drawHorizontalLine((int) (y + height * 0.5f), sliderPos - 5.0f, sliderPos + 5.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +89,7 @@ void NeonLookAndFeel::drawComboBox(juce::Graphics& g, int width, int height, boo
|
|||
void NeonLookAndFeel::positionComboBoxText(juce::ComboBox& box, juce::Label& label)
|
||||
{
|
||||
label.setBounds(2, 1, box.getWidth() - 22, box.getHeight() - 2);
|
||||
label.setFont(juce::FontOptions(13.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
label.setFont(monoFont(13.0f));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -95,17 +98,14 @@ void NeonLookAndFeel::positionComboBoxText(juce::ComboBox& box, juce::Label& lab
|
|||
|
||||
GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor& p)
|
||||
: AudioProcessorEditor(&p),
|
||||
processor(p),
|
||||
analyserAttachment(processor.getAPVTS().parameterState,
|
||||
"analyserEnabled",
|
||||
[this](float v) { processor.getAnalyser().setEnabled(v > 0.5f); })
|
||||
processor(p)
|
||||
{
|
||||
setLookAndFeel(&lookAndFeel);
|
||||
setOpaque(true);
|
||||
|
||||
titleLabel = std::make_unique<juce::Label>("title", "GELYKEQ");
|
||||
titleLabel->setFont(juce::FontOptions(26.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
titleLabel->setColour(juce::Label::textColourId, cyan);
|
||||
titleLabel->setFont(monoFont(26.0f));
|
||||
titleLabel->setColour(juce::Label::textColourId, lookAndFeel.cyan);
|
||||
titleLabel->setJustificationType(juce::Justification::centredLeft);
|
||||
addAndMakeVisible(*titleLabel);
|
||||
|
||||
|
|
@ -116,25 +116,25 @@ GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor&
|
|||
scaleBox->addItem("175%", 175);
|
||||
scaleBox->addItem("200%", 200);
|
||||
scaleBox->setSelectedId(100, juce::dontSendNotification);
|
||||
scaleBox->setTooltip("UI scaling");
|
||||
scaleBox->onChange = [this] { scaleUI((float) scaleBox->getSelectedId()); };
|
||||
addAndMakeVisible(*scaleBox);
|
||||
|
||||
// Master gain (vertical fader, far right).
|
||||
masterLabel = std::make_unique<juce::Label>("mlab", "MASTER");
|
||||
masterLabel->setFont(juce::FontOptions(11.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
masterLabel->setColour(juce::Label::textColourId, magenta);
|
||||
masterLabel->setFont(monoFont(11.0f));
|
||||
masterLabel->setColour(juce::Label::textColourId, lookAndFeel.magenta);
|
||||
masterLabel->setJustificationType(juce::Justification::centred);
|
||||
master = std::make_unique<juce::Slider>(juce::Slider::LinearVertical,
|
||||
juce::Slider::TextBoxBelow);
|
||||
master = std::make_unique<juce::Slider>(juce::Slider::LinearVertical, juce::Slider::TextBoxBelow);
|
||||
master->setLookAndFeel(&lookAndFeel);
|
||||
master->setSliderStyle(juce::Slider::LinearVertical);
|
||||
master->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 40, 14);
|
||||
master->setRange(-24.0, 24.0, 0.01);
|
||||
master->setDoubleClickReturnValue(true, 0.0);
|
||||
addAndMakeVisible(*masterLabel);
|
||||
addAndMakeVisible(*master);
|
||||
masterAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processor.getAPVTS(), "master", *master);
|
||||
|
||||
// Band faders.
|
||||
const char* const names[numBands] = { "LOW", "LOW-MID", "MID", "HIGH-MID", "HIGH" };
|
||||
const char* const freqs[numBands] = { "60", "200", "1K", "4K", "10K" };
|
||||
|
|
@ -143,13 +143,14 @@ GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor&
|
|||
auto& w = bands[i];
|
||||
|
||||
w.freqLabel = std::make_unique<juce::Label>("freq", freqs[i]);
|
||||
w.freqLabel->setFont(juce::FontOptions(13.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
w.freqLabel->setColour(juce::Label::textColourId, textDim);
|
||||
w.freqLabel->setFont(monoFont(13.0f));
|
||||
w.freqLabel->setColour(juce::Label::textColourId, lookAndFeel.textDim);
|
||||
w.freqLabel->setJustificationType(juce::Justification::centred);
|
||||
|
||||
w.gainLabel = std::make_unique<juce::Label>("gname", names[i]);
|
||||
w.gainLabel->setFont(juce::FontOptions(11.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
w.gainLabel->setColour(juce::Label::textColourId, i % 2 == 0 ? cyan : magenta);
|
||||
w.gainLabel->setFont(monoFont(11.0f));
|
||||
w.gainLabel->setColour(juce::Label::textColourId,
|
||||
i % 2 == 0 ? lookAndFeel.cyan : lookAndFeel.magenta);
|
||||
w.gainLabel->setJustificationType(juce::Justification::centred);
|
||||
|
||||
w.gain = std::make_unique<juce::Slider>(juce::Slider::LinearVertical, juce::Slider::TextBoxBelow);
|
||||
|
|
@ -177,14 +178,14 @@ GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor&
|
|||
|
||||
// Status bar.
|
||||
statusGit = std::make_unique<juce::Label>("git", processor.getGitInfo());
|
||||
statusGit->setFont(juce::FontOptions(11.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
statusGit->setColour(juce::Label::textColourId, textDim);
|
||||
statusGit->setFont(monoFont(11.0f));
|
||||
statusGit->setColour(juce::Label::textColourId, lookAndFeel.textDim);
|
||||
statusGit->setJustificationType(juce::Justification::centredRight);
|
||||
|
||||
statusBar = std::make_unique<juce::Label>("status",
|
||||
"GELYKEQ v" + juce::String(ProjectInfo::versionString) + " | AU / VST3 ");
|
||||
statusBar->setFont(juce::FontOptions(11.0f).setTypefaceName(juce::Font::getDefaultMonospacedFontName()));
|
||||
statusBar->setColour(juce::Label::textColourId, textDim);
|
||||
"GELYKEQ v" + juce::String(ProjectInfo::versionString) + " | ANALYSER");
|
||||
statusBar->setFont(monoFont(11.0f));
|
||||
statusBar->setColour(juce::Label::textColourId, lookAndFeel.cyan.withAlpha(0.8f));
|
||||
statusBar->setJustificationType(juce::Justification::centredLeft);
|
||||
addAndMakeVisible(*statusBar);
|
||||
addAndMakeVisible(*statusGit);
|
||||
|
|
@ -193,7 +194,6 @@ GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor&
|
|||
freqBins.ensureStorageAllocated(128);
|
||||
|
||||
startTimerHz(30);
|
||||
rebuildFaders();
|
||||
scaleUI(100.0f);
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +211,6 @@ void GelykEQAudioProcessorEditor::timerCallback()
|
|||
|
||||
void GelykEQAudioProcessorEditor::rebuildFaders()
|
||||
{
|
||||
// Nothing needed at construction; sliders are attachments.
|
||||
}
|
||||
|
||||
void GelykEQAudioProcessorEditor::scaleUI(float percent)
|
||||
|
|
@ -233,7 +232,7 @@ void GelykEQAudioProcessorEditor::resized()
|
|||
|
||||
// Header.
|
||||
juce::Rectangle<int> header = area.removeFromTop(headerH);
|
||||
titleLabel->setBounds(header.removeFromLeft((int) (160 * s)).reduced(pad, 0));
|
||||
titleLabel->setBounds(header.removeFromLeft((int) (170 * s)).reduced(pad, 0));
|
||||
scaleBox->setBounds(header.removeFromRight((int) (90 * s)).reduced((int)(6*s), (int)(9*s)));
|
||||
|
||||
// Footer.
|
||||
|
|
@ -244,12 +243,12 @@ void GelykEQAudioProcessorEditor::resized()
|
|||
|
||||
// Main grid.
|
||||
const int masterW = (int) (64 * s);
|
||||
juce::Rectangle<int> mainArea = area;
|
||||
juce::Rectangle<int> masterArea = mainArea.removeFromRight(masterW);
|
||||
juce::Rectangle<int> masterArea = area.removeFromRight(masterW);
|
||||
|
||||
const int x0 = mainArea.getX();
|
||||
const int y0 = mainArea.getY();
|
||||
const int totalW = mainArea.getWidth();
|
||||
const int x0 = area.getX();
|
||||
const int y0 = area.getY();
|
||||
const int totalW = area.getWidth();
|
||||
const int height = area.getHeight();
|
||||
|
||||
const int gap = (int) (10 * s);
|
||||
const int colW = (totalW - gap * (numBands - 1)) / numBands;
|
||||
|
|
@ -257,35 +256,37 @@ void GelykEQAudioProcessorEditor::resized()
|
|||
for (int i = 0; i < numBands; ++i)
|
||||
{
|
||||
auto& w = bands[i];
|
||||
juce::Rectangle<int> col(x0 + i * (colW + gap), y0, colW, mainArea.getHeight());
|
||||
juce::Rectangle<int> col(x0 + i * (colW + gap), y0, colW, height);
|
||||
|
||||
const int topLabelH = (int) (18 * s);
|
||||
const int topLabelH = (int) (20 * s);
|
||||
const int freqH = (int) (16 * s);
|
||||
const int gainH = (int) (14 * s);
|
||||
const int gainSliderH = (int) (58 * s);
|
||||
const int qH = (int) (18 * s);
|
||||
|
||||
juce::Rectangle<int> sub = col.reduced((int)(8*s), 0);
|
||||
w.gainLabel->setBounds(sub.removeFromTop(topLabelH));
|
||||
w.gainLabel->setBounds(col.removeFromTop(topLabelH));
|
||||
|
||||
juce::Rectangle<int> analyserArea = sub.removeFromTop(sub.getHeight() - freqH - gainH - (int)(18*s) - (int)(10*s));
|
||||
const int analyserH = col.getHeight() - freqH - gainSliderH - qH - (int)(8*s);
|
||||
// reserve analyser space by subtracting it (faders sit on top of the spectrum)
|
||||
col.removeFromTop(juce::jmax(0, analyserH));
|
||||
|
||||
w.freqLabel->setBounds(sub.removeFromTop(freqH));
|
||||
w.gain->setBounds(sub.removeFromTop(gainH + (int)(34*s)));
|
||||
w.freqLabel->setBounds(col.removeFromTop(freqH));
|
||||
w.gain->setBounds(col.removeFromTop(gainSliderH));
|
||||
|
||||
w.q->setBounds(col.removeFromBottom((int)(18*s)).reduced((int)(8*s), 0));
|
||||
juce::Rectangle<int> qArea = col.removeFromBottom(qH);
|
||||
w.q->setBounds(qArea.reduced((int)(12*s), 0));
|
||||
}
|
||||
|
||||
// Master column.
|
||||
juce::Rectangle<int> msub = masterArea.reduced((int)(14*s), 0);
|
||||
masterLabel->setBounds(msub.removeFromTop((int) (18 * s)));
|
||||
juce::Rectangle<int> msub = masterArea.reduced((int)(14*s), (int)(6*s));
|
||||
masterLabel->setBounds(msub.removeFromTop((int) (20 * s)));
|
||||
master->setBounds(msub);
|
||||
}
|
||||
|
||||
void GelykEQAudioProcessorEditor::paint(juce::Graphics& g)
|
||||
{
|
||||
const float s = scaling.load();
|
||||
g.fillAll(bg);
|
||||
g.fillAll(lookAndFeel.bg);
|
||||
|
||||
// Analyser canvas area matches fader columns.
|
||||
juce::Rectangle<int> area = getLocalBounds();
|
||||
const int headerH = (int) (46 * s);
|
||||
const int footerH = (int) (26 * s);
|
||||
|
|
@ -294,38 +295,37 @@ void GelykEQAudioProcessorEditor::paint(juce::Graphics& g)
|
|||
mainArea.removeFromBottom(footerH);
|
||||
mainArea.removeFromRight((int) (64 * s));
|
||||
|
||||
// Neon grid lines behind faders.
|
||||
g.setColour(line);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
const int topPad = (int) (20 * s);
|
||||
const int botPad = (int) (28 * s);
|
||||
const float topY = (float) (mainArea.getY() + topPad);
|
||||
const float botY = (float) (mainArea.getY() + mainArea.getHeight() - botPad);
|
||||
|
||||
// Neon grid lines.
|
||||
g.setColour(lookAndFeel.line);
|
||||
for (int i = 1; i <= 4; ++i)
|
||||
{
|
||||
float f = (i + 1) / 5.0f;
|
||||
// 0 dB line.
|
||||
if (i == 2)
|
||||
continue;
|
||||
float yy = mainArea.getY() + 18.0f * s + (mainArea.getHeight() - 18.0f * s - 60.0f * s) * f;
|
||||
g.drawHorizontalLine((int) yy + (int)(18*s), (float) mainArea.getX(), (float) mainArea.getRight());
|
||||
if (i == 3) continue; // skip the 0 dB centre (drawn separately)
|
||||
float yy = topY + (botY - topY) * (i / 5.0f);
|
||||
g.drawHorizontalLine((int) yy, (float) mainArea.getX(), (float) mainArea.getRight());
|
||||
}
|
||||
|
||||
// The 0 dB centre line (glowing).
|
||||
const float centreLineY = mainArea.getY() + (int)(18*s) +
|
||||
(mainArea.getHeight() - (int)(18*s) - (int)(60*s)) * 0.5f;
|
||||
g.setColour(cyan.withAlpha(0.55f));
|
||||
// 0 dB centre line (glowing cyan).
|
||||
const float centreLineY = topY + (botY - topY) * 0.5f;
|
||||
g.setColour(lookAndFeel.cyan.withAlpha(0.55f));
|
||||
g.drawHorizontalLine((int) centreLineY, (float) mainArea.getX(), (float) mainArea.getRight());
|
||||
|
||||
// Spectrum overlay.
|
||||
if (spectrum.size() > 2)
|
||||
{
|
||||
juce::Path path;
|
||||
const int x0 = mainArea.getX();
|
||||
const int width = mainArea.getWidth();
|
||||
const float topY = mainArea.getY() + 18.0f * s;
|
||||
const float botY = mainArea.getY() + (int)(8*s) + (mainArea.getHeight() - (int)(18*s) - (int)(60*s)) * 0.92f;
|
||||
const float x0 = (float) mainArea.getX();
|
||||
const float width = (float) mainArea.getWidth();
|
||||
|
||||
for (int i = 0; i < spectrum.size(); ++i)
|
||||
{
|
||||
float db = juce::jlimit(-72.0f, 12.0f, spectrum[i]);
|
||||
float norm = (db + 72.0f) / 84.0f; // 0..1 up
|
||||
float xx = (float) x0 + (float) i / (float) (spectrum.size() - 1) * width;
|
||||
float xx = x0 + (float) i / (float) (spectrum.size() - 1) * width;
|
||||
float yy = botY - norm * (botY - topY);
|
||||
if (i == 0) path.startNewSubPath(xx, yy);
|
||||
else path.lineTo(xx, yy);
|
||||
|
|
@ -336,11 +336,12 @@ void GelykEQAudioProcessorEditor::paint(juce::Graphics& g)
|
|||
filled.lineTo((float) mainArea.getX(), botY);
|
||||
filled.closeSubPath();
|
||||
|
||||
g.setGradientFill(juce::ColourGradient(magenta.withAlpha(0.35f), (float) mainArea.getX(), topY,
|
||||
magenta.withAlpha(0.05f), (float) mainArea.getX(), botY,
|
||||
false));
|
||||
g.setGradientFill(juce::ColourGradient(lookAndFeel.magenta.withAlpha(0.30f),
|
||||
(float) mainArea.getX(), topY,
|
||||
lookAndFeel.magenta.withAlpha(0.05f),
|
||||
(float) mainArea.getX(), botY, false));
|
||||
g.fillPath(filled);
|
||||
g.setColour(cyan.withAlpha(0.9f));
|
||||
g.setColour(lookAndFeel.cyan.withAlpha(0.9f));
|
||||
g.strokePath(path, juce::PathStrokeType(1.6f * s));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,12 +67,10 @@ private:
|
|||
std::unique_ptr<juce::Label> statusBar;
|
||||
std::unique_ptr<juce::Label> statusGit;
|
||||
|
||||
juce::AudioProcessorValueTreeState::SliderAttachment gainAttachments[numBands];
|
||||
juce::AudioProcessorValueTreeState::SliderAttachment qAttachments[numBands];
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gainAttachments[numBands];
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> qAttachments[numBands];
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> masterAttachment;
|
||||
|
||||
juce::AudioProcessorValueTreeState::ParameterAttachment analyserAttachment;
|
||||
|
||||
juce::Array<float> spectrum;
|
||||
juce::Array<float> freqBins;
|
||||
std::atomic<float> scaling { 1.0f }; // for resized()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue