2 Commit-ok

Szerző SHA1 Üzenet Dátum
hariel1985
35cf01a163 v1.3.2: Log-spaced auto makeup gain for musical accuracy
Auto makeup now evaluates at log-spaced frequencies (equal weight per
octave) instead of linear. Fixes over-compensation on high shelf boosts
where 5k-22kHz dominated the calculation despite having little musical
energy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:57:28 +01:00
hariel1985
aa546c7357 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) <noreply@anthropic.com>
2026-03-25 22:28:31 +01:00
9 fájl változott, egészen pontosan 250 új sor hozzáadva és 73 régi sor törölve

Fájl megtekintése

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.22) cmake_minimum_required(VERSION 3.22)
project(InstaLPEQ VERSION 1.2.2) project(InstaLPEQ VERSION 1.3.2)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)

144
README.md
Fájl megtekintése

@@ -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) ![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 ## 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 ### Windows
| File | Description | | 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) ### macOS (Universal Binary: Apple Silicon + Intel)
| File | Description | | 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-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.1/InstaLPEQ-AU-macOS.zip) | Audio Unit — copy to `~/Library/Audio/Plug-Ins/Components/` | | [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) ### Linux (x64, built on Ubuntu 22.04)
| File | Description | | 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-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.1/InstaLPEQ-LV2-Linux-x64.zip) | LV2 plugin — copy to `~/.lv2/` | | [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: > **macOS note:** Builds are Universal Binary (Apple Silicon + Intel). Not code-signed — after copying the plugin, remove the quarantine flag in Terminal:
> ```bash > ```bash
@@ -33,43 +49,97 @@ Free, open-source linear phase EQ plugin built with JUCE. Available as VST3, AU
## Features ## Features
### Linear Phase EQ ### Linear Phase EQ Engine
- True linear phase processing using symmetric FIR convolution - True linear phase processing using symmetric FIR convolution
- Zero phase distortion at any gain setting - Zero phase distortion — the waveform shape is perfectly preserved at any gain setting
- 8192-tap FIR filter (configurable: 4096 / 8192 / 16384) - Mathematically transparent: only magnitude changes, phase stays untouched
- DAW-compensated latency (~93ms at 44.1kHz default) - FIR impulse response normalized for unity passthrough (0 dB at flat settings)
- Background thread FIR generation — glitch-free parameter changes - 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 ### Interactive EQ Curve Display
- Logarithmic frequency axis (20 Hz — 20 kHz) - Logarithmic frequency axis (20 Hz — 20 kHz)
- Linear gain axis (-24 dB to +24 dB) - Linear gain axis (-24 dB to +24 dB)
- Click to add EQ nodes (up to 8 bands) - Click anywhere to add an EQ node (up to 8 bands)
- Drag nodes to adjust frequency and gain - Drag nodes to adjust frequency and gain in real time
- Scroll wheel to adjust Q/bandwidth - Scroll wheel over a node to adjust Q/bandwidth
- Right-click for band type selection and delete - Right-click a node for band type selection or delete
- Double-click to reset band to 0 dB - Double-click a node to reset it to 0 dB
- Real-time frequency response curve with glow effect - Combined frequency response curve with glow effect
- Per-band curve overlay - Individual per-band curve overlays (color-coded)
- Real-time FFT spectrum analyzer behind the EQ curves (shows live audio content)
### Band Types ### Band Types
- Peak (parametric) - **Peak** (parametric) — boost or cut a specific frequency range
- Low Shelf - **Low Shelf** — boost or cut everything below a frequency
- High Shelf - **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 ### Controls
- Per-band: Frequency, Gain, Q knobs - **Per-band:** Frequency, Gain, Q knobs with 3D metal styling
- Master gain (+/- 24 dB) - **Master Gain:** +/- 24 dB output level control
- Bypass toggle - **Bypass:** global bypass toggle
- State save/restore (DAW session recall) - **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 ### GUI
- Dark modern UI matching InstaDrums visual style - Dark modern UI with InstaDrums visual style
- 3D metal knobs with glow effects (orange for EQ, blue for Q) - 3D metal knobs with multi-layer glow effects (orange for frequency/gain, blue for Q)
- Carbon fiber background texture - Carbon fiber background texture
- Rajdhani custom font - Rajdhani custom font (embedded)
- Fully resizable window with proportional scaling - Fully resizable window (700x450 — 1920x1080) with proportional scaling
- Animated toggle switches - Animated toggle switches with smooth lerp
- Color-coded EQ bands (8 distinct colors) - 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 ## Building
@@ -105,22 +175,10 @@ Output:
- AU: `build/InstaLPEQ_artefacts/Release/AU/InstaLPEQ.component` (macOS) - AU: `build/InstaLPEQ_artefacts/Release/AU/InstaLPEQ.component` (macOS)
- LV2: `build/InstaLPEQ_artefacts/Release/LV2/InstaLPEQ.lv2` - 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 ## Tech Stack
- **Language:** C++17 - **Language:** C++17
- **Framework:** JUCE 8 - **Framework:** JUCE 8
- **Build:** CMake + MSVC / Xcode / GCC - **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) - **Font:** Rajdhani (SIL Open Font License)

