Skip to content

Commit cb1257d

Browse files
committed
test(processor): add gate threshold tuning tests
- Add TestTuneGateThreshold with 11 test cases covering: - Recording quality tiers (clean/typical/noisy) - Offset selection (10dB/8dB/6dB based on noise floor) - Clamping at min (-70dB) and max (-25dB) limits - Boundary conditions at tier thresholds - Add linearToDB helper for clearer test error messages
1 parent 96d98a2 commit cb1257d

1 file changed

Lines changed: 134 additions & 0 deletions

File tree

internal/processor/adaptive_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package processor
22

33
import (
4+
"math"
45
"testing"
56
)
67

@@ -456,3 +457,136 @@ func TestTuneDeesser(t *testing.T) {
456457
})
457458
}
458459
}
460+
461+
func TestTuneGateThreshold(t *testing.T) {
462+
// Constants from adaptive.go for reference:
463+
// gateOffsetClean = 10.0 dB (above noise floor for clean recordings)
464+
// gateOffsetTypical = 8.0 dB (above noise floor for typical podcasts)
465+
// gateOffsetNoisy = 6.0 dB (above noise floor for noisy recordings)
466+
// gateThresholdMinDB = -70.0 dB (professional studio clean)
467+
// gateThresholdMaxDB = -25.0 dB (very noisy environment)
468+
// noiseFloorClean = -60.0 dBFS
469+
// noiseFloorTypical = -50.0 dBFS
470+
// noiseFloorNoisy = -40.0 dBFS
471+
// dbToLinear(db) = 10^(db/20)
472+
473+
tests := []struct {
474+
name string
475+
noiseFloor float64 // dBFS input
476+
wantThresholdDB float64 // expected threshold in dB (before linear conversion)
477+
wantThresholdDesc string // description of expected behaviour
478+
}{
479+
// Clean recording tier (noise floor < -60 dBFS) - uses 10dB offset
480+
{
481+
name: "professional studio, very clean",
482+
noiseFloor: -70,
483+
wantThresholdDB: -60, // -70 + 10, but clamped to -70 min? No: -60 > -70, OK
484+
wantThresholdDesc: "clean offset applied",
485+
},
486+
{
487+
name: "clean home studio",
488+
noiseFloor: -65,
489+
wantThresholdDB: -55, // -65 + 10
490+
wantThresholdDesc: "clean offset applied",
491+
},
492+
{
493+
name: "boundary: exactly at noiseFloorClean",
494+
noiseFloor: -60,
495+
wantThresholdDB: -52, // -60 + 8 (not < -60, so uses typical offset)
496+
wantThresholdDesc: "typical offset (boundary)",
497+
},
498+
499+
// Typical podcast tier (noise floor -60 to -50 dBFS) - uses 8dB offset
500+
{
501+
name: "typical podcast recording",
502+
noiseFloor: -55,
503+
wantThresholdDB: -47, // -55 + 8
504+
wantThresholdDesc: "typical offset applied",
505+
},
506+
{
507+
name: "boundary: exactly at noiseFloorTypical",
508+
noiseFloor: -50,
509+
wantThresholdDB: -44, // -50 + 6 (not < -50, so uses noisy offset)
510+
wantThresholdDesc: "noisy offset (boundary)",
511+
},
512+
513+
// Noisy recording tier (noise floor >= -50 dBFS) - uses 6dB offset
514+
{
515+
name: "noisy home recording",
516+
noiseFloor: -45,
517+
wantThresholdDB: -39, // -45 + 6
518+
wantThresholdDesc: "noisy offset applied",
519+
},
520+
{
521+
name: "very noisy room",
522+
noiseFloor: -35,
523+
wantThresholdDB: -29, // -35 + 6
524+
wantThresholdDesc: "noisy offset applied",
525+
},
526+
527+
// Clamping behaviour
528+
{
529+
name: "extreme noise - clamped to max",
530+
noiseFloor: -20,
531+
wantThresholdDB: -25, // -20 + 6 = -14, clamped to gateThresholdMaxDB (-25)
532+
wantThresholdDesc: "clamped to max threshold",
533+
},
534+
{
535+
name: "extremely clean - clamped to min",
536+
noiseFloor: -85,
537+
wantThresholdDB: -70, // -85 + 10 = -75, clamped to gateThresholdMinDB (-70)
538+
wantThresholdDesc: "clamped to min threshold",
539+
},
540+
541+
// Edge cases
542+
{
543+
name: "boundary: exactly produces max threshold",
544+
noiseFloor: -31,
545+
wantThresholdDB: -25, // -31 + 6 = -25, exactly at max
546+
wantThresholdDesc: "at max boundary",
547+
},
548+
{
549+
name: "boundary: exactly produces min threshold",
550+
noiseFloor: -80,
551+
wantThresholdDB: -70, // -80 + 10 = -70, exactly at min
552+
wantThresholdDesc: "at min boundary",
553+
},
554+
}
555+
556+
for _, tt := range tests {
557+
t.Run(tt.name, func(t *testing.T) {
558+
// Setup
559+
config := DefaultFilterConfig()
560+
measurements := &AudioMeasurements{
561+
NoiseFloor: tt.noiseFloor,
562+
}
563+
564+
// Execute
565+
tuneGateThreshold(config, measurements)
566+
567+
// Calculate expected linear value from expected dB
568+
wantLinear := dbToLinear(tt.wantThresholdDB)
569+
570+
// Verify with tolerance for floating point
571+
tolerance := 0.0001
572+
diff := config.GateThreshold - wantLinear
573+
if diff < 0 {
574+
diff = -diff
575+
}
576+
if diff > tolerance {
577+
// Convert actual back to dB for clearer error message
578+
actualDB := linearToDB(config.GateThreshold)
579+
t.Errorf("GateThreshold = %.6f (%.1f dB), want %.6f (%.1f dB) [noiseFloor=%.1f dB, %s]",
580+
config.GateThreshold, actualDB, wantLinear, tt.wantThresholdDB, tt.noiseFloor, tt.wantThresholdDesc)
581+
}
582+
})
583+
}
584+
}
585+
586+
// linearToDB converts linear amplitude to dB for test error messages
587+
func linearToDB(linear float64) float64 {
588+
if linear <= 0 {
589+
return -1000 // avoid math.Log10(0) = -Inf
590+
}
591+
return 20 * math.Log10(linear)
592+
}

0 commit comments

Comments
 (0)