Skip to content

Commit 004d8dd

Browse files
committed
feat(processor): migrate LUFS target from -18 to -16
Update NormTargetLUFS constant, filter defaults, level meter boundary, and test comments to reflect new target loudness of -16 LUFS for podcast normalisation. Signed-off-by: Martin Wimpress <code@wimpress.io>
1 parent 057c206 commit 004d8dd

3 files changed

Lines changed: 10 additions & 10 deletions

File tree

internal/processor/filters.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ var Pass2FilterOrder = []FilterID{
7676
// Normalisation target and tolerance for Pass 3 gain adjustment
7777
const (
7878
// NormTargetLUFS is the podcast loudness standard.
79-
NormTargetLUFS = -18.0
79+
NormTargetLUFS = -16.0
8080

8181
// NormToleranceLU is the acceptable deviation from target.
8282
// ±0.5 LU is industry standard for loudness compliance.
@@ -212,7 +212,7 @@ type FilterChainConfig struct {
212212
DeessFreq float64 // 0.0-1.0, how much original frequency content to keep
213213

214214
// Target values (for reference only)
215-
TargetI float64 // LUFS target reference (podcast standard: -18)
215+
TargetI float64 // LUFS target reference (podcast standard: -16)
216216
TargetTP float64 // dBTP, true peak ceiling reference
217217
TargetLRA float64 // LU, loudness range reference
218218

@@ -251,7 +251,7 @@ type FilterChainConfig struct {
251251
// Replaces simple volume gain + limiting with integrated dynamic normalisation
252252
// Uses two-pass mode with measurements from Pass 2 for optimal transparency
253253
LoudnormEnabled bool // Enable loudnorm in Pass 3 (default: true)
254-
LoudnormTargetI float64 // Target integrated loudness (LUFS), default: -18.0
254+
LoudnormTargetI float64 // Target integrated loudness (LUFS), default: -16.0
255255
LoudnormTargetTP float64 // Target true peak (dBTP), default: -1.5
256256
LoudnormTargetLRA float64 // Target loudness range (LU), default: 11.0
257257
LoudnormDualMono bool // Treat mono as dual-mono (CRITICAL for mono files)
@@ -341,7 +341,7 @@ func DefaultFilterConfig() *FilterChainConfig {
341341
DeessFreq: 0.5, // Keep 50% of original frequency content (balanced)
342342

343343
// Target values (for reference only)
344-
TargetI: -18.0, // Reference LUFS target (not enforced)
344+
TargetI: -16.0, // Reference LUFS target (not enforced)
345345
TargetTP: -0.3, // Reference true peak (not enforced, alimiter does real limiting at -1.5)
346346
TargetLRA: 7.0, // Reference loudness range (EBU R128 default)
347347

@@ -369,7 +369,7 @@ func DefaultFilterConfig() *FilterChainConfig {
369369

370370
// Loudnorm - enabled by default with podcast-optimised settings
371371
LoudnormEnabled: true,
372-
LoudnormTargetI: -18.0, // Broadcast standard (was -16 podcast standard, -18 for more headroom)
372+
LoudnormTargetI: -16.0, // Podcast standard (-16 LUFS)
373373
LoudnormTargetTP: -2.0, // Conservative headroom (prevents limiter clipping to 0.0 dBTP)
374374
LoudnormTargetLRA: 20.0, // High value to prevent dynamic mode fallback (must be >= source LRA)
375375
LoudnormDualMono: true, // CRITICAL for mono recordings

internal/processor/processor_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77

88
// TestProcessAudio tests the complete three-pass processing pipeline
99
func TestProcessAudio(t *testing.T) {
10-
// Generate synthetic test audio: 3-second 440Hz tone at -18 LUFS
11-
// (needs to be loud enough for normalisation to be within ±12 dB of -16 LUFS)
10+
// Generate synthetic test audio: 3-second 440Hz tone at -18 dBFS input level
11+
// (needs to be loud enough for normalisation to be within ±12 dB of -16 LUFS target)
1212
// Short duration for fast test execution
1313
testFile := generateTestAudio(t, TestAudioOptions{
1414
DurationSecs: 3.0,

internal/ui/views.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,10 @@ func renderAudioLevelMeter(currentLevel, peakLevel float64) string {
164164
peakPos := max(0, min(int(((peakLevel-minDB)/(maxDB-minDB))*float64(width)), width))
165165

166166
// Build the meter bar with color zones
167-
// Green: -60 to -18 dB (safe)
168-
// Orange: -18 to -6 dB (approaching loud)
167+
// Green: -60 to -16 dB (safe)
168+
// Orange: -16 to -6 dB (approaching loud)
169169
// Red: -6 to 0 dB (loud/clipping risk)
170-
greenZone := int((((-18.0) - minDB) / (maxDB - minDB)) * float64(width))
170+
greenZone := int((((-16.0) - minDB) / (maxDB - minDB)) * float64(width))
171171
orangeZone := int((((-6.0) - minDB) / (maxDB - minDB)) * float64(width))
172172

173173
// Build meter character by character with appropriate colors

0 commit comments

Comments
 (0)