Skip to content

Commit a933efb

Browse files
committed
chore(processor): remove obsolete BleedGate filter
1 parent da8ff35 commit a933efb

4 files changed

Lines changed: 0 additions & 211 deletions

File tree

internal/logging/report.go

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,6 @@ func formatFilter(f *os.File, filterID processor.FilterID, cfg *processor.Filter
670670
formatSpeechnormFilter(f, cfg, m, prefix)
671671
case processor.FilterDynaudnorm:
672672
formatDynaudnormFilter(f, cfg, prefix)
673-
case processor.FilterBleedGate:
674-
formatBleedGateFilter(f, cfg, m, prefix)
675673
case processor.FilterAlimiter:
676674
formatAlimiterFilter(f, cfg, prefix)
677675
default:
@@ -1205,33 +1203,6 @@ func formatDynaudnormFilter(f *os.File, cfg *processor.FilterChainConfig, prefix
12051203
fmt.Fprintln(f, " Mode: conservative fixed parameters (prevents artifacts)")
12061204
}
12071205

1208-
// formatBleedGateFilter outputs bleed gate filter details
1209-
func formatBleedGateFilter(f *os.File, cfg *processor.FilterChainConfig, m *processor.AudioMeasurements, prefix string) {
1210-
if !cfg.BleedGateEnabled {
1211-
fmt.Fprintf(f, "%sbleedgate: DISABLED\n", prefix)
1212-
return
1213-
}
1214-
1215-
thresholdDB := linearToDb(cfg.BleedGateThreshold)
1216-
rangeDB := linearToDb(cfg.BleedGateRange)
1217-
1218-
fmt.Fprintf(f, "%sbleedgate: threshold %.1f dB, ratio %.1f:1\n", prefix, thresholdDB, cfg.BleedGateRatio)
1219-
fmt.Fprintf(f, " Timing: attack %.0fms, release %.0fms\n", cfg.BleedGateAttack, cfg.BleedGateRelease)
1220-
fmt.Fprintf(f, " Range: %.1f dB reduction, knee %.1f\n", rangeDB, cfg.BleedGateKnee)
1221-
1222-
// Show rationale
1223-
if m != nil && m.NoiseProfile != nil {
1224-
np := m.NoiseProfile
1225-
hasBleed := np.CrestFactor > 15.0 || (np.PeakLevel-np.MeasuredNoiseFloor) > 20.0
1226-
if hasBleed {
1227-
fmt.Fprintf(f, " Rationale: bleed detected (crest %.1f dB, peak-floor %.1f dB)\n",
1228-
np.CrestFactor, np.PeakLevel-np.MeasuredNoiseFloor)
1229-
} else {
1230-
fmt.Fprintf(f, " Rationale: noise amplification (predicted output above -40 dB)\n")
1231-
}
1232-
}
1233-
}
1234-
12351206
// formatAlimiterFilter outputs alimiter filter details
12361207
func formatAlimiterFilter(f *os.File, cfg *processor.FilterChainConfig, prefix string) {
12371208
if !cfg.LimiterEnabled {

internal/processor/adaptive.go

Lines changed: 0 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -248,19 +248,6 @@ const (
248248
speechnormPeakTarget = 0.95 // Headroom for limiter
249249
speechnormSmoothingFast = 0.001 // Fast response time
250250

251-
// Bleed gate parameters
252-
// Catches bleed/crosstalk that was amplified by speechnorm/dynaudnorm
253-
// Threshold is calculated from: predicted_output_bleed = silence_peak_level + worst_case_gain
254-
bleedGateMarginDB = 6.0 // dB above predicted bleed to set threshold (safety margin)
255-
bleedGateEnableThresholdDB = -40.0 // dBFS - only enable if predicted output bleed is above this
256-
bleedGateMinThresholdDB = -50.0 // dBFS - minimum threshold (never gate below this)
257-
bleedGateMaxThresholdDB = -20.0 // dBFS - maximum threshold (never gate above this, would cut speech)
258-
bleedGateDefaultRatio = 4.0 // Gentler than pre-gate (suppress rather than cut)
259-
bleedGateDefaultAttack = 15.0 // ms - faster than pre-gate
260-
bleedGateDefaultRelease = 200.0 // ms - smooth release
261-
bleedGateDefaultRange = 0.125 // -18dB reduction (less aggressive than pre-gate)
262-
bleedGateDefaultKnee = 3.0 // Soft knee
263-
264251
// Mains hum filter parameters
265252
humEntropyThreshold = 0.7 // Below this = tonal noise detected (hum/buzz)
266253
humFreq50Hz = 50.0 // UK/EU mains fundamental frequency
@@ -482,7 +469,6 @@ func AdaptConfig(config *FilterChainConfig, measurements *AudioMeasurements) {
482469
tuneLA2ACompressor(config, measurements)
483470
tuneDynaudnorm(config)
484471
tuneSpeechnorm(config, measurements, lufsGap)
485-
tuneBleedGate(config, measurements, lufsGap) // Bleed gate for amplified bleed/crosstalk
486472

487473
// Final safety checks
488474
sanitizeConfig(config)
@@ -1664,111 +1650,6 @@ func tuneSpeechnormDenoise(config *FilterChainConfig, expansion float64) {
16641650
}
16651651
}
16661652

1667-
// tuneBleedGate adapts the bleed gate based on predicted output bleed level.
1668-
//
1669-
// The bleed gate catches bleed/crosstalk that was amplified by speechnorm/dynaudnorm
1670-
// after the denoisers have run. Denoisers preserve speech-like content (which is what
1671-
// they're designed to do), but headphone bleed IS speech-like content from other speakers.
1672-
//
1673-
// Strategy:
1674-
// - Use silence sample PEAK level (not noise floor) - captures bleed bursts, not just hiss
1675-
// - Calculate worst-case gain: speechnorm normalises each half-cycle to peak target
1676-
// - For silence with bleed, this can mean 40-50dB of gain applied
1677-
// - Use crest factor to detect presence of bleed (high crest = impulsive content in silence)
1678-
// - Adjust ratio/range based on how much bleed is detected
1679-
//
1680-
// Key insight: Speechnorm applies VARIABLE gain per half-cycle. For quiet sections
1681-
// (like silence with bleed), it applies much more gain than the "expansion" factor
1682-
// suggests. The actual gain on silence can be:
1683-
//
1684-
// silence_input_peak → target_peak (0.95 = -0.45 dBFS)
1685-
//
1686-
// Measurements used:
1687-
// - NoiseProfile.PeakLevel: captures the loudest bleed burst
1688-
// - NoiseProfile.MeasuredNoiseFloor: captures the background hiss
1689-
// - NoiseProfile.CrestFactor: high crest = impulsive content (bleed), low = steady hiss
1690-
// - NoiseProfile.Entropy: low entropy = tonal (hum), high = broadband (hiss/bleed)
1691-
func tuneBleedGate(config *FilterChainConfig, measurements *AudioMeasurements, lufsGap float64) {
1692-
// Respect user's intent: if filter is disabled, don't touch it
1693-
if !config.BleedGateEnabled {
1694-
return
1695-
}
1696-
1697-
// Need noise profile with measurements to calculate threshold
1698-
if measurements.NoiseProfile == nil {
1699-
config.BleedGateEnabled = false
1700-
return
1701-
}
1702-
1703-
np := measurements.NoiseProfile
1704-
1705-
// Calculate worst-case gain: speechnorm can apply gain to bring quiet content to peak
1706-
// The target peak is typically 0.95 (-0.45 dBFS)
1707-
targetPeakDB := -0.45 // 20 * log10(0.95)
1708-
if config.SpeechnormEnabled && config.SpeechnormPeak > 0 {
1709-
targetPeakDB = 20.0 * math.Log10(config.SpeechnormPeak)
1710-
}
1711-
1712-
// Worst-case gain = what's needed to bring silence peak to target peak
1713-
// This is the maximum gain speechnorm could apply to the silence content
1714-
worstCaseGainDB := targetPeakDB - np.PeakLevel
1715-
1716-
// Calculate predicted output level for the silence PEAK (the bleed content)
1717-
predictedOutputPeakDB := np.PeakLevel + worstCaseGainDB
1718-
1719-
// Calculate predicted output noise floor
1720-
predictedOutputNoiseDB := np.MeasuredNoiseFloor + worstCaseGainDB
1721-
1722-
// Detect bleed presence using crest factor and peak-to-floor ratio
1723-
// Crest factor = peak - RMS; high crest means impulsive content in silence
1724-
// For pure hiss, crest factor is ~10-12dB; for bleed it's typically 20-30dB
1725-
peakToFloorDB := np.PeakLevel - np.MeasuredNoiseFloor
1726-
hasSignificantBleed := np.CrestFactor > 15.0 || peakToFloorDB > 20.0
1727-
1728-
// Determine threshold strategy based on bleed detection
1729-
var thresholdDB float64
1730-
if hasSignificantBleed {
1731-
// Bleed detected - use peak-based threshold (more aggressive)
1732-
// Set threshold to catch the amplified bleed peaks
1733-
thresholdDB = predictedOutputPeakDB - 3.0 // 3dB below predicted peak
1734-
} else {
1735-
// No significant bleed - use noise floor based threshold (standard approach)
1736-
thresholdDB = predictedOutputNoiseDB + bleedGateMarginDB
1737-
}
1738-
1739-
// Only enable bleed gate if predicted output would be audible
1740-
if thresholdDB < bleedGateEnableThresholdDB {
1741-
config.BleedGateEnabled = false
1742-
return
1743-
}
1744-
1745-
// Enable bleed gate
1746-
config.BleedGateEnabled = true
1747-
1748-
// Clamp threshold to safety limits
1749-
thresholdDB = clamp(thresholdDB, bleedGateMinThresholdDB, bleedGateMaxThresholdDB)
1750-
1751-
// Convert to linear for agate filter
1752-
config.BleedGateThreshold = dbToLinear(thresholdDB)
1753-
1754-
// Adapt ratio and range based on bleed severity
1755-
if hasSignificantBleed {
1756-
// Significant bleed - use stronger settings
1757-
config.BleedGateRatio = 6.0 // Stronger ratio for bleed
1758-
config.BleedGateRange = 0.063 // -24dB reduction (more aggressive)
1759-
config.BleedGateAttack = 10.0 // Faster attack to catch bleed transients
1760-
config.BleedGateRelease = 150.0
1761-
} else {
1762-
// Mild bleed or just noise amplification - use gentler settings
1763-
config.BleedGateRatio = bleedGateDefaultRatio
1764-
config.BleedGateRange = bleedGateDefaultRange
1765-
config.BleedGateAttack = bleedGateDefaultAttack
1766-
config.BleedGateRelease = bleedGateDefaultRelease
1767-
}
1768-
1769-
config.BleedGateKnee = bleedGateDefaultKnee
1770-
}
1771-
17721653
// sanitizeConfig ensures no NaN or Inf values remain after adaptive tuning
17731654
func sanitizeConfig(config *FilterChainConfig) {
17741655
// DS201-inspired highpass filter
@@ -1801,11 +1682,6 @@ func sanitizeConfig(config *FilterChainConfig) {
18011682
if config.DS201HumHarmonics < 1 || config.DS201HumHarmonics > 8 {
18021683
config.DS201HumHarmonics = defaultHumHarmonics
18031684
}
1804-
1805-
// BleedGateThreshold needs additional check for zero/negative (like pre-gate)
1806-
if math.IsNaN(config.BleedGateThreshold) || math.IsInf(config.BleedGateThreshold, 0) || config.BleedGateThreshold <= 0 {
1807-
config.BleedGateThreshold = ds201DefaultGateThreshold // Use same default as pre-gate
1808-
}
18091685
}
18101686

18111687
// sanitizeFloat returns defaultVal if val is NaN or Inf

internal/processor/filters.go

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const (
4444
FilterDeesser FilterID = "deesser"
4545
FilterSpeechnorm FilterID = "speechnorm"
4646
FilterDynaudnorm FilterID = "dynaudnorm"
47-
FilterBleedGate FilterID = "bleedgate" // Catches amplified bleed/crosstalk after normalisation
4847
FilterAlimiter FilterID = "alimiter"
4948
)
5049

@@ -71,7 +70,6 @@ var Pass1FilterOrder = []FilterID{
7170
// - Deesser: after compression (which emphasises sibilance)
7271
// - Speechnorm: cycle-level normalisation for speech
7372
// - Dynaudnorm: frame-level normalisation for final consistency
74-
// - BleedGate: catches amplified bleed/crosstalk after normalisation
7573
// - Alimiter: brick-wall safety net
7674
// - Analysis: measures output for comparison with Pass 1
7775
// - Resample: standardises output format (44.1kHz/16-bit/mono)
@@ -88,7 +86,6 @@ var Pass2FilterOrder = []FilterID{
8886
FilterDeesser,
8987
FilterSpeechnorm,
9088
FilterDynaudnorm,
91-
FilterBleedGate,
9289
FilterAlimiter,
9390
FilterAnalysis,
9491
FilterResample,
@@ -298,18 +295,6 @@ type FilterChainConfig struct {
298295
LimiterAttack float64 // ms, attack time
299296
LimiterRelease float64 // ms, release time
300297

301-
// Bleed Gate (bleedgate) - catches amplified bleed/crosstalk after normalisation
302-
// Positioned AFTER speechnorm/dynaudnorm to gate content that denoisers missed
303-
// (denoisers preserve speech-like content, but headphone bleed IS speech-like)
304-
// Uses gentler ratio than pre-gate to suppress rather than hard-cut
305-
BleedGateEnabled bool // Enable bleed gate
306-
BleedGateThreshold float64 // Activation threshold (0.0-1.0, linear) - calculated from predicted output bleed level
307-
BleedGateRatio float64 // Reduction ratio (gentler than pre-gate, e.g., 4:1)
308-
BleedGateAttack float64 // Attack time (ms)
309-
BleedGateRelease float64 // Release time (ms)
310-
BleedGateRange float64 // Level of gain reduction below threshold (0.0-1.0)
311-
BleedGateKnee float64 // Knee curve softness (1.0-8.0)
312-
313298
// Filter chain order - controls the sequence of filters in the processing chain
314299
// Use DefaultFilterOrder or customise for experimentation
315300
FilterOrder []FilterID
@@ -462,15 +447,6 @@ func DefaultFilterConfig() *FilterChainConfig {
462447
ArnnDnEnabled: false,
463448
ArnnDnMix: 0.35, // Initial mix (will be tuned adaptively based on measurements)
464449

465-
// Bleed Gate - catches amplified bleed/crosstalk after normalisation
466-
BleedGateEnabled: false,
467-
BleedGateThreshold: 0.01, // -40dBFS default (will be calculated from predicted output bleed level)
468-
BleedGateRatio: 4.0, // 4:1 ratio (gentler than pre-gate, suppresses rather than cuts)
469-
BleedGateAttack: 15, // 15ms attack (faster than pre-gate to catch transient bleed)
470-
BleedGateRelease: 200, // 200ms release (smooth, natural decay)
471-
BleedGateRange: 0.125, // -18dB reduction (less aggressive than pre-gate's -24dB)
472-
BleedGateKnee: 3.0, // Soft knee for smooth engagement
473-
474450
// Limiter - brick-wall safety net with soft knee (via ASC)
475451
LimiterEnabled: false,
476452
LimiterCeiling: 0.84, // -1.5dBTP (actual limiting target)
@@ -1002,29 +978,6 @@ func (cfg *FilterChainConfig) buildArnnDnFilter() string {
1002978
return fmt.Sprintf("arnndn=m=%s:mix=%.2f", modelPath, cfg.ArnnDnMix)
1003979
}
1004980

1005-
// buildBleedGateFilter builds the bleed gate filter specification.
1006-
// Positioned AFTER speechnorm/dynaudnorm to catch bleed/crosstalk that was amplified
1007-
// during normalisation. Uses gentler ratio (4:1) compared to pre-gate (2:1) because
1008-
// it's suppressing rather than cleaning inter-speech gaps.
1009-
//
1010-
// This filter addresses the "headphone bleed" problem where normalisation amplifies
1011-
// low-level content (like bleed from headphones) that denoisers couldn't remove because
1012-
// it's speech-like content that they're designed to preserve.
1013-
func (cfg *FilterChainConfig) buildBleedGateFilter() string {
1014-
if !cfg.BleedGateEnabled {
1015-
return ""
1016-
}
1017-
return fmt.Sprintf(
1018-
"agate=threshold=%.6f:ratio=%.1f:attack=%.0f:release=%.0f:range=%.4f:knee=%.1f",
1019-
cfg.BleedGateThreshold,
1020-
cfg.BleedGateRatio,
1021-
cfg.BleedGateAttack,
1022-
cfg.BleedGateRelease,
1023-
cfg.BleedGateRange,
1024-
cfg.BleedGateKnee,
1025-
)
1026-
}
1027-
1028981
// buildAlimiterFilter builds the alimiter (true peak limiter) filter specification.
1029982
// Brick-wall safety net using lookahead and ASC for smooth, musical limiting.
1030983
func (cfg *FilterChainConfig) buildAlimiterFilter() string {
@@ -1066,7 +1019,6 @@ func (cfg *FilterChainConfig) BuildFilterSpec() string {
10661019
FilterDeesser: cfg.buildDeesserFilter,
10671020
FilterSpeechnorm: cfg.buildSpeechnormFilter,
10681021
FilterArnndn: cfg.buildArnnDnFilter,
1069-
FilterBleedGate: cfg.buildBleedGateFilter,
10701022
FilterDynaudnorm: cfg.buildDynaudnormFilter,
10711023
FilterAlimiter: cfg.buildAlimiterFilter,
10721024
}
@@ -1252,7 +1204,6 @@ func buildNoiseProfileFilterSpec(noiseDuration time.Duration, config *FilterChai
12521204
FilterDeesser: config.buildDeesserFilter,
12531205
FilterSpeechnorm: config.buildSpeechnormFilter,
12541206
FilterDynaudnorm: config.buildDynaudnormFilter,
1255-
FilterBleedGate: config.buildBleedGateFilter,
12561207
FilterAlimiter: config.buildAlimiterFilter,
12571208
}
12581209

internal/processor/filters_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ func newTestConfig() *FilterChainConfig {
3333
DynaudnormEnabled: false,
3434
SpeechnormEnabled: false,
3535
ArnnDnEnabled: false,
36-
BleedGateEnabled: false,
3736
LimiterEnabled: false,
3837

3938
// Sensible defaults for parameters (used when filter is enabled)
@@ -84,13 +83,6 @@ func newTestConfig() *FilterChainConfig {
8483

8584
ArnnDnMix: 0.8,
8685

87-
BleedGateThreshold: 0.01,
88-
BleedGateRatio: 4.0,
89-
BleedGateAttack: 15,
90-
BleedGateRelease: 200,
91-
BleedGateRange: 0.125,
92-
BleedGateKnee: 3.0,
93-
9486
LimiterCeiling: 0.84,
9587
LimiterAttack: 5.0,
9688
LimiterRelease: 50.0,
@@ -1040,7 +1032,6 @@ func TestPass2FilterOrder(t *testing.T) {
10401032
FilterDeesser,
10411033
FilterSpeechnorm,
10421034
FilterDynaudnorm,
1043-
FilterBleedGate,
10441035
FilterAlimiter,
10451036
FilterAnalysis,
10461037
FilterResample,

0 commit comments

Comments
 (0)