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

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -15,17 +14,16 @@
*
* 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.afsk;
import io.github.dsheirer.bits.IBinarySymbolProcessor;
import io.github.dsheirer.buffer.FloatAveragingBuffer;
import io.github.dsheirer.dsp.filter.resample.RealResampler;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,7 +37,7 @@ import org.slf4j.LoggerFactory;
*
* Provides normal or inverted decoded output.
*/
public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
public class AFSK1200Decoder implements Listener<float[]>
{
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class);
@@ -71,7 +69,7 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
private boolean mSampleDecision;
//Resample to an integral of the baud rate 1200 baud * 6 samples per symbol = 7200.0 Hertz
private RealResampler mResampler = new RealResampler(8000.0, SAMPLE_RATE, 2000, 1);
private RealResampler mResampler = new RealResampler(8000.0, SAMPLE_RATE, 8192, 512);
/**
* Constructs a decoder using the provided arguments.
@@ -115,7 +113,7 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
*/
@Override
public void receive(ReusableFloatBuffer buffer)
public void receive(float[] buffer)
{
mResampler.resample(buffer);
}
@@ -147,17 +145,15 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
}
public class Decoder implements Listener<ReusableFloatBuffer>
public class Decoder implements Listener<float[]>
{
@Override
public void receive(ReusableFloatBuffer buffer)
public void receive(float[] buffer)
{
//Calculate correlation values against each 1200/1800 reference signal
mCorrelationValuesMark = mCorrelatorMark.process(buffer);
mCorrelationValuesSpace = mCorrelatorSpace.process(buffer);
buffer.decrementUserCount();
for(int x = 0; x < mCorrelationValuesMark.length; x++)
{
//1200 = Mark (1) and 1800 = Space (0)
@@ -207,8 +203,8 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
{
mAveragingBuffer = new FloatAveragingBuffer(averagingPeriod);
IOscillator referenceSignalGenerator = new Oscillator(frequency, sampleRate);
mReferenceSamples = referenceSignalGenerator.generateReal(correlationPeriod);
IRealOscillator referenceSignalGenerator = OscillatorFactory.getRealOscillator(frequency, sampleRate);
mReferenceSamples = referenceSignalGenerator.generate(correlationPeriod);
mDemodulatedSamples = new float[correlationPeriod];
}
@@ -217,18 +213,16 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
* Processes each sample in the incoming sample buffer against a generated reference sample set for one symbol
* period to derive a correlation value that is in-turn averaged over one symbol period.
*
* @param reusableFloatBuffer containing FM demodulated samples
* @param samples containing FM demodulated samples
* @return a reusable array of correlation values for each sample
*/
public float[] process(ReusableFloatBuffer reusableFloatBuffer)
public float[] process(float[] samples)
{
if(mCorrelationValues == null || mCorrelationValues.length != reusableFloatBuffer.getSampleCount())
if(mCorrelationValues == null || mCorrelationValues.length != samples.length)
{
mCorrelationValues = new float[reusableFloatBuffer.getSampleCount()];
mCorrelationValues = new float[samples.length];
}
float[] samples = reusableFloatBuffer.getSamples();
int y;
for(int x = 0; x < samples.length; x++)

View File

@@ -1,123 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.afsk;
import io.github.dsheirer.buffer.FloatAveragingBuffer;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.complex.Complex;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
public class AFSKTest
{
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class);
private static final DecimalFormat df = new DecimalFormat("00.000");
public static String format(double value)
{
if(value > 0)
{
return " " + df.format(value);
}
else
{
return df.format(value);
}
}
public static float[] generateAFSK1200(boolean[] symbols, int samplesPerSymbol, float gain)
{
float[] samples = new float[symbols.length * samplesPerSymbol];
Oscillator oscillator = new Oscillator(1200.0, (double)samplesPerSymbol * 1200);
for(int x = 0; x < symbols.length; x++)
{
oscillator.setFrequency(symbols[x] ? 1200.0 : 1800.0);
for(int y = 0; y < samplesPerSymbol; y++)
{
Complex sample = new Complex(oscillator.inphase(), oscillator.quadrature());
sample.multiply(gain);
samples[x * samplesPerSymbol + y] = sample.inphase();
oscillator.rotate();
}
}
return samples;
}
public static void main(String[] args)
{
int sampleSize = 6;
FloatAveragingBuffer avg1200 = new FloatAveragingBuffer(sampleSize);
FloatAveragingBuffer avg1800 = new FloatAveragingBuffer(sampleSize);
IOscillator oscillator1200 = new Oscillator(1200.0, 7200.0);
IOscillator oscillator1800 = new Oscillator(1800.0, 7200.0);
oscillator1200.generateReal(1);
oscillator1800.generateReal(1);
float[] reference1200 = oscillator1200.generateReal(sampleSize);
float[] reference1800 = oscillator1800.generateReal(sampleSize);
boolean[] symbols = new boolean[]{true,false,true,false,true,false,true,false,true,true,true,false,true,false,
false,false,true,false,false,false,false,true,false,true,true,true,true,false,true,true,false,true,false,
true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,
false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,
true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,
false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,
true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,
false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,
true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,
false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false};
float[] samples = generateAFSK1200(symbols, 6, 0.5f);
mLog.debug("Length:" + samples.length);
int offset = 0;
float correlation1200 = 0.0f;
float correlation1800 = 0.0f;
while(offset + sampleSize < samples.length)
{
correlation1200 = 0.0f;
correlation1800 = 0.0f;
for(int x = 0; x < sampleSize; x++)
{
correlation1200 += reference1200[x] * samples[x + offset];
correlation1800 += reference1800[x] * samples[x + offset];
}
correlation1200 = avg1200.get(FastMath.abs(correlation1200));
correlation1800 = avg1800.get(FastMath.abs(correlation1800));
mLog.debug(offset + " 1200:" + format(correlation1200) + " 1800:" + format(correlation1800) +
" Decision: " + (correlation1200 > correlation1800 ? "0" : "1"));
offset++;
}
}
}

View File

@@ -1,32 +1,30 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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 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.
* 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/>
*
******************************************************************************/
* 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.am;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.apache.commons.math3.util.FastMath;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
*/
public class AMDemodulator
public class AMDemodulator implements IAmDemodulator
{
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("AMDemodulator");
private int mOutputBufferPointer;
private float mGain;
/**
@@ -59,25 +57,15 @@ public class AMDemodulator
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
}
/**
* Demodulates the reusable comples sample buffer and returns a demodulated audio buffer.
* @param complexBuffer to demodulate
* @return demodulated audio buffer.
*/
public ReusableFloatBuffer demodulate(ReusableComplexBuffer complexBuffer)
@Override public float[] demodulate(float[] i, float[] q)
{
ReusableFloatBuffer reusableFloatBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
float[] input = complexBuffer.getSamples();
float[] output = reusableFloatBuffer.getSamples();
mOutputBufferPointer = 0;
float[] demodulated = new float[i.length];
for(int x= 0; x < complexBuffer.getSamples().length; x += 2)
for(int x = 0; x < i.length; x++)
{
output[mOutputBufferPointer++] = demodulate(input[x], input[x + 1]);
demodulated[x] = (float)FastMath.sqrt((i[x] * i[x]) + (q[x] * q[x])) * mGain;
}
complexBuffer.decrementUserCount();
return reusableFloatBuffer;
return demodulated;
}
}

View File

@@ -0,0 +1,15 @@
package io.github.dsheirer.dsp.am;
/**
* AM Demodulator interface
*/
public interface IAmDemodulator
{
/**
* Demodulate the complex I & Q sample arrays
* @param i samples
* @param q samples
* @return demodulated samples.
*/
float[] demodulate(float[] i, float[] q);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2021 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -18,10 +18,44 @@
*/
package io.github.dsheirer.dsp.filter;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter128Bit;
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter256Bit;
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter512Bit;
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter64Bit;
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilterDefaultBit;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesignerWithLagrange;
import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap128Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap256Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap512Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap64Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter128Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap128Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap256Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap512Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap64Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap128Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap256Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap512Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap64Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter256Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter512Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap128Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap256Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap512Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap64Bit;
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter64Bit;
import io.github.dsheirer.dsp.window.WindowFactory;
import io.github.dsheirer.dsp.window.WindowType;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
@@ -47,9 +81,9 @@ public class FilterFactory
* @param frequency - cutoff in hertz
* @param length - filter length
* @param window - to apply against the coefficients
* @return
* @return coefficients
*/
public static float[] getSinc(double sampleRate, long frequency, int length, Window.WindowType window)
public static float[] getSinc(double sampleRate, long frequency, int length, WindowType window)
{
int evenLength = length % 2 == 0 ? length : length + 1;
@@ -90,7 +124,7 @@ public class FilterFactory
}
//Apply the window against the coefficients
coefficients = Window.apply(window, coefficients);
coefficients = WindowFactory.apply(window, coefficients);
return coefficients;
}
@@ -223,7 +257,7 @@ public class FilterFactory
* @param windowType - window to apply against the generated coefficients
* @return
*/
public static float[] getLowPass(double sampleRate, long cutoff, int filterLength, Window.WindowType windowType)
public static float[] getLowPass(double sampleRate, long cutoff, int filterLength, WindowType windowType)
{
return getSinc(sampleRate, cutoff, filterLength, windowType);
}
@@ -243,7 +277,7 @@ public class FilterFactory
* - stopFrequency <= sampleRate/2
*/
public static float[] getLowPass(double sampleRate, int passFrequency, int stopFrequency, int attenuation,
Window.WindowType windowType, boolean forceOddLength)
WindowType windowType, boolean forceOddLength)
{
if(stopFrequency < passFrequency || stopFrequency > (sampleRate / 2))
{
@@ -276,7 +310,7 @@ public class FilterFactory
* @param windowType - window to apply against the generated coefficients
* @return
*/
public static float[] getHighPass(int sampleRate, long cutoff, int filterLength, Window.WindowType windowType)
public static float[] getHighPass(int sampleRate, long cutoff, int filterLength, WindowType windowType)
{
//Convert the high frequency cutoff to its low frequency cutoff
//equivalent, so that when we generate the low pass filter, prior to
@@ -287,7 +321,7 @@ public class FilterFactory
}
public static float[] getHighPass(int sampleRate, long stopFrequency, long passFrequency, int attenuation,
Window.WindowType windowType, boolean forceOddLength)
WindowType windowType, boolean forceOddLength)
{
/* reverse the stop and pass frequency to get the low pass variant */
int tapCount = getTapCount(sampleRate, stopFrequency, passFrequency, attenuation);
@@ -508,7 +542,7 @@ public class FilterFactory
* @param outputSampleRate
* @return
*/
public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, Window.WindowType window)
public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, WindowType window)
{
int taps = getTapCount(outputSampleRate, passFrequency, passFrequency + 1500,
attenuation);
@@ -760,7 +794,7 @@ public class FilterFactory
double cutoff = (channelBandwidth * 1.10) / (channelSampleRate * (double)channels);
//Design the prototype synthesizer with 105% of the channel bandwidth produced by the channelizer.
float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0);
float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0f);
//This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient
float[] extendedTaps = new float[taps.length + 1];
@@ -819,7 +853,7 @@ public class FilterFactory
//Get an initial filter and band edge frequency response using the channel bandwidth as a cutoff
float[] taps = null;
taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0);
taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0f);
double response = FilterFactory.evaluate(taps, bandEdge);
//Set cutoff adjustment threshold - we'll test cutoff frequencies to around 1 hertz resolution
@@ -832,7 +866,7 @@ public class FilterFactory
if(matchesObjective(response, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE, MARGIN_OF_ERROR) &&
(cutoffFrequency + increment <= bandEdge))
{
float[] higherCutoffTaps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency + increment, 80.0);
float[] higherCutoffTaps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency + increment, 80.0f);
double higherCutoffResponse = FilterFactory.evaluate(higherCutoffTaps, bandEdge);
if(matchesObjective(higherCutoffResponse, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE, MARGIN_OF_ERROR))
@@ -878,7 +912,7 @@ public class FilterFactory
increment = cutoffFrequency * 0.1;
}
taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0);
taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0f);
response = FilterFactory.evaluate(taps, bandEdge);
}
}
@@ -900,7 +934,7 @@ public class FilterFactory
mLog.debug("Input Sample Rate: " + sampleRate);
mLog.debug("Channel Bandwidth: " + channelBandwidth);
mLog.debug("Channels: " + channels);
mLog.debug("Window Type: " + Window.WindowType.KAISER.name());
mLog.debug("Window Type: " + WindowType.KAISER.name());
mLog.debug("Taps Per Channel - Requested:" + tapsPerChannel + " Actual:" + ((double)extendedTaps.length / (double)channels));
mLog.debug("Filter Length: " + (extendedTaps.length));
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
@@ -928,7 +962,7 @@ public class FilterFactory
* @return filter coefficients.
* @throws FilterDesignException if the requested length is not odd
*/
public static float[] getSinc(double cutoff, int length, Window.WindowType windowType) throws FilterDesignException
public static float[] getSinc(double cutoff, int length, WindowType windowType) throws FilterDesignException
{
if(length % 2 == 0)
{
@@ -938,7 +972,7 @@ public class FilterFactory
float[] coefficients = new float[length];
int half = length / 2;
double[] window = Window.getWindow(windowType, length);
float[] window = WindowFactory.getWindow(windowType, length);
double scalor = 2.0 * cutoff;
double piScalor = FastMath.PI * scalor;
@@ -967,7 +1001,7 @@ public class FilterFactory
* @return filter coefficients.
* @throws FilterDesignException if the requested length is not odd
*/
public static float[] getKaiserSinc(int length, double cutoff, double attenuation) throws FilterDesignException
public static float[] getKaiserSinc(int length, double cutoff, float attenuation) throws FilterDesignException
{
if(length % 2 == 0)
{
@@ -977,7 +1011,7 @@ public class FilterFactory
float[] coefficients = new float[length];
int half = length / 2;
double[] window = Window.getKaiser(length, attenuation);
float[] window = WindowFactory.getKaiser(length, attenuation);
double scalor = 2.0 * cutoff;
double piScalor = FastMath.PI * scalor;
@@ -1004,7 +1038,7 @@ public class FilterFactory
* @param windowType to apply to the filter.
* @return coefficients
*/
public static float[] getHalfBand(int length, Window.WindowType windowType)
public static float[] getHalfBand(int length, WindowType windowType)
{
if((length - 3) % 4 != 0)
{
@@ -1012,7 +1046,7 @@ public class FilterFactory
"integer multiple in (N=4m+3), e.g. 7, 11, 15, etc");
}
double[] window = Window.getWindow(windowType, length);
float[] window = WindowFactory.getWindow(windowType, length);
float[] taps = new float[length];
int halfLength = length / 2;
@@ -1035,6 +1069,131 @@ public class FilterFactory
return taps;
}
/**
* Creates the optimal FIR filter implementation using calibration data to select
* from among the scalar and vector implementations.
* @param coefficients for the filter
* @return fir filter implementation
*/
public static IRealFilter getRealFilter(float[] coefficients)
{
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_FIR);
switch(implementation)
{
case VECTOR_SIMD_PREFERRED:
return new VectorRealFIRFilterDefaultBit(coefficients);
case VECTOR_SIMD_64:
return new VectorRealFIRFilter64Bit(coefficients);
case VECTOR_SIMD_128:
return new VectorRealFIRFilter128Bit(coefficients);
case VECTOR_SIMD_256:
return new VectorRealFIRFilter256Bit(coefficients);
case VECTOR_SIMD_512:
return new VectorRealFIRFilter512Bit(coefficients);
case UNCALIBRATED:
case SCALAR:
default:
return new RealFIRFilter(coefficients);
}
}
/**
* Constructs the optimal decimation filter implementation for filter length and window type
* using calibration data to select among scalar and vector implementation options.
* @param length of decimation filter
* @param windowType for designing the filter
* @return filter implementation
*/
public static IRealDecimationFilter getRealDecimationFilter(int length, WindowType windowType)
{
float[] coefficients = getHalfBand(length, windowType);
switch(length)
{
case 11:
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_11_TAP))
{
case VECTOR_SIMD_64:
return new VectorRealHalfBandDecimationFilter11Tap64Bit(coefficients);
case VECTOR_SIMD_128:
return new VectorRealHalfBandDecimationFilter11Tap128Bit(coefficients);
case VECTOR_SIMD_256:
return new VectorRealHalfBandDecimationFilter11Tap256Bit(coefficients);
case VECTOR_SIMD_512:
return new VectorRealHalfBandDecimationFilter11Tap512Bit(coefficients);
case SCALAR:
case UNCALIBRATED:
default:
return new RealHalfBandDecimationFilter(coefficients);
}
case 15:
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_15_TAP))
{
case VECTOR_SIMD_64:
return new VectorRealHalfBandDecimationFilter15Tap64Bit(coefficients);
case VECTOR_SIMD_128:
return new VectorRealHalfBandDecimationFilter15Tap128Bit(coefficients);
case VECTOR_SIMD_256:
return new VectorRealHalfBandDecimationFilter15Tap256Bit(coefficients);
case VECTOR_SIMD_512:
return new VectorRealHalfBandDecimationFilter15Tap512Bit(coefficients);
case SCALAR:
case UNCALIBRATED:
default:
return new RealHalfBandDecimationFilter(coefficients);
}
case 23:
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_23_TAP))
{
case VECTOR_SIMD_64:
return new VectorRealHalfBandDecimationFilter23Tap64Bit(coefficients);
case VECTOR_SIMD_128:
return new VectorRealHalfBandDecimationFilter23Tap128Bit(coefficients);
case VECTOR_SIMD_256:
return new VectorRealHalfBandDecimationFilter23Tap256Bit(coefficients);
case VECTOR_SIMD_512:
return new VectorRealHalfBandDecimationFilter23Tap512Bit(coefficients);
case SCALAR:
case UNCALIBRATED:
default:
return new RealHalfBandDecimationFilter(coefficients);
}
case 63:
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_63_TAP))
{
case VECTOR_SIMD_64:
return new VectorRealHalfBandDecimationFilter63Tap64Bit(coefficients);
case VECTOR_SIMD_128:
return new VectorRealHalfBandDecimationFilter63Tap128Bit(coefficients);
case VECTOR_SIMD_256:
return new VectorRealHalfBandDecimationFilter63Tap256Bit(coefficients);
case VECTOR_SIMD_512:
return new VectorRealHalfBandDecimationFilter63Tap512Bit(coefficients);
case SCALAR:
case UNCALIBRATED:
default:
return new RealHalfBandDecimationFilter(coefficients);
}
default:
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_DEFAULT))
{
case VECTOR_SIMD_64:
return new VectorRealHalfBandDecimationFilter64Bit(coefficients);
case VECTOR_SIMD_128:
return new VectorRealHalfBandDecimationFilter128Bit(coefficients);
case VECTOR_SIMD_256:
return new VectorRealHalfBandDecimationFilter256Bit(coefficients);
case VECTOR_SIMD_512:
return new VectorRealHalfBandDecimationFilter512Bit(coefficients);
case SCALAR:
case UNCALIBRATED:
default:
return new RealHalfBandDecimationFilter(coefficients);
}
}
}
/**
* Compares two doubles for equals and avoids any rounding error that are present.
*
@@ -1053,7 +1212,7 @@ public class FilterFactory
int length = 15;
float [] taps = FilterFactory.getHalfBand(length, Window.WindowType.HAMMING);
float [] taps = FilterFactory.getHalfBand(length, WindowType.HAMMING);
for(int x = 0; x < length; x++)
{

File diff suppressed because it is too large Load Diff

View File

@@ -1,204 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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.filter;
import io.github.dsheirer.sample.real.RealSampleListener;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList;
public class FloatHalfBandFilter implements RealSampleListener
{
private RealSampleListener mListener;
private ArrayList<Float> mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
private boolean mDispatchFlag = false;
/**
* Half-Band filter with decimation by 2 against real valued floats.
*
* Takes advantage of the symmetrical nature of FIR filter coefficients by
* adding oldest and newest sample first, then multiplying once by the
* corresponding coefficient
*
* Also, takes advantage of the 0-valued FIR half-band coefficents inherent
* in the half-band filter, and does not calculate those coefficients.
*
* This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample.
*
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public FloatHalfBandFilter( Filters filter, float gain )
{
mCoefficients = filter.getCoefficients();
mBuffer = new ArrayList<Float>();
mBufferSize = mCoefficients.length;
//Fill the buffer with zero valued samples
for( int x = 0; x < mCoefficients.length; x++ )
{
mBuffer.add( 0.0f );
}
generateIndexMap( mCoefficients.length );
mGain = gain;
}
public void dispose()
{
mListener = null;
}
/**
* Calculate the filtered value by applying the coefficients against
* the complex samples in mBuffer
*/
public void receive( float newSample )
{
//Add the new sample to the buffer
mBuffer.set( mBufferPointer, newSample );
//Increment & Adjust the buffer pointer for circular wrap around
mBufferPointer++;
if( mBufferPointer >= mBufferSize )
{
mBufferPointer = 0;
}
//Toggle the flag every time, so that we can calculate and dispatch a
//sample every other time, when the flag is true (ie decimate by 2)
mDispatchFlag = !mDispatchFlag;
//Calculate a filtered sample when the flag is true
if( mDispatchFlag )
{
//Convolution - multiply filter coefficients by the circular buffer
//samples to calculate a new filtered value
float accumulator = 0;
//Start with the center tap value
accumulator += mCoefficients[ mCenterCoefficient ] *
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] );
//For the remaining coefficients, add the symmetric samples, oldest and newest
//first, then multiply by the single coefficient
for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 )
{
accumulator += mCoefficients[ x ] *
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ) +
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) );
}
//We're almost finished ... apply gain, cast the floats to shorts and
//send it on it's merry way
if( mListener != null )
{
mListener.receive( (float)( accumulator * mGain ) );
}
}
}
/**
* Creates an n X (n + 1 / 2) index map enabling quick access to the
* circular buffer samples.
*
* As the buffer shifts right with each subsequent sample, we have to move
* the index pointers with it, for efficient access of the samples.
*
* The first array index value in the index map corresponds to the current
* buffer pointer location.
*
* The second array index value points to the samples that should be
* multiplied by the coefficients as follows:
*
* 0 = center tap sample, to be multiplied by center coefficient
*
* 0 = sample( 1 )
* 1 = sample( size - 1 )
*
* Indexes 0 and 1 will be multiplied by coefficient( 0 ).
*
* Subsequent indexes 3, 4, etc, point to the oldest and newest samples that
* correspond to the matching ( 3 ) coefficient index.
*
* @param odd-sized number of filter taps (ie coefficients) and buffer
*/
private void generateIndexMap( int size )
{
//Ensure we have an odd size
Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
//Set center tap index for coefficients array
mCenterCoefficient = ( size - 1 ) / 2;
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
mIndexMap = new int[ size ][ mapWidth ];
//Setup the first row, buffer pointer index 0, as a starting point
for( int x = 0; x < mapWidth - 2; x += 2 )
{
mIndexMap[ 0 ][ x ] = x;
mIndexMap[ 0 ][ x + 1 ] = size - 1 - x;
}
//Place center tap index in last element
mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient;
//For each subsequent row, increment the previous row's value by 1,
//subtracting size as needed, to keep the values between 0 and size - 1
for( int x = 1; x < size; x++ )
{
for( int y = 0; y < mapWidth; y++ )
{
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1;
if( mIndexMap[ x ][ y ] >= size )
{
mIndexMap[ x ][ y ] -= size;
}
}
}
}
/**
* Registers a listener for filtered samples
*/
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
/**
* Removes (if exists) a registered filtered sample listener
*/
public void clearListener()
{
mListener = null;
}
}

