From ac686f5f647e4052a7f45d10d7c7317984f045e9 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 14 Apr 2017 06:41:43 -0400 Subject: [PATCH] WIP - correctly implements the 2x oversampled polyphase channelizer. --- src/buffer/FloatCircularBuffer.java | 206 ++++++------ src/buffer/ReverseFloatCircularBuffer.java | 89 +++++ .../filter/channelizer/ChannelizerViewer.java | 15 +- .../ComplexPolyphaseChannelizer.java | 19 +- .../ComplexPolyphaseChannelizer2.java | 306 ------------------ ...ava => ComplexPolyphaseChannelizerM2.java} | 159 +++++---- .../filter/correction/IQCorrectionFilter.java | 51 +++ src/dsp/gain/AutomaticGainControl.java | 2 +- .../gain/ComplexFeedForwardGainControl.java | 2 +- 9 files changed, 360 insertions(+), 489 deletions(-) create mode 100644 src/buffer/ReverseFloatCircularBuffer.java delete mode 100644 src/dsp/filter/channelizer/ComplexPolyphaseChannelizer2.java rename src/dsp/filter/channelizer/{ComplexPolyphaseChannelizer3.java => ComplexPolyphaseChannelizerM2.java} (58%) create mode 100644 src/dsp/filter/correction/IQCorrectionFilter.java 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 */