Updates complex decimation filters to use either scalar or vector optimal implementation. (#1148)

Updates HackRF native byte buffers with automatic DC removal.
Creates new half-band channel source to replace the cic-based channel source in the heterodyne channelizer.  Resolves issue in RTL native buffer processing.  Creates a CACHED thread pool and reduces the number of threads for the SCHEDULED thread pool to 2.  Changes each of the channel source implementations to use a dedicated thread (Dispatcher class) instead of the scheduled thread pool, to reduce the amount of buffered/retained samples objects in memory. Lots of other small changes and tweaks.
Removes last of Reusable buffer code.  Updates DFTProcessor to use a single thread scheduled executor.  Updates Polyphase channelizer to use threads instead of an executor.  Removes a number of unused classes.
Commenting out window calibration.  Scalar is faster in both implementations.
Adds vector and scalar implementations for windows.  Repackages window and window type classes.
WIP adds vector implementations for Airspy non-interleaved buffer iterator and calibration plugins for both interleaved and non-interleaved buffer iterators.
WIP adds vector implementations for Airspy interleaved buffer iterator.
Work on developing scalar and vector implementations of airspy sample conversion and airspy native buffer processing.
Introduces new INativeBuffer framework where tuner raw samples are retained in their native (compressed) format until when they're needed and they are converted on-demand into smaller complex sample buffers to aid garbage collection.
Removes all usage of ReusableComplexBuffers and ReusableChannelREsultsBuffers.  Updates polyphase channelizer to be more efficient in output channel processing.
Updates ViterbiDecoder to change Map usage to array for memory efficiency.
Updates channel processors to used scalar implementation for gain and mixer, until the polyphase channel results can be fixed to be a multiple of the SIMD lane width.
Resolves final changes for the conversion from interleaved sample arrays to complexsamples buffers for distributing baseband samples to channels and processing chains.
Updates tuner channel sources and all decoders to receive new complex samples buffers, removing the previous reusable complex buffers as the default baseband samples buffer object.
Still to resolve is the ComplexMixer implementations.  Needs validation and a calibration implementation with an update to the complex mixer factory.
Updates the DMR decoder to use optimal (scalar vs vector) implementations of filter and AGC controls.
Updates the AM demodulator for separated I&Q float sample processing.
Code review and testing changes.
updates build script for runtime configuration.
Integrates calibration manager with main sdrtrunk application.  Updates NBFMDecoder to use new optimized FIR and Decimation filter options.
Updates real half-band decimation filters to select the optimal (scalar vs vector) implementation.
Adds calibration manager plugins.
Adds calibration manager and plugins to determine the optimal (scalar vs vector) implementation type to use for various classes of dsp components.
Adds vector squelching FM demodulator and cleans up the IIR filter and power squelch to use all floats.
Adds vector implementation of FM demodulator.  This introduces a new interface that accepts i/q samples as two separate float arrays, versus an interleaved array, which enables more efficient processing.  This new complex sample interface will be used across the codebase eventually.
Adds ComplexOscillator and a SIMD version VectorComplexOscillator.  Deprecates the LowPhaseNoiseOscillator because the ComplexOscillator is about 35% faster.  The vectorized version is about 43% faster when SIMD lane width is 8x.
Adds new vector complex filters and new real oscillators.  Note: the RealOscillator using straight Java math sin() function is 3x slower than the existing legacy implementation.  Will replace this with a complex oscillator that only uses the real part.
Updates ComplexFIRFilter to be 50% more efficient.
Adds 23 tap filter implementations.  Will not add 63-tap implementations because they are slower than the default vector implementation.
Adds 15-tap customized vector half band filters.
Adds initial custom complex vector half-band decimation filters for 11-tap and 15-tap decimation filters..
Adds vector complex half band filter implementations.  Note: performance of the generic 64-bit 2-lane filter is horrible and is not included.
Optimizes complex half-band filter to be 27% more efficient.
Implements vectorized real half-band filter implementations.
Implements a new FIR filter class that processes sample buffers instead of single sample at a time.
Refactored all of the calibratons to be time-duration based.  Testing on windows i7 gen 3 CPU showed that it took 1000x longer for one of the calibrations to run, versus my AMD 5800x.
Changed gradle jvm args so that the windows plaf export is always applied.
Updates gradle build script to ensure incubator module is included and to cleanup javafx library management for building runtime images.
Testing tweaks and changes.
Adds logging limiter to dispatcher for buffer overflows
Adds User Preferences editor for performing Vector/SIMD calibrations and new startup dialog prompting the user to perform calibrations.
Updates calibration manager and all calibration implementations to add warm-up and test phases and to reduce the overall time it takes to calibrate a system.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-03-04 08:45:05 -05:00
committed by GitHub
parent 5e9c239ea3
commit 0eec2e7ab8
403 changed files with 22916 additions and 15088 deletions

View File

@@ -0,0 +1,29 @@
package io.github.dsheirer.dsp.window;
/**
* Scalar window implementation.
*/
public class ScalarWindow extends Window
{
/**
* Constructs an instance.
*
* @param coefficients coefficients
*/
public ScalarWindow(float[] coefficients)
{
super(coefficients);
}
@Override public float[] apply(float[] samples)
{
validate(samples);
for(int x = 0; x < mCoefficients.length; x++)
{
samples[x] *= mCoefficients[x];
}
return samples;
}
}

View File

@@ -0,0 +1,45 @@
package io.github.dsheirer.dsp.window;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector SIMD window implementation.
*/
public class VectorWindow extends Window
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
/**
* Constructs an instance.
*
* @param coefficients coefficients
*/
public VectorWindow(float[] coefficients)
{
super(coefficients);
}
@Override public float[] apply(float[] samples)
{
validate(samples);
int x = 0;
//Applies the coefficients to the samples in multiples of the SIMD lane width
for(; x < VECTOR_SPECIES.loopBound(mCoefficients.length); x += VECTOR_SPECIES.length())
{
FloatVector.fromArray(VECTOR_SPECIES, samples, x)
.mul(FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, x))
.intoArray(samples, x);
}
//Cleanup loop to calculate the final indices that are not a multiple of the SIMD lane width
for(; x < mCoefficients.length; x++)
{
samples[x] *= mCoefficients[x];
}
return samples;
}
}