View File

@@ -1,194 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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.filter;
import io.github.dsheirer.sample.real.RealSampleListener;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList;
public class FloatHalfBandNoDecimateFilter implements RealSampleListener
{
private RealSampleListener mListener;
private ArrayList<Float> mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
/**
* Half-Band filter with decimation by a real valued float.
*
* Takes advantage of the symmetrical nature of FIR filter coefficients by
* adding oldest and newest sample first, then multiplying once by the
* corresponding coefficient
*
* Also, takes advantage of the 0-valued FIR half-band coefficents inherent
* in the half-band filter, and does not calculate those coefficients.
*
* This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample.
*
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public FloatHalfBandNoDecimateFilter( Filters filter, float gain )
{
mCoefficients = filter.getCoefficients();
mBuffer = new ArrayList<Float>();
mBufferSize = mCoefficients.length;
//Fill the buffer with zero valued samples
for( int x = 0; x < mCoefficients.length; x++ )
{
mBuffer.add( 0.0f );
}
generateIndexMap( mCoefficients.length );
mGain = gain;
}
public void dispose()
{
mListener = null;
}
/**
* Calculate the filtered value by applying the coefficients against
* the complex samples in mBuffer
*/
public void receive( float newSample )
{
//Add the new sample to the buffer
mBuffer.set( mBufferPointer, newSample );
//Increment & Adjust the buffer pointer for circular wrap around
mBufferPointer++;
if( mBufferPointer >= mBufferSize )
{
mBufferPointer = 0;
}
//Convolution - multiply filter coefficients by the circular buffer
//samples to calculate a new filtered value
float accumulator = 0;
//Start with the center tap value
accumulator += mCoefficients[ mCenterCoefficient ] *
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] );
//For the remaining coefficients, add the symmetric samples, oldest and newest
//first, then multiply by the single coefficient
for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 )
{
accumulator += mCoefficients[ x ] *
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ) +
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) );
}
//We're almost finished ... apply gain, cast the floats to floats and
//send it on it's merry way
if( mListener != null )
{
mListener.receive( (float)( accumulator * mGain ) );
}
}
/**
* Creates an n X (n + 1 / 2) index map enabling quick access to the
* circular buffer samples.
*
* As the buffer shifts right with each subsequent sample, we have to move
* the index pointers with it, for efficient access of the samples.
*
* The first array index value in the index map corresponds to the current
* buffer pointer location.
*
* The second array index value points to the samples that should be
* multiplied by the coefficients as follows:
*
* 0 = center tap sample, to be multiplied by center coefficient
*
* 0 = sample( 1 )
* 1 = sample( size - 1 )
*
* Indexes 0 and 1 will be multiplied by coefficient( 0 ).
*
* Subsequent indexes 3, 4, etc, point to the oldest and newest samples that
* correspond to the matching ( 3 ) coefficient index.
*
* @param odd-sized number of filter taps (ie coefficients) and buffer
*/
private void generateIndexMap( int size )
{
//Ensure we have an odd size
Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
//Set center tap index for coefficients array
mCenterCoefficient = ( size - 1 ) / 2;
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
mIndexMap = new int[ size ][ mapWidth ];
//Setup the first row, buffer pointer index 0, as a starting point
for( int x = 0; x < mapWidth - 2; x += 2 )
{
mIndexMap[ 0 ][ x ] = x;
mIndexMap[ 0 ][ x + 1 ] = size - 1 - x;
}
//Place center tap index in last element
mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient;
//For each subsequent row, increment the previous row's value by 1,
//subtracting size as needed, to keep the values between 0 and size - 1
for( int x = 1; x < size; x++ )
{
for( int y = 0; y < mapWidth; y++ )
{
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1;
if( mIndexMap[ x ][ y ] >= size )
{
mIndexMap[ x ][ y ] -= size;
}
}
}
}
/**
* Registers a listener for filtered samples
*/
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
/**
* Removes (if exists) a registered filtered sample listener
*/
public void clearListener()
{
mListener = null;
}
}

View File