Fájl megtekintése

@@ -124,11 +124,11 @@ juce::AudioBuffer<float> FIREngine::generateFIR (const std::vector<EQBand>& band
magnitudes[i] *= bandMag[i]; magnitudes[i] *= bandMag[i];
} }
// Store magnitude in dB for display // Store theoretical magnitude in dB for display (from IIR target curve)
{ {
std::vector<float> magDb (numBins); std::vector<float> magDb (numBins);
for (int i = 0; i < numBins; ++i) 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); const juce::SpinLock::ScopedLockType lock (magLock);
magnitudeDb = std::move (magDb); magnitudeDb = std::move (magDb);
@@ -168,5 +168,94 @@ juce::AudioBuffer<float> FIREngine::generateFIR (const std::vector<EQBand>& band
juce::dsp::WindowingFunction<float> window (fftSize, juce::dsp::WindowingFunction<float>::blackmanHarris); juce::dsp::WindowingFunction<float> window (fftSize, juce::dsp::WindowingFunction<float>::blackmanHarris);
window.multiplyWithWindowingTable (firData, fftSize); 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<float> 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, ...]
// Evaluate at log-spaced frequencies (equal weight per octave)
// This matches musical content much better than linear spacing
// (linear gives 77% weight to 5k-22k where music has little energy)
const int numPoints = 512;
double powerSum = 0.0;
double binRes = sr / (double) fftSize; // Hz per bin
for (int p = 0; p < numPoints; ++p)
{
// Log-spaced 20 Hz — 20 kHz
double freq = 20.0 * std::pow (1000.0, (double) p / (double) (numPoints - 1));
// Interpolate magnitude from FFT bins
double binFloat = freq / binRes;
int bin = (int) binFloat;
double frac = binFloat - (double) bin;
if (bin < 1 || bin >= fftSize / 2 - 1)
continue;
float re0 = analysisBuf[bin * 2];
float im0 = analysisBuf[bin * 2 + 1];
float re1 = analysisBuf[(bin + 1) * 2];
float im1 = analysisBuf[(bin + 1) * 2 + 1];
double mag0 = std::sqrt ((double) (re0 * re0 + im0 * im0));
double mag1 = std::sqrt ((double) (re1 * re1 + im1 * im1));
double mag = mag0 * (1.0 - frac) + mag1 * frac;
powerSum += mag * mag;
}
if (numPoints > 0)
{
double avgPower = powerSum / (double) numPoints;
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; 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);
}

Fájl megtekintése

@@ -27,6 +27,9 @@ public:
int getFIRLength() const { return 1 << fftOrder.load(); } int getFIRLength() const { return 1 << fftOrder.load(); }
int getLatencySamples() const { return getFIRLength() / 2; } int getLatencySamples() const { return getFIRLength() / 2; }
// Auto makeup gain: A-weighted RMS loudness compensation (dB)
float getAutoMakeupGainDb() const { return autoMakeupDb.load(); }
private: private:
void run() override; void run() override;
juce::AudioBuffer<float> generateFIR (const std::vector<EQBand>& bands, double sr, int order); juce::AudioBuffer<float> generateFIR (const std::vector<EQBand>& bands, double sr, int order);
@@ -43,4 +46,7 @@ private:
std::vector<float> magnitudeDb; std::vector<float> magnitudeDb;
mutable juce::SpinLock magLock; mutable juce::SpinLock magLock;
std::atomic<float> autoMakeupDb { 0.0f };
static float aWeighting (float freq);
}; };

Fájl megtekintése