View File

@@ -0,0 +1,38 @@
package io.github.dsheirer.dsp.window;
/**
* Base window implementation.
*/
public abstract class Window
{
protected float[] mCoefficients;
/**
* Constructs an instance.
* @param coefficients coefficients
*/
public Window(float[] coefficients)
{
mCoefficients = coefficients;
}
/**
* Validates that the window coefficients and samples array lengths are the same.
* @param samples to validate
*/
protected void validate(float[] samples)
{
if(mCoefficients.length != samples.length)
{
throw new IllegalArgumentException("Sample array length [" + samples.length +
"] must match window coefficients length[" + mCoefficients.length + "]");
}
}
/**
* Applies this window to the samples
* @param samples to window
* @return windowed samples
*/
public abstract float[] apply(float[] samples);
}

View File

@@ -0,0 +1,442 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2021 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.dsp.window;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
/**
* Window factory and corresponding utility methods.
*/
public class WindowFactory
{
private final static Logger mLog = LoggerFactory.getLogger(WindowFactory.class);
private static final float PI = (float)FastMath.PI;
private static final float TWO_PI = (float)FastMath.PI * 2.0f;
private static final float FOUR_PI = (float)FastMath.PI * 4.0f;
private static final float SIX_PI = (float)FastMath.PI * 6.0f;
private static final float EIGHT_PI = (float)FastMath.PI * 8.0f;
/**
* Creates a window of the specified type and length.
*
* @param type of window
* @param length of window
* @return window of the specified type and length
*/
public static float[] getWindow(WindowType type, int length)
{
switch(type)
{
case BLACKMAN:
return getBlackman(length);
case BLACKMAN_HARRIS_4:
return getBlackmanHarris4(length);
case BLACKMAN_HARRIS_7:
return getBlackmanHarris7(length);
case BLACKMAN_NUTALL:
return getBlackmanNutall(length);
case COSINE:
return getCosine(length);
case FLAT_TOP:
return getFlatTop(length);
case HAMMING:
return getHamming(length);
case HANN:
return getHann(length);
case KAISER:
throw new IllegalArgumentException("Kaiser Window cannot be created via this method. Use the getKaiser() method instead.");
case NUTALL:
return getNutall(length);
case NONE:
default:
return getRectangular(length);
}
}
/**
* Creates an all-pass or rectangular window.
*
* @param length of the window
* @return rectangular window
*/
public static float[] getRectangular(int length)
{
float[] coefficients = new float[length];
Arrays.fill(coefficients, 1.0f);
return coefficients;
}
/**
* Creates a cosine window of the specified length.
*
* @param length of the window
* @return cosine window
*/
public static float[] getCosine(int length)
{
float[] coefficients = new float[length];
if(length % 2 == 0) //Even length
{
int half = (int)((length - 1) / 2);
for(int x = -half; x < length / 2 + 1; x++)
{
coefficients[x + half] = (float)FastMath.cos(
((float)x * PI) / ((float)length + 1.0f));
}
}
else //Odd length
{
int half = (int)length / 2;
for(int x = -half; x < half + 1; x++)
{
coefficients[x + half] = (float)FastMath.cos(((float)x * PI) / ((float)length + 1.0f));
}
}
return coefficients;
}
/**
* Creates a 3-term Blackman window.
*
* @param length of the window
* @return Blackman window
*/
public static float[] getBlackman(int length)
{
float[] coefficients = new float[length];
float denominator = length - 1;
float a0 = 0.426590713672f;
float a1 = 0.496560619089f;
float a2 = 0.0768486672399f;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * (float)FastMath.cos((TWO_PI * (float)x) / denominator)) +
(a2 * (float)FastMath.cos((FOUR_PI * (float)x) / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Nutall window
*
* @param length of the window
* @return Nutall window
*/
public static float[] getNutall(int length)
{
float[] coefficients = new float[length];
float denominator = length - 1;
float a0 = 0.355768f;
float a1 = 0.487396f;
float a2 = 0.144232f;
float a3 = 0.012604f;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) +
(a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) -
(a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Nutall window
*
* @param length of the window
* @return Blackman-Nutall window
*/
public static float[] getBlackmanNutall(int length)
{
float[] coefficients = new float[length];
float denominator = length - 1;
float a0 = 0.3635819f;
float a1 = 0.4891775f;
float a2 = 0.1365995f;
float a3 = 0.0106411f;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) +
(a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) -
(a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static float[] getBlackmanHarris4(int length)
{
float[] coefficients = new float[length];
float denominator = length - 1;
float a0 = 0.35875f;
float a1 = 0.48829f;
float a2 = 0.14128f;
float a3 = 0.01168f;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) +
(a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) -
(a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator));
}
return coefficients;
}
/**
* Creates a 7-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static float[] getBlackmanHarris7(int length)
{
float[] coefficients = new float[length];
float denominator = length - 1;
float a0 = 0.27105140069342f;
float a1 = 0.43329793923448f;
float a2 = 0.21812299954311f;
float a3 = 0.06592544638803f;
float a4 = 0.01081174209837f;
float a5 = 0.00077658482522f;
float a6 = 0.00001388721735f;
for(int x = 0; x < length; x++)
{
float w = TWO_PI * (float)x / denominator;
coefficients[x] = a0 -
(a1 * (float)FastMath.cos(w)) +
(a2 * (float)FastMath.cos(2.0 * w)) -
(a3 * (float)FastMath.cos(3.0 * w)) +
(a4 * (float)FastMath.cos(4.0 * w)) -
(a5 * (float)FastMath.cos(5.0 * w)) +
(a6 * (float)FastMath.cos(6.0 * w));
}
return coefficients;
}
/**
* Creates a Hamming window
*
* @param length of the window
* @return Hamming window
*/
public static float[] getHamming(int length)
{
float[] coefficients = new float[length];
for(int x = 0; x < length; x++)
{
coefficients[x] = 0.54f - (0.46f * (float)FastMath.cos((TWO_PI * x) / (length - 1)));
}
return coefficients;
}
/**
* Creates a Hann window
*
* @param length of the window
* @return Hann window
*/
public static float[] getHann(int length)
{
float[] coefficients = new float[length];
for(int x = 0; x < length; x++)
{
coefficients[x] = 0.5f - (0.5f * (float)FastMath.cos(TWO_PI * (float)x / (length - 1)));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static float[] getFlatTop(int length)
{
float[] coefficients = new float[length];
float denominator = length - 1;
float a0 = 0.215578948f;
float a1 = 0.41663158f;
float a2 = 0.277263158f;
float a3 = 0.083578947f;
float a4 = 0.006947368f;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) +
(a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) -
(a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)) +
(a4 * (float)FastMath.cos(EIGHT_PI * (float)x / denominator));
}
return coefficients;
}
/**
* Calculates the beta (roll-off factor) for a Kaiser window based on the specified attenuation.
*
* @param attenuation in decibels
* @return beta value in the range of 0.0 to 1.0
*/
public static float getKaiserBeta(float attenuation)
{
if(attenuation > 50.0)
{
return 0.1102f * (attenuation - 8.7f);
}
else if(attenuation >= 21.0)
{
return (0.5842f * (float)FastMath.pow(attenuation - 21.0, 0.4)) + (0.07886f * (attenuation - 21.0f));
}
else
{
return 0.0f;
}
}
/**
* Creates a Kaiser window.
*
* @param length of the window
* @param attenuation desired
* @return window
*/
public static float[] getKaiser(int length, float attenuation)
{
float[] coefficients = new float[length];
float beta = getKaiserBeta(attenuation);
float betaBesselZerothOrder = getBesselZerothOrder(beta);
float temp;
for(int x = 0; x < coefficients.length; x++)
{
temp = beta * (float)FastMath.sqrt(1.0 - FastMath.pow(2.0 * x / (length - 1) - 1.0, 2));
coefficients[x] = getBesselZerothOrder(temp) / betaBesselZerothOrder;
}
return coefficients;
}
/**
* Zeroth order modified Bessel function.
*
* @param x Value.
* @return Return value.
*/
public final static float getBesselZerothOrder(final float x)
{
float f = 1;
final float x2 = x * x * 0.25f;
float xc = x2;
float v = 1 + x2;
for (int i = 2; i < 100; i++)
{
f *= i;
xc *= x2;
final float a = xc / (f * f);
v += a;
if (a < 1e-20) break;
}
return v;
}
/**
* Apply the window against an array of float-type samples
*/
public static float[] apply(float[] coefficients, float[] samples)
{
for(int x = 0; x < coefficients.length; x++)
{
samples[x] = samples[x] * coefficients[x];
}
return samples;
}
/**
* Apply the window type against the float array of samples
*
* @param type of window to use
* @param samples to be windowed
* @return windowed samples
*/
public static float[] apply(WindowType type, float[] samples)
{
float[] coefficients = getWindow(type, samples.length);
return apply(coefficients, samples);
}
public static void main(String[] args)
{
float[] kaiser = getKaiser(16, 80.0f);
mLog.debug("Window:" + Arrays.toString(kaiser));
}
}

View File

@@ -0,0 +1,38 @@
package io.github.dsheirer.dsp.window;
import java.util.EnumSet;
/**
* Window types
*/
public enum WindowType
{
BLACKMAN("Blackman"),
BLACKMAN_HARRIS_4("Blackman-Harris 4"),
BLACKMAN_HARRIS_7("Blackman-Harris 7"),
BLACKMAN_NUTALL("Blackman-Nutall"),
COSINE("Cosine"),
FLAT_TOP("Flat Top"),
HAMMING("Hamming"),
HANN("Hann"),
KAISER("Kaiser"),
NUTALL("Nutall"),
NONE("None");
private String mLabel;
WindowType(String label)
{
mLabel = label;
}
public String toString()
{
return mLabel;
}
public static final EnumSet<WindowType> NO_PARAMETER_WINDOWS = EnumSet.of(WindowType.BLACKMAN, WindowType.BLACKMAN_HARRIS_4,
WindowType.BLACKMAN_HARRIS_7, WindowType.BLACKMAN_NUTALL, WindowType.COSINE, WindowType.FLAT_TOP,
WindowType.HAMMING, WindowType.HANN, WindowType.NUTALL);
}