@@ -1,30 +1,30 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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.filter;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.window.WindowFactory;
import io.github.dsheirer.dsp.window.WindowType;
import org.apache.commons.math3.util.FastMath;
/**
* Implements the Goertzel Filter as described in
* http://en.wikipedia.org/wiki/Goertzel_algorithm
*
* <p>
* Calculates the magnitude of a specific frequency component within a window of
* samples, using a Goertzel Filter (ie an optimized DFT).
*/
@@ -33,34 +33,27 @@ public class GoertzelFilter
private int mSampleRate;
private long mTargetFrequency;
private int mBlockSize;
private double[] mWindowCoefficients;
private float[] mWindowCoefficients;
private double mCoefficient;
/**
* Constructs the Goertzel Filter to use for block processing of
* time-domain signal samples.
*
* @param sampleRate
* - sample rate (Hz)
* @param targetFrequency
* - target frequency (Hz)
* @param blockSize
* - number of samples per block of processing
* @param window
* - window to apply against the samples prior to calculating
* the magnitude
*
* @param sampleRate - sample rate (Hz)
* @param targetFrequency - target frequency (Hz)
* @param blockSize - number of samples per block of processing
* @param window - window to apply against the samples prior to calculating
* the magnitude
*/
public GoertzelFilter( int sampleRate,
long targetFrequency,
int blockSize,
WindowType window )
public GoertzelFilter(int sampleRate, long targetFrequency, int blockSize, WindowType window)
{
mSampleRate = sampleRate;
mTargetFrequency = targetFrequency;
mBlockSize = blockSize;
mWindowCoefficients = Window.getWindow( window, blockSize );
init();
mSampleRate = sampleRate;
mTargetFrequency = targetFrequency;
mBlockSize = blockSize;
mWindowCoefficients = WindowFactory.getWindow(window, blockSize);
init();
}
/**
@@ -68,77 +61,74 @@ public class GoertzelFilter
*/
private void init()
{
double normalizedFrequency = (double) mTargetFrequency / mSampleRate;
mCoefficient = 2.0D * FastMath.cos( 2 * FastMath.PI * normalizedFrequency );
double normalizedFrequency = (double)mTargetFrequency / mSampleRate;
mCoefficient = 2.0D * FastMath.cos(2 * FastMath.PI * normalizedFrequency);
}
/**
* Returns the power (dB) of the target frequency within the block of
* Returns the power (dB) of the target frequency within the block of
* samples as normalized against the bin(0) magnitude value
*
* @param samples
* - array of time-domain samples -- array size must be the same
* size as the windowSize parameter passed upon construction
*
*
* @param samples - array of time-domain samples -- array size must be the same
* size as the windowSize parameter passed upon construction
* @return - power measurement in dB
* @throws IllegalArgumentException if the sample array size is not equal
* to the defined block size
* to the defined block size
*/
public int getPower( float[] samples ) throws IllegalArgumentException
public int getPower(float[] samples) throws IllegalArgumentException
{
// Verify size of samples array against block size
if ( samples.length != mBlockSize )
{
throw new IllegalArgumentException(
"Sample array size does not equal Block Size" );
}
// Apply the window against the samples
samples = Window.apply( mWindowCoefficients, samples );
// Process the samples
double s = 0.0D;
double s_prev = 0.0D;
double s_prev2 = 0.0D;
// Verify size of samples array against block size
if(samples.length != mBlockSize)
{
throw new IllegalArgumentException("Sample array size does not equal Block Size");
}
for (float sample : samples) {
// Apply the window against the samples
samples = WindowFactory.apply(mWindowCoefficients, samples);
// Process the samples
double s = 0.0D;
double s_prev = 0.0D;
double s_prev2 = 0.0D;
for(float sample : samples)
{
s = sample + (mCoefficient * s_prev) - s_prev2;
s_prev2 = s_prev;
s_prev = s;
}
//power = s_prev2 * s_prev2 + s_prev * s_prev - coeff * s_prev * s_prev2 ;
double magnitude = ( s_prev2 * s_prev2 ) + ( s_prev * s_prev) - (mCoefficient * s_prev * s_prev2);
int binZero = getBinZeroPower( samples );
int power = (int) (20 * FastMath.log10( magnitude / binZero ) );
return power;
//power = s_prev2 * s_prev2 + s_prev * s_prev - coeff * s_prev * s_prev2 ;
double magnitude = (s_prev2 * s_prev2) + (s_prev * s_prev) - (mCoefficient * s_prev * s_prev2);
int binZero = getBinZeroPower(samples);
int power = (int)(20 * FastMath.log10(magnitude / binZero));
return power;
}
/**
* Sums all of the sample values to derive the bin 0 power
*/
private int getBinZeroPower( float[] samples )
private int getBinZeroPower(float[] samples)
{
int retVal = 0;
for (float sample : samples) {
for(float sample : samples)
{
retVal += sample;
}
return retVal;
}
/**
* @return the Sample Rate
*/
public int getSampleRate()
{
return mSampleRate;
return mSampleRate;
}
/**
@@ -146,7 +136,7 @@ public class GoertzelFilter
*/
public long getTargetFrequency()
{
return mTargetFrequency;
return mTargetFrequency;
}
/**
@@ -154,7 +144,7 @@ public class GoertzelFilter
*/
public int getBlockSize()
{
return mBlockSize;
return mBlockSize;
}
}

View File

@@ -1,505 +0,0 @@
/*
* *****************************************************************************
* 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.filter;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.EnumSet;
/**
* Window factory and corresponding utility methods.
*/
public class Window
{
private final static Logger mLog = LoggerFactory.getLogger(Window.class);
private static final double TWO_PI = FastMath.PI * 2.0d;
private static final double FOUR_PI = FastMath.PI * 4.0d;
private static final double SIX_PI = FastMath.PI * 6.0d;
private static final double EIGHT_PI = FastMath.PI * 8.0d;
/**
* 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 double[] 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 double[] getRectangular(int length)
{
double[] coefficients = new double[length];
Arrays.fill(coefficients, 1.0d);
return coefficients;
}
/**
* Creates a cosine window of the specified length.
*
* @param length of the window
* @return cosine window
*/
public static double[] getCosine(int length)
{
double[] coefficients = new double[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] = FastMath.cos(
((double)x * FastMath.PI) / ((double)length + 1.0d));
}
}
else //Odd length
{
int half = (int)length / 2;
for(int x = -half; x < half + 1; x++)
{
coefficients[x + half] = FastMath.cos(
((double)x * FastMath.PI) / ((double)length + 1.0d));
}
}
return coefficients;
}
/**
* Creates a 3-term Blackman window.
*
* @param length of the window
* @return Blackman window
*/
public static double[] getBlackman(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.426590713672;
double a1 = 0.496560619089;
double a2 = 0.0768486672399;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * FastMath.cos((TWO_PI * (double)x) / denominator)) +
(a2 * FastMath.cos((FOUR_PI * (double)x) / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Nutall window
*
* @param length of the window
* @return Nutall window
*/
public static double[] getNutall(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.355768;
double a1 = 0.487396;
double a2 = 0.144232;
double a3 = 0.012604;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * FastMath.cos(TWO_PI * (double)x / denominator)) +
(a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) -
(a3 * FastMath.cos(SIX_PI * (double)x / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Nutall window
*
* @param length of the window
* @return Blackman-Nutall window
*/
public static double[] getBlackmanNutall(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.3635819;
double a1 = 0.4891775;
double a2 = 0.1365995;
double a3 = 0.0106411;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * FastMath.cos(TWO_PI * (double)x / denominator)) +
(a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) -
(a3 * FastMath.cos(SIX_PI * (double)x / denominator));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static double[] getBlackmanHarris4(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.35875;
double a1 = 0.48829;
double a2 = 0.14128;
double a3 = 0.01168;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * FastMath.cos(TWO_PI * (double)x / denominator)) +
(a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) -
(a3 * FastMath.cos(SIX_PI * (double)x / denominator));
}
return coefficients;
}
/**
* Creates a 7-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static double[] getBlackmanHarris7(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.27105140069342;
double a1 = 0.43329793923448;
double a2 = 0.21812299954311;
double a3 = 0.06592544638803;
double a4 = 0.01081174209837;
double a5 = 0.00077658482522;
double a6 = 0.00001388721735;
for(int x = 0; x < length; x++)
{
double w = TWO_PI * (double)x / denominator;
coefficients[x] = a0 -
(a1 * FastMath.cos(w)) +
(a2 * FastMath.cos(2.0 * w)) -
(a3 * FastMath.cos(3.0 * w)) +
(a4 * FastMath.cos(4.0 * w)) -
(a5 * FastMath.cos(5.0 * w)) +
(a6 * FastMath.cos(6.0 * w));
}
return coefficients;
}
/**
* Creates a Hamming window
*
* @param length of the window
* @return Hamming window
*/
public static double[] getHamming(int length)
{
double[] coefficients = new double[length];
for(int x = 0; x < length; x++)
{
coefficients[x] = 0.54 - (0.46 * FastMath.cos((TWO_PI * x) / (length - 1)));
}
return coefficients;
}
/**
* Creates a Hann window
*
* @param length of the window
* @return Hann window
*/
public static double[] getHann(int length)
{
double[] coefficients = new double[length];
for(int x = 0; x < length; x++)
{
coefficients[x] = 0.5 - (0.5 * FastMath.cos(TWO_PI * (double)x / (length - 1)));
}
return coefficients;
}
/**
* Creates a 4-term Blackman-Harris window
*
* @param length of the window
* @return Blackman-Harris window
*/
public static double[] getFlatTop(int length)
{
double[] coefficients = new double[length];
double denominator = length - 1;
double a0 = 0.215578948;
double a1 = 0.41663158;
double a2 = 0.277263158;
double a3 = 0.083578947;
double a4 = 0.006947368;
for(int x = 0; x < length; x++)
{
coefficients[x] = a0 -
(a1 * FastMath.cos(TWO_PI * (double)x / denominator)) +
(a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) -
(a3 * FastMath.cos(SIX_PI * (double)x / denominator)) +
(a4 * FastMath.cos(EIGHT_PI * (double)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 double getKaiserBeta(double attenuation)
{
if(attenuation > 50.0)
{
return 0.1102 * (attenuation - 8.7);
}
else if(attenuation >= 21.0)
{
return (0.5842 * FastMath.pow(attenuation - 21.0, 0.4)) + (0.07886 * (attenuation - 21.0));
}
else
{
return 0.0;
}
}
/**
* Creates a Kaiser window.
*
* @param length of the window
* @param attenuation desired
* @return window
*/
public static double[] getKaiser(int length, double attenuation)
{
double[] coefficients = new double[length];
double beta = getKaiserBeta(attenuation);
double betaBesselZerothOrder = getBesselZerothOrder(beta);
double temp;
for(int x = 0; x < coefficients.length; x++)
{
temp = beta * 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 double getBesselZerothOrder(final double x)
{
double f = 1;
final double x2 = x * x * 0.25;
double xc = x2;
double v = 1 + x2;
for (int i = 2; i < 100; i++)
{
f *= i;
xc *= x2;
final double 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(double[] coefficients, float[] samples)
{
for(int x = 0; x < coefficients.length; x++)
{
samples[x] = (float)(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)
{
double[] coefficients = getWindow(type, samples.length);
return apply(coefficients, samples);
}
/**
* Apply the window against an array of double-type samples
*/
public static double[] apply(double[] coefficients, double[] samples)
{
for(int x = 0; x < coefficients.length; x++)
{
samples[x] = samples[x] * coefficients[x];
}
return samples;
}
/**
* Apply the window type against the double array of samples
*
* @param type of window to use
* @param samples to be windowed
* @return windowed samples
*/
public static double[] apply(WindowType type, double[] samples)
{
double[] coefficients = getWindow(type, samples.length);
return apply(coefficients, samples);
}
/**
* 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);
public static void main(String[] args)
{
double[] kaiser = getKaiser(16, 80.0);
mLog.debug("Window:" + Arrays.toString(kaiser));
}
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -14,15 +14,13 @@
*
* 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.filter.channelizer;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
@@ -31,10 +29,9 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<ReusableComplexBuffer>, ISourceEventListener
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<InterleavedComplexSamples>, ISourceEventListener
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class);
private ReusableChannelResultsBufferQueue mBufferQueue = new ReusableChannelResultsBufferQueue("AbstractComplexPolyphaseChannelizer");
private Broadcaster<SourceEvent> mSourceChangeBroadcaster = new Broadcaster();
private List<PolyphaseChannelSource> mChannels = new CopyOnWriteArrayList<>();
private double mSampleRate;
@@ -103,17 +100,14 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Re
/**
* Dispatches the processed channel samples to any registered polyphase channel outputs.
*
* @param channelResultsBuffer containing an array of an array of I/Q samples per channel
* @param channelResultsList a list of arrays of I/Q samples per channel
*/
protected void dispatch(ReusableChannelResultsBuffer channelResultsBuffer)
protected void dispatch(List<float[]> channelResultsList)
{
for(PolyphaseChannelSource channel : mChannels)
{
channelResultsBuffer.incrementUserCount();
channel.receiveChannelResults(channelResultsBuffer);
channel.receiveChannelResults(channelResultsList);
}
channelResultsBuffer.decrementUserCount();
}
/**
@@ -152,16 +146,6 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Re
return mChannels.size();
}
/**
* Creates or reuses a channel results buffer for subclass implementations to use for temporary
* storage of channel results through distribution and consumption of channel results by
* channel output processors.
*/
protected ReusableChannelResultsBuffer getChannelResultsBuffer()
{
return mBufferQueue.getBuffer();
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -14,24 +14,22 @@
*
* 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.filter.channelizer;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import io.github.dsheirer.util.Dispatcher;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -67,8 +65,13 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000;
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152));
/**
* Determines how many processed channel results to dispatch for threaded IFFT processing per batch
*/
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
//Sized at 152 buffers a second where max = 5 seconds
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(5 * 152);
private FloatFFT_1D mFFT;
private float[] mInlineSamples;
private float[] mInlineFilter;
@@ -78,6 +81,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
private int mSampleBufferPointer;
private int mSamplesPerBlock;
private int mTapsPerChannel;
private List<float[]> mProcessedChannelResultsList = new ArrayList<>();
/**
* Creates a NMDPFB channelizer instance.
@@ -128,7 +132,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
*/
public void start()
{
mIFFTProcessor.start();
mIFFTProcessorDispatcher.start();
}
/**
@@ -136,7 +140,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
*/
public void stop()
{
mIFFTProcessor.stop();
mIFFTProcessorDispatcher.stop();
}
/**
@@ -187,12 +191,10 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
* Receives the complex sample buffer and processes the results through the channelizer.
*/
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
public void receive(InterleavedComplexSamples complexSamples)
{
ReusableChannelResultsBuffer channelResultsBuffer = getChannelResultsBuffer();
channelResultsBuffer.setTimestamp(reusableComplexBuffer.getTimestamp());
float[] samples = reusableComplexBuffer.getSamples();
//TODO: how do we pass the timestamp onto the sample assemblers?
float[] samples = complexSamples.samples();
int samplesPointer = 0;
int samplesToCopy;
@@ -218,20 +220,19 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
if(mSampleBufferPointer >= mSamplesPerBlock)
{
//Filter buffered samples and produce a single sample across each of the polyphase channels
process(channelResultsBuffer);
mProcessedChannelResultsList.add(process());
if(mProcessedChannelResultsList.size() >= PROCESSED_CHANNEL_RESULTS_THRESHOLD)
{
mIFFTProcessorDispatcher.receive(new ArrayList<>(mProcessedChannelResultsList));
mProcessedChannelResultsList.clear();
}
//Right-shift the samples in the buffer over to make room for a new block of samples
//Note: since JDK 8, hotspot JIT compiler uses native processor intrinsics for efficiency
System.arraycopy(mInlineSamples, 0, mInlineSamples, mSamplesPerBlock, (mInlineSamples.length - mSamplesPerBlock));
mSampleBufferPointer = 0;
}
}
//Enqueue the channel results buffer for IFFT processing and distribution on a different thread
mIFFTProcessor.receive(channelResultsBuffer);
//Decrement the user count to let the originator know we're done with their buffer
reusableComplexBuffer.decrementUserCount();
}
/**
@@ -334,7 +335,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
* Processes the sample buffer for each new block of sample data that is loaded and distributes the results to any
* registered channel listeners.
*/
private void process(ReusableChannelResultsBuffer channelResultsBuffer)
private float[] process()
{
int bufferLength = getSubChannelCount() * mTapsPerChannel;
float[] inlineInterimOutput = new float[bufferLength];
@@ -360,7 +361,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
}
}
float[] processed = channelResultsBuffer.getEmptyBuffer(getSubChannelCount());
float[] processed = new float[getSubChannelCount()];
if(mTopBlockIndicator)
{
@@ -377,9 +378,9 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
}
}
channelResultsBuffer.addChannelResults(processed);
mTopBlockIndicator = !mTopBlockIndicator;
return processed;
}
/**
@@ -389,10 +390,10 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
*/
private void init(float[] coefficients)
{
mFFT = new FloatFFT_1D(getChannelCount());
int channelCount = getChannelCount();
mFFT = new FloatFFT_1D(channelCount);
int bufferLength = getSubChannelCount() * mTapsPerChannel;
mSamplesPerBlock = getChannelCount(); //Same as subChannelCount / 2
mSamplesPerBlock = channelCount; //Same as subChannelCount / 2
mTopBlockMap = getTopBlockMap(channelCount);
mMiddleBlockMap = getMiddleBlockMap(channelCount);
mInlineFilter = getAlignedFilter(coefficients, channelCount, mTapsPerChannel);
@@ -404,46 +405,28 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
* as required to align the phase of each polyphase channel, and then dispatch the results to any registered
* sample consumer channels.
*/
public class IFFTProcessor extends ContinuousBufferProcessor<ReusableChannelResultsBuffer>
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
{
public IFFTProcessor(int maximumSize, int resetThreshold)
public IFFTProcessorDispatcher(int maximumSize)
{
super(maximumSize, resetThreshold);
super(maximumSize, "sdrtrunk polyphase ifft processor", Collections.emptyList());
//We create a listener interface to receive the buffers from the scheduled thread pool
//We create a listener interface to receive the batched channel results arrays from the scheduled thread pool
//dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each
//channel results array contained in each results buffer and then dispatch the buffer
//so that it can be distributed to each channel listener.
setListener(buffers -> {
for(ReusableChannelResultsBuffer buffer: buffers)
setListener(list -> {
List<float[]> processedChannelResults = new ArrayList<>();
for(float[] channelResults: list)
{
for(float[] channelResults: buffer.getChannelResults())
{
//Rotate each of the channels to the correct phase using the IFFT
mFFT.complexInverse(channelResults, true);
}
dispatch(buffer);
//Rotate each of the channels to the correct phase using the IFFT
mFFT.complexInverse(channelResults, true);
processedChannelResults.add(channelResults);
}
dispatch(processedChannelResults);
});
setOverflowListener(overflow -> mLog.debug("IFFTProcessor overflow changed - overflow:" + overflow));
}
/**
* Clears any buffers from the dispatch/processing queue. Overrides the parent method so that we can set
* the user count to 0 to allow the buffer to be reclaimed.
*/
protected Collection<ReusableChannelResultsBuffer> clearQueue()
{
Collection<ReusableChannelResultsBuffer> buffersToDispose = super.clearQueue();
for(ReusableChannelResultsBuffer buffer: buffersToDispose)
{
buffer.clearUserCount();
}
return buffersToDispose;
}
}
}

View File

@@ -1,227 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 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.filter.channelizer;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class ContinuousBufferProcessor<E> implements Listener<E>
{
private final static Logger mLog = LoggerFactory.getLogger(ContinuousBufferProcessor.class);
protected OverflowableTransferQueue<E> mQueue;
private Listener<List<E>> mListener;
private ScheduledFuture<?> mScheduledFuture;
private AtomicBoolean mRunning = new AtomicBoolean();
private long mProcessingPeriod = 5; //milliseconds
/**
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
* for periodically distributing internally queued elements to the registered listener. This processor provides
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
* processing workload.
*
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
*
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
*/
public ContinuousBufferProcessor(int maximumSize, int resetThreshold)
{
this(new OverflowableTransferQueue<>(maximumSize, resetThreshold));
}
/**
* Listener to receive the queued buffers each time this processor runs.
*/
protected Listener<List<E>> getListener()
{
return mListener;
}
/**
* Sets the processing periodicity or run interval for how often the buffer queue is processed.
*
* @param milliseconds (default = 5 milliseconds)
*/
public void setProcessingPeriod(long milliseconds)
{
mProcessingPeriod = milliseconds;
}
/**
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
* for periodically distributing internally queued elements to the registered listener. This processor provides
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
* processing workload.
*
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
*
* @param queue implmentation of an overflowable transfer queue
*/
public ContinuousBufferProcessor(OverflowableTransferQueue<E> queue)
{
mQueue = queue;
}
/**
* Sets the listener to receive notifications of buffer overflow and/or reset. Note: this method can also be
* invoked with a null argument to clear the previously registered overflow listener.
*
* @param listener to receive overflow/reset notifications.
*/
public void setOverflowListener(IOverflowListener listener)
{
mQueue.setOverflowListener(listener);
}
/**
* Sets or changes the listener to receive buffers from this processor.
* @param listener to receive buffers
*/
public void setListener(Listener<List<E>> listener)
{
mListener = listener;
}
/**
* Primary input method for adding buffers to this processor. Note: incoming buffers will be ignored if this
* processor is in a stopped state. You must invoke start() to allow incoming buffers and initiate buffer
* processing.
*
* @param e to enqueue for distribution to a registered listener
*/
@Override
public void receive(E e)
{
mQueue.offer(e);
}
/**
* Starts this buffer processor and allows queuing of incoming buffers.
*/
public void start()
{
if(mRunning.compareAndSet(false, true))
{
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0, mProcessingPeriod, TimeUnit.MILLISECONDS);
}
}
/**
* Stops this buffer processor, stops queuing of incoming buffers and clears the internal buffer queue.
*/
public void stop()
{
if(mRunning.compareAndSet(true, false))
{
if(mScheduledFuture != null)
{
mScheduledFuture.cancel(true);
mScheduledFuture = null;
}
clearQueue();
}
}
/**
* Stops this buffer processor,stops queuing of incoming buffers and flushes the internal buffer queue
* contents to the listener.
*/
public void flushAndStop()
{
if(mRunning.compareAndSet(true, false))
{
if(mScheduledFuture != null)
{
mScheduledFuture.cancel(true);
mScheduledFuture = null;
}
process();
}
}
/**
* Distributes queued buffers to the listener
*/
protected void process()
{
List<E> buffers = new ArrayList<>();
try
{
mQueue.drainTo(buffers);
if(mListener != null)
{
mListener.receive(buffers);
}
}
catch(Throwable throwable)
{
mLog.error("Error while dispatching buffers to listener", throwable);
}
}
/**
* Clears any buffers from the queue
*/
protected Collection<E> clearQueue()
{
Collection<E> clearedElements = new ArrayList<>();
mQueue.drainTo(clearedElements);
return clearedElements;
}
/**
* Indicates if this processor is currently running
*/
public boolean isRunning()
{
return mRunning.get();
}
/**
* Processor to service the buffer queue and distribute the buffers to the registered listener
*/
class Processor implements Runnable
{
@Override
public void run()
{
process();
}
}
}

View File

@@ -1,88 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.filter.channelizer;
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class ContinuousReusableBufferProcessor<T extends AbstractReusableBuffer> extends ContinuousBufferProcessor<T>
{
private final static Logger mLog = LoggerFactory.getLogger(ContinuousReusableBufferProcessor.class);
/**
* Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing
* task for periodically distributing internally queued elements to the registered listener. This processor
* provides a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
* processing workload.
*
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
*
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
*/
public ContinuousReusableBufferProcessor(int maximumSize, int resetThreshold)
{
super(new OverflowableReusableBufferTransferQueue<T>(maximumSize, resetThreshold));
}
/**
* Distributes queued buffers to the listener
*/
@Override
protected void process()
{
List<T> buffers = new ArrayList<>();
mQueue.drainTo(buffers);
try
{
if(getListener() != null)
{
getListener().receive(buffers);
}
buffers.clear();
}
catch(Throwable throwable)
{
mLog.error("Error while dispatching buffers to listener. Performing buffer user count cleanup", throwable);
if(!buffers.isEmpty())
{
for(T buffer: buffers)
{
try
{
buffer.decrementUserCount();
}
catch(IllegalStateException ise)
{
mLog.error("Error while performing user count cleanup on reusable buffers.");
}
}
}
}
}
}

View File

@@ -1,26 +1,26 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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/>
* * *****************************************************************************
* 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.filter.channelizer;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.buffer.INativeBufferProvider;
import io.github.dsheirer.buffer.NativeBufferPoisonPill;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.channelizer.output.OneChannelOutputProcessor;
@@ -28,21 +28,21 @@ import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcesso
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferProvider;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.util.Dispatcher;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
@@ -76,32 +76,31 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
private IReusableComplexBufferProvider mReusableBufferProvider;
private INativeBufferProvider mNativeBufferProvider;
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
private ChannelCalculator mChannelCalculator;
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener();
private BufferSourceEventMonitor mBufferSourceEventMonitor = new BufferSourceEventMonitor();
private ContinuousBufferProcessor<ReusableComplexBuffer> mBufferProcessor;
private NativeBufferReceiver mNativeBufferReceiver = new NativeBufferReceiver();
private Dispatcher mBufferDispatcher;
private Map<Integer,float[]> mOutputProcessorFilters = new HashMap<>();
/**
* Creates a polyphase channel manager instance.
*
* @param reusableComplexBufferProvider (ie tuner) that supports register/deregister for reusable baseband sample buffer
* @param nativeBufferProvider (ie tuner) that supports register/deregister for reusable baseband sample buffer
* streams
* @param frequency of the baseband complex buffer sample stream (ie center frequency)
* @param sampleRate of the baseband complex buffer sample stream
*/
public PolyphaseChannelManager(IReusableComplexBufferProvider reusableComplexBufferProvider,
long frequency, double sampleRate)
public PolyphaseChannelManager(INativeBufferProvider nativeBufferProvider, long frequency, double sampleRate)
{
if(reusableComplexBufferProvider == null)
if(nativeBufferProvider == null)
{
throw new IllegalArgumentException("Complex buffer provider argument cannot be null");
}
mReusableBufferProvider = reusableComplexBufferProvider;
mNativeBufferProvider = nativeBufferProvider;
int channelCount = (int)(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
@@ -112,9 +111,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
}
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
mBufferProcessor = new ContinuousBufferProcessor(200, 50);
mBufferProcessor.setListener(mBufferSourceEventMonitor);
mBufferDispatcher = new Dispatcher(500, "sdrtrunk polyphase buffer processor",
new NativeBufferPoisonPill());
mBufferDispatcher.setListener(mNativeBufferReceiver);
}
/**
@@ -161,7 +160,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* @param tunerChannel specifying center frequency and bandwidth.
* @return source or null.
*/
public TunerChannelSource getChannel(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
public TunerChannelSource getChannel(TunerChannel tunerChannel)
{
PolyphaseChannelSource channelSource = null;
@@ -176,7 +175,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
try
{
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
mChannelCalculator.getChannelSampleRate(), centerFrequency);
mChannelSources.add(channelSource);
}
@@ -229,7 +228,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
*/
private void startChannelSource(PolyphaseChannelSource channelSource)
{
synchronized(mBufferProcessor)
synchronized(mBufferDispatcher)
{
//Note: the polyphase channel source has already been added to the mChannelSources in getChannel() method
@@ -241,9 +240,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
//If this is the first channel, register to start the sample buffers flowing
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
{
mReusableBufferProvider.addBufferListener(mBufferProcessor);
mNativeBufferProvider.addBufferListener(mBufferDispatcher);
mPolyphaseChannelizer.start();
mBufferProcessor.start();
mBufferDispatcher.start();
}
}
}
@@ -256,7 +255,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
*/
private void stopChannelSource(PolyphaseChannelSource channelSource)
{
synchronized(mBufferProcessor)
synchronized(mBufferDispatcher)
{
mChannelSources.remove(channelSource);
mPolyphaseChannelizer.removeChannel(channelSource);
@@ -265,8 +264,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
//If this is the last/only channel, deregister to stop the sample buffers
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
{
mReusableBufferProvider.removeBufferListener(mBufferProcessor);
mBufferProcessor.stop();
mNativeBufferProvider.removeBufferListener(mBufferDispatcher);
mBufferDispatcher.stop();
mPolyphaseChannelizer.stop();
}
}
@@ -298,7 +297,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
//Defer channelizer configuration changes to be handled on the buffer processor thread
mBufferSourceEventMonitor.receive(sourceEvent);
mNativeBufferReceiver.receive(sourceEvent);
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
//Update channel calculator immediately so that channels can be allocated
@@ -565,7 +564,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer
* and/or the channel sources and output processors.
*/
public class BufferSourceEventMonitor implements Listener<List<ReusableComplexBuffer>>
public class NativeBufferReceiver implements Listener<INativeBuffer>
{
private Queue<SourceEvent> mQueuedSourceEvents = new ConcurrentLinkedQueue<>();
@@ -579,7 +578,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
}
@Override
public void receive(List<ReusableComplexBuffer> reusableComplexBuffers)
public void receive(INativeBuffer nativeBuffer)
{
try
{
@@ -599,26 +598,19 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
queuedSourceEvent = mQueuedSourceEvents.poll();
}
for(ReusableComplexBuffer reusableComplexBuffer: reusableComplexBuffers)
if(mPolyphaseChannelizer != null)
{
if(mPolyphaseChannelizer != null)
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
while(iterator.hasNext())
{
//User count management is handled by the channelizer
mPolyphaseChannelizer.receive(reusableComplexBuffer);
}
else
{
reusableComplexBuffer.decrementUserCount();
mPolyphaseChannelizer.receive(iterator.next());
}
}
}
catch(Throwable throwable)
{
mLog.error("Error", throwable);
for(ReusableComplexBuffer buffer: reusableComplexBuffers)
{
buffer.decrementUserCount();
}
}
}
}

View File

@@ -20,36 +20,23 @@ package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
public class PolyphaseChannelSource extends TunerChannelSource
public class PolyphaseChannelSource extends TunerChannelSource implements Listener<ComplexSamples>
{
// private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private static final int PROCESSED_BUFFER_SAMPLE_SIZE = 2048;
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
private long mReplacementFrequency;
private ComplexFIRFilter2 mLowPassFilter;
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection;
private Listener<ComplexSamples> mComplexSamplesListener;
/**
* Polyphase channelizer tuner channel source implementation. Adapts the channel array output samples from the
@@ -65,37 +52,57 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate,
long centerFrequency, ChannelSpecification channelSpecification)
throws FilterDesignException
long centerFrequency) throws FilterDesignException
{
super(producerSourceEventListener, tunerChannel);
mPolyphaseChannelOutputProcessor = outputProcessor;
mPolyphaseChannelOutputProcessor.setListener(this);
mChannelSampleRate = channelSampleRate;
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(PROCESSED_BUFFER_SAMPLE_SIZE, mChannelSampleRate);
float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(),
channelSpecification.getStopFrequency());
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
setFrequency(centerFrequency);
}
@Override
public void start()
{
super.start();
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.start();
}
}
@Override
public void stop()
{
super.stop();
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.stop();
}
}
/**
* Registers the listener to receive complex sample buffers from this channel source
*/
@Override
public void setListener(final Listener<ReusableComplexBuffer> listener)
public void setListener(final Listener<ComplexSamples> listener)
{
mReusableComplexBufferAssembler.setListener(listener);
}
mComplexSamplesListener = listener;
mPolyphaseChannelOutputProcessor.setListener(this);
}
/**
* Removes the listener from receiving complex sample buffers from this channel source
*/
@Override
public void removeListener(Listener<ReusableComplexBuffer> listener)
public void receive(ComplexSamples complexSamples)
{
mReusableComplexBufferAssembler.setListener(null);
getHeartbeatManager().broadcast();
if(mComplexSamplesListener != null)
{
mComplexSamplesListener.receive(complexSamples);
}
}
/**
@@ -119,6 +126,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
if(mPolyphaseChannelOutputProcessor == null)
{
mPolyphaseChannelOutputProcessor = outputProcessor;
mPolyphaseChannelOutputProcessor.setListener(this);
}
//Otherwise, we have to swap out the processor on the sample processing thread
else
@@ -138,17 +146,14 @@ public class PolyphaseChannelSource extends TunerChannelSource
if(mReplacementPolyphaseChannelOutputProcessor != null)
{
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
existingProcessor.stop();
existingProcessor.setListener(null);
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
mPolyphaseChannelOutputProcessor = mReplacementPolyphaseChannelOutputProcessor;
mReplacementPolyphaseChannelOutputProcessor = null;
//Remove the buffer overflow listener and clear any previous buffer overflow state
existingProcessor.setSourceOverflowListener(null);
broadcastOverflowState(false);
//Register to receive buffer overflow notifications so we can push them up through the source chain
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
mPolyphaseChannelOutputProcessor.setListener(this);
mPolyphaseChannelOutputProcessor.start();
//Finally, setup the frequency offset for the output processor.
mIndexCenterFrequency = mReplacementFrequency;
@@ -161,17 +166,16 @@ public class PolyphaseChannelSource extends TunerChannelSource
* queued for processing to extract the target channel samples, process them for frequency correction and/or
* channel aggregation, and dispatch the results to the downstream sample listener/consumer.
*
* @param channelResultsBuffer containing an array of polyphase channelizer outputs.
* @param channelResultsList containing a list of polyphase channelizer output arrays.
*/
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
public void receiveChannelResults(List<float[]> channelResultsList)
{
if(mReplacementPolyphaseChannelOutputProcessor != null)
{
swapOutputProcessor();
}
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList);
}
/**
@@ -198,13 +202,9 @@ public class PolyphaseChannelSource extends TunerChannelSource
@Override
public void dispose()
{
if(mReusableComplexBufferAssembler != null)
{
mReusableComplexBufferAssembler.dispose();
}
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.setListener(null);
mPolyphaseChannelOutputProcessor.dispose();
}
}
@@ -223,6 +223,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
protected void setChannelFrequencyCorrection(long value)
{
//TODO: push this frequency correction down to the output processor ...
mChannelFrequencyCorrection = value;
updateFrequencyOffset();
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
@@ -242,12 +243,6 @@ public class PolyphaseChannelSource extends TunerChannelSource
setChannelFrequencyCorrection(0);
}
@Override
protected void processSamples()
{
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
/**
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
* within the channel
@@ -273,43 +268,4 @@ public class PolyphaseChannelSource extends TunerChannelSource
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
getTunerChannel().getFrequency();
}
/**
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
*
* @param sampleRate for the final output channel rate
* @param passFrequency for half of the desired channel rate
* @param stopFrequency for the attenuated band
* @return a newly designed filter or a previously designed (cached) filter
* @throws FilterDesignException
*/
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
//Use existing filter if we've already designed one
float[] filter = sLowPassFilters.get((int) sampleRate);
if(filter != null)
{
return filter;
}
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.passBandCutoff(passFrequency)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(stopFrequency)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//This will throw an exception if the filter cannot be designed
float[] taps = designer.getImpulseResponse();
sLowPassFilters.put((int)sampleRate, taps);
return taps;
}
}

View File

@@ -1,22 +1,24 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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 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.
* 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/>
*
******************************************************************************/
* 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.filter.channelizer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
import io.github.dsheirer.sample.complex.ComplexSamples;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
@@ -45,8 +47,6 @@ import org.slf4j.LoggerFactory;
public class TwoChannelSynthesizerM2
{
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelSynthesizerM2.class);
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Two Channel Synthesizer M2");
private float[] mSerpentineDataBuffer;
private float[] mIQInterleavedFilter;
private float[] mFilterVectorProduct;
@@ -87,28 +87,30 @@ public class TwoChannelSynthesizerM2
* @param channelBuffer2 input channel
* @return synthesized channel results of the same length as both channel 1/2 input arrays.
*/
public ReusableComplexBuffer process(ReusableComplexBuffer channelBuffer1, ReusableComplexBuffer channelBuffer2)
public ComplexSamples process(ComplexSamples channelBuffer1, ComplexSamples channelBuffer2)
{
if(channelBuffer1.getSamples().length != channelBuffer1.getSamples().length)
if(channelBuffer1.i().length != channelBuffer1.i().length)
{
throw new IllegalArgumentException("Channel 1 and 2 array length must be equal");
}
float[] channel1 = channelBuffer1.getSamples();
float[] channel2 = channelBuffer2.getSamples();
float[] iCh1 = channelBuffer1.i();
float[] qCh1 = channelBuffer1.q();
float[] iCh2 = channelBuffer2.i();
float[] qCh2 = channelBuffer2.q();
ReusableComplexBuffer synthesizedComplexBuffer = mReusableComplexBufferQueue.getBuffer(channel1.length);
float[] i = new float[iCh1.length];
float[] q = new float[qCh1.length];
float[] output = synthesizedComplexBuffer.getSamples();
float[] IFFTBuffer = new float[4];
for(int x = 0; x < channel1.length; x += 2)
for(int x = 0; x < iCh1.length; x++)
{
//Load samples from each channel into buffer for IFFT
IFFTBuffer[0] = channel1[x]; //i
IFFTBuffer[1] = channel1[x + 1]; //q
IFFTBuffer[2] = channel2[x]; //i
IFFTBuffer[3] = channel2[x + 1]; //q
IFFTBuffer[0] = iCh1[x]; //i
IFFTBuffer[1] = qCh1[x]; //q
IFFTBuffer[2] = iCh2[x]; //i
IFFTBuffer[3] = qCh2[x]; //q
//Perform Inverse FFT (IFFT)
mFFT.complexInverse(IFFTBuffer, true);
@@ -148,16 +150,13 @@ public class TwoChannelSynthesizerM2
mQAccumulator += mFilterVectorProduct[y + 1];
}
output[x] = mIAccumulator;
output[x + 1] = mQAccumulator;
i[x] = mIAccumulator;
q[x] = mQAccumulator;
mTopBlockFlag = !mTopBlockFlag;
}
channelBuffer1.decrementUserCount();
channelBuffer2.decrementUserCount();
return synthesizedComplexBuffer;
return new ComplexSamples(i, q);
}
/**

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -14,35 +14,26 @@
*
* 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.filter.channelizer.output;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.util.Dispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
private OverflowableReusableBufferTransferQueue<ReusableChannelResultsBuffer> mChannelResultsQueue;
private List<ReusableChannelResultsBuffer> mChannelResultsToProcess = new ArrayList<>();
private int mMaxResultsToProcess;
private Dispatcher<List<float[]>> mChannelResultsDispatcher;
protected Listener<ComplexSamples> mComplexSamplesListener;
private int mInputChannelCount;
private IOscillator mFrequencyCorrectionMixer;
private boolean mFrequencyCorrectionEnabled;
private double mGain = 1.0;
/**
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
@@ -52,21 +43,32 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
* channel sample rate for frequency translation/correction.
*/
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, double gain)
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
{
mInputChannelCount = inputChannelCount;
mGain = gain;
//TODO: swap this out and use the LowPhaseNoiseOscillator
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
mMaxResultsToProcess = (int)(sampleRate / 10) * 2; //process at 100 millis interval, twice the expected inflow rate
mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
mChannelResultsDispatcher = new Dispatcher<>((int)sampleRate, "sdrtrunk polyphase channel", Collections.emptyList());
mChannelResultsDispatcher.setListener(floats -> process(floats));
}
protected double getGain()
@Override
public void start()
{
return mGain;
mChannelResultsDispatcher.start();
}
@Override
public void stop()
{
mChannelResultsDispatcher.stop();
}
/**
* Registers the listener to receive the assembled complex sample buffers from this processor.
*/
@Override
public void setListener(Listener<ComplexSamples> listener)
{
mComplexSamplesListener = listener;
}
@Override
@@ -77,97 +79,23 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
public void dispose()
{
if(mChannelResultsQueue != null)
{
mChannelResultsQueue.dispose();
}
}
/**
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
* @param frequencyOffset in hertz
*/
@Override
public void setFrequencyOffset(long frequencyOffset)
{
mFrequencyCorrectionMixer.setFrequency(frequencyOffset);
mFrequencyCorrectionEnabled = (frequencyOffset != 0);
}
/**
* Oscillator/mixer to use for frequency correction against incoming sample stream
*/
protected IOscillator getFrequencyCorrectionMixer()
{
return mFrequencyCorrectionMixer;
}
@Override
public void receiveChannelResults(ReusableChannelResultsBuffer channelResults)
public void receiveChannelResults(List<float[]> channelResultsList)
{
mChannelResultsQueue.offer(channelResults);
}
/**
* Processes all enqueued polyphase channelizer results until the internal queue is empty
* @param reusableComplexBufferAssembler to receive the processed channel results
*/
@Override
public void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler)
{
try
{
int toProcess = mChannelResultsQueue.drainTo(mChannelResultsToProcess, mMaxResultsToProcess);
if(toProcess > 0)
{
process(mChannelResultsToProcess, reusableComplexBufferAssembler);
}
}
catch(Throwable throwable)
{
mLog.error("Error while processing polyphase channel samples", throwable);
}
mChannelResultsToProcess.clear();
mChannelResultsDispatcher.receive(channelResultsList);
}
/**
* Sub-class implementation to process one polyphase channelizer result array.
* @param channelResults to process
* @param reusableComplexBufferAssembler to receive the processed channelizer results
*/
public abstract void process(List<ReusableChannelResultsBuffer> channelResults,
ReusableComplexBufferAssembler reusableComplexBufferAssembler);
/**
* Sets the overflow listener to monitor the internal channelizer channel results queue overflow state
*/
public void setSourceOverflowListener(Source source)
{
mChannelResultsQueue.setSourceOverflowListener(source);
}
/**
* Removes the overflow listener from monitoring the internal channelizer channel results queue overflow state
*/
public void removeOverflowListener(IOverflowListener listener)
{
mChannelResultsQueue.setOverflowListener(null);
}
public abstract void process(List<float[]> channelResults);
@Override
public int getInputChannelCount()
{
return mInputChannelCount;
}
/**
* Indicates if this channel has a frequency correction offset
*/
public boolean hasFrequencyCorrection()
{
return mFrequencyCorrectionEnabled;
}
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -14,30 +14,41 @@
*
* 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.filter.channelizer.output;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.List;
public interface IPolyphaseChannelOutputProcessor
{
/**
* Receive and enqueue output results from the polyphase analysis channelizer
* @param channelResultsBuffer to enqueue
* Start processing channel results
*/
void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer);
void start();
/**
* Process the channel output channel results queue and deliver the output to the listener
*
* @param reusableComplexBufferAssembler to receive the processed channel results
* Stop processing channel results.
*/
void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler);
void stop();
/**
* Receive and enqueue output results from the polyphase analysis channelizer
* @param channelResults to enqueue
*/
void receiveChannelResults(List<float[]> channelResults);
/**
* Listener to receive assembled complex samples buffers
*/
void setListener(Listener<ComplexSamples> listener);
// /**
// * Process the channel output channel results queue and deliver the output to the listener
// */
// void processChannelResults();
/**
* Sets the desired frequency offset from center. The samples will be mixed with an oscillator set to this offset
@@ -49,13 +60,12 @@ public interface IPolyphaseChannelOutputProcessor
/**
* Indicates the number of input channels processed by this output processor
* @return
*/
int getInputChannelCount();
/**
* Updates the input polyphase channel index(es) used by this output processor
* @param indexes
* @param indexes for the channel
*/
void setPolyphaseChannelIndices(List<Integer> indexes);
@@ -66,16 +76,9 @@ public interface IPolyphaseChannelOutputProcessor
/**
* Updates the synthesis filter taps for this output processor
* @param filter
* @param filter for the output processor
*/
void setSynthesisFilter(float[] filter);
/**
* Sets the listener to receive buffer overflow notifications
* @param source to receive overflow notifications
*/
void setSourceOverflowListener(Source source);
void dispose();
}

