feat(processor): implement pre-gain compensation for clamped LUFS ceilings#58
Merged
flexiondotorg merged 8 commits intomainfrom Mar 15, 2026
Merged
feat(processor): implement pre-gain compensation for clamped LUFS ceilings#58flexiondotorg merged 8 commits intomainfrom
flexiondotorg merged 8 commits intomainfrom
Conversation
…stics - Move safetyMarginDB (1.5) and minLimiterCeilingDB (-24.0) to package level - Remove duplicate const declarations from calculateLimiterCeiling() - Update function to reference package-level constants - Add PreGainDB and LimiterClamped diagnostic fields to NormalisationResult - Document coupling between normalise.go and adaptive.go constants Signed-off-by: Martin Wimpress <code@wimpress.io>
- Modify buildLoudnormFilterSpec() to capture clamped flag and calculate gain deficit - Insert volume filter with pre-gain dB adjustment before alimiter in Pass 4 chain - Re-derive limiter ceiling from post-gain values to maintain integrity - Update applyLoudnormAndMeasure() to return (filterSpec, preGainDB, clamped) tuple - Thread pre-gain metadata through ApplyNormalisation to NormalisationResult - Populate PreGainDB and LimiterClamped fields when limiter ceiling is clamped Signed-off-by: Martin Wimpress <code@wimpress.io>
…rivation - Add TestBuildLoudnormFilterSpec_PreGain: 4 cases verifying volume filter presence/absence and correct deficit and ceiling values in filter strings - Extend TestCalculateLimiterCeiling: 2 new cases (Anna's exact values, boundary case) validating deficit arithmetic and minLimiterCeilingDB formula - Add TestPreGainCeilingRederivation: 4 scenarios confirming original measurement clamping and post-gain recalculation, asserting re-derived ceiling matches minLimiterCeilingDB within 0.01 dB tolerance These tests verify the core pre-gain logic: deficit calculation, filter construction, and ceiling re-derivation across nominal and edge cases. Signed-off-by: Martin Wimpress <code@wimpress.io>
Report pre-gain compensation details when result.PreGainDB > 0: - Original ceiling, ideal ceiling, deficit, pre-gain applied - Re-derived ceiling and projected post-gain TP - Output conditionally omitted when pre-gain not applied Signed-off-by: Martin Wimpress <code@wimpress.io>
- Update README Pass 4 description to explain pre-gain as the first normalisation step - Detail when and why pre-gain activates (ceiling clamping and deficit calculation) - Update AGENTS.md pipeline to show volume (pre-gain, when clamped) in normalisation - Add "Pre-gain for Clamped Ceilings" subsection to FilterLimiter-CBS-Volumax.md - Cover the -24.0 dBTP minimum problem, deficit formula re-derivation, and noise floor impact - Include Anna example table and both filter chain variants (volume before/after alimiter) Signed-off-by: Martin Wimpress <code@wimpress.io>
Contributor
There was a problem hiding this comment.
2 issues found across 6 files
Confidence score: 4/5
- This PR is likely safe to merge with minimal risk: the reported issues are moderate and focused on documentation accuracy and test coverage rather than a confirmed runtime break.
- In
docs/FilterLimiter-CBS-Volumax.md, the worked pre-gain math no longer matches implementation constants, which can mislead users/operators configuring the limiter. - In
internal/processor/normalise_test.go, the non-clamped limiter test only covers inputs wherealimiter=already exists, so a regression that drops limiter insertion for normal peak-limited inputs could slip through CI. - Pay close attention to
docs/FilterLimiter-CBS-Volumax.mdandinternal/processor/normalise_test.go- align documented arithmetic with current constants and broaden the non-clamped limiter assertion path.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docs/FilterLimiter-CBS-Volumax.md">
<violation number="1" location="docs/FilterLimiter-CBS-Volumax.md:186">
P2: The worked example's pre-gain arithmetic doesn't match the current implementation constants, so the table documents the feature incorrectly.</violation>
</file>
<file name="internal/processor/normalise_test.go">
<violation number="1" location="internal/processor/normalise_test.go:445">
P2: The non-clamped limiter case only asserts the branch when `alimiter=` is already present, so this test will miss a regression that drops the limiter for ordinary peak-limited inputs.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
…rtions - Update example arithmetic in FilterLimiter-CBS-Volumax.md: deficit 6.7 dB, idealCeiling -30.7 dBTP, postGainI -36.5 LUFS - Rename ceiling label from "Ideal" to "Limiter" with before/after annotations - Replace conditional alimiter check with unconditional assertion in normalise_test.go - Assert limiter presence matches needsLimiting result; catch silent filter regressions - Add measured_TP verification for non-clamped limiter path Signed-off-by: Martin Wimpress <code@wimpress.io>
Contributor
There was a problem hiding this comment.
0 issues found across 2 files (changes from recent commits).
Requires human review: Modifies core audio normalization logic and filter chain processing, introducing complex arithmetic for pre-gain compensation which requires manual verification by a human reviewer.
- Declare effectiveMeasuredI before if/else chain in normalise.go - Set effectiveMeasuredI = postGainI in clamped branch (previously shadowed) - Pass effectiveMeasuredI to calculateLinearModeTarget instead of raw measurement.InputI - Allows target calculation to correctly derive -16.0 LUFS instead of reducing to -21.5 LUFS Fixes circular dependency where pre-gain compensation cannot be calculated until post-gain input level is known to calculateLinearModeTarget. Signed-off-by: Martin Wimpress <code@wimpress.io>
Contributor
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/processor/normalise_test.go">
<violation number="1" location="internal/processor/normalise_test.go:670">
P3: This test bypasses the clamped-target propagation it claims to verify by assigning `effectiveTargetI` into `config` directly instead of exercising `ApplyNormalisation`'s real forwarding path.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Renamed TestApplyNormalisation_ClampedTargetPropagation to TestClampedTargetPropagation_Arithmetic and updated doc comment to accurately describe that it validates the pure-function chain (calculateLimiterCeiling → postGainI → calculateLinearModeTarget → buildLoudnormFilterSpec) without exercising ApplyNormalisation itself. Signed-off-by: Martin Wimpress <code@wimpress.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements (pre-gain before alimiter) to address LUFS normalisation shortfalls on very quiet recordings. When the peak limiter ceiling gets clamped to the safety floor due to headroom constraints, pre-gain is inserted before the alimiter to restore the target loudness whilst respecting peak limits.
This resolves the issue where recordings below -50 dBFS cannot reach the target LUFS level without exceeding 0 dB peak after limiting.
Changes
Phase 1: Constants and diagnostics
safetyMarginDB=1.5,minLimiterCeilingDB=-24.0PreGainDBandLimiterClampedfields toNormalisationResultPhase 2: Pre-gain implementation
buildLoudnormFilterSpec()to detect when limiter ceiling is clampedminLimiterCeilingDB - idealCeilingvolume={deficit}dBfilter before alimiter in Pass 4 chainmeasured_Iandmeasured_TPfor loudnorm metadata accuracy(string, float64, bool)with pre-gain metadataPhase 3: Comprehensive test coverage
TestBuildLoudnormFilterSpec_PreGainwith 4 scenariosTestCalculateLimiterCeilingwith 2 new clamping casesTestPreGainCeilingRederivationwith 4 re-derivation scenariosPhase 4: Diagnostics
Phase 5: Documentation
Testing
just lintpassesjust testpasses (all processor tests, including pre-gain scenarios)