mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
WIP - correctly implements the 2x oversampled polyphase channelizer.
This commit is contained in:
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* 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 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;
|
||||
}
|
||||
}
|
||||
|
||||
89
src/buffer/ReverseFloatCircularBuffer.java
Normal file
89
src/buffer/ReverseFloatCircularBuffer.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
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 + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ComplexBuffer>
|
||||
{
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -38,32 +38,23 @@ public class ComplexPolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
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<ComplexBuffer>
|
||||
|
||||
mLog.debug(sb.toString());
|
||||
|
||||
ComplexPolyphaseChannelizer channelizer = new ComplexPolyphaseChannelizer(taps, 8, 1);
|
||||
ComplexPolyphaseChannelizer channelizer = new ComplexPolyphaseChannelizer(taps, 8);
|
||||
|
||||
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
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<ComplexBuffer>
|
||||
{
|
||||
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!");
|
||||
}
|
||||
}
|
||||
@@ -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<ComplexBuffer>
|
||||
public class ComplexPolyphaseChannelizerM2 implements Listener<ComplexBuffer>
|
||||
{
|
||||
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<ComplexBuffer>
|
||||
* @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<ComplexBuffer>
|
||||
|
||||
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<ComplexBuffer>
|
||||
|
||||
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<ComplexBuffer>
|
||||
{
|
||||
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<ComplexBuffer>
|
||||
|
||||
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<ComplexBuffer>
|
||||
|
||||
mLog.debug(sb.toString());
|
||||
|
||||
ComplexPolyphaseChannelizer3 channelizer = new ComplexPolyphaseChannelizer3(taps, 8);
|
||||
ComplexPolyphaseChannelizerM2 channelizer = new ComplexPolyphaseChannelizerM2(taps, 8);
|
||||
|
||||
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
|
||||
|
||||
51
src/dsp/filter/correction/IQCorrectionFilter.java
Normal file
51
src/dsp/filter/correction/IQCorrectionFilter.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user