View File

@@ -0,0 +1,71 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.channelizer.output;
import io.github.dsheirer.dsp.gain.complex.ComplexGain;
import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory;
import io.github.dsheirer.dsp.mixer.ComplexMixer;
import io.github.dsheirer.dsp.mixer.ComplexMixerFactory;
import io.github.dsheirer.sample.complex.ComplexSamples;
/**
* Sample assembler for a channel output processor that incorporates a mixer to perform final frequency
* translation on an extracted channel.
*/
public abstract class MixerAssembler
{
protected static final int BUFFER_SIZE = 2048;
private ComplexMixer mMixer = ComplexMixerFactory.getMixer(0, 0);
private ComplexGain mGain;
/**
* Constructs an instance.
* @param gain to apply to fully assembled buffers.
*/
protected MixerAssembler(float gain)
{
mGain = ComplexGainFactory.getComplexGain(gain);
}
/**
* Complex samples mixer for this assembler
*/
protected ComplexMixer getMixer()
{
return mMixer;
}
protected ComplexGain getGain()
{
return mGain;
}
/**
* Indicates if this assembler has a buffer ready to dispatch to consumers.
*/
public abstract boolean hasBuffer();
/**
* Access the fully assembled buffer when hasBuffer() indicates that a buffer is ready.
* @return true if a full buffer is assembled and ready.
*/
public abstract ComplexSamples getBuffer();
}

View File

@@ -0,0 +1,77 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSamples;
/**
* Sample assembler to extract a single channel from the channel results array of a polyphase channel results array.
*/
public class OneChannelMixerAssembler extends MixerAssembler
{
private SampleAssembler mSampleAssembler = new SampleAssembler(BUFFER_SIZE);
/**
* Constructs an instance
* @param gain to apply to fully assembled buffers.
*/
public OneChannelMixerAssembler(float gain)
{
super(gain);
}
/**
* Receive a complex I & Q sample pair.
* @param i inphase
* @param q quadrature
*/
public void receive(float i, float q)
{
mSampleAssembler.receive(i, q);
}
/**
* Indicates if there is a fully assembled buffer ready.
*/
@Override
public boolean hasBuffer()
{
return mSampleAssembler.hasBuffer();
}
/**
* Access the assembled buffer. Frequency offset and gain are applied to the returned buffer.
* @return assembled buffer.
*/
@Override
public ComplexSamples getBuffer()
{
ComplexSamples buffer = mSampleAssembler.getBufferAndReset();
if(getMixer().hasFrequency())
{
buffer = getMixer().mix(buffer);
}
buffer = getGain().apply(buffer);
return buffer;
}
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -14,13 +14,11 @@
*
* 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.filter.channelizer.output;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.sample.complex.ComplexSamples;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,7 +27,7 @@ import java.util.List;
public class OneChannelOutputProcessor extends ChannelOutputProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(OneChannelOutputProcessor.class);
private final OneChannelMixerAssembler mMixerAssembler;
private int mChannelOffset;
/**
@@ -38,12 +36,14 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
*
* @param sampleRate of the output sample stream.
* @param channelIndexes containing a single channel index.
* @param gain value to apply. Typically this is the same as the channelizer's channel count.
* @param gain value to apply. This is typically the same as the channelizer's channel count.
*/
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, double gain)
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain)
{
super(1, sampleRate, gain);
super(1, sampleRate);
setPolyphaseChannelIndices(channelIndexes);
mMixerAssembler = new OneChannelMixerAssembler(gain);
mMixerAssembler.getMixer().setSampleRate(sampleRate);
}
@Override
@@ -62,7 +62,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
if(indexes.size() != 1)
{
throw new IllegalArgumentException("Single channel output processor requires a single index to " +
"process - provided indexes " + indexes.toString());
"process - provided indexes " + indexes);
}
//Set the channelized output results offset to twice the channel index to account for each channel having
@@ -70,38 +70,45 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
mChannelOffset = indexes.get(0) * 2;
}
@Override
public void setFrequencyOffset(long frequency)
{
mMixerAssembler.getMixer().setFrequency(frequency);
}
/**
* Extract the channel from the channel results array, apply frequency translation, and deliver the
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
* Extract the channel from the channel results array and pass to the assembler. The assembler will
* apply frequency translation and gain and indicate when a buffer is fully assembled.
*
* @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param reusableComplexBufferAssembler to receive the extracted, frequency-translated channel results
* @param lists to process containing a list of a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
*/
@Override
public void process(List<ReusableChannelResultsBuffer> channelResults,
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
public void process(List<float[]> channelResultsList)
{
for(ReusableChannelResultsBuffer channelResultsBuffer: channelResults)
for(float[] channelResults: channelResultsList)
{
try
{
ReusableComplexBuffer channelBuffer = channelResultsBuffer.getChannel(mChannelOffset);
mMixerAssembler.receive(channelResults[mChannelOffset], channelResults[mChannelOffset + 1]);
if(hasFrequencyCorrection())
if(mMixerAssembler.hasBuffer())
{
ComplexSamples buffer = mMixerAssembler.getBuffer();
if(mComplexSamplesListener != null)
{
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
try
{
mComplexSamplesListener.receive(buffer);
}
catch(NullPointerException npe)
{
//Ignore ... can happen when the listener is nullified on another thread
}
catch(Exception e)
{
mLog.error("Error extracting channel samples from one polyphase channel results buffer", e);
}
}
channelBuffer.applyGain(getGain());
reusableComplexBufferAssembler.receive(channelBuffer);
}
catch(IllegalArgumentException iae)
{
mLog.error("Error extracting channel samples from polyphase channel results buffer");
}
channelResultsBuffer.decrementUserCount();
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSamples;
public class SampleAssembler
{
private final int mBufferSize;
private float[] mI;
private float[] mQ;
private int mPointer = 0;
/**
* Creates a sample assembler to produce buffers of the specified size.
* @param bufferSize for a full buffer
*/
public SampleAssembler(int bufferSize)
{
mBufferSize = bufferSize;
reset();
}
/**
* Resets the buffer pointer and creates new (empty) I and Q arrays.
*/
private void reset()
{
mPointer = 0;
mI = new float[mBufferSize];
mQ = new float[mBufferSize];
}
/**
* Adds the I & Q samples to this assembler.
* @param i inphase sample
* @param q quadrature sample.
*/
public void receive(float i, float q)
{
if(mPointer >= mBufferSize)
{
throw new IllegalStateException("Buffer is full. Use flushBuffer() to reset the assembler.");
}
mI[mPointer] = i;
mQ[mPointer++] = q;
}
/**
* Indicates if this assembler has a full buffer.
* Note: use getBufferAndReset() to access the full buffer.
*/
public boolean hasBuffer()
{
return mPointer >= mBufferSize;
}
/**
* Extracts the full buffer from this assembler and resets the assembler to accept more samples.
*/
public ComplexSamples getBufferAndReset()
{
ComplexSamples buffer = new ComplexSamples(mI, mQ);
reset();
return buffer;
}
}

View File

@@ -0,0 +1,104 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.channelizer.output;
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
import io.github.dsheirer.dsp.oscillator.FS4DownConverter;
import io.github.dsheirer.sample.complex.ComplexSamples;
/**
* Sample assembler to extract a single channel from the channel results array of a polyphase channel results array.
*/
public class TwoChannelMixerAssembler extends MixerAssembler
{
private SampleAssembler mChannel1SampleAssembler = new SampleAssembler(BUFFER_SIZE);
private SampleAssembler mChannel2SampleAssembler = new SampleAssembler(BUFFER_SIZE);
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
private TwoChannelSynthesizerM2 mTwoChannelSynthesizer;
/**
* Constructs an instance
* @param gain to apply to fully assembled buffers.
*/
public TwoChannelMixerAssembler(float gain)
{
super(gain);
}
/**
* Sets the filter coefficients to use for two-channel synthesis
* @param filter
*/
public void setSynthesisFilter(float[] filter)
{
mTwoChannelSynthesizer = new TwoChannelSynthesizerM2(filter);
}
/**
* Receive a complex I & Q sample pair.
* @param iChannel1 inphase
* @param qChannel1 quadrature
* @param iChannel2 inphase
* @param qChannel2 quadrature
*/
public void receive(float iChannel1, float qChannel1, float iChannel2, float qChannel2)
{
mChannel1SampleAssembler.receive(iChannel1, qChannel1);
mChannel2SampleAssembler.receive(iChannel2, qChannel2);
}
/**
* Indicates if there is a fully assembled buffer ready.
*/
@Override
public boolean hasBuffer()
{
//Both channel 1 and channel 2 assemblers work in parallel, so we only need to check one of them.
return mChannel1SampleAssembler.hasBuffer();
}
/**
* Access the assembled buffer. Frequency offset and gain are applied to the returned buffer.
* @return assembled buffer.
*/
@Override
public ComplexSamples getBuffer()
{
ComplexSamples channel1 = mChannel1SampleAssembler.getBufferAndReset();
ComplexSamples channel2 = mChannel2SampleAssembler.getBufferAndReset();
//Synthesize two channels into one, reducing sample rate by 2 so it's same as single channel rate
ComplexSamples buffer = mTwoChannelSynthesizer.process(channel1, channel2);
//Mix to baseband
buffer = mFS4DownConverter.mixComplex(buffer);
//Apply frequency correction if necessary
if(getMixer().hasFrequency())
{
buffer = getMixer().mix(buffer);
}
//Apply gain
buffer = getGain().apply(buffer);
return buffer;
}
}

View File

@@ -1,34 +1,33 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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 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.
* 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/>
*
******************************************************************************/
* 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.filter.channelizer.output;
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.sample.complex.ComplexSamples;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
{
// private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private TwoChannelSynthesizerM2 mSynthesizer;
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private TwoChannelMixerAssembler mMixerAssembler;
private int mChannelOffset1;
private int mChannelOffset2;
@@ -40,12 +39,14 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
* @param channelIndexes containing two channel indices.
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
*/
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, double gain)
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain)
{
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
//after synthesizing both input channels
super(2, sampleRate, gain);
super(2, sampleRate);
setPolyphaseChannelIndices(channelIndexes);
mMixerAssembler = new TwoChannelMixerAssembler(gain);
mMixerAssembler.getMixer().setSampleRate(sampleRate);
setSynthesisFilter(filter);
}
@@ -57,14 +58,13 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
@Override
public void setFrequencyOffset(long frequencyOffset)
{
super.setFrequencyOffset(frequencyOffset);
mMixerAssembler.getMixer().setFrequency(frequencyOffset);
}
@Override
public void setSynthesisFilter(float[] filter)
{
mSynthesizer = new TwoChannelSynthesizerM2(filter);
mMixerAssembler.setSynthesisFilter(filter);
}
/**
@@ -78,7 +78,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
if(indexes.size() != 2)
{
throw new IllegalArgumentException("Double channel output processor requires two indexes to " +
"process - provided indexes " + indexes.toString());
"process - provided indexes " + indexes);
}
//Set the channelized output results offsets to twice the channel index to account for each channel having
@@ -91,32 +91,36 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
* Extract the channel from the channel results array, apply frequency translation, and deliver the
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
*
* @param channelResultsBuffers to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param reusableComplexBufferAssembler to receive the extracted, frequency-translated channel results
* @param lists to process containing a list of a list of an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
*/
@Override
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
public void process(List<float[]> channelResultsList)
{
for(ReusableChannelResultsBuffer buffer : channelResultsBuffers)
for(float[] channelResults : channelResultsList)
{
ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1);
ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2);
mMixerAssembler.receive(channelResults[mChannelOffset1], channelResults[mChannelOffset1 + 1],
channelResults[mChannelOffset2], channelResults[mChannelOffset2 + 1]);
//Join the two channels using the synthesizer
ReusableComplexBuffer synthesized = mSynthesizer.process(channel1, channel2);
if(mMixerAssembler.hasBuffer())
{
ComplexSamples buffer = mMixerAssembler.getBuffer();
//The synthesized channels are centered at +FS/4 ... downconvert to center the spectrum
mFS4DownConverter.mixComplex(synthesized.getSamples());
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples());
synthesized.applyGain(getGain());
reusableComplexBufferAssembler.receive(synthesized);
buffer.decrementUserCount();
if(mComplexSamplesListener != null)
{
try
{
mComplexSamplesListener.receive(buffer);
}
catch(NullPointerException npe)
{
//Ignore ... can happen when the listener is nullified on another thread
}
catch(Exception e)
{
mLog.error("Error extracting channel samples from two polyphase channel results buffer", e);
}
}
}
}
}
}

View File

@@ -1,456 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 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.filter.cic;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import io.github.dsheirer.sample.decimator.ComplexDecimator;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.primes.Primes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Non-Recursive Prime-Factor CIC Filter.
*
* Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This
* filter is comprised of multiple decimating stages each with a prime factor decimation rate. Multiple stages are
* cascaded to achieve the overall decimation rate.
*
* This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by
* adding additional prime factors to the PRIMES array.
*/
public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
{
// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate.class);
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
private static Map<Integer,List<Integer>> sPrimeFactors = new HashMap();
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage;
private Output mOutput;
/**
* Constructs a new decimation filter.
*
* An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel
* pass band might be:
* sampleRate = 2,400,000.0
* decimation = 96
* passFrequency = 6250 (1/4 of the channel rate)
* stopFrequency = 7000
*
* Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter,
* where smaller transition bandwidth creates a longer filter that requires more processing effort.
*
* @param sampleRate of the input sample stream
* @param decimation - overall decimation rate
* @param passFrequency for the final cleanup filter
* @param stopFrequency for the final cleanup filter
* @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate
* and specified pass/stop frequencies.
*/
public ComplexPrimeCICDecimate(double sampleRate, int decimation, double passFrequency, double stopFrequency)
throws FilterDesignException
{
List<Integer> stageSizes = getPrimeFactors(decimation);
for(int x = 0; x < stageSizes.size(); x++)
{
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), 1);
mDecimatingStages.add(stage);
if(x == 0)
{
/* Reference to first stage -- will receive all samples */
mFirstDecimatingStage = stage;
}
else
{
/* Wire the current stage to the previous stage */
mDecimatingStages.get(x - 1).setListener(stage);
}
}
double channelRate = sampleRate / (double)decimation;
mOutput = new Output(channelRate, passFrequency, stopFrequency);
mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput);
}
public void dispose()
{
for(DecimatingStage stage : mDecimatingStages)
{
stage.dispose();
}
mDecimatingStages.clear();
mDecimatingStages = null;
mFirstDecimatingStage = null;
mOutput.dispose();
mOutput = null;
}
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener(Listener<ReusableComplexBuffer> listener)
{
mOutput.setListener(listener);
}
/**
* Removes listener from output of this CIC decimation filter
*/
public void removeListener()
{
mOutput.removeListener();
}
/**
* Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the
* highest rate listed in the PRIMES array, then add additional prime factors to the PRIMES array.
*
* @param decimation - integral decimation rate
* @return - ordered list (smallest to largest) of prime factors
*/
public static List<Integer> getPrimeFactors(int decimation)
{
List<Integer> primeFactors = sPrimeFactors.get(decimation);
if(primeFactors != null)
{
return primeFactors;
}
primeFactors = Primes.primeFactors(decimation);
sPrimeFactors.put(decimation, primeFactors);
return primeFactors;
}
/**
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
*/
@Override
public void receive(ReusableComplexBuffer buffer)
{
if(mFirstDecimatingStage != null)
{
float[] samples = buffer.getSamples();
for(int x = 0; x < samples.length; x += 2)
{
mFirstDecimatingStage.receive(samples[x], samples[x + 1]);
}
}
buffer.decrementUserCount();
}
/**
* Decimating stage combines multiple CIC stages with a decimator. The
* number of stages is indicated by the order value and the size indicates
* the decimation rate of this stage.
*/
public class DecimatingStage implements ComplexSampleListener
{
private List<Stage> mStages = new ArrayList<Stage>();
private Stage mFirstStage;
private ComplexDecimator mDecimator;
public DecimatingStage(int size, int order)
{
for(int x = 0; x < order; x++)
{
Stage stage;
if(size == 2)
{
stage = new TwoStage();
}
else
{
stage = new Stage(size);
}
mStages.add(stage);
if(x == 0)
{
mFirstStage = stage;
}
else
{
mStages.get(x - 1).setListener(stage);
}
}
mDecimator = new ComplexDecimator(size);
mStages.get(mStages.size() - 1).setListener(mDecimator);
}
public void dispose()
{
for(Stage stage : mStages)
{
stage.dispose();
}
mStages.clear();
mDecimator.dispose();
mDecimator = null;
mFirstStage = null;
mStages = null;
}
@Override
public void receive(float i, float q)
{
mFirstStage.receive(i, q);
}
public void setListener(ComplexSampleListener listener)
{
mDecimator.setListener(listener);
}
}
/**
* Single non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement
* the stage so that stage size has essentially no impact on the computational requirements of the stage
*/
public class Stage implements ComplexSampleListener
{
protected ComplexSampleListener mListener;
private float[] mISamples;
private float[] mQSamples;
protected float mISum;
protected float mQSum;
private int mSamplePointer = 0;
private int mSize;
protected float mGain;
protected Stage()
{
}
public Stage(int size)
{
mSize = size - 1;
mISamples = new float[mSize];
mQSamples = new float[mSize];
float baseGain = 1.0f / (float)size;
mGain = baseGain * (1.0f / (1.0f - baseGain));
}
public void dispose()
{
mListener = null;
}
public void receive(float i, float q)
{
/* Subtract the oldest sample and add in the newest sample */
mISum = mISum - mISamples[mSamplePointer] + i;
mQSum = mQSum - mQSamples[mSamplePointer] + q;
/* Overwrite the oldest sample with the newest */
mISamples[mSamplePointer] = i;
mQSamples[mSamplePointer] = q;
mSamplePointer++;
if(mSamplePointer >= mSize)
{
mSamplePointer = 0;
}
if(mListener != null)
{
mListener.receive((mISum * mGain), (mQSum * mGain));
}
}
public void setListener(ComplexSampleListener listener)
{
mListener = listener;
}
}
/**
* Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage.
*/
public class TwoStage extends Stage
{
public TwoStage()
{
mGain = 0.5f;
}
public void receive(float i, float q)
{
if(mListener != null)
{
mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain));
}
mISum = i;
mQSum = q;
}
}
/**
* Output adapter - applies gain correction and cleanup filter and broadcast
* to registered listener.
*/
public class Output implements ComplexSampleListener
{
/* Decimated output buffers will contain 1024 complex samples */
private ReusableComplexBufferAssembler mBufferAssembler;
private ComplexFIRFilter2 mLowPassFilter;
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate);
//This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies
float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency);
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
mBufferAssembler.setListener(new Listener<ReusableComplexBuffer>()
{
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
if(mReusableComplexBufferListener != null)
{
ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer);
mReusableComplexBufferListener.receive(filteredBuffer);
// mReusableComplexBufferListener.receive(reusableComplexBuffer);
}
else
{
reusableComplexBuffer.decrementUserCount();
}
}
});
}
public void dispose()
{
mBufferAssembler.dispose();
mLowPassFilter.dispose();
}
/**
* Interface for receiving CIC decimated output samples
*/
@Override
public void receive(float inphase, float quadrature)
{
mBufferAssembler.receive(inphase, quadrature);
}
public void setListener(Listener<ReusableComplexBuffer> listener)
{
mReusableComplexBufferListener = listener;
if(mReusableComplexBufferListener == null)
{
removeListener();
}
}
public void removeListener()
{
mReusableComplexBufferListener = new Listener<ReusableComplexBuffer>()
{
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
//empty receiver
reusableComplexBuffer.decrementUserCount();
}
};
}
/**
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
*
* @param sampleRate for the final output channel rate
* @param passFrequency for half of the desired channel rate
* @param stopFrequency for the attenuated band
* @return a newly designed filter or a previously designed (cached) filter
* @throws FilterDesignException
*/
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
//Use existing filter if we've already designed one
float[] existingFilter = sLowPassFilters.get((int) sampleRate);
if(existingFilter != null)
{
return existingFilter;
}
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.passBandCutoff(passFrequency)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(stopFrequency)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//This will throw an exception if the filter cannot be designed
float[] taps = designer.getImpulseResponse();
sLowPassFilters.put((int)sampleRate, taps);
return taps;
}
}
}

View File

