This commit is contained in:
Denny
2017-04-08 08:35:48 -04:00
parent 41d307cdad
commit f7cd798f07
3 changed files with 287 additions and 38 deletions

View File

@@ -208,7 +208,7 @@ public class ChannelizerViewer extends JFrame
public class ChannelArrayPanel extends JPanel implements Listener<ComplexBuffer>
{
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);

View File

@@ -37,15 +37,14 @@ public class ComplexPolyphaseChannelizer2 implements Listener<ComplexBuffer>
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<ComplexBuffer>
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<ComplexBuffer>
//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<ComplexBuffer>
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<ComplexBuffer>
* 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<ComplexBuffer>
{
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<ComplexBuffer>
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;
}
/**

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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<ComplexBuffer>
{
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!");
}
}