@@ -92,17 +92,17 @@ InstaLPEQEditor::InstaLPEQEditor (InstaLPEQProcessor& p)
limiterLabel.setJustificationType (juce::Justification::centred); limiterLabel.setJustificationType (juce::Justification::centred);
addAndMakeVisible (limiterLabel); addAndMakeVisible (limiterLabel);
// Makeup gain // Auto makeup gain
makeupGainSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); autoMakeupToggle.setToggleState (processor.autoMakeupEnabled.load(), juce::dontSendNotification);
makeupGainSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 60, 16); addAndMakeVisible (autoMakeupToggle);
makeupGainSlider.setRange (-24.0, 24.0, 0.1); autoMakeupLabel.setFont (customLookAndFeel.getMediumFont (13.0f));
makeupGainSlider.setValue (0.0); autoMakeupLabel.setColour (juce::Label::textColourId, InstaLPEQLookAndFeel::textSecondary);
makeupGainSlider.setTextValueSuffix (" dB"); autoMakeupLabel.setJustificationType (juce::Justification::centred);
makeupGainSlider.setDoubleClickReturnValue (true, 0.0); addAndMakeVisible (autoMakeupLabel);
addAndMakeVisible (makeupGainSlider); autoMakeupValue.setFont (customLookAndFeel.getRegularFont (12.0f));
makeupGainLabel.setFont (customLookAndFeel.getMediumFont (13.0f)); autoMakeupValue.setColour (juce::Label::textColourId, InstaLPEQLookAndFeel::accent);
makeupGainLabel.setJustificationType (juce::Justification::centred); autoMakeupValue.setJustificationType (juce::Justification::centred);
addAndMakeVisible (makeupGainLabel); addAndMakeVisible (autoMakeupValue);
// Signal chain panel // Signal chain panel
chainPanel.setListener (this); chainPanel.setListener (this);
@@ -189,10 +189,12 @@ void InstaLPEQEditor::resized()
limiterLabel.setBounds (masterArea.removeFromLeft (55)); limiterLabel.setBounds (masterArea.removeFromLeft (55));
limiterToggle.setBounds (masterArea.removeFromLeft (40)); limiterToggle.setBounds (masterArea.removeFromLeft (40));
// Makeup gain knob // Auto makeup gain toggle + value display
makeupGainLabel.setFont (customLookAndFeel.getMediumFont (std::max (11.0f, 14.0f * scale))); autoMakeupLabel.setFont (customLookAndFeel.getMediumFont (std::max (11.0f, 14.0f * scale)));
makeupGainLabel.setBounds (masterArea.removeFromLeft (55)); autoMakeupLabel.setBounds (masterArea.removeFromLeft (70));
makeupGainSlider.setBounds (masterArea.removeFromLeft (masterH)); 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 // Quality selector on the right side of master row
qualityLabel.setFont (customLookAndFeel.getMediumFont (std::max (11.0f, 14.0f * scale))); qualityLabel.setFont (customLookAndFeel.getMediumFont (std::max (11.0f, 14.0f * scale)));
@@ -218,7 +220,12 @@ void InstaLPEQEditor::timerCallback()
processor.bypassed.store (bypassToggle.getToggleState()); processor.bypassed.store (bypassToggle.getToggleState());
processor.masterGainDb.store ((float) masterGainSlider.getValue()); processor.masterGainDb.store ((float) masterGainSlider.getValue());
processor.limiterEnabled.store (limiterToggle.getToggleState()); 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 // Update spectrum analyzer
{ {

Fájl megtekintése

@@ -44,7 +44,7 @@ private:
NodeParameterPanel nodePanel; NodeParameterPanel nodePanel;
juce::Label titleLabel { {}, "INSTALPEQ" }; juce::Label titleLabel { {}, "INSTALPEQ" };
juce::Label versionLabel { {}, "v1.2.2" }; juce::Label versionLabel { {}, "v1.3.2" };
juce::ToggleButton bypassToggle; juce::ToggleButton bypassToggle;
juce::Label bypassLabel { {}, "BYPASS" }; juce::Label bypassLabel { {}, "BYPASS" };
@@ -57,8 +57,9 @@ private:
juce::Label masterGainLabel { {}, "MASTER" }; juce::Label masterGainLabel { {}, "MASTER" };
juce::ToggleButton limiterToggle; juce::ToggleButton limiterToggle;
juce::Label limiterLabel { {}, "LIMITER" }; juce::Label limiterLabel { {}, "LIMITER" };
juce::Slider makeupGainSlider; juce::ToggleButton autoMakeupToggle;
juce::Label makeupGainLabel { {}, "MAKEUP" }; juce::Label autoMakeupLabel { {}, "AUTO GAIN" };
juce::Label autoMakeupValue { {}, "0.0 dB" };
SignalChainPanel chainPanel; SignalChainPanel chainPanel;

Fájl megtekintése

@@ -61,7 +61,7 @@ void InstaLPEQProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::M
if (bypassed.load() || ! firLoaded) if (bypassed.load() || ! firLoaded)
return; return;
// Process through convolution // Process through convolution (EQ)
juce::dsp::AudioBlock<float> block (buffer); juce::dsp::AudioBlock<float> block (buffer);
juce::dsp::ProcessContextReplacing<float> context (block); juce::dsp::ProcessContextReplacing<float> context (block);
convolution.process (context); convolution.process (context);
@@ -99,9 +99,12 @@ void InstaLPEQProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::M
} }
case MakeupGain: case MakeupGain:
{ {
float mkGain = juce::Decibels::decibelsToGain (makeupGainDb.load()); if (autoMakeupEnabled.load())
{
float mkGain = juce::Decibels::decibelsToGain (firEngine.getAutoMakeupGainDb());
if (std::abs (mkGain - 1.0f) > 0.001f) if (std::abs (mkGain - 1.0f) > 0.001f)
buffer.applyGain (mkGain); buffer.applyGain (mkGain);
}
break; break;
} }
default: break; default: break;
@@ -207,6 +210,11 @@ bool InstaLPEQProcessor::getSpectrum (float* dest, int maxBins) const
return true; return true;
} }
float InstaLPEQProcessor::getActiveAutoMakeupDb() const
{
return autoMakeupEnabled.load() ? firEngine.getAutoMakeupGainDb() : 0.0f;
}
std::array<InstaLPEQProcessor::ChainStage, InstaLPEQProcessor::numChainStages> InstaLPEQProcessor::getChainOrder() const std::array<InstaLPEQProcessor::ChainStage, InstaLPEQProcessor::numChainStages> InstaLPEQProcessor::getChainOrder() const
{ {
const juce::SpinLock::ScopedLockType lock (chainLock); const juce::SpinLock::ScopedLockType lock (chainLock);
@@ -242,7 +250,7 @@ void InstaLPEQProcessor::getStateInformation (juce::MemoryBlock& destData)
xml.setAttribute ("bypass", bypassed.load()); xml.setAttribute ("bypass", bypassed.load());
xml.setAttribute ("masterGain", (double) masterGainDb.load()); xml.setAttribute ("masterGain", (double) masterGainDb.load());
xml.setAttribute ("limiter", limiterEnabled.load()); xml.setAttribute ("limiter", limiterEnabled.load());
xml.setAttribute ("makeupGain", (double) makeupGainDb.load()); xml.setAttribute ("autoMakeup", autoMakeupEnabled.load());
auto order = getChainOrder(); auto order = getChainOrder();
juce::String chainStr; juce::String chainStr;
@@ -276,7 +284,7 @@ void InstaLPEQProcessor::setStateInformation (const void* data, int sizeInBytes)
bypassed.store (xml->getBoolAttribute ("bypass", false)); bypassed.store (xml->getBoolAttribute ("bypass", false));
masterGainDb.store ((float) xml->getDoubleAttribute ("masterGain", 0.0)); masterGainDb.store ((float) xml->getDoubleAttribute ("masterGain", 0.0));
limiterEnabled.store (xml->getBoolAttribute ("limiter", true)); 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 chainStr = xml->getStringAttribute ("chainOrder", "0,1,2");
auto tokens = juce::StringArray::fromTokens (chainStr, ",", ""); auto tokens = juce::StringArray::fromTokens (chainStr, ",", "");

Fájl megtekintése

@@ -48,7 +48,10 @@ public:
std::atomic<bool> bypassed { false }; std::atomic<bool> bypassed { false };
std::atomic<float> masterGainDb { 0.0f }; std::atomic<float> masterGainDb { 0.0f };
std::atomic<bool> limiterEnabled { true }; std::atomic<bool> limiterEnabled { true };
std::atomic<float> makeupGainDb { 0.0f }; // -24 to +24 dB std::atomic<bool> autoMakeupEnabled { true };
float getActiveAutoMakeupDb() const;
float getMeasuredAutoMakeupDb() const { return measuredMakeupDb.load(); }
// Chain order (read/write from GUI, read from audio thread) // Chain order (read/write from GUI, read from audio thread)
std::array<ChainStage, numChainStages> getChainOrder() const; std::array<ChainStage, numChainStages> getChainOrder() const;
@@ -86,6 +89,11 @@ public:
int currentBlockSize = 512; int currentBlockSize = 512;
bool firLoaded = false; bool firLoaded = false;
// Signal-based auto makeup measurement
double smoothedInputRms = 0.0;
double smoothedOutputRms = 0.0;
std::atomic<float> measuredMakeupDb { 0.0f };
std::array<ChainStage, numChainStages> chainOrder { MasterGain, Limiter, MakeupGain }; std::array<ChainStage, numChainStages> chainOrder { MasterGain, Limiter, MakeupGain };
juce::SpinLock chainLock; juce::SpinLock chainLock;

Fájl megtekintése

@@ -21,7 +21,7 @@ juce::String SignalChainPanel::getStageName (InstaLPEQProcessor::ChainStage stag
{ {
case InstaLPEQProcessor::MasterGain: return "MASTER GAIN"; case InstaLPEQProcessor::MasterGain: return "MASTER GAIN";
case InstaLPEQProcessor::Limiter: return "LIMITER"; case InstaLPEQProcessor::Limiter: return "LIMITER";
case InstaLPEQProcessor::MakeupGain: return "MAKEUP GAIN"; case InstaLPEQProcessor::MakeupGain: return "AUTO GAIN";
default: return "?"; default: return "?";
} }
} }