@@ -8,17 +8,29 @@ namespace Spectrogram.Tests
88{
99 class TestAGC
1010 {
11- // [Test]
12- public void Test_QRSS_noAGC ( )
11+ [ Test ]
12+ public void Test_AGC_off ( )
1313 {
1414 string wavFilePath = "../../../../../data/qrss-10min.wav" ;
1515 ( int sampleRate , double [ ] L ) = WavFile . ReadMono ( wavFilePath ) ;
16-
1716
1817 int fftSize = 8192 ;
1918 var spec = new Spectrogram ( sampleRate , fftSize , stepSize : 2000 , maxFreq : 3000 ) ;
2019 spec . Add ( L ) ;
21- spec . SaveImage ( "qrss-AGCoff.png" , intensity : 3 ) ;
20+ spec . SaveImage ( "qrss-agc-off.png" , intensity : 3 ) ;
21+ }
22+
23+ [ Test ]
24+ public void Test_AGC_normToNoiseFloor ( )
25+ {
26+ // strategy here is to normalize to the magnitude of the quietest 20% of frequencies
27+
28+ string wavFilePath = "../../../../../data/qrss-10min.wav" ;
29+ ( int sampleRate , double [ ] L ) = WavFile . ReadMono ( wavFilePath ) ;
30+
31+ int fftSize = 8192 ;
32+ var spec = new Spectrogram ( sampleRate , fftSize , stepSize : 2000 , maxFreq : 3000 ) ;
33+ spec . Add ( L ) ;
2234
2335 var ffts = spec . GetFFTs ( ) ;
2436 double normalIntensity = 2 ;
@@ -40,7 +52,71 @@ public void Test_QRSS_noAGC()
4052 Console . WriteLine ( floorValue ) ;
4153 }
4254
43- spec . SaveImage ( "qrss-AGCon.png" , intensity : 3 ) ;
55+ spec . SaveImage ( "qrss-agc-norm-floor.png" , intensity : 3 ) ;
56+ }
57+
58+ [ Test ]
59+ public void Test_AGC_normWindow ( )
60+ {
61+ // strategy here is to create a weighted moving window mean and normalize to that
62+
63+ string wavFilePath = "../../../../../data/qrss-10min.wav" ;
64+ ( int sampleRate , double [ ] L ) = WavFile . ReadMono ( wavFilePath ) ;
65+
66+ int fftSize = 8192 ;
67+ var spec = new Spectrogram ( sampleRate , fftSize , stepSize : 2000 , maxFreq : 3000 ) ;
68+ spec . Add ( L ) ;
69+
70+ var ffts = spec . GetFFTs ( ) ;
71+ for ( int i = 0 ; i < ffts . Count ; i ++ )
72+ ffts [ i ] = SubtractMovingWindowFloor ( ffts [ i ] ) ;
73+
74+ spec . SaveImage ( "qrss-agc-norm-window.png" , intensity : 3 ) ;
75+ }
76+
77+ private double [ ] SubtractMovingWindow ( double [ ] input , int windowSizePx = 100 )
78+ {
79+ // return a copy of the input array with the moving window subtracted
80+
81+ double [ ] window = FftSharp . Window . Hanning ( windowSizePx ) ;
82+ double windowSum = window . Sum ( ) ;
83+
84+ double [ ] windowed = new double [ input . Length ] ;
85+ double [ ] normalized = new double [ input . Length ] ;
86+
87+ for ( int i = 0 ; i < input . Length - window . Length ; i ++ )
88+ {
89+ double windowedInputSum = 0 ;
90+ for ( int j = 0 ; j < window . Length ; j ++ )
91+ windowedInputSum += input [ i + j ] * window [ j ] ;
92+ windowed [ i + window . Length / 2 ] = windowedInputSum / windowSum ;
93+ }
94+
95+ for ( int i = 0 ; i < input . Length ; i ++ )
96+ normalized [ i ] = Math . Max ( input [ i ] - windowed [ i ] , 0 ) ;
97+
98+ return normalized ;
99+ }
100+
101+ private double [ ] SubtractMovingWindowFloor ( double [ ] input , int windowSizePx = 20 , double percentile = .2 )
102+ {
103+ // return a copy of the input with the noise floor subtracted
104+ // where the noise floor is calculated from a moving window
105+ // this is good but very slow
106+
107+ double [ ] normalized = new double [ input . Length ] ;
108+ int floorIndex = ( int ) ( percentile * windowSizePx ) ;
109+
110+ double [ ] segment = new double [ windowSizePx ] ;
111+ for ( int i = 0 ; i < input . Length - windowSizePx ; i ++ )
112+ {
113+ for ( int j = 0 ; j < windowSizePx ; j ++ )
114+ segment [ j ] = input [ i + j ] ;
115+ Array . Sort ( segment ) ;
116+ normalized [ i ] = Math . Max ( input [ i ] - segment [ floorIndex ] , 0 ) ;
117+ }
118+
119+ return normalized ;
44120 }
45121 }
46122}
0 commit comments