@@ -0,0 +1,471 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.cic;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
import org.apache.commons.math3.primes.Primes;
import java.util.Arrays;
import java.util.List;
/**
* Non-Recursive Prime-Factor CIC Filter.
*
* Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This
* filter contains multiple decimating stages each with a prime factor decimation rate. Multiple stages are
* cascaded to achieve the overall decimation rate.
*
* This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by
* adding additional prime factors to the PRIMES array.
*/
public class PrimeCicDecimationFilter
{
private Stage mFirstStage;
/**
* Constructs a new decimation filter.
*
* An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel
* pass band might be:
* sampleRate = 2,400,000.0
* decimation = 96
* passFrequency = 6250 (1/4 of the channel rate)
* stopFrequency = 7000
*
* Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter,
* where smaller transition bandwidth creates a longer filter that requires more processing effort.
*
* @param sampleRate of the input sample stream
* @param decimation - overall decimation rate
* @param passFrequency for the final cleanup filter
* @param stopFrequency for the final cleanup filter
* @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate
* and specified pass/stop frequencies.
*/
public PrimeCicDecimationFilter(double sampleRate, int decimation, double passFrequency, double stopFrequency)
throws FilterDesignException
{
List<Integer> stageSizes = Primes.primeFactors(decimation);
Stage stage = null;
for(int x = 0; x < stageSizes.size(); x++)
{
Stage nextStage = new DecimatingStage(stageSizes.get(x), 1);
if(stage != null)
{
stage.setChild(nextStage);
}
stage = nextStage;
if(x == 0)
{
/* Reference to first stage -- will receive all samples */
mFirstStage = stage;
}
}
//stage should reference the last stage that was created at this point. Create an
//output and chain it to the final stage.
if(stage != null)
{
double channelRate = sampleRate / (double)decimation;
Stage output = new Output(channelRate, passFrequency, stopFrequency);
stage.setChild(output);
}
}
/**
* Decimates the sample array.
* @param samples to decimate
* @return decimated samples
*/
public float[] decimate(float[] samples)
{
return mFirstStage.process(samples);
}
/**
* Base stage for all decimating and non-decimating stages
*/
public abstract class Stage
{
private Stage mChild;
/**
* Sets the child stage to follow this stage
* @param stage child
*/
public void setChild(Stage stage)
{
mChild = stage;
}
/**
* Indicates if this stage has an optional child stage that should process the samples
* after this stage processes those samples.
*/
protected boolean hasChild()
{
return mChild != null;
}
/**
* Optional child stage that should process samples after this stage.
*/
protected Stage getChild()
{
return mChild;
}
/**
* Process the samples. Note: original argument array will be reused if/when possible.
* @param samples to process
* @return processed samples.
*/
public abstract float[] process(float[] samples);
}
/**
* Decimating stage chains multiple non-decimating stages with a decimator. The
* number of stages in the chain is dictated by the order value and the size indicates
* the decimation rate of this stage.
*/
public class DecimatingStage extends Stage
{
private Stage mFirstStage;
private Stage mDecimator;
/**
* Constructs a decimating stage
* @param size
* @param order
*/
public DecimatingStage(int size, int order)
{
Stage stage = null;
for(int x = 0; x < order; x++)
{
Stage nextStage;
if(size == 2)
{
nextStage = new NoDecimateTwoStage();
}
else
{
nextStage = new NoDecimateStage(size);
}
//Chain each stage together
if(stage != null)
{
stage.setChild(nextStage);
}
stage = nextStage;
if(x == 0)
{
mFirstStage = stage;
}
}
mDecimator = new Decimator(size);
//Add a decimator to the end of the stage chain.
if(stage != null)
{
stage.setChild(mDecimator);
}
}
@Override
public float[] process(float[] samples)
{
if(hasChild())
{
return getChild().process(mFirstStage.process(samples));
}
return mFirstStage.process(samples);
}
/**
* Override the default behavior since this stage is a chain of stages and we want any external
* child to be added to the final stage in the chain, which is the decimator.
* @param stage child to add
*/
@Override
public void setChild(Stage stage)
{
mDecimator.setChild(stage);
}
}
/**
* Decimates samples of float arrays.
*/
public class Decimator extends Stage
{
private float[] mResidual;
private int mDecimationFactor;
/**
* Constructs an instance.
* @param decimationFactor ratio of 1:decimate.
*/
public Decimator(int decimationFactor)
{
mDecimationFactor = decimationFactor;
}
/**
* Decimates the sample array by combining the array with any residual from the previous decimation
* operation, decimating, and then storing any residual samples leftover from the decimation.
* @param samples to decimate
* @return decimated samples array.
*/
public float[] process(float[] samples)
{
if(mResidual != null)
{
float[] expanded = new float[mResidual.length + samples.length];
System.arraycopy(mResidual, 0, expanded, 0, mResidual.length);
System.arraycopy(samples, 0, expanded, mResidual.length, samples.length);
samples = expanded;
mResidual = null;
}
float[] decimated = new float[samples.length / mDecimationFactor];
for(int x = 0; x < decimated.length; x++)
{
decimated[x] = samples[x * mDecimationFactor];
}
int residual = samples.length % mDecimationFactor;
if(residual > 0)
{
mResidual = Arrays.copyOfRange(samples, samples.length - residual, samples.length);
}
if(hasChild())
{
return getChild().process(decimated);
}
return decimated;
}
}
/**
* Single, non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement
* the stage so that stage size has essentially no impact on the computational requirements of the stage
*/
public class NoDecimateStage extends Stage
{
private float[] mSampleBuffer;
private int mSamplePointer = 0;
private int mSize;
protected float mSum;
protected float mGain;
/**
* Constructs an instance.
* @param size of the stage
*/
public NoDecimateStage(int size)
{
mSize = size - 1;
mSampleBuffer = new float[mSize];
float baseGain = 1.0f / (float)size;
mGain = baseGain * (1.0f / (1.0f - baseGain));
}
/**
* Alternate constructor that specifies gain only.
* @param gain to apply
*/
protected NoDecimateStage(float gain)
{
mGain = gain;
}
@Override
public float[] process(float[] samples)
{
float sum = mSum;
float gain = mGain;
float size = mSize;
int pointer = mSamplePointer;
for(int x = 0; x < samples.length; x++)
{
/* Subtract the oldest sample and add in the newest sample */
sum -= (mSampleBuffer[pointer] + samples[x]);
/* Overwrite the oldest sample with the newest */
mSampleBuffer[pointer] = samples[x];
pointer++;
pointer %= size;
samples[x] = (sum * gain);
}
mSum = sum;
mSamplePointer = pointer;
if(hasChild())
{
return getChild().process(samples);
}
return samples;
}
}
/**
* Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage.
*/
public class NoDecimateTwoStage extends NoDecimateStage
{
public NoDecimateTwoStage()
{
//Use gain of 1/2 or 0.5
super(0.5f);
}
@Override
public float[] process(float[] samples)
{
float previousSample = mSum;
float temp;
for(int x = 0; x < samples.length; x++)
{
//Calculate average of the current and previous samples
temp = (previousSample + samples[x]) * 0.5f;
previousSample = samples[x];
samples[x] = temp;
}
mSum = previousSample;
if(hasChild())
{
return getChild().process(samples);
}
return samples;
}
}
/**
* Output adapter - applies gain correction and cleanup filter and broadcast
* to registered listener.
*/
public class Output extends Stage
{
private IRealFilter mFilter;
public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
//This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies
float[] coefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency);
mFilter = FilterFactory.getRealFilter(coefficients);
}
@Override
public float[] process(float[] samples)
{
if(hasChild())
{
return getChild().process(mFilter.filter(samples));
}
return mFilter.filter(samples);
}
/**
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
*
* @param sampleRate for the final output channel rate
* @param passFrequency for half of the desired channel rate
* @param stopFrequency for the attenuated band
* @return a newly designed filter or a previously designed (cached) filter
* @throws FilterDesignException
*/
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.passBandCutoff(passFrequency)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(stopFrequency)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//This will throw an exception if the filter cannot be designed
return designer.getImpulseResponse();
}
}
public static void main(String[] args)
{
double sampleRate = 25.0;
int decimation = 5;
double finalRate = sampleRate / decimation;
IRealOscillator oscillator = OscillatorFactory.getRealOscillator(1.0, sampleRate);
try
{
PrimeCicDecimationFilter filter = new PrimeCicDecimationFilter(sampleRate, decimation, 1.5, 2.0);
for(int x = 0; x < 20; x++)
{
float[] samples = oscillator.generate(5000);
float[] decimated = filter.decimate(samples);
System.out.println(Arrays.toString(decimated));
}
}
catch(FilterDesignException fde)
{
fde.printStackTrace();
}
}
}

View File

@@ -1,382 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 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.filter.cic;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.decimator.RealDecimator;
import io.github.dsheirer.sample.real.RealSampleListener;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.primes.Primes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class RealPrimeCICDecimate
{
private final static Logger mLog = LoggerFactory.getLogger(RealPrimeCICDecimate.class);
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage;
private Output mOutput;
private int mOutputBufferSize = 2048;
/**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated float sample output.
*
* Implements the CIC filter described in Understanding Digital Signal
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
* decimating stages each with a prime factor decimation rate. Multiple
* stages are cascaded to achieve the overall decimation rate.
*
* This filter supports a maximum decimation rate of 700. This filter can
* be adapted to higher decimation rates by adding additional prime factors
* to the PRIMES array.
*
* @param decimation - overall decimation rate
* @param order - filter order
*/
public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType)
{
this(decimation, order, passFrequency, attenuation, windowType, 2048);
}
public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize)
{
Validate.isTrue(decimation <= 700);
mOutputBufferSize = outputBufferSize;
List<Integer> stageSizes = Primes.primeFactors(decimation);
for(int x = 0; x < stageSizes.size(); x++)
{
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
mDecimatingStages.add(stage);
if(x == 0)
{
/* Reference to first stage -- will receive all samples */
mFirstDecimatingStage = stage;
}
else
{
/* Wire the current stage to the previous stage */
mDecimatingStages.get(x - 1).setListener(stage);
}
}
mOutput = new Output(48000, passFrequency, attenuation, windowType,
outputBufferSize);
mDecimatingStages.get(mDecimatingStages.size() - 1)
.setListener(mOutput);
}
public void dispose()
{
for(DecimatingStage stage : mDecimatingStages)
{
stage.dispose();
}
mDecimatingStages.clear();
mDecimatingStages = null;
mFirstDecimatingStage = null;
mOutput.dispose();
mOutput = null;
}
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener(Listener<ReusableFloatBuffer> listener)
{
mOutput.setListener(listener);
}
/**
* Removes listener from output of this CIC decimation filter
*/
public void removeListener()
{
mOutput.removeListener();
}
/**
* Primary input method for receiving real buffers.
*/
public void receive(ReusableFloatBuffer buffer)
{
if(mFirstDecimatingStage != null)
{
for(float sample : buffer.getSamples())
{
mFirstDecimatingStage.receive(sample);
}
}
buffer.decrementUserCount();
}
/**
* Decimating stage combines multiple CIC stages with a decimator. The
* number of stages is indicated by the order value and the size indicates
* the decimation rate of this stage.
*/
public class DecimatingStage implements RealSampleListener
{
private ArrayList<Stage> mStages = new ArrayList<Stage>();
private Stage mFirstStage;
private RealDecimator mDecimator;
public DecimatingStage(int size, int order)
{
for(int x = 0; x < order; x++)
{
Stage stage;
if(size == 2)
{
stage = new TwoStage();
}
else
{
stage = new Stage(size);
}
mStages.add(stage);
if(x == 0)
{
mFirstStage = stage;
}
else
{
mStages.get(x - 1).setListener(stage);
}
}
mDecimator = new RealDecimator(size);
mStages.get(mStages.size() - 1).setListener(mDecimator);
}
public void dispose()
{
for(Stage stage : mStages)
{
stage.dispose();
}
mStages.clear();
mDecimator.dispose();
mDecimator = null;
mFirstStage = null;
mStages = null;
}
@Override
public void receive(float sample)
{
mFirstStage.receive(sample);
}
public void setListener(RealSampleListener listener)
{
mDecimator.setListener(listener);
}
}
/**
* Single non-decimating CIC stage component. Uses a circular buffer and a
* running average internally to implement the stage so that stage size has
* essentially no impact on the computational requirements of the stage
*/
public class Stage implements RealSampleListener
{
protected RealSampleListener mListener;
private float[] mSamples;
protected float mSum;
private int mSamplePointer = 0;
private int mSize;
protected float mGain;
public Stage()
{
}
public Stage(int size)
{
mSize = size - 1;
mSamples = new float[mSize];
mGain = 1.0f / (float)size;
}
public void dispose()
{
mListener = null;
}
public void receive(float sample)
{
/* Subtract the oldest sample and add back in the newest */
mSum = mSum - mSamples[mSamplePointer] + sample;
/* Overwrite the oldest sample with the newest */
mSamples[mSamplePointer] = sample;
mSamplePointer++;
if(mSamplePointer >= mSize)
{
mSamplePointer = 0;
}
if(mListener != null)
{
mListener.receive(mSum * mGain);
}
}
public void setListener(RealSampleListener listener)
{
mListener = listener;
}
}
/**
* Size 2 stage that removes the unnecessary circular buffer management for
* a two-stage.
*/
public class TwoStage extends Stage
{
public TwoStage()
{
super();
mGain = 0.5f;
}
public void receive(float sample)
{
float sum = mSum + sample;
mSum = sample;
if(mListener != null)
{
mListener.receive(sum * mGain);
}
}
}
/**
* Output adapter - applies gain correction, applies cleanup filter, casts
* double-valued samples into float-valued complex sample and sends the
* result to the registered listener.
*/
public class Output implements RealSampleListener
{
/* Decimated output buffers will contain 1024 samples */
private ReusableBufferAssembler mAssembler;
private Listener<ReusableFloatBuffer> mReusableBufferListener;
private RealFIRFilter2 mCleanupFilter;
private HalfBandFilter mHalfBandFilter = new HalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
public Output(int outputSampleRate, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize)
{
mCleanupFilter = new RealFIRFilter2(FilterFactory.getCICCleanupFilter(outputSampleRate, passFrequency,
attenuation, windowType), 0.4f);
mAssembler = new ReusableBufferAssembler(outputBufferSize);
mAssembler.setListener(new Listener<ReusableFloatBuffer>()
{
@Override
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
if(mReusableBufferListener != null)
{
ReusableFloatBuffer cleanupFiltered = mCleanupFilter.filter(reusableFloatBuffer);
ReusableFloatBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
mReusableBufferListener.receive(halfBandFiltered);
}
else
{
reusableFloatBuffer.decrementUserCount();
}
}
});
}
public void dispose()
{
mAssembler.dispose();
mCleanupFilter.dispose();
mHalfBandFilter.dispose();
}
/**
* Receiver method for the output adapter to receive a filtered,
* decimated sample, apply gain correction, apply cleanup filtering
* and output the sample values as a complex sample.
*/
@Override
public void receive(float sample)
{
mAssembler.receive(sample * 32.0f);
}
/**
* Adds a listener to receive output samples
*/
public void setListener(Listener<ReusableFloatBuffer> listener)
{
mReusableBufferListener = listener;
}
/**
* Removes the listener from receiving output samples
*/
public void removeListener()
{
mReusableBufferListener = null;
}
}
}

View File

@@ -1,58 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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.filter.correction;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
public class IQCorrectionFilter
{
private float mRatio = 0.00001f;
private float mAverageInphase = 0.0f;
private float mAverageQuadrature = 0.0f;
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("IQCorrectionFilter");
public IQCorrectionFilter(float ratio)
{
mRatio = ratio;
}
public IQCorrectionFilter()
{
}
public ReusableComplexBuffer filter(ReusableComplexBuffer buffer)
{
float[] samples = buffer.getSamples();
ReusableComplexBuffer filtered = mReusableComplexBufferQueue.getBuffer(samples.length);
float[] filteredSamples = filtered.getSamples();
for(int x = 0; x < samples.length; x += 2)
{
mAverageInphase = mAverageInphase + (mRatio * (samples[x] - mAverageInphase));
filteredSamples[x] = samples[x] - mAverageInphase;
mAverageQuadrature = mAverageQuadrature + (mRatio * (samples[x + 1] - mAverageQuadrature));
filteredSamples[x + 1] = samples[x + 1] - mAverageQuadrature;
}
return filtered;
}
}

View File

@@ -1,97 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BufferDCFilter
{
private final static Logger mLog = LoggerFactory.getLogger(BufferDCFilter.class);
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Buffer DC Filter");
private double mAccumulator;
/**
* Calculates the DC offset present in the samples and subtracts the offset from all samples.
*/
public void filterReal(float[] samples)
{
float adjustment = getOffset(samples);
for(int x = 0; x < samples.length; x++)
{
samples[x] = samples[x] - adjustment;
}
}
private float getOffset(float[] samples)
{
mAccumulator = 0.0;
for (float sample : samples) {
mAccumulator += sample;
}
return (float)(mAccumulator / (double)samples.length);
}
/**
* Calculates the DC offset present in the samples and subtracts the offset from all samples.
*/
public static void filterComplex(float[] samples)
{
double iAccumulator = 0.0;
double qAccumulator = 0.0;
for(int x = 0; x < samples.length; x += 2)
{
iAccumulator += samples[x];
qAccumulator += samples[x + 1];
}
double half = (double)samples.length / 2.0;
float iAdjustment = (float)(iAccumulator / half);
float qAdjustment = (float)(qAccumulator / half);
for(int x = 0; x < samples.length; x += 2)
{
samples[x] = samples[x] - iAdjustment;
samples[x + 1] = samples[x + 1] - qAdjustment;
}
}
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
float[] unfilteredSamples = unfilteredBuffer.getSamples();
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
float[] filteredSamples = filteredBuffer.getSamples();
float offset = getOffset(unfilteredSamples);
for(int x = 0; x < unfilteredSamples.length; x++)
{
filteredSamples[x] = unfilteredSamples[x] - offset;
}
unfilteredBuffer.decrementUserCount();
return filteredBuffer;
}
}

View File

@@ -1,96 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* DC removal filter for removing any DC offset that may be present when a signal is not aligned within the
* demodulated channel.
*/
public class DCRemovalFilter
{
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("DC Removal Filter");
protected float mAverage;
protected float mRatio;
/**
* Creates a DC removal filter.
*
* @param ratio
*/
public DCRemovalFilter(float ratio)
{
mRatio = ratio;
}
/**
* Resets the running average to zero.
*/
public void reset()
{
mAverage = 0.0f;
}
/**
* Filters the sample and returns a filtered (ie DC-removed) sample.
* @param sample to filter
* @return filtered sample
*/
public float filter(float sample)
{
float filtered = sample - mAverage;
mAverage += mRatio * filtered;
return filtered;
}
public float[] filter( float[] samples )
{
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return samples;
}
/**
* Filters the buffer samples and returns a new reusable buffer with the filtered samples.
*
* Note: this method automatically manages the user accounting between the buffer argument and the new returned
* filtered sample buffer.
*
* @param unfilteredBuffer containing samples to filter
* @return a new reusable buffer containing the filtered samples with the user count set to 1.
*/
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
float[] unfilteredSamples = unfilteredBuffer.getSamples();
float[] filteredSamples = filteredBuffer.getSamples();
for(int x = 0; x < unfilteredSamples.length; x++)
{
filteredSamples[x] = filter(unfilteredSamples[x]);
}
unfilteredBuffer.decrementUserCount();
return filteredBuffer;
}
}

View File

@@ -0,0 +1,49 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.dc;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
/**
* Factory for constructing the optimal DC removal filter.
*/
public class DcRemovalFilterFactory
{
/**
* Creates the optimal DC removal filter for this computer
* @param gain to apply to DC removal
* @return optimal implementation.
*/
public static IDcRemovalFilter getFilter(float gain)
{
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DC_REMOVAL_REAL);
switch(implementation)
{
case VECTOR_SIMD_PREFERRED:
return new VectorDcRemovalFilter(gain);
case SCALAR:
default:
return new ScalarDcRemovalFilter(gain);
}
}
}

View File

@@ -17,24 +17,12 @@
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
package io.github.dsheirer.dsp.filter.dc;
/**
* No decimation filter.
* Interface for a DC removal filter.
*/
public class ComplexDecimateX0Filter implements IComplexDecimationFilter
public interface IDcRemovalFilter
{
@Override
public float[] decimateComplex(float[] samples)
{
return samples;
}
@Override
public ReusableComplexBuffer decimate(ReusableComplexBuffer buffer)
{
return buffer;
}
float[] filter(float[] samples);
}

View File

@@ -1,34 +1,30 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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 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.
* 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/>
*
******************************************************************************/
* 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.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
public class IIRSinglePoleDCRemovalFilter
{
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("IIR DC Filter");
private float mAlpha;
private float mPreviousInput = 0.0f;
private float mPreviousOutput = 0.0f;
private float mCurrentOutput = 0.0f;
private RealSampleListener mListener;
/**
* IIR single-pole DC removal filter, as described by J M de Freitas in
* 29Jan2007 paper at:
@@ -64,42 +60,16 @@ public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
* Buffer user accounting is handled by this method where the input buffer user count is decremented and the
* returned output buffer has the user count incremented to one.
*
* @param inputBuffer to filter
* @param samples to filter
* @return filtered samples in a new (ie reused) output buffer
*/
public ReusableFloatBuffer filter(ReusableFloatBuffer inputBuffer)
public float[] filter(float[] samples)
{
ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
float[] inputSamples = inputBuffer.getSamples();
float[] outputSamples = outputBuffer.getSamples();
for(int x = 0; x < inputSamples.length; x++)
for(int x = 0; x < samples.length; x++)
{
outputSamples[x] = filter(inputSamples[x]);
samples[x] = filter(samples[x]);
}
inputBuffer.decrementUserCount();
return outputBuffer;
}
/**
* Implements the RealSampleListener interface for single sample streams.
*/
@Override
public void receive(float sample)
{
if(mListener != null)
{
mListener.receive(filter(sample));
}
}
/**
* Assigns the listener to receive filtered samples processed by the RealSampleListener interface
*/
public void setListener(RealSampleListener listener)
{
mListener = listener;
return samples;
}
}

View File

@@ -0,0 +1,86 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.dc;
import java.util.Random;
public class ScalarDcRemovalFilter implements IDcRemovalFilter
{
private float mAverage;
private float mGain;
public ScalarDcRemovalFilter(float gain)
{
mGain = gain;
}
@Override
public float[] filter(float[] samples)
{
float accumulator = 0.0f;
float average = mAverage;
for(float sample: samples)
{
accumulator += sample;
}
float averageDCNow = (accumulator / samples.length) - average;
average += (mGain * averageDCNow);
for(int x = 0; x < samples.length; x++)
{
samples[x] -= average;
}
mAverage = average;
return samples;
}
public static void main(String[] args)
{
System.out.println("Starting ...");
Random random = new Random();
ScalarDcRemovalFilter scalarFilter = new ScalarDcRemovalFilter(0.15f);
VectorDcRemovalFilter vectorFilter = new VectorDcRemovalFilter(0.15f);
float dcOffset = 0.03f;
float[] samples = new float[8192];
for(int y = 0; y < samples.length; y++)
{
samples[y] = (random.nextFloat() * 2.0f) - 1.0f + dcOffset;
}
long start = System.currentTimeMillis();
for(int x = 0; x < 1_000_000; x++)
{
// scalarFilter.filter(samples);
vectorFilter.filter(samples);
}
long duration = System.currentTimeMillis() - start;
System.out.println("Finished - Duration: " + duration);
}
}

View File

@@ -0,0 +1,87 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.dc;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import java.util.Random;
public class VectorDcRemovalFilter implements IDcRemovalFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
private float mAverageDc;
private float mGain;
public VectorDcRemovalFilter(float gain)
{
mGain = gain;
}
@Override
public float[] filter(float[] samples)
{
FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int pointer = 0; pointer < samples.length; pointer += VECTOR_SPECIES.length())
{
accumulator = accumulator.add(FloatVector.fromArray(VECTOR_SPECIES, samples, pointer));
}
float sum = accumulator.reduceLanes(VectorOperators.ADD);
float average = mAverageDc;
float averageDCNow = (sum / samples.length) - average;
average += (mGain * averageDCNow);
for(int pointer = 0; pointer < samples.length; pointer += VECTOR_SPECIES.length())
{
//Load into vector, subtract dc, and write back to sample array
FloatVector.fromArray(VECTOR_SPECIES, samples, pointer).add(average).intoArray(samples, pointer);
}
mAverageDc = average;
return samples;
}
public static void main(String[] args)
{
System.out.println("Starting ...");
Random random = new Random();
VectorDcRemovalFilter filter = new VectorDcRemovalFilter(0.15f);
float dcOffset = 0.03f;
for(int x = 0; x < 10; x++)
{
float[] samples = new float[8192];
for(int y = 0; y < samples.length; y++)
{
samples[y] = (random.nextFloat() * 2.0f) - 1.0f + dcOffset;
}
samples = filter.filter(samples);
}
System.out.println("Finished!");
}
}

