using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Spectrogram.Tests { class TestAGC { [Test] public void Test_AGC_off() { string wavFilePath = "../../../../../data/qrss-10min.wav"; (double[] audio, int sampleRate) = AudioFile.ReadWAV(wavFilePath); int fftSize = 8192; var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 2000, maxFreq: 3000); spec.Add(audio); spec.SaveImage("qrss-agc-off.png", intensity: 3); } [Test] public void Test_AGC_normToNoiseFloor() { // strategy here is to normalize to the magnitude of the quietest 20% of frequencies string wavFilePath = "../../../../../data/qrss-10min.wav"; (double[] audio, int sampleRate) = AudioFile.ReadWAV(wavFilePath); int fftSize = 8192; var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 2000, maxFreq: 3000); spec.Add(audio); var ffts = spec.GetFFTs(); double normalIntensity = 2; for (int i = 0; i < ffts.Count; i++) { double[] sorted = new double[ffts[i].Length]; ffts[i].CopyTo(sorted, 0); Array.Sort(sorted); double percentile = 0.25; int percentileIndex = (int)(percentile * ffts[0].Length); double floorValue = sorted[percentileIndex]; for (int y = 0; y < ffts[i].Length; y++) { ffts[i][y] = ffts[i][y] / floorValue * normalIntensity; } Console.WriteLine(floorValue); } spec.SaveImage("qrss-agc-norm-floor.png", intensity: 3); } [Test] public void Test_AGC_normWindow() { // strategy here is to create a weighted moving window mean and normalize to that string wavFilePath = "../../../../../data/qrss-10min.wav"; (double[] audio, int sampleRate) = AudioFile.ReadWAV(wavFilePath); int fftSize = 8192; var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 2000, maxFreq: 3000); spec.Add(audio); var ffts = spec.GetFFTs(); for (int i = 0; i < ffts.Count; i++) ffts[i] = SubtractMovingWindowFloor(ffts[i]); spec.SaveImage("qrss-agc-norm-window.png", intensity: 3); } private double[] SubtractMovingWindow(double[] input, int windowSizePx = 100) { // return a copy of the input array with the moving window subtracted var hanningWindow = new FftSharp.Windows.Hanning(); double[] window = hanningWindow.Create(windowSizePx); double windowSum = window.Sum(); double[] windowed = new double[input.Length]; double[] normalized = new double[input.Length]; for (int i = 0; i < input.Length - window.Length; i++) { double windowedInputSum = 0; for (int j = 0; j < window.Length; j++) windowedInputSum += input[i + j] * window[j]; windowed[i + window.Length / 2] = windowedInputSum / windowSum; } for (int i = 0; i < input.Length; i++) normalized[i] = Math.Max(input[i] - windowed[i], 0); return normalized; } private double[] SubtractMovingWindowFloor(double[] input, int windowSizePx = 20, double percentile = .2) { // return a copy of the input with the noise floor subtracted // where the noise floor is calculated from a moving window // this is good but very slow double[] normalized = new double[input.Length]; int floorIndex = (int)(percentile * windowSizePx); double[] segment = new double[windowSizePx]; for (int i = 0; i < input.Length - windowSizePx; i++) { for (int j = 0; j < windowSizePx; j++) segment[j] = input[i + j]; Array.Sort(segment); normalized[i] = Math.Max(input[i] - segment[floorIndex], 0); } return normalized; } } }