diff --git a/src/buffer/FloatCircularBuffer.java b/src/buffer/FloatCircularBuffer.java
index ccb749e1..4f6d600a 100644
--- a/src/buffer/FloatCircularBuffer.java
+++ b/src/buffer/FloatCircularBuffer.java
@@ -1,19 +1,20 @@
/*******************************************************************************
- * 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
+ * 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
+ *
******************************************************************************/
package buffer;
@@ -21,98 +22,105 @@ import java.util.Arrays;
/**
* Circular sample buffer - allocates a buffer and stores samples in a circular
- * fashion, overwriting older samples with newly arrived samples. Initially
- * fills buffer with 0-valued samples
- *
- * Can be used as a delay-type buffer, to delay samples by the 'size' amount
- * @author denny
+ * fashion, overwriting older samples with newly arrived samples.
*
+ * Can be used as a delay-type buffer, to delay samples by the 'size' amount
*/
public class FloatCircularBuffer
{
- float[] mBuffer;
- int mBufferPointer = 0;
-
- public FloatCircularBuffer( int size )
- {
- mBuffer = new float[ size ];
-
- Arrays.fill( mBuffer, (float)0.0 );
- }
-
- public int getSize()
- {
- return mBuffer.length;
- }
-
- public float[] getBuffer()
- {
- return mBuffer;
- }
+ float[] mBuffer;
+ int mBufferPointer = 0;
- /**
- * Puts the new value into the buffer and returns the oldest buffer value
- * that it replaced
- * @param newValue
- * @return
- */
- public float get( float newValue )
- {
- float oldestSample = mBuffer[ mBufferPointer ];
+ public FloatCircularBuffer(int size)
+ {
+ mBuffer = new float[size];
+ }
- mBuffer[ mBufferPointer ] = newValue;
-
- mBufferPointer++;
-
- if( mBufferPointer >= mBuffer.length )
- {
- mBufferPointer = 0;
- }
-
- return oldestSample;
- }
+ public int getSize()
+ {
+ return mBuffer.length;
+ }
- /**
- * Convenience method to put a new value in the buffer, while discarding
- * the old value
- */
- public void put( float newValue )
- {
- get( newValue );
- }
+ public float[] getBuffer()
+ {
+ return mBuffer;
+ }
- /**
- * Returns an array of arraySize, loaded with sampleCount of the
- * oldest samples, loaded into the array starting at index 0.
- *
- * This is useful for fetching an array filled half way with samples to
- * use in FFT processing.
- * @param arraySize - size of the array (must be larger than sampleCount)
- * @param sampleCount - number of the oldest samples in the buffer to load
- * @return
- */
- public float[] get( int arraySize, int sampleCount )
- {
- float[] samples = new float[ arraySize ];
+ /**
+ * Puts the new value into the buffer and returns the oldest buffer value
+ * that it replaced
+ *
+ * @param newValue
+ * @return
+ */
+ public float putAndGet(float newValue)
+ {
+ float oldestSample = mBuffer[mBufferPointer];
- if( arraySize > sampleCount )
- {
- int tempCounter = mBufferPointer;
+ put(newValue);
- for( int x = 0; x < sampleCount; x++ )
- {
- samples[ x ] = mBuffer[ tempCounter ];
-
- tempCounter++;
-
- if( tempCounter > mBuffer.length )
- {
- tempCounter = 0;
- }
- }
- }
-
- return samples;
- }
-
+ return oldestSample;
+ }
+
+ /**
+ * Adds the new value to this buffer.
+ */
+ public void put(float newValue)
+ {
+ mBuffer[mBufferPointer] = newValue;
+
+ mBufferPointer++;
+
+ if(mBufferPointer >= mBuffer.length)
+ {
+ mBufferPointer = 0;
+ }
+ }
+
+ /**
+ * Returns the sample at the specified index, where index 0 is the oldest sample and index (length - 1) is the
+ * newest sample.
+ * @param index in range of 0 to length-1
+ * @return value at indexed position
+ */
+
+ public float get(int index)
+ {
+ if(index < mBuffer.length)
+ {
+ int pointer = mBufferPointer + index;
+
+ if(pointer >= mBuffer.length)
+ {
+ pointer -= mBuffer.length;
+ }
+
+ return mBuffer[pointer];
+ }
+ else
+ {
+ throw new ArrayIndexOutOfBoundsException("Index [" + index + "] is not valid for the buffer size of [" +
+ mBuffer.length + "]");
+ }
+ }
+
+ /**
+ * Returns the contents of the circular buffer unwrapped where index 0 is the oldest sample and index length-1
+ * is the newest sample.
+ *
+ * @return array of samples
+ */
+ public float[] get()
+ {
+ float[] samples = new float[mBuffer.length];
+
+ System.arraycopy(mBuffer, mBufferPointer, samples, 0, mBuffer.length - mBufferPointer);
+
+ if(mBufferPointer != 0)
+ {
+ System.arraycopy(mBuffer, 0, samples, mBuffer.length - mBufferPointer, mBufferPointer);
+ }
+
+ return samples;
+ }
}
diff --git a/src/buffer/ReverseFloatCircularBuffer.java b/src/buffer/ReverseFloatCircularBuffer.java
new file mode 100644
index 00000000..3c3adb40
--- /dev/null
+++ b/src/buffer/ReverseFloatCircularBuffer.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * 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
+ *
+ ******************************************************************************/
+package buffer;
+
+/**
+ * Circular sample buffer - allocates a buffer and stores samples in a circular
+ * fashion, overwriting older samples with newly arrived samples.
+ */
+public class ReverseFloatCircularBuffer
+{
+ private float[] mBuffer;
+ private int mBufferPointer = 0;
+
+ public ReverseFloatCircularBuffer(int size)
+ {
+ mBuffer = new float[size];
+ mBufferPointer = mBuffer.length - 1;
+ }
+
+ /**
+ * Size of the buffer
+ * @return
+ */
+ public int getSize()
+ {
+ return mBuffer.length;
+ }
+
+ public float[] getBuffer()
+ {
+ return mBuffer;
+ }
+
+ /**
+ * Adds the new value to this buffer.
+ */
+ public void put(float newValue)
+ {
+ mBuffer[mBufferPointer--] = newValue;
+
+ if(mBufferPointer < 0)
+ {
+ mBufferPointer = mBuffer.length - 1;
+ }
+ }
+
+ /**
+ * Returns the sample at the specified index, where index 0 is the newest sample and index (length - 1) is the
+ * oldest sample.
+ * @param index in range of 0 to length-1
+ * @return value at indexed position
+ */
+
+ public float get(int index)
+ {
+ if(index < mBuffer.length)
+ {
+ int pointer = mBufferPointer + 1 + index;
+
+ if(pointer >= mBuffer.length)
+ {
+ pointer -= mBuffer.length;
+ }
+
+ return mBuffer[pointer];
+ }
+ else
+ {
+ throw new ArrayIndexOutOfBoundsException("Index [" + index + "] is not valid for the buffer size of [" +
+ mBuffer.length + "]");
+ }
+ }
+}
diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java
index 00c1affd..b6064c62 100644
--- a/src/dsp/filter/channelizer/ChannelizerViewer.java
+++ b/src/dsp/filter/channelizer/ChannelizerViewer.java
@@ -20,6 +20,7 @@ package dsp.filter.channelizer;
import dsp.filter.FilterFactory;
import dsp.filter.Window;
+import dsp.filter.correction.IQCorrectionFilter;
import dsp.filter.design.FilterDesignException;
import dsp.mixer.LowPhaseNoiseOscillator;
import net.miginfocom.swing.MigLayout;
@@ -59,6 +60,7 @@ public class ChannelizerViewer extends JFrame
private LowPhaseNoiseOscillator mOscillator;
private int mChannelCount;
private int mChannelsPerRow;
+ private int mFilterTapsPerChannel;
private int mSampleRate;
private int mToneFrequency;
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
@@ -70,10 +72,12 @@ public class ChannelizerViewer extends JFrame
* @param channelCount
* @param channelsPerRow
*/
- public ChannelizerViewer(int channelCount, int channelsPerRow)
+ public ChannelizerViewer(int channelCount, int channelsPerRow, int tapsPerChannel)
{
mChannelCount = channelCount;
mChannelsPerRow = channelsPerRow;
+ mFilterTapsPerChannel = tapsPerChannel;
+
mSampleRate = mChannelCount * CHANNEL_BANDWIDTH;
mToneFrequency = (int)(CHANNEL_BANDWIDTH * 1); //Set to channel 1 as default
@@ -202,13 +206,13 @@ public class ChannelizerViewer extends JFrame
private float[] getFilter() throws FilterDesignException
{
- return FilterFactory.getSincChannelizer(CHANNEL_BANDWIDTH, mChannelCount, 19,
+ return FilterFactory.getSincChannelizer(CHANNEL_BANDWIDTH, mChannelCount, mFilterTapsPerChannel,
Window.WindowType.BLACKMAN_HARRIS_7, true);
}
public class ChannelArrayPanel extends JPanel implements Listener
{
- private ComplexPolyphaseChannelizer3 mPolyphaseChannelizer;
+ private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
private ChannelDistributor mChannelDistributor;
public ChannelArrayPanel(float[] taps)
@@ -219,7 +223,7 @@ public class ChannelizerViewer extends JFrame
bufferSize++;
}
- mPolyphaseChannelizer = new ComplexPolyphaseChannelizer3(taps, mChannelCount);
+ mPolyphaseChannelizer = new ComplexPolyphaseChannelizerM2(taps, mChannelCount);
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
@@ -298,8 +302,9 @@ public class ChannelizerViewer extends JFrame
{
int channelCount = 4;
int channelsPerRow = 4;
+ int tapsPerChannel = 17;
- final ChannelizerViewer frame = new ChannelizerViewer(channelCount, channelsPerRow);
+ final ChannelizerViewer frame = new ChannelizerViewer(channelCount, channelsPerRow, tapsPerChannel);
EventQueue.invokeLater(new Runnable()
{
diff --git a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer.java b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer.java
index 117b0382..408f844d 100644
--- a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer.java
+++ b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer.java
@@ -38,32 +38,23 @@ public class ComplexPolyphaseChannelizer implements Listener
private RealFIRFilter[] mQuadratureFilters;
private int mFilterPointer = 0;
private int mChannelCount;
- private int mBlockSize;
private float[] mFilteredSamples;
private FloatFFT_1D mFFT;
private ChannelDistributor mChannelDistributor;
- private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000");
-
/**
- * Creates a polyphase filter bank that divides the input frequency band into evenly spaced channels that
- * are oversampled by 2x for output.
+ * Maximally Decimated Polyphase Filter Bank (MDPFB) channelizer that divides the input frequency band into
+ * equal bandwidth channels.
*
* @param taps of a low-pass filter designed for the inbound sample rate with a cutoff frequency
* equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more
* filte outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed
* as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency
*
- * @param channels - number of filters/channels to output. Since this filter bank oversamples each filter
- * output, this number must be even (divisible by the oversample rate).
+ * @param channels - number of channels to output.
*/
- public ComplexPolyphaseChannelizer(float[] taps, int channels, int oversample)
+ public ComplexPolyphaseChannelizer(float[] taps, int channels)
{
- if(oversample > 1 && channels % 2 != 0)
- {
- throw new IllegalArgumentException("Channel count must be a multiple of 2 when oversampling");
- }
-
mChannelCount = channels;
initFilters(taps);
@@ -187,7 +178,7 @@ public class ComplexPolyphaseChannelizer implements Listener
mLog.debug(sb.toString());
- ComplexPolyphaseChannelizer channelizer = new ComplexPolyphaseChannelizer(taps, 8, 1);
+ ComplexPolyphaseChannelizer channelizer = new ComplexPolyphaseChannelizer(taps, 8);
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
diff --git a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer2.java b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer2.java
deleted file mode 100644
index 2579f2b7..00000000
--- a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer2.java
+++ /dev/null
@@ -1,306 +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
- *
- ******************************************************************************/
-package dsp.filter.channelizer;
-
-import dsp.filter.FilterFactory;
-import dsp.filter.Window;
-import dsp.filter.design.FilterDesignException;
-import dsp.mixer.Oscillator;
-import org.jtransforms.fft.FloatFFT_1D;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import sample.Listener;
-import sample.complex.ComplexBuffer;
-
-import java.text.DecimalFormat;
-import java.util.Arrays;
-
-public class ComplexPolyphaseChannelizer2 implements Listener
-{
- private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizer2.class);
-
- private float[][] mFilterCoefficients;
-
- private float[][] mIBuffer;
- private float[][] mQBuffer;
- private int mBufferRowPointer = 0;
- private int mBufferColumnPointer = 0;
- private int[][] mIndexMap;
- private int mStrideLength;
- private int mStrideCount;
-
- private int mChannelCount;
- private float[] mFilteredSamples;
- private FloatFFT_1D mFFT;
- private ChannelDistributor mChannelDistributor;
-
- private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000");
-
- /**
- * Creates a polyphase filter bank that divides the input frequency band into evenly spaced channels that
- * are oversampled by 2x for output.
- *
- * @param taps of a low-pass filter designed for the inbound sample rate with a cutoff frequency
- * equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more
- * filte outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed
- * as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency
- *
- * @param channels - number of filters/channels to output. Since this filter bank oversamples each filter
- * output, this number must be even (divisible by the oversample rate).
- */
- public ComplexPolyphaseChannelizer2(float[] taps, int channels)
- {
- if(channels % 2 != 0)
- {
- throw new IllegalArgumentException("Channel count must be a multiple of 2");
- }
-
- mChannelCount = channels;
-
- initFilters(taps);
- }
-
- public void filter(float inphase, float quadrature)
- {
- mIBuffer[mBufferRowPointer][mBufferColumnPointer] = inphase;
- mQBuffer[mBufferRowPointer][mBufferColumnPointer] = quadrature;
-
- mBufferRowPointer--;
-
- mStrideCount++;
-
- if(mStrideCount >= mStrideLength)
- {
- dispatch();
- mStrideCount = 0;
- }
-
- //Adjust the circular buffer pointers, wrapping as necessary
- if(mBufferRowPointer < 0)
- {
- mBufferRowPointer += mChannelCount;
- mBufferColumnPointer--;
-
- if(mBufferColumnPointer < 0)
- {
- mBufferColumnPointer += mIBuffer[mBufferRowPointer].length;
- }
- }
- }
-
- @Override
- public void receive(ComplexBuffer complexBuffer)
- {
- float[] samples = complexBuffer.getSamples();
-
- for(int x = 0; x < samples.length; x += 2)
- {
- filter(samples[x], samples[x + 1]);
- }
- }
-
- private void dispatch()
- {
- float[] samples = calculate();
-
- //FFT is executed in-place where the output overwrites the input
- mFFT.complexForward(samples);
-
- dispatch(samples);
- }
-
- /**
- * Calculates the value of the current data row and the current filter coefficient set and assigns the value to the
- * current filtered samples array
- */
- private float[] calculate()
- {
- float[] samples = new float[mChannelCount * 2];
-
- try
- {
- for(int x = 0; x < mChannelCount; x++)
- {
- float iAccumulator = 0.0f;
- float qAccumulator = 0.0f;
-
- int coefficientIndex;
-
- if(mBufferRowPointer <= 0)
- {
- coefficientIndex = x;
- }
- else if(x < mChannelCount / 2)
- {
- coefficientIndex = x + mChannelCount / 2;
- }
- else
- {
- coefficientIndex = x - mChannelCount / 2;
- }
-
- float[] coefficients = mFilterCoefficients[coefficientIndex];
- float[] iSamples = mIBuffer[x];
- float[] qSamples = mQBuffer[x];
-
- for( int y = 0; y < coefficients.length; y++ )
- {
- int column = mIndexMap[mBufferColumnPointer][y];
-
- iAccumulator += coefficients[y] * iSamples[column];
- qAccumulator += coefficients[y] * qSamples[column];
- }
-
- samples[coefficientIndex * 2] = iAccumulator;
- samples[coefficientIndex * 2 + 1] = qAccumulator;
- }
- }
- catch(Exception e)
- {
- mLog.error("Error", e);
- }
-
- return samples;
- }
-
- /**
- * Dispatches the filtered, de-spun samples as an array of channel I/Q sample pairs
- * @param channels arranged as: channel0i, channel0q, channel1i, channel1q ... channelN-1i, channelN-1q
- */
- private void dispatch(float[] channels)
- {
- if(mChannelDistributor != null)
- {
- mChannelDistributor.receive(channels);
- }
- }
-
- public void setChannelDistributor(ChannelDistributor channelDistributor)
- {
- mChannelDistributor = channelDistributor;
- }
-
- private double magnitude(float real, float imaginary, int fftSize)
- {
- return Math.sqrt(Math.pow(real, 2.0) + Math.pow(imaginary, 2.0));
- }
-
- /**
- * Distributes the filter taps to each of the polyphase filters
- *
- * @param taps
- */
- private void initFilters(float[] taps)
- {
- int tapCount = (int)Math.ceil((double)taps.length / (double)mChannelCount);
-
- mIBuffer = new float[mChannelCount][tapCount];
- mQBuffer = new float[mChannelCount][tapCount];
-
- mFilterCoefficients = new float[mChannelCount][tapCount];
-
- for(int tap = 0; tap < tapCount; tap++)
- {
- for(int filter = 0; filter < mChannelCount; filter++)
- {
- int index = tap * mChannelCount + filter;
-
- if(index < taps.length)
- {
- mFilterCoefficients[filter][tap] = taps[index];
- }
- }
- }
-
- generateIndexMap(tapCount);
-
- mFilteredSamples = new float[mChannelCount * 2];
- mFFT = new FloatFFT_1D(mChannelCount);
- mStrideLength = mChannelCount / 2; //channel count * 2 / 2
- mStrideCount = 0;
- mBufferRowPointer = mChannelCount / 2 - 1; //Start filling data half way into buffer
- mBufferColumnPointer = tapCount - 1;
- }
-
- /**
- * Generates a circular buffer index map to support lookup of the translated
- * index based on the current buffer pointer and the desired sample index.
- */
- private void generateIndexMap( int size )
- {
- mIndexMap = new int[ size ][ size ];
-
- for( int x = 0; x < size; x++ )
- {
- for( int y = 0; y < size; y++ )
- {
- int z = x + y;
-
- mIndexMap[ x ][ y ] = z < size ? z : z - size;
- }
- }
- }
-
-
- public static void main(String[] args)
- {
- mLog.debug("Starting ...");
-
- int channelBandwidth = 12500;
- int channels = 4;
- int tapsPerChannel = 15;
-
- float[] taps = null;
-
- try
- {
- taps = FilterFactory.getSincChannelizer( channelBandwidth, channels, tapsPerChannel,
- Window.WindowType.BLACKMAN_HARRIS_7, true);
-
- }
- catch(FilterDesignException fde)
- {
- mLog.error("Error in filter design", fde);
- }
-
- int channelCenter = (int)(channelBandwidth * 1.5);
-
- StringBuilder sb = new StringBuilder();
- sb.append("\nPolyphase Channelizer\n");
- sb.append("Sample Rate:" + (channelBandwidth * channels) + "\n");
- sb.append("Channels:" + channels + "\n");
- sb.append("Channel Rate:" + channelBandwidth + "\n");
- sb.append("Tap Count:" + taps.length + "\n");
- sb.append("Channel:" + channelCenter + "\n");
-
- mLog.debug(sb.toString());
-
- ComplexPolyphaseChannelizer2 channelizer = new ComplexPolyphaseChannelizer2(taps, channels);
-
- Oscillator oscillator = new Oscillator(channelCenter, 2 * (channelBandwidth * channels));
-
- for(int x = 0; x < 2000; x++)
- {
- channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature());
- oscillator.rotate();
- }
-
- mLog.debug("Finished!");
- }
-}
diff --git a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer3.java b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java
similarity index 58%
rename from src/dsp/filter/channelizer/ComplexPolyphaseChannelizer3.java
rename to src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java
index e5ddfd30..4ee0f32d 100644
--- a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer3.java
+++ b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java
@@ -18,9 +18,9 @@
******************************************************************************/
package dsp.filter.channelizer;
+import buffer.ReverseFloatCircularBuffer;
import dsp.filter.FilterFactory;
import dsp.filter.Window;
-import dsp.filter.fir.real.RealFIRFilter;
import dsp.mixer.Oscillator;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
@@ -30,27 +30,22 @@ import sample.complex.ComplexBuffer;
import java.text.DecimalFormat;
-public class ComplexPolyphaseChannelizer3 implements Listener
+public class ComplexPolyphaseChannelizerM2 implements Listener
{
- private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizer3.class);
+ private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
- private RealFIRFilter[] mInphaseFilters;
- private RealFIRFilter[] mQuadratureFilters;
- private int mInputPointer = 0;
+ private ReverseFloatCircularBuffer[] mDataBuffers;
+ private float[][] mFilterCoefficients;
+ private int mFilterPointer = 0;
private int mChannelCount;
- private int mBlockSize;
- private float[] mInputISamples;
- private float[] mInputQSamples;
- private float[] mOutputSamples;
private FloatFFT_1D mFFT;
private ChannelDistributor mChannelDistributor;
- private boolean mFlipFlop = false;
private DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0000");
/**
- * Creates a polyphase filter bank that divides the input frequency band into evenly spaced channels that
- * are oversampled by 2x for output.
+ * Non-Maximally Decimated Polyphase Filter Bank (NMDPFB) channelizer that divides the input frequency band into
+ * equal bandwidth channels that are each oversampled by 2x for output.
*
* @param taps of a low-pass filter designed for the inbound sample rate with a cutoff frequency
* equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more
@@ -60,7 +55,7 @@ public class ComplexPolyphaseChannelizer3 implements Listener
* @param channels - number of filters/channels to output. Since this filter bank oversamples each filter
* output, this number must be even (divisible by the oversample rate).
*/
- public ComplexPolyphaseChannelizer3(float[] taps, int channels)
+ public ComplexPolyphaseChannelizerM2(float[] taps, int channels)
{
if(channels % 2 != 0)
{
@@ -74,17 +69,26 @@ public class ComplexPolyphaseChannelizer3 implements Listener
public void filter(float inphase, float quadrature)
{
- mInputISamples[mInputPointer] = inphase;
- mInputQSamples[mInputPointer] = quadrature;
+ try
+ {
+ mDataBuffers[mFilterPointer--].put(quadrature);
+ mDataBuffers[mFilterPointer].put(inphase);
- if(mInputPointer == 0)
- {
- calculate();
- mInputPointer = mChannelCount / 2 - 1;
+ if(mFilterPointer == 0)
+ {
+ calculate();
+ mFilterPointer = mChannelCount * 2;
+ }
+ else if(mFilterPointer == mChannelCount)
+ {
+ calculate();
+ }
+
+ mFilterPointer--;
}
- else
+ catch(Exception e)
{
- mInputPointer--;
+ mLog.error("Error!", e);
}
}
@@ -101,39 +105,73 @@ public class ComplexPolyphaseChannelizer3 implements Listener
private void calculate()
{
- float[] filteredSamples = new float[mChannelCount * 2];
+ float[] samples = new float[mChannelCount * 2];
- for(int x = 0; x < mChannelCount; x++)
+ if(mFilterPointer == 0)
{
- int index = x * 2;
+ for(int x = 0; x < mFilterCoefficients.length; x++)
+ {
+ int index = x * 2;
- filteredSamples[index] = mInphaseFilters[x].filter(mInputISamples[x]);
- filteredSamples[index + 1] = mQuadratureFilters[x].filter(mInputQSamples[x]);
+ //Inphase sample
+ samples[index] = convolve(mFilterCoefficients[x], mDataBuffers[index]);
+
+ //Quadrature sample
+ samples[index + 1] = convolve(mFilterCoefficients[x], mDataBuffers[index + 1]);
+
+ }
+ }
+ else
+ {
+ int half = mFilterCoefficients.length / 2;
+
+ for(int x = 0; x < half; x++)
+ {
+ int index = x * 2;
+ int filterIndex = x + half;
+
+ //Inphase sample
+ samples[index] = convolve(mFilterCoefficients[filterIndex], mDataBuffers[index]);
+
+ //Quadrature sample
+ samples[index + 1] = convolve(mFilterCoefficients[filterIndex], mDataBuffers[index + 1]);
+ }
+
+ for(int x = half; x < mFilterCoefficients.length; x++)
+ {
+ int index = x * 2;
+ int filterIndex = x - half;
+
+ //Inphase sample
+ samples[index] = convolve(mFilterCoefficients[filterIndex], mDataBuffers[index]);
+
+ //Quadrature sample
+ samples[index + 1] = convolve(mFilterCoefficients[filterIndex], mDataBuffers[index + 1]);
+ }
}
- //Toggled flip-flop here
- if(mFlipFlop)
+ //IFFT is executed in-place with the output overwriting the input
+ mFFT.complexInverse(samples, true);
+
+ dispatch(samples);
+ }
+
+ /**
+ * Filters the samples contained in the buffer against the filter coefficients
+ * @param coefficients of the sub filter
+ * @param buffer containing data samples
+ * @return
+ */
+ private float convolve(float[] coefficients, ReverseFloatCircularBuffer buffer)
+ {
+ float accumulator = 0.0f;
+
+ for( int x = 0; x < coefficients.length; x++ )
{
- float[] flippedSamples = new float[mChannelCount * 2];
-
- System.arraycopy(filteredSamples, 0, flippedSamples, mChannelCount, mChannelCount);
- System.arraycopy(filteredSamples, mChannelCount, flippedSamples, 0, mChannelCount);
-
- filteredSamples = flippedSamples;
+ accumulator += coefficients[ x ] * buffer.get(x);
}
- mFlipFlop = !mFlipFlop;
-
- //FFT is executed in-place where the output overwrites the input
- mFFT.complexForward(filteredSamples);
-
- dispatch(filteredSamples);
-
- int half = mChannelCount / 2;
-
- //Serpentine shift the 0 to m/2 samples to bottom of the input arrays
- System.arraycopy(mInputISamples, half, mInputISamples, 0, half);
- System.arraycopy(mInputQSamples, half, mInputQSamples, 0, half);
+ return accumulator;
}
private void dispatch(float[] channels)
@@ -163,7 +201,14 @@ public class ComplexPolyphaseChannelizer3 implements Listener
{
int tapCount = (int)Math.ceil((double)taps.length / (double)mChannelCount);
- float[][] coefficients = new float[mChannelCount][tapCount];
+ mDataBuffers = new ReverseFloatCircularBuffer[mChannelCount * 2];
+
+ for(int x = 0; x < mChannelCount * 2; x++)
+ {
+ mDataBuffers[x] = new ReverseFloatCircularBuffer(tapCount);
+ }
+
+ mFilterCoefficients = new float[mChannelCount][tapCount];
for(int tap = 0; tap < tapCount; tap++)
{
@@ -173,25 +218,13 @@ public class ComplexPolyphaseChannelizer3 implements Listener
if(index < taps.length)
{
- coefficients[filter][tap] = taps[index];
+ mFilterCoefficients[filter][tap] = taps[index];
}
}
}
- mInphaseFilters = new RealFIRFilter[mChannelCount];
- mQuadratureFilters = new RealFIRFilter[mChannelCount];
-
- for(int filter = 0; filter < mChannelCount; filter++)
- {
- mInphaseFilters[filter] = new RealFIRFilter(coefficients[filter], 1.0f);
- mQuadratureFilters[filter] = new RealFIRFilter(coefficients[filter], 1.0f);
- }
-
- mInputISamples = new float[mChannelCount];
- mInputQSamples = new float[mChannelCount];
- mOutputSamples = new float[mChannelCount * 2];
mFFT = new FloatFFT_1D(mChannelCount);
- mInputPointer = mChannelCount -1;
+ mFilterPointer = mChannelCount - 1; //Start at M/2
}
public static void main(String[] args)
@@ -216,7 +249,7 @@ public class ComplexPolyphaseChannelizer3 implements Listener
mLog.debug(sb.toString());
- ComplexPolyphaseChannelizer3 channelizer = new ComplexPolyphaseChannelizer3(taps, 8);
+ ComplexPolyphaseChannelizerM2 channelizer = new ComplexPolyphaseChannelizerM2(taps, 8);
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
diff --git a/src/dsp/filter/correction/IQCorrectionFilter.java b/src/dsp/filter/correction/IQCorrectionFilter.java
new file mode 100644
index 00000000..14f84de8
--- /dev/null
+++ b/src/dsp/filter/correction/IQCorrectionFilter.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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
+ *
+ ******************************************************************************/
+package dsp.filter.correction;
+
+import sample.complex.ComplexBuffer;
+
+public class IQCorrectionFilter
+{
+ private float mRatio = 0.00001f;
+ private float mAverageInphase = 0.0f;
+ private float mAverageQuadrature = 0.0f;
+
+ public IQCorrectionFilter(float ratio)
+ {
+ mRatio = ratio;
+ }
+
+ public IQCorrectionFilter()
+ {
+ }
+
+ public void filter(ComplexBuffer buffer)
+ {
+ float[] samples = buffer.getSamples();
+
+ for(int x = 0; x < samples.length; x += 2)
+ {
+ mAverageInphase = mAverageInphase + (mRatio * (samples[x] - mAverageInphase));
+ samples[x] -= mAverageInphase;
+
+ mAverageQuadrature = mAverageQuadrature + (mRatio * (samples[x + 1] - mAverageQuadrature));
+ samples[x + 1] -= mAverageQuadrature;
+ }
+ }
+}
diff --git a/src/dsp/gain/AutomaticGainControl.java b/src/dsp/gain/AutomaticGainControl.java
index 8f397ebd..b46dd105 100644
--- a/src/dsp/gain/AutomaticGainControl.java
+++ b/src/dsp/gain/AutomaticGainControl.java
@@ -142,7 +142,7 @@ public class AutomaticGainControl
public float process( float currentSample )
{
- float delayedSample = mDelayBuffer.get( currentSample );
+ float delayedSample = mDelayBuffer.putAndGet( currentSample );
double gain = MANUAL_AGC_GAIN;
diff --git a/src/dsp/gain/ComplexFeedForwardGainControl.java b/src/dsp/gain/ComplexFeedForwardGainControl.java
index 6abcd62c..20449301 100644
--- a/src/dsp/gain/ComplexFeedForwardGainControl.java
+++ b/src/dsp/gain/ComplexFeedForwardGainControl.java
@@ -74,7 +74,7 @@ public class ComplexFeedForwardGainControl implements ComplexSampleListener
}
/* Replace oldest envelope value with current envelope value */
- float oldestEnvelope = mEnvelopeHistory.get( envelope );
+ float oldestEnvelope = mEnvelopeHistory.putAndGet( envelope );
/* If the oldest envelope value was the max envelope value, then we
* have to rediscover the max value from the envelope history */