@@ -8,17 +8,29 @@ namespace Spectrogram.Tests
8
8
{
9
9
class TestAGC
10
10
{
11
- // [Test]
12
- public void Test_QRSS_noAGC ( )
11
+ [ Test ]
12
+ public void Test_AGC_off ( )
13
13
{
14
14
string wavFilePath = "../../../../../data/qrss-10min.wav" ;
15
15
( int sampleRate , double [ ] L ) = WavFile . ReadMono ( wavFilePath ) ;
16
-
17
16
18
17
int fftSize = 8192 ;
19
18
var spec = new Spectrogram ( sampleRate , fftSize , stepSize : 2000 , maxFreq : 3000 ) ;
20
19
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 ) ;
22
34
23
35
var ffts = spec . GetFFTs ( ) ;
24
36
double normalIntensity = 2 ;
@@ -40,7 +52,71 @@ public void Test_QRSS_noAGC()
40
52
Console . WriteLine ( floorValue ) ;
41
53
}
42
54
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 ;
44
120
}
45
121
}
46
122
}
0 commit comments