View File

@@ -1,53 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Decimate by 1024 filter for complex valued sample buffers.
*/
public class ComplexDecimateX1024Filter extends ComplexDecimateX512Filter
{
private static final int VALIDATION_LENGTH = 1024 * 2;
private static final int DECIMATE_BY_1024_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_1024_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX1024Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_1024_FILTER_LENGTH,
DECIMATE_BY_1024_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -1,53 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Decimate by 128 filter for complex valued sample buffers.
*/
public class ComplexDecimateX128Filter extends ComplexDecimateX64Filter
{
private static final int VALIDATION_LENGTH = 128 * 2;
private static final int DECIMATE_BY_128_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_128_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX128Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_128_FILTER_LENGTH,
DECIMATE_BY_128_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -1,53 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Decimate by 16 filter for complex valued sample buffers.
*/
public class ComplexDecimateX16Filter extends ComplexDecimateX8Filter
{
private static final int VALIDATION_LENGTH = 16 * 2;
private static final int DECIMATE_BY_16_FILTER_LENGTH = 15;
private static final Window.WindowType DECIMATE_BY_16_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX16Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_16_FILTER_LENGTH,
DECIMATE_BY_16_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -1,53 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Decimate by 256 filter for complex valued sample buffers.
*/
public class ComplexDecimateX256Filter extends ComplexDecimateX128Filter
{
private static final int VALIDATION_LENGTH = 256 * 2;
private static final int DECIMATE_BY_256_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_256_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX256Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_256_FILTER_LENGTH,
DECIMATE_BY_256_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -1,55 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Constructs the decimation filter.
*/
public class ComplexDecimateX2Filter extends ComplexHalfBandDecimationFilter
{
private static final int DECIMATE_BY_2_FILTER_LENGTH = 63;
private static final Window.WindowType DECIMATE_BY_2_WINDOW_TYPE = Window.WindowType.HAMMING;
/**
* Creates a half band filter with inherent decimation by two.
*/
public ComplexDecimateX2Filter()
{
super(FilterFactory.getHalfBand(DECIMATE_BY_2_FILTER_LENGTH, DECIMATE_BY_2_WINDOW_TYPE));
}
/**
* Validates that the samples length is an integer multiple of the specified validation multiple argument.
* @param samples to validate
* @param validationMultiple to validate against
*/
protected static void validate(float[] samples, int validationMultiple)
{
if(samples.length % validationMultiple != 0)
{
throw new IllegalArgumentException("Sample buffer length [" + samples.length +
"] must be an integer multiple of " + validationMultiple);
}
}
}

View File

@@ -1,53 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Decimate by 32 filter for complex valued sample buffers.
*/
public class ComplexDecimateX32Filter extends ComplexDecimateX16Filter
{
private static final int VALIDATION_LENGTH = 32 * 2;
private static final int DECIMATE_BY_32_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_32_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX32Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_32_FILTER_LENGTH,
DECIMATE_BY_32_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -1,54 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
/**
* Decimate by 4 filter for complex valued sample buffers.
*/
public class ComplexDecimateX4Filter extends ComplexDecimateX2Filter
{
private static final int VALIDATION_LENGTH = 4 * 2;
private static final int DECIMATE_BY_4_FILTER_LENGTH = 23;
private static final Window.WindowType DECIMATE_BY_4_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX4Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_4_FILTER_LENGTH,
DECIMATE_BY_4_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -1,53 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Decimate by 512 filter for complex valued sample buffers.
*/
public class ComplexDecimateX512Filter extends ComplexDecimateX256Filter
{
private static final int VALIDATION_LENGTH = 512 * 2;
private static final int DECIMATE_BY_512_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_512_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX512Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_512_FILTER_LENGTH,
DECIMATE_BY_512_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -1,53 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Decimate by 64 filter for complex valued sample buffers.
*/
public class ComplexDecimateX64Filter extends ComplexDecimateX32Filter
{
private static final int VALIDATION_LENGTH = 64 * 2;
private static final int DECIMATE_BY_64_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_64_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX64Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_64_FILTER_LENGTH,
DECIMATE_BY_64_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -1,53 +0,0 @@
/*
* *****************************************************************************
* 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.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
/**
* Decimate by 8 filter for complex valued sample buffers.
*/
public class ComplexDecimateX8Filter extends ComplexDecimateX4Filter
{
private static final int VALIDATION_LENGTH = 8 * 2;
private static final int DECIMATE_BY_8_FILTER_LENGTH = 15;
private static final Window.WindowType DECIMATE_BY_8_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private ComplexHalfBandDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public ComplexDecimateX8Filter()
{
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_8_FILTER_LENGTH,
DECIMATE_BY_8_WINDOW_TYPE));
}
@Override
public float[] decimateComplex(float[] samples)
{
validate(samples, VALIDATION_LENGTH);
//Decimate by this filter, then by the parent decimation filter
return super.decimateComplex(mFilter.decimateComplex(samples));
}
}

View File

@@ -19,11 +19,15 @@
package io.github.dsheirer.dsp.filter.decimate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for creating real and complex decimation filters.
*/
public class DecimationFilterFactory
{
private static final Logger mLog = LoggerFactory.getLogger(DecimationFilterFactory.class);
private static final int[] SUPPORTED_RATES = new int[]{0,2,4,8,16,32,64,128,256,512,1024};
/**
@@ -66,41 +70,25 @@ public class DecimationFilterFactory
}
/**
* Creates a complex-valued decimation filter for float array sample buffers providing greater than 100 dB of
* attenuation for out of band signal aliases. Supports power of 2 decimation rates up to 1024.
*
* @param decimationRate requested @see SUPPORTED_RATES
* @return constructed decimation filter
* Finds the greatest factor of 2 decimation rate that is less than the requested rate.
* @param requestedRate for decimation
* @return factor of 2 decimation rate that is closest to the requested rate
*/
public static IComplexDecimationFilter getComplexDecimationFilter(int decimationRate)
public static int getDecimationRate(int requestedRate)
{
switch(decimationRate)
if(requestedRate < 2)
{
case 0:
return new ComplexDecimateX0Filter();
case 2:
return new ComplexDecimateX2Filter();
case 4:
return new ComplexDecimateX4Filter();
case 8:
return new ComplexDecimateX8Filter();
case 16:
return new ComplexDecimateX16Filter();
case 32:
return new ComplexDecimateX32Filter();
case 64:
return new ComplexDecimateX64Filter();
case 128:
return new ComplexDecimateX128Filter();
case 256:
return new ComplexDecimateX256Filter();
case 512:
return new ComplexDecimateX512Filter();
case 1024:
return new ComplexDecimateX1024Filter();
default:
throw new IllegalArgumentException("Unsupported decimation rate: " + decimationRate +
". Supported decimation rates are:" + SUPPORTED_RATES);
throw new IllegalArgumentException("Requested decimation rate must be greater than 2");
}
for(int x = SUPPORTED_RATES.length - 1; x >= 0; x--)
{
if(SUPPORTED_RATES[x] < requestedRate)
{
return SUPPORTED_RATES[x];
}
}
return 2;
}
}

View File

@@ -20,8 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 1024 filter for real valued sample buffers.
@@ -30,16 +29,16 @@ public class RealDecimateX1024Filter extends RealDecimateX512Filter
{
private static final int VALIDATION_LENGTH = 1024;
private static final int DECIMATE_BY_1024_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_1024_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_1024_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX1024Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_1024_FILTER_LENGTH,
DECIMATE_BY_1024_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_1024_FILTER_LENGTH,
DECIMATE_BY_1024_WINDOW_TYPE);
}
@Override

View File

@@ -20,9 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.gui.playlist.alias.ViewAliasIdentifierRequest;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 128 filter for real valued sample buffers.
@@ -31,16 +29,16 @@ public class RealDecimateX128Filter extends RealDecimateX64Filter
{
private static final int VALIDATION_LENGTH = 128;
private static final int DECIMATE_BY_128_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_128_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_128_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX128Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_128_FILTER_LENGTH,
DECIMATE_BY_128_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_128_FILTER_LENGTH,
DECIMATE_BY_128_WINDOW_TYPE);
}
@Override

View File

@@ -20,8 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 16 filter for real valued sample buffers.
@@ -30,16 +29,16 @@ public class RealDecimateX16Filter extends RealDecimateX8Filter
{
private static final int VALIDATION_LENGTH = 16;
private static final int DECIMATE_BY_16_FILTER_LENGTH = 15;
private static final Window.WindowType DECIMATE_BY_16_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_16_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX16Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_16_FILTER_LENGTH,
DECIMATE_BY_16_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_16_FILTER_LENGTH,
DECIMATE_BY_16_WINDOW_TYPE);
}
@Override

View File

@@ -20,8 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 256 filter for real valued sample buffers.
@@ -30,16 +29,16 @@ public class RealDecimateX256Filter extends RealDecimateX128Filter
{
private static final int VALIDATION_LENGTH = 256;
private static final int DECIMATE_BY_256_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_256_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_256_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX256Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_256_FILTER_LENGTH,
DECIMATE_BY_256_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_256_FILTER_LENGTH,
DECIMATE_BY_256_WINDOW_TYPE);
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2021 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -20,23 +20,29 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Constructs the decimation filter.
*/
public class RealDecimateX2Filter extends RealHalfBandDecimationFilter
public class RealDecimateX2Filter implements IRealDecimationFilter
{
private static final int DECIMATE_BY_2_FILTER_LENGTH = 63;
private static final Window.WindowType DECIMATE_BY_2_WINDOW_TYPE = Window.WindowType.HAMMING;
private static final WindowType DECIMATE_BY_2_WINDOW_TYPE = WindowType.HAMMING;
private IRealDecimationFilter mFilter;
/**
* Creates a half band filter with inherent decimation by two.
*/
public RealDecimateX2Filter()
{
super(FilterFactory.getHalfBand(DECIMATE_BY_2_FILTER_LENGTH, DECIMATE_BY_2_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_2_FILTER_LENGTH,
DECIMATE_BY_2_WINDOW_TYPE);
}
@Override public float[] decimateReal(float[] samples)
{
return mFilter.decimateReal(samples);
}
/**

View File

@@ -20,8 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 32 filter for real valued sample buffers.
@@ -30,16 +29,16 @@ public class RealDecimateX32Filter extends RealDecimateX16Filter
{
private static final int VALIDATION_LENGTH = 32;
private static final int DECIMATE_BY_32_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_32_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_32_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX32Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_32_FILTER_LENGTH,
DECIMATE_BY_32_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_32_FILTER_LENGTH,
DECIMATE_BY_32_WINDOW_TYPE);
}
@Override

View File

@@ -20,8 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 4 filter for real valued sample buffers.
@@ -30,16 +29,16 @@ public class RealDecimateX4Filter extends RealDecimateX2Filter
{
private static final int VALIDATION_LENGTH = 4;
private static final int DECIMATE_BY_4_FILTER_LENGTH = 23;
private static final Window.WindowType DECIMATE_BY_4_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_4_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX4Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_4_FILTER_LENGTH,
DECIMATE_BY_4_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_4_FILTER_LENGTH,
DECIMATE_BY_4_WINDOW_TYPE);
}
@Override

View File

@@ -20,8 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 512 filter for real valued sample buffers.
@@ -30,16 +29,16 @@ public class RealDecimateX512Filter extends RealDecimateX256Filter
{
private static final int VALIDATION_LENGTH = 512;
private static final int DECIMATE_BY_512_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_512_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_512_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX512Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_512_FILTER_LENGTH,
DECIMATE_BY_512_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_512_FILTER_LENGTH,
DECIMATE_BY_512_WINDOW_TYPE);
}
@Override

View File

@@ -20,8 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 64 filter for real valued sample buffers.
@@ -30,16 +29,16 @@ public class RealDecimateX64Filter extends RealDecimateX32Filter
{
private static final int VALIDATION_LENGTH = 64;
private static final int DECIMATE_BY_64_FILTER_LENGTH = 11;
private static final Window.WindowType DECIMATE_BY_64_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_64_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX64Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_64_FILTER_LENGTH,
DECIMATE_BY_64_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_64_FILTER_LENGTH,
DECIMATE_BY_64_WINDOW_TYPE);
}
@Override

View File

@@ -20,8 +20,7 @@
package io.github.dsheirer.dsp.filter.decimate;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
/**
* Decimate by 8 filter for real valued sample buffers.
@@ -30,16 +29,16 @@ public class RealDecimateX8Filter extends RealDecimateX4Filter
{
private static final int VALIDATION_LENGTH = 8;
private static final int DECIMATE_BY_8_FILTER_LENGTH = 15;
private static final Window.WindowType DECIMATE_BY_8_WINDOW_TYPE = Window.WindowType.BLACKMAN;
private RealHalfBandDecimationFilter mFilter;
private static final WindowType DECIMATE_BY_8_WINDOW_TYPE = WindowType.BLACKMAN;
private IRealDecimationFilter mFilter;
/**
* Constructs the decimation filter.
*/
public RealDecimateX8Filter()
{
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_8_FILTER_LENGTH,
DECIMATE_BY_8_WINDOW_TYPE));
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_8_FILTER_LENGTH,
DECIMATE_BY_8_WINDOW_TYPE);
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2021 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -20,8 +20,8 @@
package io.github.dsheirer.dsp.filter.design;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
import io.github.dsheirer.dsp.window.WindowType;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -59,11 +59,13 @@ public class FilterViewer extends Application
*/
private float[] getFilter()
{
int filterLength = 63;
Window.WindowType windowType = Window.WindowType.HAMMING;
int filterLength = 47;
WindowType windowType = WindowType.HAMMING;
float[] taps = FilterFactory.getHalfBand(filterLength, windowType);
taps = HilbertTransform.HALF_BAND_FILTER_47_TAP;
if(taps == null)
{
throw new IllegalStateException("Couldn't design filter");

View File

@@ -20,7 +20,7 @@
package io.github.dsheirer.dsp.filter.design;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.window.WindowType;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
@@ -35,7 +35,7 @@ public class HalfBandDecimationFilterAnalysis
private static FloatFFT_1D FFT = new FloatFFT_1D(FFT_SIZE);
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
private static final Window.WindowType WINDOW_TYPE = Window.WindowType.BLACKMAN;
private static final WindowType WINDOW_TYPE = WindowType.BLACKMAN;
private static final int[] TAP_LENGTHS = new int[]{7,11,15,19,23,27,31,35,39,43,47,51,55,59,63,67,71,75,79,83,87,91,95,99,103,107};
private static final int BY_2 = (int)(FFT_SIZE / 2 * (1.0 / 2.0));
private static final int BY_2_25 = (int)(FFT_SIZE / 2 * (1.0 / 2.25));
@@ -58,7 +58,7 @@ public class HalfBandDecimationFilterAnalysis
public static void analyze()
{
for(Window.WindowType windowType: Window.NO_PARAMETER_WINDOWS)
for(WindowType windowType: WindowType.NO_PARAMETER_WINDOWS)
{
StringBuilder sb = new StringBuilder();
sb.append("Window: " + windowType).append("\n");
@@ -133,7 +133,7 @@ public class HalfBandDecimationFilterAnalysis
* @param windowType to apply to the taps during filter creation
* @return filter
*/
private static float[] getTaps(int filterLength, Window.WindowType windowType)
private static float[] getTaps(int filterLength, WindowType windowType)
{
return FilterFactory.getHalfBand(filterLength, windowType);
}

View File

@@ -1,138 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.filter.fir.complex;
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
import io.github.dsheirer.sample.complex.Complex;
public class ComplexFIRFilter2 extends FIRFilter
{
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Complex FIR Filter");
private RealFIRFilter2 mIFilter;
private RealFIRFilter2 mQFilter;
/**
* Complex FIR Filter for processing complex sample pairs that internally uses two RealFIRFilter
* instances.
*
* @param coefficients - filter taps
* @param gain - gain to apply to filtered outputs - use 1.0f for no gain
*/
public ComplexFIRFilter2(float[] coefficients, float gain)
{
mIFilter = new RealFIRFilter2(coefficients, gain);
mQFilter = new RealFIRFilter2(coefficients, gain);
}
/**
* Complex FIR Filter for processing complex sample pairs that internally uses two RealFIRFilter
* instances. This constructor uses a default gain of 1.0f.
*
* @param coefficients - filter taps
*/
public ComplexFIRFilter2(float[] coefficients)
{
this(coefficients, 1.0f);
}
/**
* Filters the inphase sample value.
* @param sample to filter
* @return filtered sample
*/
public float filterInphase(float sample)
{
return mIFilter.filter(sample);
}
/**
* Current filtered inphase sample value after invoking the filterInphase() method.
*/
public float currentInphaseValue()
{
return mIFilter.currentValue();
}
/**
* Filters the quadrature sample value.
* @param sample to filter
* @return filtered sample
*/
public float filterQuadrature(float sample)
{
return mQFilter.filter(sample);
}
/**
* Current filtered quadrature sample value after invoking the filterInphase() method.
*/
public float currentQuadratureValue()
{
return mQFilter.currentValue();
}
/**
* Filters the complex sample.
* @param sample to filter
* @return filtered sample
*/
public Complex filter(Complex sample)
{
float i = filterInphase(sample.inphase());
float q = filterQuadrature(sample.quadrature());
return new Complex(i, q);
}
/**
* Filters the complex samples from the reusable buffer and returns a new complex buffer with the filtered output
*
* Note: the original reusable buffer user count is decremented and a new reusable buffer is returned
* with the user count already incremented to one.
*
* @param originalBuffer with complex samples to filter
* @return new buffer containing filtered complex samples
*/
public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer)
{
ReusableComplexBuffer filteredBuffer = mReusableComplexBufferQueue.getBuffer(originalBuffer.getSamples().length);
filteredBuffer.setTimestamp(originalBuffer.getTimestamp());
float[] samples = originalBuffer.getSamples();
float[] filteredSamples = filteredBuffer.getSamples();
for(int x = 0; x < originalBuffer.getSamples().length; x += 2)
{
filteredSamples[x] = filterInphase(samples[x]);
filteredSamples[x + 1] = filterQuadrature(samples[x + 1]);
}
originalBuffer.decrementUserCount();
return filteredBuffer;
}
@Override
public void dispose()
{
mIFilter.dispose();
mQFilter.dispose();
mReusableComplexBufferQueue.dispose();
}
}

View File

@@ -0,0 +1,28 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.complex;
/**
* Interface for a filter for float array sample buffers
*/
public interface IComplexFilter
{
float[] filter(float[] samples);
}

View File

@@ -0,0 +1,152 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.complex;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.window.WindowType;
import org.apache.commons.lang3.ArrayUtils;
import java.text.DecimalFormat;
import java.util.Random;
public class ScalarComplexFIRFilter implements IComplexFilter
{
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public ScalarComplexFIRFilter(float[] coefficients)
{
//Reverse the order of the coefficients.
ArrayUtils.reverse(coefficients);
//Double the size of the coefficients array to align with the complex samples array
mCoefficients = new float[coefficients.length * 2];
for(int x = 0; x < coefficients.length; x++)
{
mCoefficients[2 * x] = coefficients[x];
}
mBufferOverlap = mCoefficients.length - 2;
//We'll resize this later when we get the first sample buffer. For now, make it non-null.
mBuffer = new float[mCoefficients.length];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
int offset = 0;
float iAccumulator, qAccumulator;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
iAccumulator = 0.0f;
qAccumulator = 0.0f;
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += 2)
{
offset = bufferPointer + coefficientPointer;
iAccumulator += mBuffer[offset] * mCoefficients[coefficientPointer];
qAccumulator += mBuffer[offset + 1] * mCoefficients[coefficientPointer];
}
filtered[bufferPointer] = iAccumulator;
filtered[bufferPointer + 1] = qAccumulator;
}
return filtered;
}
public static void main(String[] args)
{
Random random = new Random();
int sampleSize = 2048;
float[] samples = new float[sampleSize];
for(int x = 0; x < samples.length; x++)
{
samples[x] = random.nextFloat() * 2.0f - 1.0f;
}
float[] coefficients = FilterFactory.getLowPass(1000, 250, 99, WindowType.BLACKMAN);
// ComplexFIRFilter2 legacyFilter = new ComplexFIRFilter2(coefficients);
ScalarComplexFIRFilter filter = new ScalarComplexFIRFilter(coefficients);
VectorComplexFIRFilter128Bit vectorFilter = new VectorComplexFIRFilter128Bit(coefficients);
double accumulator = 0.0d;
int iterations = 1_000_000;
long start = System.currentTimeMillis();
for(int x = 0; x < iterations; x++)
{
// float[] filtered = legacyFilter.filter(samples);
// float[] filtered = filter.filter(samples);
float[] filtered = vectorFilter.filter(samples);
// float[] vfiltered = vectorFilter.filter(samples);
accumulator += filtered[3];
}
// System.out.println("REG:" + Arrays.toString(filtered));
// System.out.println("VEC:" + Arrays.toString(vfiltered));
double elapsed = System.currentTimeMillis() - start;
DecimalFormat df = new DecimalFormat("0.000");
System.out.println("Accumulator: " + accumulator);
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
}
}

View File

@@ -0,0 +1,124 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.complex;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorMask;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.lang3.ArrayUtils;
/**
* Finite Impulse Response (FIR) filter for filtering float sample arrays.
* Note: this filter uses Project Panama SIMD instructions available in JDK 17+
*/
public class VectorComplexFIRFilter128Bit implements IComplexFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
private static final VectorMask<Float> I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES);
private static final VectorMask<Float> Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES);
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public VectorComplexFIRFilter128Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < (coefficients.length * 2))
{
arrayLength += VECTOR_SPECIES.length();
}
mCoefficients = new float[arrayLength];
//Copy the coefficients with each tap duplicated for complex filtering
ArrayUtils.reverse(coefficients);
for(int x = 0; x < coefficients.length; x++)
{
mCoefficients[2 * x] = coefficients[x];
mCoefficients[2 * x + 1] = coefficients[x];
}
//Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2);
//Set to non-null and we'll resize on the first buffer.
mBuffer = new float[mCoefficients.length];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array to end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK);
filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK);
}
return filtered;
}
}

View File

@@ -0,0 +1,124 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.complex;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorMask;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.lang3.ArrayUtils;
/**
* Finite Impulse Response (FIR) filter for filtering float sample arrays.
* Note: this filter uses Project Panama SIMD instructions available in JDK 17+
*/
public class VectorComplexFIRFilter64Bit implements IComplexFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
private static final VectorMask<Float> I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES);
private static final VectorMask<Float> Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES);
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public VectorComplexFIRFilter64Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < (coefficients.length * 2))
{
arrayLength += VECTOR_SPECIES.length();
}
mCoefficients = new float[arrayLength];
//Copy the coefficients with each tap duplicated for complex filtering
ArrayUtils.reverse(coefficients);
for(int x = 0; x < coefficients.length; x++)
{
mCoefficients[2 * x] = coefficients[x];
mCoefficients[2 * x + 1] = coefficients[x];
}
//Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2);
//Set to non-null and we'll resize on the first buffer.
mBuffer = new float[mCoefficients.length];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array to end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK);
filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK);
}
return filtered;
}
}

