Skip to content

Commit 769917e

Browse files
committed
demonstrate moving window noise floor subtraction
swharden/FSKview#15
1 parent 027a12c commit 769917e

File tree

1 file changed

+81
-5
lines changed

1 file changed

+81
-5
lines changed

src/Spectrogram.Tests/TestAGC.cs

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)