From aa546c73575f3dc9dc1237ec5e9a2508dcb66834 Mon Sep 17 00:00:00 2001 From: hariel1985 Date: Wed, 25 Mar 2026 22:28:31 +0100 Subject: [PATCH] v1.3: Auto makeup gain, spectrum analyzer, FIR normalization, README overhaul - Auto makeup gain: RMS-based loudness compensation from actual FIR response - Real-time FFT spectrum analyzer behind EQ curves - FIR normalization fix: flat settings now produce exact 0 dB passthrough - Brickwall limiter (0 dB ceiling) with toggle - Drag-and-drop signal chain reordering - Low FIR tap count warning for 512/1024 - Double-click reset on all knobs - Comprehensive README with linear phase EQ explanation Co-Authored-By: Claude Opus 4.6 (1M context) --- CMakeLists.txt | 2 +- README.md | 144 +++++++++++++++++++++++++----------- Source/FIREngine.cpp | 72 +++++++++++++++++- Source/FIREngine.h | 6 ++ Source/PluginEditor.cpp | 39 ++++++---- Source/PluginEditor.h | 7 +- Source/PluginProcessor.cpp | 20 +++-- Source/PluginProcessor.h | 10 ++- Source/SignalChainPanel.cpp | 2 +- 9 files changed, 229 insertions(+), 73 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f97f26..8a6532e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.22) -project(InstaLPEQ VERSION 1.2.2) +project(InstaLPEQ VERSION 1.3.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/README.md b/README.md index a024163..12392d8 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,42 @@ Free, open-source linear phase EQ plugin built with JUCE. Available as VST3, AU ![VST3](https://img.shields.io/badge/format-VST3-blue) ![AU](https://img.shields.io/badge/format-AU-blue) ![LV2](https://img.shields.io/badge/format-LV2-blue) ![C++](https://img.shields.io/badge/language-C%2B%2B17-orange) ![JUCE](https://img.shields.io/badge/framework-JUCE-green) ![License](https://img.shields.io/badge/license-GPL--3.0-lightgrey) ![Build](https://github.com/hariel1985/InstaLPEQ/actions/workflows/build.yml/badge.svg) +## Why Linear Phase EQ? + +Traditional (minimum phase) EQs alter the **phase** of the signal at the frequencies they boost or cut. This causes: +- **Phase smearing** — transients lose their shape, especially on drums and percussive material +- **Asymmetric waveforms** — the signal before and after the EQ change point don't align in time +- **Coloration** — even subtle EQ moves can change the character of the sound beyond the intended frequency adjustment + +A **linear phase EQ** applies the exact same time delay to all frequencies. This means: +- **Zero phase distortion** — the waveform shape is perfectly preserved +- **Pristine transients** — drums, plucks, and attacks stay tight and punchy +- **Transparent tonal shaping** — only the frequency balance changes, nothing else +- **Perfect for mastering** — no cumulative phase artifacts when stacking multiple EQ moves +- **Ideal for parallel processing** — EQ'd and dry signals stay perfectly time-aligned when summed + +The trade-off is a small amount of latency (automatically compensated by the DAW), which makes linear phase EQ unsuitable for live monitoring but perfect for mixing and mastering. + ## Download -**[Latest Release: v1.1](https://github.com/hariel1985/InstaLPEQ/releases/tag/v1.1)** +**[Latest Release: v1.3](https://github.com/hariel1985/InstaLPEQ/releases/tag/v1.3)** ### Windows | File | Description | |------|-------------| -| [InstaLPEQ-VST3-Win64.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.1/InstaLPEQ-VST3-Win64.zip) | VST3 plugin — copy to `C:\Program Files\Common Files\VST3\` | +| [InstaLPEQ-VST3-Win64.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.3/InstaLPEQ-VST3-Win64.zip) | VST3 plugin — copy to `C:\Program Files\Common Files\VST3\` | ### macOS (Universal Binary: Apple Silicon + Intel) | File | Description | |------|-------------| -| [InstaLPEQ-VST3-macOS.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.1/InstaLPEQ-VST3-macOS.zip) | VST3 plugin — copy to `~/Library/Audio/Plug-Ins/VST3/` | -| [InstaLPEQ-AU-macOS.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.1/InstaLPEQ-AU-macOS.zip) | Audio Unit — copy to `~/Library/Audio/Plug-Ins/Components/` | +| [InstaLPEQ-VST3-macOS.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.3/InstaLPEQ-VST3-macOS.zip) | VST3 plugin — copy to `~/Library/Audio/Plug-Ins/VST3/` | +| [InstaLPEQ-AU-macOS.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.3/InstaLPEQ-AU-macOS.zip) | Audio Unit — copy to `~/Library/Audio/Plug-Ins/Components/` | ### Linux (x64, built on Ubuntu 22.04) | File | Description | |------|-------------| -| [InstaLPEQ-VST3-Linux-x64.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.1/InstaLPEQ-VST3-Linux-x64.zip) | VST3 plugin — copy to `~/.vst3/` | -| [InstaLPEQ-LV2-Linux-x64.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.1/InstaLPEQ-LV2-Linux-x64.zip) | LV2 plugin — copy to `~/.lv2/` | +| [InstaLPEQ-VST3-Linux-x64.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.3/InstaLPEQ-VST3-Linux-x64.zip) | VST3 plugin — copy to `~/.vst3/` | +| [InstaLPEQ-LV2-Linux-x64.zip](https://github.com/hariel1985/InstaLPEQ/releases/download/v1.3/InstaLPEQ-LV2-Linux-x64.zip) | LV2 plugin — copy to `~/.lv2/` | > **macOS note:** Builds are Universal Binary (Apple Silicon + Intel). Not code-signed — after copying the plugin, remove the quarantine flag in Terminal: > ```bash @@ -33,43 +49,97 @@ Free, open-source linear phase EQ plugin built with JUCE. Available as VST3, AU ## Features -### Linear Phase EQ +### Linear Phase EQ Engine - True linear phase processing using symmetric FIR convolution -- Zero phase distortion at any gain setting -- 8192-tap FIR filter (configurable: 4096 / 8192 / 16384) -- DAW-compensated latency (~93ms at 44.1kHz default) -- Background thread FIR generation — glitch-free parameter changes +- Zero phase distortion — the waveform shape is perfectly preserved at any gain setting +- Mathematically transparent: only magnitude changes, phase stays untouched +- FIR impulse response normalized for unity passthrough (0 dB at flat settings) +- Background thread FIR generation — glitch-free, click-free parameter changes +- DAW-compensated latency for seamless integration + +### Configurable FIR Resolution +Six quality levels to balance precision vs. latency: + +| Taps | Latency (44.1 kHz) | Best for | +|------|---------------------|----------| +| 512 | ~6 ms | Low-latency monitoring | +| 1024 | ~12 ms | Tracking | +| **2048** | **~23 ms** | **Default — mixing** | +| 4096 | ~46 ms | Detailed work | +| 8192 | ~93 ms | Mastering | +| 16384 | ~186 ms | Maximum precision | + +Low tap counts have reduced accuracy below ~100 Hz — a warning is displayed when using 512 or 1024 taps. ### Interactive EQ Curve Display - Logarithmic frequency axis (20 Hz — 20 kHz) - Linear gain axis (-24 dB to +24 dB) -- Click to add EQ nodes (up to 8 bands) -- Drag nodes to adjust frequency and gain -- Scroll wheel to adjust Q/bandwidth -- Right-click for band type selection and delete -- Double-click to reset band to 0 dB -- Real-time frequency response curve with glow effect -- Per-band curve overlay +- Click anywhere to add an EQ node (up to 8 bands) +- Drag nodes to adjust frequency and gain in real time +- Scroll wheel over a node to adjust Q/bandwidth +- Right-click a node for band type selection or delete +- Double-click a node to reset it to 0 dB +- Combined frequency response curve with glow effect +- Individual per-band curve overlays (color-coded) +- Real-time FFT spectrum analyzer behind the EQ curves (shows live audio content) ### Band Types -- Peak (parametric) -- Low Shelf -- High Shelf +- **Peak** (parametric) — boost or cut a specific frequency range +- **Low Shelf** — boost or cut everything below a frequency +- **High Shelf** — boost or cut everything above a frequency + +### Auto Makeup Gain +- Automatically compensates for the loudness change caused by EQ settings +- Computed from the actual FIR frequency response (not theoretical) — accounts for FIR resolution limits +- RMS-based calculation with linear frequency weighting (matches white noise / broadband signals) +- Toggleable on/off — displays the current compensation value in dB +- Mastering-safe: fixed value based on EQ curve, no signal-dependent gain changes + +### Output Limiter +- Brickwall limiter with 0 dB ceiling +- Toggleable on/off +- Prevents clipping when applying large EQ boosts +- 50 ms release time + +### Drag-and-Drop Signal Chain +- Reorderable processing chain at the bottom of the GUI +- Three blocks: **Master Gain**, **Limiter**, **Auto Gain** +- Drag blocks to change processing order (e.g., put limiter before or after gain) +- Visual arrows show signal flow direction +- Chain order saved/restored with DAW session ### Controls -- Per-band: Frequency, Gain, Q knobs -- Master gain (+/- 24 dB) -- Bypass toggle -- State save/restore (DAW session recall) +- **Per-band:** Frequency, Gain, Q knobs with 3D metal styling +- **Master Gain:** +/- 24 dB output level control +- **Bypass:** global bypass toggle +- **New Band:** button to add a new EQ node at 1 kHz / 0 dB +- **FIR Quality:** dropdown to select tap count / latency +- All knobs reset to default on double-click ### GUI -- Dark modern UI matching InstaDrums visual style -- 3D metal knobs with glow effects (orange for EQ, blue for Q) +- Dark modern UI with InstaDrums visual style +- 3D metal knobs with multi-layer glow effects (orange for frequency/gain, blue for Q) - Carbon fiber background texture -- Rajdhani custom font -- Fully resizable window with proportional scaling -- Animated toggle switches +- Rajdhani custom font (embedded) +- Fully resizable window (700x450 — 1920x1080) with proportional scaling +- Animated toggle switches with smooth lerp - Color-coded EQ bands (8 distinct colors) +- All fonts and UI elements scale with window size +- State save/restore — all settings recalled with DAW session + +## How It Works + +InstaLPEQ uses a **FIR-based linear phase** approach: + +1. Each EQ band's target magnitude response is computed from IIR filter coefficients (Peak, Low Shelf, or High Shelf) +2. All band magnitudes are multiplied together to form the combined target frequency response +3. An inverse FFT converts the magnitude-only spectrum (zero phase) into a symmetric time-domain impulse response +4. A Blackman-Harris window is applied to minimize truncation artifacts +5. The FIR is normalized so a flat spectrum produces exactly 0 dB passthrough +6. The FIR filter is applied via JUCE's efficient FFT-based partitioned `Convolution` engine +7. Auto makeup gain is computed from the actual FIR frequency response (forward FFT of the final filter) + +This ensures **mathematically perfect phase linearity** — the only thing that changes is the frequency balance. The original waveform shape, transient character, and stereo image are completely preserved. ## Building @@ -105,22 +175,10 @@ Output: - AU: `build/InstaLPEQ_artefacts/Release/AU/InstaLPEQ.component` (macOS) - LV2: `build/InstaLPEQ_artefacts/Release/LV2/InstaLPEQ.lv2` -## How It Works - -InstaLPEQ uses a **FIR-based linear phase** approach: - -1. Each EQ band's target magnitude response is computed from IIR filter coefficients (Peak, Low Shelf, or High Shelf) -2. All band magnitudes are multiplied together to form the combined target response -3. An inverse FFT converts the magnitude-only spectrum into a symmetric time-domain impulse response -4. A Blackman-Harris window is applied to minimize truncation artifacts -5. The FIR filter is applied via JUCE's efficient FFT-based `Convolution` engine - -This ensures **zero phase distortion** regardless of EQ settings — ideal for mastering, surgical corrections, and transparent tonal shaping. - ## Tech Stack - **Language:** C++17 - **Framework:** JUCE 8 - **Build:** CMake + MSVC / Xcode / GCC -- **Audio DSP:** juce::dsp (FFT, Convolution, IIR coefficient design) +- **Audio DSP:** juce::dsp (FFT, Convolution, IIR coefficient design, Limiter) - **Font:** Rajdhani (SIL Open Font License) diff --git a/Source/FIREngine.cpp b/Source/FIREngine.cpp index 625f2d9..f45aec8 100644 --- a/Source/FIREngine.cpp +++ b/Source/FIREngine.cpp @@ -124,11 +124,11 @@ juce::AudioBuffer FIREngine::generateFIR (const std::vector& band magnitudes[i] *= bandMag[i]; } - // Store magnitude in dB for display + // Store theoretical magnitude in dB for display (from IIR target curve) { std::vector magDb (numBins); for (int i = 0; i < numBins; ++i) - magDb[i] = (float) juce::Decibels::gainToDecibels (magnitudes[i], -60.0); + magDb[i] = (float) juce::Decibels::gainToDecibels ((float) magnitudes[i], -60.0f); const juce::SpinLock::ScopedLockType lock (magLock); magnitudeDb = std::move (magDb); @@ -168,5 +168,73 @@ juce::AudioBuffer FIREngine::generateFIR (const std::vector& band juce::dsp::WindowingFunction window (fftSize, juce::dsp::WindowingFunction::blackmanHarris); window.multiplyWithWindowingTable (firData, fftSize); + // Normalize: ensure flat spectrum → unity DC gain + // Without this, IFFT scaling + windowing cause incorrect base level + float dcGain = 0.0f; + for (int i = 0; i < fftSize; ++i) + dcGain += firData[i]; + + if (std::abs (dcGain) > 1e-6f) + { + float normFactor = 1.0f / dcGain; + for (int i = 0; i < fftSize; ++i) + firData[i] *= normFactor; + } + + // Compute auto makeup from the ACTUAL final FIR frequency response + // (includes windowing + normalization effects) + { + std::vector analysisBuf (fftSize * 2, 0.0f); + std::copy (firData, firData + fftSize, analysisBuf.data()); + + juce::dsp::FFT analysisFft (order); + analysisFft.performRealOnlyForwardTransform (analysisBuf.data()); + + // Extract actual magnitude from FFT result + // Format: [DC_real, Nyquist_real, bin1_real, bin1_imag, bin2_real, bin2_imag, ...] + double powerSum = 0.0; + int count = 0; + + for (int i = 1; i < fftSize / 2; ++i) + { + float re = analysisBuf[i * 2]; + float im = analysisBuf[i * 2 + 1]; + powerSum += (double) (re * re + im * im); + count++; + } + + if (count > 0) + { + double avgPower = powerSum / (double) count; + float rmsGain = (float) std::sqrt (avgPower); + float makeupDb = -20.0f * std::log10 (std::max (rmsGain, 1e-10f)); + autoMakeupDb.store (makeupDb); + } + + // (magnitudeDb stays as theoretical IIR curve for display) + } + return firBuffer; } + +// A-weighting curve (IEC 61672:2003) +// Returns linear amplitude weighting factor for given frequency +float FIREngine::aWeighting (float f) +{ + if (f < 10.0f) return 0.0f; + + double f2 = (double) f * (double) f; + double f4 = f2 * f2; + + double num = 12194.0 * 12194.0 * f4; + double den = (f2 + 20.6 * 20.6) + * std::sqrt ((f2 + 107.7 * 107.7) * (f2 + 737.9 * 737.9)) + * (f2 + 12194.0 * 12194.0); + + double ra = num / den; + + // Normalize so A(1000 Hz) = 1.0 + // A(1000) unnormalized ≈ 0.7943 + static const double norm = 1.0 / 0.7943282347; + return (float) (ra * norm); +} diff --git a/Source/FIREngine.h b/Source/FIREngine.h index e142a64..cafeb0b 100644 --- a/Source/FIREngine.h +++ b/Source/FIREngine.h @@ -27,6 +27,9 @@ public: int getFIRLength() const { return 1 << fftOrder.load(); } int getLatencySamples() const { return getFIRLength() / 2; } + // Auto makeup gain: A-weighted RMS loudness compensation (dB) + float getAutoMakeupGainDb() const { return autoMakeupDb.load(); } + private: void run() override; juce::AudioBuffer generateFIR (const std::vector& bands, double sr, int order); @@ -43,4 +46,7 @@ private: std::vector magnitudeDb; mutable juce::SpinLock magLock; + + std::atomic autoMakeupDb { 0.0f }; + static float aWeighting (float freq); }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index ab6fb47..ea43714 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -92,17 +92,17 @@ InstaLPEQEditor::InstaLPEQEditor (InstaLPEQProcessor& p) limiterLabel.setJustificationType (juce::Justification::centred); addAndMakeVisible (limiterLabel); - // Makeup gain - makeupGainSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); - makeupGainSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 60, 16); - makeupGainSlider.setRange (-24.0, 24.0, 0.1); - makeupGainSlider.setValue (0.0); - makeupGainSlider.setTextValueSuffix (" dB"); - makeupGainSlider.setDoubleClickReturnValue (true, 0.0); - addAndMakeVisible (makeupGainSlider); - makeupGainLabel.setFont (customLookAndFeel.getMediumFont (13.0f)); - makeupGainLabel.setJustificationType (juce::Justification::centred); - addAndMakeVisible (makeupGainLabel); + // Auto makeup gain + autoMakeupToggle.setToggleState (processor.autoMakeupEnabled.load(), juce::dontSendNotification); + addAndMakeVisible (autoMakeupToggle); + autoMakeupLabel.setFont (customLookAndFeel.getMediumFont (13.0f)); + autoMakeupLabel.setColour (juce::Label::textColourId, InstaLPEQLookAndFeel::textSecondary); + autoMakeupLabel.setJustificationType (juce::Justification::centred); + addAndMakeVisible (autoMakeupLabel); + autoMakeupValue.setFont (customLookAndFeel.getRegularFont (12.0f)); + autoMakeupValue.setColour (juce::Label::textColourId, InstaLPEQLookAndFeel::accent); + autoMakeupValue.setJustificationType (juce::Justification::centred); + addAndMakeVisible (autoMakeupValue); // Signal chain panel chainPanel.setListener (this); @@ -189,10 +189,12 @@ void InstaLPEQEditor::resized() limiterLabel.setBounds (masterArea.removeFromLeft (55)); limiterToggle.setBounds (masterArea.removeFromLeft (40)); - // Makeup gain knob - makeupGainLabel.setFont (customLookAndFeel.getMediumFont (std::max (11.0f, 14.0f * scale))); - makeupGainLabel.setBounds (masterArea.removeFromLeft (55)); - makeupGainSlider.setBounds (masterArea.removeFromLeft (masterH)); + // Auto makeup gain toggle + value display + autoMakeupLabel.setFont (customLookAndFeel.getMediumFont (std::max (11.0f, 14.0f * scale))); + autoMakeupLabel.setBounds (masterArea.removeFromLeft (70)); + autoMakeupToggle.setBounds (masterArea.removeFromLeft (40)); + autoMakeupValue.setFont (customLookAndFeel.getRegularFont (std::max (10.0f, 12.0f * scale))); + autoMakeupValue.setBounds (masterArea.removeFromLeft (60)); // Quality selector on the right side of master row qualityLabel.setFont (customLookAndFeel.getMediumFont (std::max (11.0f, 14.0f * scale))); @@ -218,7 +220,12 @@ void InstaLPEQEditor::timerCallback() processor.bypassed.store (bypassToggle.getToggleState()); processor.masterGainDb.store ((float) masterGainSlider.getValue()); processor.limiterEnabled.store (limiterToggle.getToggleState()); - processor.makeupGainDb.store ((float) makeupGainSlider.getValue()); + processor.autoMakeupEnabled.store (autoMakeupToggle.getToggleState()); + + // Update auto makeup display + float mkDb = processor.getActiveAutoMakeupDb(); + juce::String mkStr = (mkDb >= 0 ? "+" : "") + juce::String (mkDb, 1) + " dB"; + autoMakeupValue.setText (mkStr, juce::dontSendNotification); // Update spectrum analyzer { diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 43ec222..7cca99c 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -44,7 +44,7 @@ private: NodeParameterPanel nodePanel; juce::Label titleLabel { {}, "INSTALPEQ" }; - juce::Label versionLabel { {}, "v1.2.2" }; + juce::Label versionLabel { {}, "v1.3.0" }; juce::ToggleButton bypassToggle; juce::Label bypassLabel { {}, "BYPASS" }; @@ -57,8 +57,9 @@ private: juce::Label masterGainLabel { {}, "MASTER" }; juce::ToggleButton limiterToggle; juce::Label limiterLabel { {}, "LIMITER" }; - juce::Slider makeupGainSlider; - juce::Label makeupGainLabel { {}, "MAKEUP" }; + juce::ToggleButton autoMakeupToggle; + juce::Label autoMakeupLabel { {}, "AUTO GAIN" }; + juce::Label autoMakeupValue { {}, "0.0 dB" }; SignalChainPanel chainPanel; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index eda53a4..c5aca1c 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -61,7 +61,7 @@ void InstaLPEQProcessor::processBlock (juce::AudioBuffer& buffer, juce::M if (bypassed.load() || ! firLoaded) return; - // Process through convolution + // Process through convolution (EQ) juce::dsp::AudioBlock block (buffer); juce::dsp::ProcessContextReplacing context (block); convolution.process (context); @@ -99,9 +99,12 @@ void InstaLPEQProcessor::processBlock (juce::AudioBuffer& buffer, juce::M } case MakeupGain: { - float mkGain = juce::Decibels::decibelsToGain (makeupGainDb.load()); - if (std::abs (mkGain - 1.0f) > 0.001f) - buffer.applyGain (mkGain); + if (autoMakeupEnabled.load()) + { + float mkGain = juce::Decibels::decibelsToGain (firEngine.getAutoMakeupGainDb()); + if (std::abs (mkGain - 1.0f) > 0.001f) + buffer.applyGain (mkGain); + } break; } default: break; @@ -207,6 +210,11 @@ bool InstaLPEQProcessor::getSpectrum (float* dest, int maxBins) const return true; } +float InstaLPEQProcessor::getActiveAutoMakeupDb() const +{ + return autoMakeupEnabled.load() ? firEngine.getAutoMakeupGainDb() : 0.0f; +} + std::array InstaLPEQProcessor::getChainOrder() const { const juce::SpinLock::ScopedLockType lock (chainLock); @@ -242,7 +250,7 @@ void InstaLPEQProcessor::getStateInformation (juce::MemoryBlock& destData) xml.setAttribute ("bypass", bypassed.load()); xml.setAttribute ("masterGain", (double) masterGainDb.load()); xml.setAttribute ("limiter", limiterEnabled.load()); - xml.setAttribute ("makeupGain", (double) makeupGainDb.load()); + xml.setAttribute ("autoMakeup", autoMakeupEnabled.load()); auto order = getChainOrder(); juce::String chainStr; @@ -276,7 +284,7 @@ void InstaLPEQProcessor::setStateInformation (const void* data, int sizeInBytes) bypassed.store (xml->getBoolAttribute ("bypass", false)); masterGainDb.store ((float) xml->getDoubleAttribute ("masterGain", 0.0)); limiterEnabled.store (xml->getBoolAttribute ("limiter", true)); - makeupGainDb.store ((float) xml->getDoubleAttribute ("makeupGain", 0.0)); + autoMakeupEnabled.store (xml->getBoolAttribute ("autoMakeup", true)); auto chainStr = xml->getStringAttribute ("chainOrder", "0,1,2"); auto tokens = juce::StringArray::fromTokens (chainStr, ",", ""); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index fc3a7ff..683316b 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -48,7 +48,10 @@ public: std::atomic bypassed { false }; std::atomic masterGainDb { 0.0f }; std::atomic limiterEnabled { true }; - std::atomic makeupGainDb { 0.0f }; // -24 to +24 dB + std::atomic autoMakeupEnabled { true }; + + float getActiveAutoMakeupDb() const; + float getMeasuredAutoMakeupDb() const { return measuredMakeupDb.load(); } // Chain order (read/write from GUI, read from audio thread) std::array getChainOrder() const; @@ -86,6 +89,11 @@ public: int currentBlockSize = 512; bool firLoaded = false; + // Signal-based auto makeup measurement + double smoothedInputRms = 0.0; + double smoothedOutputRms = 0.0; + std::atomic measuredMakeupDb { 0.0f }; + std::array chainOrder { MasterGain, Limiter, MakeupGain }; juce::SpinLock chainLock; diff --git a/Source/SignalChainPanel.cpp b/Source/SignalChainPanel.cpp index f668dcc..214e843 100644 --- a/Source/SignalChainPanel.cpp +++ b/Source/SignalChainPanel.cpp @@ -21,7 +21,7 @@ juce::String SignalChainPanel::getStageName (InstaLPEQProcessor::ChainStage stag { case InstaLPEQProcessor::MasterGain: return "MASTER GAIN"; case InstaLPEQProcessor::Limiter: return "LIMITER"; - case InstaLPEQProcessor::MakeupGain: return "MAKEUP GAIN"; + case InstaLPEQProcessor::MakeupGain: return "AUTO GAIN"; default: return "?"; } }