View File

@@ -0,0 +1,124 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.complex;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorMask;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.lang3.ArrayUtils;
/**
* Finite Impulse Response (FIR) filter for filtering float sample arrays.
* Note: this filter uses Project Panama SIMD instructions available in JDK 17+
*/
public class VectorComplexFIRFilterDefaultBit implements IComplexFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
private static final VectorMask<Float> I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES);
private static final VectorMask<Float> Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES);
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public VectorComplexFIRFilterDefaultBit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < (coefficients.length * 2))
{
arrayLength += VECTOR_SPECIES.length();
}
mCoefficients = new float[arrayLength];
//Copy the coefficients with each tap duplicated for complex filtering
ArrayUtils.reverse(coefficients);
for(int x = 0; x < coefficients.length; x++)
{
mCoefficients[2 * x] = coefficients[x];
mCoefficients[2 * x + 1] = coefficients[x];
}
//Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2);
//Set to non-null and we'll resize on the first buffer.
mBuffer = new float[mCoefficients.length];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array to end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK);
filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK);
}
return filtered;
}
}

View File

@@ -0,0 +1,28 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.real;
/**
* Interface for a filter for float array sample buffers
*/
public interface IRealFilter
{
float[] filter(float[] samples);
}

View File

@@ -0,0 +1,135 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.real;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.window.WindowType;
import org.apache.commons.lang3.ArrayUtils;
import java.text.DecimalFormat;
import java.util.Random;
/**
* Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays.
*
* Note: filtering operations in this class are structured to leverage SIMD processor intrinsics when
* available to the Java runtime.
*/
public class RealFIRFilter implements IRealFilter
{
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public RealFIRFilter(float[] coefficients)
{
//Reverse the order of the coefficients.
mCoefficients = coefficients;
ArrayUtils.reverse(mCoefficients);
mBufferOverlap = mCoefficients.length - 1;
//We'll resize this later when we get the first sample buffer. For now, make it non-null.
mBuffer = new float[mCoefficients.length];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
{
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer++)
{
filtered[bufferPointer] += mBuffer[bufferPointer + coefficientPointer] * mCoefficients[coefficientPointer];
}
}
return filtered;
}
public static void main(String[] args)
{
Random random = new Random();
int sampleSize = 2048;
float[] samples = new float[sampleSize];
for(int x = 0; x < samples.length; x++)
{
samples[x] = random.nextFloat() * 2.0f - 1.0f;
}
float[] coefficients = FilterFactory.getLowPass(1000, 250, 99, WindowType.BLACKMAN);
RealFIRFilter filter = new RealFIRFilter(coefficients);
VectorRealFIRFilter256Bit vectorFilter = new VectorRealFIRFilter256Bit(coefficients);
double accumulator = 0.0d;
int iterations = 1_000_000;
long start = System.currentTimeMillis();
for(int x = 0; x < iterations; x++)
{
float[] filtered = filter.filter(samples);
// float[] filtered = vectorFilter.filter(samples);
// float[] vfiltered = vectorFilter.filter(samples);
accumulator += filtered[3];
}
// System.out.println("REG:" + Arrays.toString(filtered));
// System.out.println("VEC:" + Arrays.toString(vfiltered));
double elapsed = System.currentTimeMillis() - start;
DecimalFormat df = new DecimalFormat("0.000");
System.out.println("Accumulator: " + accumulator);
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
}
}

View File

@@ -1,132 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.filter.fir.real;
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays.
*
* Note: filtering operations in this class are structured to leverage SIMD processor intrinsics when
* available to the Java runtime.
*/
public class RealFIRFilter2 extends FIRFilter
{
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealFIRFilter2");
private float[] mData;
private float[] mCoefficients;
private float mGain;
private float mAccumulator;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
* @param gain value to apply to the filtered output. Use 1.0f for no gain
*/
public RealFIRFilter2(float[] coefficients, float gain)
{
mGain = gain;
mCoefficients = coefficients;
mData = new float[coefficients.length];
}
/**
* Float sample FIR filter base class that uses a default gain value of 1.0f.
*
* @param coefficients - filter coefficients in normal order.
*/
public RealFIRFilter2(float[] coefficients)
{
this(coefficients, 1.0f);
}
/**
* Disposes this filter to prepare for garbage collection.
*/
@Override
public void dispose()
{
mCoefficients = null;
mData = null;
}
/**
* Filters the sample argument. Loads the sample into the internal data buffer and performs
* convolution between the sample buffer and the filter coefficients.
*
* @param sample to load
* @return filtered value
*/
public float filter(float sample)
{
//Use array copy to leverage SIMD intrinsics
System.arraycopy(mData, 0, mData, 1, mData.length - 1);
mData[0] = sample;
mAccumulator = 0.0f;
//Use vector dot product to leverage SIMD intrinsics
for(int x = 0; x < mCoefficients.length; x++)
{
mAccumulator = Math.fma(mData[x], mCoefficients[x], mAccumulator);
}
/* Apply gain and return the filtered value */
mAccumulator *= mGain;
return mAccumulator;
}
/**
* Current filtered output value for the filter after the filter() method has been invoked.
*/
public float currentValue()
{
return mAccumulator;
}
/**
* Filters the samples contained in the unfilteredBuffer and returns a new reusable buffer with the
* filtered samples.
*
* Note: user count on the returned (new) buffer is set to one and the user count is decremented on
* the unfiltered buffer argument.
*
* @param unfilteredBuffer containing a sample array to be filtered
* @return a new reusable buffer with the filtered samples.
*/
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
float[] unfilteredSamples = unfilteredBuffer.getSamples();
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
float[] filteredSamples = filteredBuffer.getSamples();
for(int x = 0; x < unfilteredSamples.length; x++)
{
filteredSamples[x] = filter(unfilteredSamples[x]);
}
unfilteredBuffer.decrementUserCount();
return filteredBuffer;
}
}

View File

@@ -0,0 +1,114 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.real;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.lang3.ArrayUtils;
/**
* Finite Impulse Response (FIR) filter for filtering float sample arrays.
* Note: this filter uses Project Panama SIMD instructions available in JDK 17+
*/
public class VectorRealFIRFilter128Bit implements IRealFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public VectorRealFIRFilter128Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < (coefficients.length))
{
arrayLength += VECTOR_SPECIES.length();
}
//Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer
ArrayUtils.reverse(coefficients);
mCoefficients = new float[arrayLength];
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
//We'll resize this later when we get the first sample buffer. For now, make it non-null.
mBuffer = new float[arrayLength];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array to end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,114 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.real;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.lang3.ArrayUtils;
/**
* Finite Impulse Response (FIR) filter for filtering float sample arrays.
* Note: this filter uses Project Panama SIMD instructions available in JDK 17+
*/
public class VectorRealFIRFilter256Bit implements IRealFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public VectorRealFIRFilter256Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < (coefficients.length))
{
arrayLength += VECTOR_SPECIES.length();
}
//Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer
ArrayUtils.reverse(coefficients);
mCoefficients = new float[arrayLength];
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
//We'll resize this later when we get the first sample buffer. For now, make it non-null.
mBuffer = new float[arrayLength];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array to end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,114 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.real;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.lang3.ArrayUtils;
/**
* Finite Impulse Response (FIR) filter for filtering float sample arrays.
* Note: this filter uses Project Panama SIMD instructions available in JDK 17+
*/
public class VectorRealFIRFilter512Bit implements IRealFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public VectorRealFIRFilter512Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < (coefficients.length))
{
arrayLength += VECTOR_SPECIES.length();
}
//Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer
ArrayUtils.reverse(coefficients);
mCoefficients = new float[arrayLength];
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
//We'll resize this later when we get the first sample buffer. For now, make it non-null.
mBuffer = new float[arrayLength];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array to end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,114 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.real;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.lang3.ArrayUtils;
/**
* Finite Impulse Response (FIR) filter for filtering float sample arrays.
* Note: this filter uses Project Panama SIMD instructions available in JDK 17+
*/
public class VectorRealFIRFilter64Bit implements IRealFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public VectorRealFIRFilter64Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < (coefficients.length))
{
arrayLength += VECTOR_SPECIES.length();
}
//Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer
ArrayUtils.reverse(coefficients);
mCoefficients = new float[arrayLength];
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
//We'll resize this later when we get the first sample buffer. For now, make it non-null.
mBuffer = new float[arrayLength];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array to end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,114 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.fir.real;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.lang3.ArrayUtils;
/**
* Finite Impulse Response (FIR) filter for filtering float sample arrays.
* Note: this filter uses Project Panama SIMD instructions available in JDK 17+
*/
public class VectorRealFIRFilterDefaultBit implements IRealFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
private float[] mBuffer;
private float[] mCoefficients;
private int mBufferOverlap;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients in normal order.
*/
public VectorRealFIRFilterDefaultBit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < (coefficients.length))
{
arrayLength += VECTOR_SPECIES.length();
}
//Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer
ArrayUtils.reverse(coefficients);
mCoefficients = new float[arrayLength];
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
//We'll resize this later when we get the first sample buffer. For now, make it non-null.
mBuffer = new float[arrayLength];
}
/**
* Filters the sample array
* @param samples to filter
* @return filtered samples
*/
public float[] filter(float[] samples)
{
int bufferLength = samples.length + mBufferOverlap;
//Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size
if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array to end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,160 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.dsp.window.WindowType;
import java.text.DecimalFormat;
import java.util.Random;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when
* supported by the host CPU.
*
* Note: this class is structured to process an entire float array, versus processing one sample at a time from the
* array.
*/
public class RealHalfBandDecimationFilter implements IRealDecimationFilter
{
private static final float CENTER_COEFFICIENT = 0.5f;
private float[] mCoefficients;
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public RealHalfBandDecimationFilter(float[] coefficients)
{
if((coefficients.length + 1) % 4 != 0)
{
throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]");
}
mCoefficients = coefficients;
mBufferOverlap = mCoefficients.length - 1;
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
float accumulator = 0.0f;
int half = mBufferOverlap / 2;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = 0.0f;
for(int coefficientPointer = 0; coefficientPointer < half; coefficientPointer += 2)
{
//Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by
//one of the coefficients to achieve the same effect.
accumulator += mCoefficients[coefficientPointer] *
(mBuffer[bufferPointer + coefficientPointer] +
mBuffer[bufferPointer + (mBufferOverlap - coefficientPointer)]);
}
accumulator += mBuffer[bufferPointer + half] * CENTER_COEFFICIENT;
filtered[bufferPointer / 2] = accumulator;
}
return filtered;
}
public static void main(String[] args)
{
Random random = new Random();
int sampleSize = 2048;
float[] samples = new float[sampleSize];
for(int x = 0; x < samples.length; x++)
{
samples[x] = random.nextFloat() * 2.0f - 1.0f;
}
float[] coefficients = FilterFactory.getHalfBand(63, WindowType.BLACKMAN);
// RealHalfBandDecimationFilter filter = new RealHalfBandDecimationFilter(coefficients);
// VectorRealHalfBandDecimationFilter63Tap64Bit vectorFilter64 = new VectorRealHalfBandDecimationFilter63Tap64Bit(coefficients);
// VectorRealHalfBandDecimationFilter63Tap128Bit vectorFilter128 = new VectorRealHalfBandDecimationFilter63Tap128Bit(coefficients);
// VectorRealHalfBandDecimationFilter63Tap256Bit vectorFilter256 = new VectorRealHalfBandDecimationFilter63Tap256Bit(coefficients);
// VectorRealHalfBandDecimationFilter23Tap512Bit vectorFilter512 = new VectorRealHalfBandDecimationFilter23Tap512Bit(coefficients);
VectorRealHalfBandDecimationFilter256Bit vectorFilter = new VectorRealHalfBandDecimationFilter256Bit(coefficients);
double accumulator = 0.0d;
int iterations = 10_000_000;
long start = System.currentTimeMillis();
for(int x = 0; x < iterations; x++)
{
// float[] filtered = filter.decimateReal(samples);
// float[] filtered = vectorFilter256.decimateReal(samples);
float[] filtered = vectorFilter.decimateReal(samples);
// float[] vfiltered = vectorFilterGeneric.decimateReal(samples);
accumulator += filtered[3];
}
// System.out.println("REG:" + Arrays.toString(filtered));
// System.out.println("VEC:" + Arrays.toString(vfiltered));
double elapsed = System.currentTimeMillis() - start;
DecimalFormat df = new DecimalFormat("0.000");
System.out.println("Accumulator: " + accumulator);
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
}
}

View File

@@ -0,0 +1,115 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 11-tap half band filter running on a CPU that supports 128-bit/4-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter11Tap128Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
private static final int COEFFICIENTS_LENGTH = 11;
private float[] mCoefficients = new float[12];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter11Tap128Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4);
FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector product1, product2, product3;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4));
product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8));
filtered[bufferPointer / 2] = product1.add(product2).add(product3).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,112 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 11-tap half band filter running on a CPU that supports 256-bit/8-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter11Tap256Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
private static final int COEFFICIENTS_LENGTH = 11;
private float[] mCoefficients = new float[16];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter11Tap256Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector product1, product2;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 3));
filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2021 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -16,29 +16,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.halfband.real;
package io.github.dsheirer.dsp.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when
* supported by the host CPU.
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* Note: this class is structured to process an entire float array, versus processing one sample at a time from the
* array.
* This filter is optimized for an 11-tap half band filter running on a CPU that supports 512-bit/16-lane floating
* point SIMD instructions.
*/
public class RealHalfBandDecimationFilter implements IRealDecimationFilter
public class VectorRealHalfBandDecimationFilter11Tap512Bit implements IRealDecimationFilter
{
private static final float CENTER_COEFFICIENT = 0.5f;
private float[] mCoefficients;
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private static final int COEFFICIENTS_LENGTH = 11;
private float[] mCoefficients = new float[16];
private float[] mBuffer;
private float mAccumulator;
private int mCoefficientPointer;
private int mCoefficientsLengthMinus1;
private int mBufferPointer;
private int mHalf;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
@@ -46,16 +47,22 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public RealHalfBandDecimationFilter(float[] coefficients)
public VectorRealHalfBandDecimationFilter11Tap512Bit(float[] coefficients)
{
if((coefficients.length + 1) % 4 != 0)
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]");
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
mCoefficients = coefficients;
mCoefficientsLengthMinus1 = mCoefficients.length - 1;
mHalf = mCoefficientsLengthMinus1 / 2;
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
@@ -65,7 +72,7 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mCoefficientsLengthMinus1;
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
@@ -75,36 +82,28 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mCoefficientsLengthMinus1, temp, 0, mCoefficientsLengthMinus1);
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mCoefficientsLengthMinus1);
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mCoefficientsLengthMinus1, samples.length);
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
for(mBufferPointer = 0; mBufferPointer < samples.length; mBufferPointer += 2)
FloatVector filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector product;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
mAccumulator = 0.0f;
for(mCoefficientPointer = 0; mCoefficientPointer < mHalf; mCoefficientPointer += 2)
{
//Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by
//one of the coefficients to achieve the same effect.
mAccumulator += mCoefficients[mCoefficientPointer] *
(mBuffer[mBufferPointer + mCoefficientPointer] +
mBuffer[mBufferPointer + (mCoefficientsLengthMinus1 - mCoefficientPointer)]);
}
mAccumulator += mBuffer[mBufferPointer + mHalf] * CENTER_COEFFICIENT;
filtered[mBufferPointer / 2] = mAccumulator;
product = filter.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
filtered[bufferPointer / 2] = product.reduceLanes(VectorOperators.ADD);
}
return filtered;

View File

@@ -0,0 +1,122 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 11-tap half band filter running on a CPU that supports 64-bit/2-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter11Tap64Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
private static final int COEFFICIENTS_LENGTH = 11;
private float[] mCoefficients = new float[12];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter11Tap64Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2);
FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4);
FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6);
FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10);
FloatVector product1, product2, product3, product4, product5, product6;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2));
product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4));
product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6));
product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8));
product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10));
filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5)
.add(product6).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,118 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter can be used for any tap length and uses SIMD 128-bit/4-lane instructions.
*/
public class VectorRealHalfBandDecimationFilter128Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
private float[] mCoefficients;
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter128Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < coefficients.length)
{
arrayLength += VECTOR_SPECIES.length();
}
mCoefficients = new float[arrayLength];
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,118 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 15-tap half band filter running on a CPU that supports 128-bit/4-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter15Tap128Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
private static final int COEFFICIENTS_LENGTH = 15;
private float[] mCoefficients = new float[16];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter15Tap128Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4);
FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12);
FloatVector product1, product2, product3, product4;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4));
product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8));
product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12));
filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,112 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 15-tap half band filter running on a CPU that supports 256-bit/8-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter15Tap256Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
private static final int COEFFICIENTS_LENGTH = 15;
private float[] mCoefficients = new float[16];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter15Tap256Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector product1, product2;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8));
filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,111 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 15-tap half band filter running on a CPU that supports 512-bit/16-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter15Tap512Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private static final int COEFFICIENTS_LENGTH = 15;
private float[] mCoefficients = new float[16];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter15Tap512Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector product;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product = filter.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
filtered[bufferPointer / 2] = product.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,127 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 15-tap half band filter running on a CPU that supports 64-bit/2-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter15Tap64Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
private static final int COEFFICIENTS_LENGTH = 15;
private float[] mCoefficients = new float[16];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter15Tap64Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2);
FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4);
FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6);
FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10);
FloatVector filter7 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12);
FloatVector filter8 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 14);
FloatVector product1, product2, product3, product4, product5, product6, product7, product8;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2));
product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4));
product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6));
product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8));
product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10));
product7 = filter7.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12));
product8 = filter8.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 14));
filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5)
.add(product6).add(product7).add(product8).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,123 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 23-tap half band filter running on a CPU that supports 128-bit/4-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter23Tap128Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
private static final int COEFFICIENTS_LENGTH = 23;
private float[] mCoefficients = new float[24];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter23Tap128Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4);
FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12);
FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16);
FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20);
FloatVector product1, product2, product3, product4, product5, product6;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4));
product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8));
product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12));
product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16));
product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 20));
filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5)
.add(product6).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,115 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 23-tap half band filter running on a CPU that supports 256-bit/8-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter23Tap256Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
private static final int COEFFICIENTS_LENGTH = 23;
private float[] mCoefficients = new float[24];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter23Tap256Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16);
FloatVector product1, product2, product3;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8));
product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16));
filtered[bufferPointer / 2] = product1.add(product2).add(product3).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,114 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 23-tap half band filter running on a CPU that supports 512-bit/16-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter23Tap512Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private static final int COEFFICIENTS_LENGTH = 23;
private float[] mCoefficients = new float[32];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter23Tap512Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
int laneMultiples = VECTOR_SPECIES.length() * (coefficients.length / VECTOR_SPECIES.length() + 1);
mBufferOverlap = Math.max(laneMultiples - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16);
FloatVector product1, product2;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16));
filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,137 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 23-tap half band filter running on a CPU that supports 64-bit/2-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter23Tap64Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
private static final int COEFFICIENTS_LENGTH = 23;
private float[] mCoefficients = new float[24];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter23Tap64Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0);
FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2);
FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4);
FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6);
FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8);
FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10);
FloatVector filter7 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12);
FloatVector filter8 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 14);
FloatVector filter9 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16);
FloatVector filter10 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 18);
FloatVector filter11 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20);
FloatVector filter12 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 22);
FloatVector product1, product2, product3, product4, product5, product6, product7, product8;
FloatVector product9, product10, product11, product12;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer));
product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2));
product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4));
product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6));
product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8));
product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10));
product7 = filter7.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12));
product8 = filter8.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 14));
product9 = filter9.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16));
product10 = filter10.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 18));
product11 = filter11.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 20));
product12 = filter12.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 22));
filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5)
.add(product6).add(product7).add(product8).add(product9).add(product10).add(product11)
.add(product12).reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,118 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter can be used for any tap length and uses SIMD 256-bit/8-lane instructions.
*/
public class VectorRealHalfBandDecimationFilter256Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
private float[] mCoefficients;
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter256Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < coefficients.length)
{
arrayLength += VECTOR_SPECIES.length();
}
mCoefficients = new float[arrayLength];
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,118 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter can be used for any tap length and uses SIMD 512-bit/16-lane instructions.
*/
public class VectorRealHalfBandDecimationFilter512Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private float[] mCoefficients;
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter512Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < coefficients.length)
{
arrayLength += VECTOR_SPECIES.length();
}
mCoefficients = new float[arrayLength];
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,117 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 63-tap half band filter running on a CPU that supports 128-bit/4-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter63Tap128Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
private static final int COEFFICIENTS_LENGTH = 63;
private float[] mCoefficients = new float[64];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter63Tap128Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,117 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 63-tap half band filter running on a CPU that supports 256-bit/8-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter63Tap256Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
private static final int COEFFICIENTS_LENGTH = 63;
private float[] mCoefficients = new float[64];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter63Tap256Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,117 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 63-tap half band filter running on a CPU that supports 512-bit/16-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter63Tap512Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private static final int COEFFICIENTS_LENGTH = 63;
private float[] mCoefficients = new float[64];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter63Tap512Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,117 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter is optimized for an 63-tap half band filter running on a CPU that supports 64-bit/2-lane floating
* point SIMD instructions.
*/
public class VectorRealHalfBandDecimationFilter63Tap64Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
private static final int COEFFICIENTS_LENGTH = 63;
private float[] mCoefficients = new float[64];
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter63Tap64Bit(float[] coefficients)
{
if(coefficients.length != COEFFICIENTS_LENGTH)
{
throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH +
" taps. You supplied a filter with " + coefficients.length + " taps.");
}
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,118 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter can be used for any tap length and uses SIMD 64-bit/2-lane instructions.
*/
public class VectorRealHalfBandDecimationFilter64Bit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
private float[] mCoefficients;
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilter64Bit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < coefficients.length)
{
arrayLength += VECTOR_SPECIES.length();
}
mCoefficients = new float[arrayLength];
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -0,0 +1,118 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband;
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter uses the Java Vector API for SIMD available in JDK 17+.
*
* This filter can be used for any combination of tap length and SIMD lane lengths.
*/
public class VectorRealHalfBandDecimationFilterDefaultBit implements IRealDecimationFilter
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
private float[] mCoefficients;
private float[] mBuffer;
private int mBufferOverlap;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public VectorRealHalfBandDecimationFilterDefaultBit(float[] coefficients)
{
VectorUtilities.checkSpecies(VECTOR_SPECIES);
//Size the coefficients array to a multiple of the vector species length, large enough to hold the taps;
int arrayLength = VECTOR_SPECIES.length();
while(arrayLength < coefficients.length)
{
arrayLength += VECTOR_SPECIES.length();
}
mCoefficients = new float[arrayLength];
//Copy the coefficients
System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length);
//Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1
//to ensure we don't get an index out of bounds exception when loading samples from the buffer.
mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1);
}
public float[] decimateReal(float[] samples)
{
if(samples.length % 2 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 2");
}
int bufferLength = samples.length + mBufferOverlap;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length);
float[] filtered = new float[samples.length / 2];
FloatVector accumulator, buffer, filter;
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2)
{
accumulator = FloatVector.zero(VECTOR_SPECIES);
for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length())
{
filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer);
buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer);
accumulator = filter.fma(buffer, accumulator);
}
filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD);
}
return filtered;
}
}

