From f7cd798f07375630b3e9a9adcb53a24f0b12bdae Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 8 Apr 2017 08:35:48 -0400 Subject: [PATCH] WIP --- .../filter/channelizer/ChannelizerViewer.java | 4 +- .../ComplexPolyphaseChannelizer2.java | 90 ++++--- .../ComplexPolyphaseChannelizer3.java | 231 ++++++++++++++++++ 3 files changed, 287 insertions(+), 38 deletions(-) create mode 100644 src/dsp/filter/channelizer/ComplexPolyphaseChannelizer3.java diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java index 343ce1d9..00c1affd 100644 --- a/src/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/dsp/filter/channelizer/ChannelizerViewer.java @@ -208,7 +208,7 @@ public class ChannelizerViewer extends JFrame public class ChannelArrayPanel extends JPanel implements Listener { - private ComplexPolyphaseChannelizer2 mPolyphaseChannelizer; + private ComplexPolyphaseChannelizer3 mPolyphaseChannelizer; private ChannelDistributor mChannelDistributor; public ChannelArrayPanel(float[] taps) @@ -219,7 +219,7 @@ public class ChannelizerViewer extends JFrame bufferSize++; } - mPolyphaseChannelizer = new ComplexPolyphaseChannelizer2(taps, mChannelCount); + mPolyphaseChannelizer = new ComplexPolyphaseChannelizer3(taps, mChannelCount); mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount); mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor); diff --git a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer2.java b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer2.java index 6351483f..2579f2b7 100644 --- a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer2.java +++ b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer2.java @@ -37,15 +37,14 @@ public class ComplexPolyphaseChannelizer2 implements Listener private float[][] mFilterCoefficients; - - private float[][] mBuffer; + 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 mFilterPointer = 0; private int mChannelCount; private float[] mFilteredSamples; private FloatFFT_1D mFFT; @@ -79,13 +78,9 @@ public class ComplexPolyphaseChannelizer2 implements Listener public void filter(float inphase, float quadrature) { - mBuffer[mBufferRowPointer][mBufferColumnPointer] = quadrature; + mIBuffer[mBufferRowPointer][mBufferColumnPointer] = inphase; + mQBuffer[mBufferRowPointer][mBufferColumnPointer] = quadrature; - calculate(); - mBufferRowPointer--; - - mBuffer[mBufferRowPointer][mBufferColumnPointer] = inphase; - calculate(); mBufferRowPointer--; mStrideCount++; @@ -99,12 +94,12 @@ public class ComplexPolyphaseChannelizer2 implements Listener //Adjust the circular buffer pointers, wrapping as necessary if(mBufferRowPointer < 0) { - mBufferRowPointer += mBuffer.length; + mBufferRowPointer += mChannelCount; mBufferColumnPointer--; if(mBufferColumnPointer < 0) { - mBufferColumnPointer += mBuffer[mBufferRowPointer].length; + mBufferColumnPointer += mIBuffer[mBufferRowPointer].length; } } } @@ -122,11 +117,7 @@ public class ComplexPolyphaseChannelizer2 implements Listener private void dispatch() { - //Create a copy of the filtered samples - float[] samples = Arrays.copyOf(mFilteredSamples, mFilteredSamples.length); - - //Copy the first half of the samples copy back to the second half of the array to prepare for the next iteration - System.arraycopy(samples, 0, mFilteredSamples, mChannelCount, mChannelCount); + float[] samples = calculate(); //FFT is executed in-place where the output overwrites the input mFFT.complexForward(samples); @@ -138,28 +129,54 @@ public class ComplexPolyphaseChannelizer2 implements Listener * 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 void calculate() + private float[] calculate() { - float accumulator = 0.0f; + float[] samples = new float[mChannelCount * 2]; - float[] coefficients = mFilterCoefficients[mBufferRowPointer / 2]; - float[] samples = mBuffer[mBufferRowPointer]; - - for( int x = 0; x < coefficients.length; x++ ) + try { - int column = mIndexMap[mBufferColumnPointer][x]; + for(int x = 0; x < mChannelCount; x++) + { + float iAccumulator = 0.0f; + float qAccumulator = 0.0f; - accumulator += coefficients[ x ] * samples[column]; + 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); } - if(mBufferRowPointer < mChannelCount) - { - mFilteredSamples[mBufferRowPointer] = accumulator; - } - else - { - mFilteredSamples[mBufferRowPointer - mChannelCount] = accumulator; - } + return samples; } /** @@ -193,7 +210,9 @@ public class ComplexPolyphaseChannelizer2 implements Listener { int tapCount = (int)Math.ceil((double)taps.length / (double)mChannelCount); - mBuffer = new float[mChannelCount * 2][tapCount]; + mIBuffer = new float[mChannelCount][tapCount]; + mQBuffer = new float[mChannelCount][tapCount]; + mFilterCoefficients = new float[mChannelCount][tapCount]; for(int tap = 0; tap < tapCount; tap++) @@ -213,11 +232,10 @@ public class ComplexPolyphaseChannelizer2 implements Listener mFilteredSamples = new float[mChannelCount * 2]; mFFT = new FloatFFT_1D(mChannelCount); - mFilterPointer = mChannelCount -1; mStrideLength = mChannelCount / 2; //channel count * 2 / 2 mStrideCount = 0; - mBufferRowPointer = mChannelCount - 1; //Start filling data half way into buffer - mBufferColumnPointer = mBuffer[mBufferRowPointer].length - 1; + mBufferRowPointer = mChannelCount / 2 - 1; //Start filling data half way into buffer + mBufferColumnPointer = tapCount - 1; } /** diff --git a/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer3.java b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer3.java new file mode 100644 index 00000000..e5ddfd30 --- /dev/null +++ b/src/dsp/filter/channelizer/ComplexPolyphaseChannelizer3.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * 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.fir.real.RealFIRFilter; +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; + +public class ComplexPolyphaseChannelizer3 implements Listener +{ + private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizer3.class); + + private RealFIRFilter[] mInphaseFilters; + private RealFIRFilter[] mQuadratureFilters; + private int mInputPointer = 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. + * + * @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 ComplexPolyphaseChannelizer3(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) + { + mInputISamples[mInputPointer] = inphase; + mInputQSamples[mInputPointer] = quadrature; + + if(mInputPointer == 0) + { + calculate(); + mInputPointer = mChannelCount / 2 - 1; + } + else + { + mInputPointer--; + } + } + + @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 calculate() + { + float[] filteredSamples = new float[mChannelCount * 2]; + + for(int x = 0; x < mChannelCount; x++) + { + int index = x * 2; + + filteredSamples[index] = mInphaseFilters[x].filter(mInputISamples[x]); + filteredSamples[index + 1] = mQuadratureFilters[x].filter(mInputQSamples[x]); + } + + //Toggled flip-flop here + if(mFlipFlop) + { + float[] flippedSamples = new float[mChannelCount * 2]; + + System.arraycopy(filteredSamples, 0, flippedSamples, mChannelCount, mChannelCount); + System.arraycopy(filteredSamples, mChannelCount, flippedSamples, 0, mChannelCount); + + filteredSamples = flippedSamples; + } + + 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); + } + + 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); + + float[][] coefficients = 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) + { + coefficients[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; + } + + public static void main(String[] args) + { + mLog.debug("Starting ..."); + + int symbolRate = 4800; + int samplesPerSymbol = 2; + int channels = 800; + int channelBandwidth = 12500; + int sampleRate = channels * channelBandwidth; + + float[] taps = FilterFactory.getLowPass( 10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7 ); + + int channelCenter = (int)(12500 * 1.5); + + StringBuilder sb = new StringBuilder(); + sb.append("\nPolyphase Channelizer\n"); + sb.append("Sample Rate:" + sampleRate + " Channels:" + channels + " Channel Rate:" + channelBandwidth + "\n"); + sb.append("Tap Count:" + taps.length + "\n"); + sb.append("Channel:" + channelCenter); + + mLog.debug(sb.toString()); + + ComplexPolyphaseChannelizer3 channelizer = new ComplexPolyphaseChannelizer3(taps, 8); + + Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate); + + for(int x = 0; x < 2000; x++) + { + channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature()); + oscillator.rotate(); + } + + mLog.debug("Finished!"); + } +}