mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 10:07:26 -05:00
* Popout configuration panel (top-left of display) * Themed color support * Reworked signal averating/smoothing * Optional decibel overlay with corrected decibel calculations * Integrated noise floor slider in channel view * Traffic channel overlays * Popup channel information when hovering over playlist channels. * Removes legacy swing components. Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.spectrum;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests the spectral display frequency, bin and screen position conversions.
|
||||
*/
|
||||
public class SpectralDisplayModelTest
|
||||
{
|
||||
private static SpectralDisplayModel model(long center, int sampleRate, int bins)
|
||||
{
|
||||
SpectralDisplayModel model = new SpectralDisplayModel();
|
||||
model.centerFrequencyProperty().set(center);
|
||||
model.sampleRateProperty().set(sampleRate);
|
||||
model.setBinCount(bins);
|
||||
return model;
|
||||
}
|
||||
|
||||
@Test
|
||||
void centerBinIsAtCenterFrequency()
|
||||
{
|
||||
SpectralDisplayModel model = model(100_000_000, 2_048_000, 4096);
|
||||
double width = 1024;
|
||||
//Bin 2048 (the DC bin) is centered at x = (2048 + 0.5) * pixels per bin
|
||||
double x = (2048 + 0.5) * model.getPixelsPerBin(width);
|
||||
assertEquals(100_000_000, model.getFrequency(x, width), 1);
|
||||
assertEquals(x, model.getX(100_000_000, width), 1E-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void frequencyAndPositionRoundTrip()
|
||||
{
|
||||
SpectralDisplayModel model = model(851_000_000, 10_000_000, 8192);
|
||||
double width = 1500;
|
||||
|
||||
for(int zoom = 0; zoom <= SpectralDisplayModel.MAX_ZOOM; zoom++)
|
||||
{
|
||||
model.setZoom(zoom, 852_000_000, 0.25);
|
||||
|
||||
for(double x = 0; x <= width; x += 37.5)
|
||||
{
|
||||
long frequency = model.getFrequency(x, width);
|
||||
assertEquals(x, model.getX(frequency, width), 0.01, "zoom " + zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoomKeepsFrequencyUnderMouse()
|
||||
{
|
||||
SpectralDisplayModel model = model(460_000_000, 2_400_000, 4096);
|
||||
double width = 1200;
|
||||
double x = 900;
|
||||
long frequency = model.getFrequency(x, width);
|
||||
|
||||
model.setZoom(3, frequency, x / width);
|
||||
|
||||
assertEquals(8, model.getZoomMultiplier());
|
||||
assertEquals(4096 / 8, model.getVisibleBinCount());
|
||||
//Within one bin width of the original frequency
|
||||
assertEquals(frequency, model.getFrequency(x, width), model.getBinWidth());
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoomOffsetIsLimitedToTheBins()
|
||||
{
|
||||
SpectralDisplayModel model = model(460_000_000, 2_400_000, 4096);
|
||||
model.setZoom(2, model.getMaxFrequency(), 0.0);
|
||||
assertEquals(4096 - 1024, model.getZoomOffset(), 1E-9);
|
||||
model.setZoomOffset(-50);
|
||||
assertEquals(0, model.getZoomOffset(), 1E-9);
|
||||
model.setZoom(0, model.getCenterFrequency(), 0.5);
|
||||
assertEquals(0, model.getZoomOffset(), 1E-9);
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoomIsLimited()
|
||||
{
|
||||
SpectralDisplayModel model = model(460_000_000, 2_400_000, 4096);
|
||||
model.setZoom(99, model.getCenterFrequency(), 0.5);
|
||||
assertEquals(SpectralDisplayModel.MAX_ZOOM, model.getZoom());
|
||||
model.setZoom(-5, model.getCenterFrequency(), 0.5);
|
||||
assertEquals(SpectralDisplayModel.MIN_ZOOM, model.getZoom());
|
||||
}
|
||||
|
||||
@Test
|
||||
void binCountChangeKeepsLeftEdgeFrequency()
|
||||
{
|
||||
SpectralDisplayModel model = model(460_000_000, 2_400_000, 4096);
|
||||
model.setZoom(2, 460_300_000, 0.0);
|
||||
double left = model.getMinFrequency() + model.getZoomOffset() * model.getBinWidth();
|
||||
model.setBinCount(16384);
|
||||
double after = model.getMinFrequency() + model.getZoomOffset() * model.getBinWidth();
|
||||
assertEquals(left, after, 1.0);
|
||||
assertTrue(model.getZoomOffset() <= model.getBinCount() - model.getVisibleBinCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dynamicRangeIncludesProcessingGain()
|
||||
{
|
||||
SpectralDisplayModel model = new SpectralDisplayModel();
|
||||
model.sampleSizeProperty().set(12.0);
|
||||
model.setBinCount(4096);
|
||||
model.noiseBandwidthProperty().set(2.0);
|
||||
//66.23 dB sample range + 10 * log10(4096 / 2) = 33.11 dB processing gain
|
||||
assertEquals(99.34, model.getDynamicRange(), 0.01);
|
||||
model.setBinCount(8192);
|
||||
assertEquals(102.35, model.getDynamicRange(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dynamicRangeFollowsSampleSize()
|
||||
{
|
||||
SpectralDisplayModel model = new SpectralDisplayModel();
|
||||
model.sampleSizeProperty().set(12.0);
|
||||
assertEquals(66.22, model.getDynamicRange(), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.spectrum;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests the spectral display themes and waterfall palettes.
|
||||
*/
|
||||
public class SpectralDisplayThemeTest
|
||||
{
|
||||
@Test
|
||||
void gradientEndsAtTheFirstAndLastColors()
|
||||
{
|
||||
int[] palette = WaterfallPalette.gradient("#000000", "#FF8000", "#FFFFFF");
|
||||
assertEquals(WaterfallPalette.SIZE, palette.length);
|
||||
assertEquals(0xFF000000, palette[0]);
|
||||
assertEquals(0xFFFFFFFF, palette[WaterfallPalette.SIZE - 1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classicPaletteMatchesTheOriginalWaterfall()
|
||||
{
|
||||
int[] palette = WaterfallPalette.classic();
|
||||
assertEquals(0xFF00007F, palette[0]);
|
||||
assertEquals(0xFF0000BF, palette[16]);
|
||||
assertEquals(0xFFFFFF00, palette[60]);
|
||||
assertEquals(0xFFFF0000, palette[255]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyThemeHasAnOpaqueWaterfallAndALabel()
|
||||
{
|
||||
for(SpectralDisplayTheme theme: SpectralDisplayTheme.values())
|
||||
{
|
||||
assertTrue(theme.getLabel() != null && !theme.getLabel().isEmpty(), theme.name());
|
||||
assertEquals(1.0, theme.getBackground().getOpacity(), 1E-9, theme.name());
|
||||
|
||||
for(int index = 0; index < WaterfallPalette.SIZE; index++)
|
||||
{
|
||||
assertEquals(0xFF, theme.getWaterfallColor(index) >>> 24, theme.name() + " index " + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeByteIndexesMapToTheUpperPalette()
|
||||
{
|
||||
SpectralDisplayTheme theme = SpectralDisplayTheme.CLASSIC;
|
||||
assertEquals(theme.getWaterfallColor(255), theme.getWaterfallColor((byte)255));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.spectrum;
|
||||
|
||||
import java.util.Random;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests the spectrum attack/decay averaging and peak hold.
|
||||
*/
|
||||
public class SpectrumAveragerTest
|
||||
{
|
||||
/**
|
||||
* Noise power in a DFT bin is exponentially distributed. Averaging its dB values reads about 2.5 dB below the
|
||||
* mean power. Averaging linear power (symmetric rates) should read the mean power.
|
||||
*/
|
||||
@Test
|
||||
void linearPowerAveragingMeasuresMeanNoisePower()
|
||||
{
|
||||
Random random = new Random(3);
|
||||
int bins = 2048;
|
||||
double meanPower = 1E-6; //-60 dB
|
||||
double linearSum = 0.0;
|
||||
double decibelSum = 0.0;
|
||||
int count = 0;
|
||||
|
||||
for(int frame = 0; frame < 4000; frame++)
|
||||
{
|
||||
for(int x = 0; x < bins; x++)
|
||||
{
|
||||
double power = -meanPower * Math.log(1.0 - random.nextDouble());
|
||||
double decibels = 10.0 * Math.log10(power);
|
||||
|
||||
if(frame >= 200)
|
||||
{
|
||||
linearSum += power;
|
||||
decibelSum += decibels;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(-60.0, 10.0 * Math.log10(linearSum / count), 0.05);
|
||||
//The bias of averaging decibel values that the linear averaging avoids
|
||||
assertEquals(-62.5, decibelSum / count, 0.1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void steadyInputSettlesAtTheInputLevel()
|
||||
{
|
||||
SpectrumAverager averager = new SpectrumAverager();
|
||||
averager.setAveraging(10);
|
||||
float[] frame = {-80f, -40f, -60f};
|
||||
SpectrumAverager.Frame result = null;
|
||||
|
||||
for(int x = 0; x < 200; x++)
|
||||
{
|
||||
result = averager.process(frame);
|
||||
}
|
||||
|
||||
assertEquals(-80f, result.average()[0], 0.01f);
|
||||
assertEquals(-40f, result.average()[1], 0.01f);
|
||||
assertEquals(-60f, result.average()[2], 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void signalsRiseFasterThanTheyFall()
|
||||
{
|
||||
SpectrumAverager averager = new SpectrumAverager();
|
||||
averager.setAveraging(10);
|
||||
averager.process(new float[]{-80f});
|
||||
|
||||
//One frame of a strong signal rises 2.5x as far (in linear power) as one frame of it disappearing falls
|
||||
float risen = averager.process(new float[]{-20f}).average()[0];
|
||||
double risePower = SpectrumAverager.toPower(risen) - SpectrumAverager.toPower(-80f);
|
||||
assertEquals(0.25 * (SpectrumAverager.toPower(-20f) - SpectrumAverager.toPower(-80f)), risePower, 1E-6);
|
||||
|
||||
float fallen = averager.process(new float[]{-80f}).average()[0];
|
||||
double fallPower = SpectrumAverager.toPower(risen) - SpectrumAverager.toPower(fallen);
|
||||
assertEquals(0.1 * (SpectrumAverager.toPower(risen) - SpectrumAverager.toPower(-80f)), fallPower, 1E-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fastestSettingFollowsTheInput()
|
||||
{
|
||||
SpectrumAverager averager = new SpectrumAverager();
|
||||
averager.setAveraging(SpectrumAverager.AVERAGING_MINIMUM);
|
||||
averager.process(new float[]{-80f});
|
||||
assertEquals(-20f, averager.process(new float[]{-20f}).average()[0], 0.001f);
|
||||
assertEquals(-90f, averager.process(new float[]{-90f}).average()[0], 0.001f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void peakHoldHoldsAndFalls()
|
||||
{
|
||||
SpectrumAverager averager = new SpectrumAverager();
|
||||
assertNull(averager.process(new float[]{-80f}).peak());
|
||||
|
||||
averager.setPeakHold(true);
|
||||
assertEquals(-30f, averager.process(new float[]{-30f}).peak()[0], 0.001f);
|
||||
|
||||
float[] peak = null;
|
||||
|
||||
for(int x = 0; x < 10; x++)
|
||||
{
|
||||
peak = averager.process(new float[]{-80f}).peak();
|
||||
}
|
||||
|
||||
assertEquals(-30f - (10 * SpectrumAverager.PEAK_DECAY_DB), peak[0], 0.001f);
|
||||
assertTrue(peak[0] > -80f);
|
||||
|
||||
averager.resetPeak();
|
||||
assertEquals(-80f, averager.process(new float[]{-80f}).peak()[0], 0.001f);
|
||||
|
||||
averager.setPeakHold(false);
|
||||
assertNull(averager.process(new float[]{-80f}).peak());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolutionLabels()
|
||||
{
|
||||
assertEquals("585.9 Hz/bin", SpectralDisplayConfigPanel.formatResolution(2_400_000.0 / 4096));
|
||||
assertEquals("2.44 kHz/bin", SpectralDisplayConfigPanel.formatResolution(10_000_000.0 / 4096));
|
||||
assertEquals("73.2 Hz/bin", SpectralDisplayConfigPanel.formatResolution(2_400_000.0 / 32768));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.spectrum.layer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests the decibel scale grid step.
|
||||
*/
|
||||
public class DecibelLayerTest
|
||||
{
|
||||
@Test
|
||||
void stepKeepsLinesAtLeast28PixelsApart()
|
||||
{
|
||||
//100 dB over 300 pixels = 3 pixels per dB: 10 dB = 30 pixels
|
||||
assertEquals(10.0, DecibelLayer.getStep(100, 300));
|
||||
//100 dB over 1000 pixels = 10 pixels per dB: 5 dB = 50 pixels
|
||||
assertEquals(5.0, DecibelLayer.getStep(100, 1000));
|
||||
//100 dB over 100 pixels = 1 pixel per dB: 50 dB = 50 pixels
|
||||
assertEquals(50.0, DecibelLayer.getStep(100, 100));
|
||||
assertEquals(50.0, DecibelLayer.getStep(100, 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.spectrum.layer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests the frequency label spacing.
|
||||
*/
|
||||
public class FrequencyLayerTest
|
||||
{
|
||||
@Test
|
||||
void niceStepRoundsUpToOneTwoOrFive()
|
||||
{
|
||||
assertEquals(1, FrequencyLayer.niceStep(0.3));
|
||||
assertEquals(1, FrequencyLayer.niceStep(1));
|
||||
assertEquals(2, FrequencyLayer.niceStep(1.5));
|
||||
assertEquals(5, FrequencyLayer.niceStep(3));
|
||||
assertEquals(10, FrequencyLayer.niceStep(7));
|
||||
assertEquals(100_000, FrequencyLayer.niceStep(100_000));
|
||||
assertEquals(200_000, FrequencyLayer.niceStep(120_000));
|
||||
assertEquals(500_000, FrequencyLayer.niceStep(250_001));
|
||||
assertEquals(1_000_000, FrequencyLayer.niceStep(600_000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.spectrum;
|
||||
|
||||
import io.github.dsheirer.dsp.window.WindowFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.spectrum.converter.FullScaleDecibelConverter;
|
||||
import java.util.Random;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests the dBFS computation: unity gain window, DFT and full scale decibel conversion, the same steps the
|
||||
* spectral display's DFT processor uses.
|
||||
*/
|
||||
public class FullScaleDecibelTest
|
||||
{
|
||||
private static final WindowType[] WINDOWS = {WindowType.NONE, WindowType.HANN, WindowType.BLACKMAN_HARRIS_7,
|
||||
WindowType.FLAT_TOP, WindowType.KAISER};
|
||||
|
||||
/**
|
||||
* Windows, transforms and converts the interleaved samples to dBFS, in display order (DC at size / 2).
|
||||
*/
|
||||
private static float[] dbfs(float[] samples, WindowType window)
|
||||
{
|
||||
int size = samples.length / 2;
|
||||
WindowFactory.apply(ComplexDftProcessor.createWindow(window, size, true), samples);
|
||||
new FloatFFT_1D(size).complexForward(samples);
|
||||
return FullScaleDecibelConverter.convert(samples);
|
||||
}
|
||||
|
||||
private static float[] tone(int size, int bin, double amplitude)
|
||||
{
|
||||
float[] samples = new float[size * 2];
|
||||
|
||||
for(int k = 0; k < size; k++)
|
||||
{
|
||||
double phase = 2.0 * Math.PI * bin * k / size;
|
||||
samples[2 * k] = (float)(amplitude * Math.cos(phase));
|
||||
samples[2 * k + 1] = (float)(amplitude * Math.sin(phase));
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Test
|
||||
void fullScaleToneIsZeroDbfsForEverySizeAndWindow()
|
||||
{
|
||||
for(DFTSize size: DFTSize.values())
|
||||
{
|
||||
for(WindowType window: WINDOWS)
|
||||
{
|
||||
int n = size.getSize();
|
||||
int bin = n / 8;
|
||||
float[] decibels = dbfs(tone(n, bin, 1.0), window);
|
||||
assertEquals(0.0, decibels[n / 2 + bin], 0.01, size + " " + window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void toneLevelFollowsAmplitude()
|
||||
{
|
||||
int n = 4096;
|
||||
//-20 dBFS tone at a negative frequency
|
||||
float[] decibels = dbfs(tone(n, -300, 0.1), WindowType.BLACKMAN_HARRIS_7);
|
||||
assertEquals(-20.0, decibels[n / 2 - 300], 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noiseFloorIsLoweredByTheProcessingGain()
|
||||
{
|
||||
//Complex white noise with total power of 1E-4 (-40 dBFS)
|
||||
double power = 1E-4;
|
||||
double sigma = Math.sqrt(power / 2.0);
|
||||
|
||||
for(DFTSize size: new DFTSize[]{DFTSize.FFT01024, DFTSize.FFT08192})
|
||||
{
|
||||
for(WindowType window: new WindowType[]{WindowType.NONE, WindowType.BLACKMAN_HARRIS_7})
|
||||
{
|
||||
int n = size.getSize();
|
||||
double sum = 0.0;
|
||||
int frames = 8;
|
||||
Random random = new Random(7);
|
||||
|
||||
for(int frame = 0; frame < frames; frame++)
|
||||
{
|
||||
float[] samples = new float[n * 2];
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
samples[x] = (float)(random.nextGaussian() * sigma);
|
||||
}
|
||||
|
||||
for(float decibel: dbfs(samples, window))
|
||||
{
|
||||
sum += Math.pow(10.0, decibel / 10.0);
|
||||
}
|
||||
}
|
||||
|
||||
double measured = 10.0 * Math.log10(sum / (frames * n));
|
||||
double expected = -40.0 - 10.0 * Math.log10(n / ComplexDftProcessor.getNoiseBandwidth(window));
|
||||
assertEquals(expected, measured, 0.3, size + " " + window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void noiseBandwidthIsInTheExpectedRange()
|
||||
{
|
||||
assertEquals(1.0, ComplexDftProcessor.getNoiseBandwidth(WindowType.NONE), 0.001);
|
||||
assertEquals(1.5, ComplexDftProcessor.getNoiseBandwidth(WindowType.HANN), 0.01);
|
||||
double blackmanHarris = ComplexDftProcessor.getNoiseBandwidth(WindowType.BLACKMAN_HARRIS_7);
|
||||
assertTrue(blackmanHarris > 2.0 && blackmanHarris < 3.5, "BH7 ENBW " + blackmanHarris);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameCoefficientIsAppliedToIAndQ()
|
||||
{
|
||||
float[] window = ComplexDftProcessor.createWindow(WindowType.HANN, 512, false);
|
||||
assertEquals(1024, window.length);
|
||||
|
||||
for(int x = 0; x < 512; x++)
|
||||
{
|
||||
assertEquals(window[2 * x], window[2 * x + 1], 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user