View File

@@ -1,137 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.filter.halfband.complex;
import io.github.dsheirer.dsp.filter.decimate.IComplexDecimationFilter;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
/**
* Complex half-band filter that processes samples on a per-array basis, versus a per-sample basis.
*/
public class ComplexHalfBandDecimationFilter implements IComplexDecimationFilter
{
private static final float CENTER_COEFFICIENT = 0.5f;
private float[] mCoefficients;
private float[] mBuffer;
private float mIAccumulator;
private float mQAccumulator;
private int mCoefficientPointer;
private int mCoefficientsLengthMinus2;
private int mBufferPointer;
private int mHalf;
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("half-band decimation filter");
/**
* Constructs a complex sample half-band decimate x2 filter using the specified filter coefficients.
* @param coefficients for the half-band filter where the middle coefficient is 0.5, and the even-numbered
* coefficients are non-zero and symmetrical, and the odd-numbered coefficients are all zero valued.
*/
public ComplexHalfBandDecimationFilter(float[] coefficients)
{
if((coefficients.length + 1) % 4 != 0)
{
throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and " +
"symmetrical where L = [x * 4 - 1]");
}
mCoefficients = new float[coefficients.length * 2];
for(int x = 0; x < coefficients.length; x++)
{
mCoefficients[2 * x] = coefficients[x];
mCoefficients[2 * x + 1] = coefficients[x];
}
mCoefficientsLengthMinus2 = mCoefficients.length - 2;
mHalf = mCoefficients.length / 2 - 1;
}
public float[] decimateComplex(float[] samples)
{
if(samples.length % 4 != 0)
{
throw new IllegalArgumentException("Samples array length must be an integer multiple of 4");
}
int bufferLength = samples.length + mCoefficientsLengthMinus2;
if(mBuffer == null)
{
mBuffer = new float[bufferLength];
}
else if(mBuffer.length != bufferLength)
{
float[] temp = new float[bufferLength];
//Move residual samples from end of old buffer to the beginning of the new temp buffer
System.arraycopy(mBuffer, mBuffer.length - mCoefficientsLengthMinus2, temp, 0, mCoefficientsLengthMinus2);
mBuffer = temp;
}
else
{
//Move residual samples from end of buffer to the beginning of the buffer
System.arraycopy(mBuffer, samples.length, mBuffer, 0, mCoefficientsLengthMinus2);
}
//Copy new sample array into end of buffer
System.arraycopy(samples, 0, mBuffer, mCoefficientsLengthMinus2, samples.length);
float[] filtered = new float[samples.length / 2];
for(mBufferPointer = 0; mBufferPointer < samples.length; mBufferPointer += 4)
{
mIAccumulator = 0.0f;
mQAccumulator = 0.0f;
for(mCoefficientPointer = 0; mCoefficientPointer < mHalf; mCoefficientPointer += 4)
{
//Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by
//one of the coefficients to achieve the same effect.
mIAccumulator += mCoefficients[mCoefficientPointer] *
(mBuffer[mBufferPointer + mCoefficientPointer] +
mBuffer[mBufferPointer + (mCoefficientsLengthMinus2 - mCoefficientPointer)]);
mQAccumulator += mCoefficients[mCoefficientPointer] *
(mBuffer[mBufferPointer + mCoefficientPointer + 1] +
mBuffer[mBufferPointer + (mCoefficientsLengthMinus2 - mCoefficientPointer) + 1]);
}
mIAccumulator += mBuffer[mBufferPointer + mHalf] * CENTER_COEFFICIENT;
mQAccumulator += mBuffer[mBufferPointer + mHalf + 1] * CENTER_COEFFICIENT;
filtered[mBufferPointer / 2] = mIAccumulator;
filtered[mBufferPointer / 2 + 1] = mQAccumulator;
}
return filtered;
}
/**
* Decimates the complex samples and returns a buffer of decimated samples.
* @param buffer to decimate
* @return decimated buffer.
*/
@Override
public ReusableComplexBuffer decimate(ReusableComplexBuffer buffer)
{
float[] decimated = decimateComplex(buffer.getSamples());
buffer.decrementUserCount();
return mReusableComplexBufferQueue.getBuffer(decimated, buffer.getTimestamp());
}
}

View File

@@ -1,111 +0,0 @@
/*
* *****************************************************************************
* 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.filter.halfband.complex;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter2;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
public class ComplexHalfBandFilter
{
private ReusableComplexBufferQueue mBufferQueue = new ReusableComplexBufferQueue("ComplexHalfBandFilter");
private HalfBandFilter2 mIFilter;
private HalfBandFilter2 mQFilter;
private float mResidualISample;
private float mResidualQSample;
private boolean mHasResidual;
private int mOutputBufferLength;
private float[] mSamples;
private int mSamplesPointer;
private float[] mFilteredSamples;
private int mFilteredSamplesPointer;
public ComplexHalfBandFilter(float[] coefficients)
{
mIFilter = new HalfBandFilter2(coefficients);
mQFilter = new HalfBandFilter2(coefficients);
}
public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer)
{
mSamples = originalBuffer.getSamples();
mSamplesPointer = 0;
mOutputBufferLength = (mHasResidual ? (mSamples.length + 2) : mSamples.length) / 4 * 2;
ReusableComplexBuffer filteredBuffer = mBufferQueue.getBuffer(mOutputBufferLength);
mFilteredSamples = filteredBuffer.getSamples();
mFilteredSamplesPointer = 0;
if(mHasResidual)
{
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mResidualISample, mSamples[mSamplesPointer++]);
mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mResidualQSample, mSamples[mSamplesPointer++]);
mHasResidual = false;
}
while(mSamplesPointer + 3 < mSamples.length)
{
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer], mSamples[mSamplesPointer + 2]);
mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mSamples[mSamplesPointer + 1], mSamples[mSamplesPointer + 3]);
mSamplesPointer += 4;
}
if(mSamplesPointer < mSamples.length)
{
mHasResidual = true;
mResidualISample = mSamples[mSamplesPointer++];
mResidualQSample = mSamples[mSamplesPointer];
}
originalBuffer.decrementUserCount();
return filteredBuffer;
}
public static void main(String[] args)
{
Oscillator oscillator = new Oscillator(1, 16);
ComplexHalfBandFilter filter = new ComplexHalfBandFilter(Filters.HALF_BAND_FILTER_27T.getCoefficients());
ReusableComplexBufferQueue bufferQueue = new ReusableComplexBufferQueue("Test");
ReusableComplexBuffer complexBuffer = bufferQueue.getBuffer(8);
oscillator.generateComplex(complexBuffer);
ReusableComplexBuffer filteredBuffer = filter.filter(complexBuffer);
filteredBuffer.decrementUserCount();
ReusableComplexBuffer complexBuffer2 = bufferQueue.getBuffer(10);
oscillator.generateComplex(complexBuffer2);
ReusableComplexBuffer filteredBuffer2 = filter.filter(complexBuffer2);
filteredBuffer2.decrementUserCount();
ReusableComplexBuffer complexBuffer3 = bufferQueue.getBuffer(8);
oscillator.generateComplex(complexBuffer3);
ReusableComplexBuffer filteredBuffer3 = filter.filter(complexBuffer3);
filteredBuffer3.decrementUserCount();
}
}

View File

@@ -1,190 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 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.filter.halfband.real;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.apache.commons.lang3.Validate;
public class HalfBandFilter
{
private float[] mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("HalfBandFilter");
/**
* Half-Band filter for processing real float samples.
*
* Takes advantage of the 0-valued FIR half-band coefficents inherent
* in the half-band filter and does not calculate those coefficients.
*
* This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample.
*
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public HalfBandFilter(float[] coefficients, float gain)
{
//TODO: update this to use SIMD structures with 2 float arrays and a toggle operation
mCoefficients = coefficients;
mGain = gain;
mBufferSize = mCoefficients.length;
mBuffer = new float[mBufferSize];
generateIndexMap(mCoefficients.length);
}
public HalfBandFilter(float[] coefficients, float gain, boolean decimate)
{
this(coefficients, gain);
throw new IllegalStateException("This filter must be updated to support non-decimate option");
}
public void dispose()
{
}
/**
* Inserts the sample into the buffer without calculating a filtered value
*/
public void insert(float sample)
{
mBuffer[mBufferPointer] = sample;
mBufferPointer++;
mBufferPointer = mBufferPointer % mBufferSize;
}
public ReusableFloatBuffer filter(ReusableFloatBuffer input)
{
float[] inputSamples = input.getSamples();
ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
float[] outputSamples = output.getSamples();
for(int x = 0; x < inputSamples.length; x++)
{
outputSamples[x] = filter(inputSamples[x]);
}
input.decrementUserCount();
return output;
}
/**
* Calculate the filtered value by applying the coefficients against the samples in mBuffer
*/
public float filter(float sample)
{
insert(sample);
float accumulator = 0;
//Start with the center tap value
accumulator += mCoefficients[mCenterCoefficient] * mBuffer[mIndexMap[mBufferPointer][mCenterCoefficientMapIndex]];
//For the remaining coefficients, add the symmetric samples, oldest and newest
//first, then multiply by the single coefficient
for(int x = 0; x < mCenterCoefficientMapIndex; x += 2)
{
accumulator += mCoefficients[x] * (mBuffer[mIndexMap[mBufferPointer][x]] + mBuffer[mIndexMap[mBufferPointer][x + 1]]);
}
return accumulator * mGain;
}
/**
* Creates an n X (n + 1 / 2) index map enabling quick access to the
* circular buffer samples.
*
* As the buffer shifts right with each subsequent sample, we have to move
* the index pointers with it, for efficient access of the samples.
*
* The first array index value in the index map corresponds to the current
* buffer pointer location.
*
* The second array index value points to the samples that should be
* multiplied by the coefficients as follows:
*
* 0 = center tap sample, to be multiplied by center coefficient
*
* 0 = sample( 1 )
* 1 = sample( size - 1 )
*
* Indexes 0 and 1 will be multiplied by coefficient( 0 ).
*
* Subsequent indexes 3, 4, etc, point to the oldest and newest samples that
* correspond to the matching ( 3 ) coefficient index.
*
* @param odd-sized number of filter taps (ie coefficients) and buffer
*/
private void generateIndexMap(int size)
{
//Ensure we have an odd size
Validate.isTrue(size % 2 == 1);
int mapWidth = ((size + 1) / 2) + 1;
//Set center tap index for coefficients array
mCenterCoefficient = (size - 1) / 2;
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
mIndexMap = new int[size][mapWidth];
//Setup the first row, buffer pointer index 0, as a starting point
for(int x = 0; x < mapWidth - 2; x += 2)
{
mIndexMap[0][x] = x;
mIndexMap[0][x + 1] = size - 1 - x;
}
//Place center tap index in last element
mIndexMap[0][mCenterCoefficientMapIndex] = mCenterCoefficient;
//For each subsequent row, increment the previous row's value by 1,
//subtracting size as needed, to keep the values between 0 and size - 1
for(int x = 1; x < size; x++)
{
for(int y = 0; y < mapWidth; y++)
{
mIndexMap[x][y] = mIndexMap[x - 1][y] + 1;
if(mIndexMap[x][y] >= size)
{
mIndexMap[x][y] -= size;
}
}
}
}
}

View File

@@ -1,132 +0,0 @@
/*
* *****************************************************************************
* 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.filter.halfband.real;
import io.github.dsheirer.buffer.FloatCircularBuffer;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import java.util.Random;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when
* supported by the host CPU.
*/
public class HalfBandFilter2
{
private static final float CENTER_COEFFICIENT = 0.5f;
private float[] mCoefficients;
private float[] mEvenSamples;
private float mAccumulator;
private int mPointer;
private FloatCircularBuffer mCircularBuffer;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
*/
public HalfBandFilter2(float[] coefficients)
{
if((coefficients.length + 1) % 4 != 0)
{
throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]");
}
int half = coefficients.length / 2 + 1;
mCircularBuffer = new FloatCircularBuffer(half / 2);
mCoefficients = new float[half];
mEvenSamples = new float[half];
//Use only the even coefficients since the odd coefficients are all zero-valued
for(int x = 0; x < coefficients.length; x += 2)
{
mCoefficients[x / 2] = coefficients[x];
}
}
public float filter(float sample1, float sample2)
{
//Structured for SIMD array copy optimization when supported by host CPU
System.arraycopy(mEvenSamples, 0, mEvenSamples, 1, mEvenSamples.length - 1);
mEvenSamples[0] = sample1;
mAccumulator = 0.0f;
//Structured for SIMD dot.product optimization when supported by host CPU
for(mPointer = 0; mPointer < mEvenSamples.length; mPointer++)
{
mAccumulator += (mEvenSamples[mPointer] * mCoefficients[mPointer]);
}
mAccumulator += (mCircularBuffer.getAndPut(sample2) * CENTER_COEFFICIENT);
return mAccumulator;
}
public float[] filter(float[] samples)
{
float[] filtered = new float[samples.length / 2];
for(int x = 0; x < samples.length; x += 2)
{
filtered[x / 2] = filter(samples[x], samples[x + 1]);
}
return filtered;
}
public static void main(String[] args)
{
System.out.println("Starting ...");
float[] taps = FilterFactory.getHalfBand(99, Window.WindowType.HAMMING);
HalfBandFilter2 halfband = new HalfBandFilter2(taps);
Random random = new Random();
float[] samples = new float[1024];
float[] filtered = new float[512];
for(int x = 0; x < samples.length; x++)
{
samples[x] = (2.0f * random.nextFloat()) - 1.0f;
}
System.out.println("Testing ...");
long current = System.currentTimeMillis();
int iterations = 10_000_000;
for(int x = 0; x < iterations; x++)
{
for(int y = 0; y < samples.length; y += 2)
{
filtered[y / 2] = halfband.filter(samples[y], samples[y + 1]);
}
}
long elapsed = System.currentTimeMillis() - current;
System.out.println("Elapsed: " + (elapsed / 1000d) + " seconds");
}
}

View File

@@ -1,246 +1,122 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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 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.
* 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/>
*
******************************************************************************/
* 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.filter.hilbert;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.sample.complex.ComplexSamples;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class HilbertTransform
/**
* Base Hilbert transform class.
*/
public abstract class HilbertTransform
{
private static final Logger mLog = LoggerFactory.getLogger(HilbertTransform.class);
private float[] mHilbertFilter;
private float[] mBuffer;
private int mBufferSize;
private int mBufferPointer;
private int[][] mIndexMap;
private int mMapHeight;
private int mCenterTapIndex;
private float mCenterCoefficent;
private boolean mInvertFlag = false;
/**
* Hilbert transform filter used for converting real-valued samples into
* complex valued samples using frequency translation (FS/4) and a half-band
* filter.
/* Half-band filter coefficients retrieved October 2015 from:
* https://github.com/airspy/host/libairspy/src/filters.h
*
* This filter uses a circular sample buffer and a pre-calculated
* index map to correctly map the filter coefficients to the circular delay
* buffer contents as each new sample is added to the buffer and processed.
* Copyright (C) 2014, Youssef Touil <youssef@airspy.com>
*
* Half-band filter coefficients used in this filter must be of length N
* where (N + 1) is a multiple of 4. This filter uses a pre-defined half
* band filter with 47 coefficients.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* This transform process is described in Understanding Digital Signal
* Processing, Lyons, 3e, 2011, sections 13.1.2 and 13.1.3 (p 674-678) and
* implemented as described in Section 13.37.1 and 13.37.2 (p 802-804)
*/
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
public static final float[] HALF_BAND_FILTER_47_TAP = new float[]
{
-0.000998606272947510f, 0.0f, 0.001695637278417295f, 0.0f,
-0.003054430179754289f, 0.0f, 0.005055504379767936f, 0.0f,
-0.007901319195893647f, 0.0f, 0.011873357051047719f, 0.0f,
-0.017411159379930066f, 0.0f, 0.025304817427568772f, 0.0f,
-0.037225225204559217f, 0.0f, 0.057533286997004301f, 0.0f,
-0.102327462004259350f, 0.0f, 0.317034472508947400f, 0.5f,
0.317034472508947400f, 0.0f, -0.102327462004259350f, 0.0f,
0.057533286997004301f, 0.0f, -0.037225225204559217f, 0.0f,
0.025304817427568772f, 0.0f, -0.017411159379930066f, 0.0f,
0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f,
0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f,
0.001695637278417295f, 0.0f, -0.000998606272947510f
};
protected float[] mCoefficients;
protected int mIOverlap = 11;
protected int mQOverlap = 23;
protected float[] mIBuffer = new float[mIOverlap];
protected float[] mQBuffer = new float[mQOverlap];
public HilbertTransform()
{
convertHalfBandToHilbert(Filters.HALF_BAND_FILTER_47T.getCoefficients());
mBufferSize = mHilbertFilter.length + 1;
mBuffer = new float[mBufferSize];
mCenterCoefficent = mHilbertFilter[mHilbertFilter.length / 2];
generateIndexMap(mHilbertFilter.length);
mCoefficients = convertHalfBandToHilbert(HALF_BAND_FILTER_47_TAP);
}
/**
* Filters the continuous real-sample stream by treating the stream as a
* set of interleaved values and applying two hilbert filters against each
* interleaved set (I & Q). Frequency translation has already been
* pre-applied to the hilbert filter coefficients.
*
* Filtering of the inphase component is a simple multiply by the center
* coefficient (0.5) since all of the other coefficients are zero-valued.
*
* Filtering of the quadrature component uses a folded FIR structure and
* expects the hilbert coefficients to be symmetric with all negative valued
* coefficients below the center coefficient and all positive-valued coefficients
* above the center coefficient. So, convolution only applies the filter
* coefficient against the difference of the two samples.
*
* ( coefficient * value1 ) + (-coefficient * value2 ) = coefficient * ( value1 - value2 )
*
* Performs frequency translation by FS/2 on the final filtered values by
* applying a sequence of 1,-1 (sign change) to each I/Q output sample.
* Converts the real sample array to complex samples as half the sample rate
* @param samples to convert
* @return converted samples
*/
public float[] filter(float[] samples)
{
float accumulator;
for(int y = 0; y < samples.length; y += 2)
{
insert(samples[y]);
insert(samples[y + 1]);
accumulator = 0.0f;
int index = mBufferPointer / 2;
for(int x = 0; x < mHilbertFilter.length / 2; x += 2)
{
accumulator += mHilbertFilter[x] *
(mBuffer[mIndexMap[index][x + 1]] -
mBuffer[mIndexMap[index][x]]);
}
//Perform FS/2 frequency translation on the final filtered values
if(mInvertFlag)
{
//inphase
samples[y] = -(mBuffer[mIndexMap[index][mCenterTapIndex]]);
//quadrature
samples[y + 1] = -accumulator;
}
else
{
//inphase
samples[y] = mBuffer[mIndexMap[index][mCenterTapIndex]];
//quadrature
samples[y + 1] = accumulator;
}
mInvertFlag = !mInvertFlag;
}
return samples;
}
public abstract ComplexSamples filter(float[] samples);
/**
* Inserts the sample into the circular buffer, overwriting the oldest value
* Converts the half-band filter coefficients for use as hilbert transform filter coefficients. Sets all
* even-numbered coefficients left of center tap to negative and all even-numbered coefficients right of
* center tap to positive and sets the center tap from 0.5 to 0.0, resulting in an overall 2x reduction
* in gain. We throw away the odd-number coefficients because they are 0.0 or 0.5 (center).
*
* Since we need the filter to be reversed for implementation and since the filter is symmetrical, we
* flip it during assignment and make the left of center positive and the right of center negative.
*
* Note: we apply a 2.0 gain to the coefficients to compensate for the loss, or zero-ing of the original
* 0.5 center coefficient.
*
* As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, sections 13.1.2 and
* 13.1.3 (p 674-678) and implemented as described in Section 13.37.1 and 13.37.2 (p 802-804)
*/
private void insert(float sample)
public static float[] convertHalfBandToHilbert(float[] coefficients)
{
mBuffer[mBufferPointer++] = sample;
mBufferPointer = mBufferPointer % mBufferSize;
}
/**
* Creates an index map to support efficient lookup of sample indexes from
* the circular buffer.
*
* The first index of the map corresponds to the current buffer pointer
* setting.
*
* The second index is structured in sample pairs to align with
* the hilbert filter coefficients. For example, the first iteration of
* the filter would use filter coefficient 0 and the index map values 0 and
* 1 would be pointing to samples 46 and 0.
*
* The final element of each index set is the index of the center sample.
*/
private void generateIndexMap(int size)
{
mMapHeight = size / 2 + 1;
int mapWidth = mMapHeight + 1;
mIndexMap = new int[mMapHeight][mapWidth];
//Setup the first row with buffer pointer index 0 as a starting point
for(int x = 0; x < mapWidth - 1; x += 2)
{
mIndexMap[0][x] = size - 1 - x;
mIndexMap[0][x + 1] = x;
}
//Place the center index at the end of the array
mCenterTapIndex = mapWidth - 1;
mIndexMap[0][mCenterTapIndex] = size / 2;
//For each subsequent row, increment the previous row's value by 2,
//wrapping as needed, to keep the values between 0 and size - 1
for(int x = 1; x < mMapHeight; x++)
{
for(int y = 0; y < mapWidth; y++)
{
mIndexMap[x][y] = mIndexMap[x - 1][y] + 2;
if(mIndexMap[x][y] >= size)
{
mIndexMap[x][y] -= size + 1;
//Special handling for center tap wrap around
if(y == mCenterTapIndex && mIndexMap[x][y] < 0)
{
mIndexMap[x][y] = size;
}
}
}
}
}
/**
* Logs the index map contents
*/
public void logIndexMap()
{
for(int[] indexSet : mIndexMap)
{
mLog.debug("Row:" + Arrays.toString(indexSet));
}
}
/**
* Converts the half-band filter coefficients for use as hilbert transform
* filter coefficients. Sets all even-numbered coefficients left of center
* to negative and all even-numbered coefficients right of center to positive
* which applies a FS/4 frequency translation to the coefficients.
*
* Note: even though the coefficients above the center coefficient are being
* defined, they are unused during the convolution process since we're using
* a folded FIR structure to exploit the symmetric nature of the half-band
* filter coefficients. The polarity difference between the upper and lower
* halves is accounted for in the filter() method.
*
* Note: we apply a 2.0 gain to the coefficients to compensate for the loss
* of splitting the signal via two filters.
*/
private void convertHalfBandToHilbert(float[] coefficients)
{
mHilbertFilter = new float[coefficients.length];
float[] hilbert = new float[coefficients.length / 2 + 1];
int middle = coefficients.length / 2;
for(int x = 0; x < coefficients.length; x++)
for(int x = 0; x < coefficients.length; x += 2)
{
if(x < middle)
{
mHilbertFilter[x] = 2.0f * -FastMath.abs(coefficients[x]);
hilbert[x / 2] = -2.0f * FastMath.abs(coefficients[x]);
}
else if(x > middle)
{
mHilbertFilter[x] = 2.0f * FastMath.abs(coefficients[x]);
}
else
{
mHilbertFilter[x] = 2.0f * coefficients[x];
hilbert[x / 2] = 2.0f * FastMath.abs(coefficients[x]);
}
}
return hilbert;
}
}

Some files were not shown because too many files have changed in this diff Show More