mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 00:57:31 -05:00
[WIP] Channelizing 6.4 MHz sample rate into 512 channels with with 512-point FFT on each channel at frame rate of 20 fps.
This commit is contained in:
@@ -26,6 +26,7 @@ import sample.complex.ComplexBuffer;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
public class ChannelDistributor
|
public class ChannelDistributor
|
||||||
{
|
{
|
||||||
@@ -85,7 +86,7 @@ public class ChannelDistributor
|
|||||||
public void receive(float inphase, float quadrature)
|
public void receive(float inphase, float quadrature)
|
||||||
{
|
{
|
||||||
mBuffer[mBufferPointer++] = inphase;
|
mBuffer[mBufferPointer++] = inphase;
|
||||||
mBuffer[mBufferPointer++] = inphase;
|
mBuffer[mBufferPointer++] = quadrature;
|
||||||
|
|
||||||
if(mBufferPointer >= mBufferSize)
|
if(mBufferPointer >= mBufferSize)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class ChannelizerViewer extends JFrame
|
|||||||
|
|
||||||
private void generateSamples()
|
private void generateSamples()
|
||||||
{
|
{
|
||||||
int size = mChannelCount * CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE * 2;
|
int size = mSampleRate / CHANNEL_FFT_FRAME_RATE;
|
||||||
|
|
||||||
float[] samples = mOscillator.generate(size);
|
float[] samples = mOscillator.generate(size);
|
||||||
|
|
||||||
@@ -137,10 +137,13 @@ public class ChannelizerViewer extends JFrame
|
|||||||
if(mControlPanel == null)
|
if(mControlPanel == null)
|
||||||
{
|
{
|
||||||
mControlPanel = new JPanel();
|
mControlPanel = new JPanel();
|
||||||
|
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
|
||||||
|
|
||||||
|
mControlPanel.add(new JLabel("Tone:"), "align left");
|
||||||
int maximumFrequency = mSampleRate / 2;
|
int maximumFrequency = mSampleRate / 2;
|
||||||
|
|
||||||
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, -maximumFrequency, maximumFrequency, CHANNEL_BANDWIDTH );
|
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, -maximumFrequency, maximumFrequency,
|
||||||
|
500 );
|
||||||
|
|
||||||
model.addChangeListener(new ChangeListener()
|
model.addChangeListener(new ChangeListener()
|
||||||
{
|
{
|
||||||
@@ -155,6 +158,12 @@ public class ChannelizerViewer extends JFrame
|
|||||||
JSpinner spinner = new JSpinner(model);
|
JSpinner spinner = new JSpinner(model);
|
||||||
|
|
||||||
mControlPanel.add(spinner);
|
mControlPanel.add(spinner);
|
||||||
|
mControlPanel.add(new JLabel("Hz"));
|
||||||
|
|
||||||
|
mControlPanel.add(new JLabel("Sample Rate: " + mSampleRate + " Hz"), "push,align right");
|
||||||
|
|
||||||
|
mControlPanel.add(new JLabel("Max: " + (mSampleRate / 2) + " Hz"), "push,align right");
|
||||||
|
mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right");
|
||||||
}
|
}
|
||||||
|
|
||||||
return mControlPanel;
|
return mControlPanel;
|
||||||
@@ -174,7 +183,7 @@ public class ChannelizerViewer extends JFrame
|
|||||||
{
|
{
|
||||||
int symbolRate = 4800;
|
int symbolRate = 4800;
|
||||||
int samplesPerSymbol = 2;
|
int samplesPerSymbol = 2;
|
||||||
int symbolCount = 8;
|
int symbolCount = 14;
|
||||||
|
|
||||||
//Alpha is the residual channel bandwidth left over from the symbol rate and samples per symbol
|
//Alpha is the residual channel bandwidth left over from the symbol rate and samples per symbol
|
||||||
float alpha = ((float)CHANNEL_BANDWIDTH / (float)(symbolRate * samplesPerSymbol)) - 1.0f;
|
float alpha = ((float)CHANNEL_BANDWIDTH / (float)(symbolRate * samplesPerSymbol)) - 1.0f;
|
||||||
@@ -193,7 +202,7 @@ public class ChannelizerViewer extends JFrame
|
|||||||
|
|
||||||
public class ChannelArrayPanel extends JPanel implements Listener<ComplexBuffer>
|
public class ChannelArrayPanel extends JPanel implements Listener<ComplexBuffer>
|
||||||
{
|
{
|
||||||
private PolyphaseChannelizer mPolyphaseChannelizer;
|
private PolyphaseChannelizer2 mPolyphaseChannelizer;
|
||||||
private ChannelDistributor mChannelDistributor;
|
private ChannelDistributor mChannelDistributor;
|
||||||
|
|
||||||
public ChannelArrayPanel(float[] taps)
|
public ChannelArrayPanel(float[] taps)
|
||||||
@@ -204,7 +213,7 @@ public class ChannelizerViewer extends JFrame
|
|||||||
bufferSize++;
|
bufferSize++;
|
||||||
}
|
}
|
||||||
|
|
||||||
mPolyphaseChannelizer = new PolyphaseChannelizer(taps, mChannelCount);
|
mPolyphaseChannelizer = new PolyphaseChannelizer2(taps, mChannelCount);
|
||||||
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
|
mChannelDistributor = new ChannelDistributor(bufferSize, mChannelCount);
|
||||||
mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
|
mPolyphaseChannelizer.setChannelDistributor(mChannelDistributor);
|
||||||
|
|
||||||
@@ -213,21 +222,23 @@ public class ChannelizerViewer extends JFrame
|
|||||||
|
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill][grow,fill]", ""));
|
setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill"));
|
||||||
|
|
||||||
|
int channelsPerRow = 32;
|
||||||
|
|
||||||
for(int x = 0; x < mChannelCount; x++)
|
for(int x = 0; x < mChannelCount; x++)
|
||||||
{
|
{
|
||||||
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2);
|
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2);
|
||||||
channelPanel.setPreferredSize(new Dimension(250,100));
|
// channelPanel.setPreferredSize(new Dimension(250,100));
|
||||||
channelPanel.setDFTSize(DFTSize.FFT00512);
|
channelPanel.setDFTSize(DFTSize.FFT00512);
|
||||||
|
|
||||||
if(x % 4 == 3)
|
if(x % channelsPerRow == channelsPerRow - 1)
|
||||||
{
|
{
|
||||||
add(channelPanel, "wrap");
|
add(channelPanel, "grow,push,wrap 2px");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
add(channelPanel);
|
add(channelPanel, "grow,push");
|
||||||
}
|
}
|
||||||
|
|
||||||
mChannelDistributor.addListener(x, channelPanel);
|
mChannelDistributor.addListener(x, channelPanel);
|
||||||
@@ -303,7 +314,7 @@ public class ChannelizerViewer extends JFrame
|
|||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
int channelCount = 24;
|
int channelCount = 512;
|
||||||
|
|
||||||
final ChannelizerViewer frame = new ChannelizerViewer(channelCount);
|
final ChannelizerViewer frame = new ChannelizerViewer(channelCount);
|
||||||
|
|
||||||
|
|||||||
@@ -153,11 +153,11 @@ public class PolyphaseChannelizer implements Listener<ComplexBuffer>
|
|||||||
|
|
||||||
float[][] coefficients = new float[mChannelCount][tapCount];
|
float[][] coefficients = new float[mChannelCount][tapCount];
|
||||||
|
|
||||||
for(int filter = 0; filter < mChannelCount; filter++)
|
for(int tap = 0; tap < tapCount; tap++)
|
||||||
{
|
{
|
||||||
for(int tap = 0; tap < tapCount; tap++)
|
for(int filter = 0; filter < mChannelCount; filter++)
|
||||||
{
|
{
|
||||||
int index = filter * mChannelCount + tap;
|
int index = tap * mChannelCount + filter;
|
||||||
|
|
||||||
if(index < taps.length)
|
if(index < taps.length)
|
||||||
{
|
{
|
||||||
|
|||||||
202
src/dsp/filter/channelizer/PolyphaseChannelizer2.java
Normal file
202
src/dsp/filter/channelizer/PolyphaseChannelizer2.java
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.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 PolyphaseChannelizer2 implements Listener<ComplexBuffer>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelizer2.class);
|
||||||
|
|
||||||
|
private RealFIRFilter[] mInphaseFilters;
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @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 PolyphaseChannelizer2(float[] taps, int channels)
|
||||||
|
{
|
||||||
|
assert(channels % 2 == 0);
|
||||||
|
|
||||||
|
mChannelCount = channels;
|
||||||
|
|
||||||
|
initFilters(taps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filter(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
int index = mFilterPointer * 2;
|
||||||
|
|
||||||
|
mFilteredSamples[index] = mInphaseFilters[mFilterPointer].filter(inphase);
|
||||||
|
mFilteredSamples[index + 1] = mQuadratureFilters[mFilterPointer].filter(quadrature);
|
||||||
|
|
||||||
|
if(mFilterPointer == 0)
|
||||||
|
{
|
||||||
|
calculate();
|
||||||
|
mFilterPointer = mChannelCount - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFilterPointer--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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[] samples = new float[mChannelCount * 2];
|
||||||
|
System.arraycopy(mFilteredSamples, 0, samples, 0, mFilteredSamples.length);
|
||||||
|
|
||||||
|
//FFT is executed in-place where the output overwrites the input
|
||||||
|
mFFT.complexForward(samples);
|
||||||
|
|
||||||
|
dispatch(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
mFilteredSamples = new float[mChannelCount * 2];
|
||||||
|
mFFT = new FloatFFT_1D(mChannelCount);
|
||||||
|
mFilterPointer = mChannelCount -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
mLog.debug("Starting ...");
|
||||||
|
|
||||||
|
int symbolRate = 4800;
|
||||||
|
int samplesPerSymbol = 2;
|
||||||
|
int symbolCount = 4;
|
||||||
|
int channels = 10;
|
||||||
|
int channelBandwidth = 12500;
|
||||||
|
int sampleRate = channels * channelBandwidth;
|
||||||
|
|
||||||
|
//Alpha is the residual channel bandwidth left over from the symbol rate and samples per symbol
|
||||||
|
float alpha = ((float)channelBandwidth / (float)(symbolRate * samplesPerSymbol)) - 1.0f;
|
||||||
|
|
||||||
|
float[] taps = FilterFactory.getRootRaisedCosine(samplesPerSymbol * channels, symbolCount, alpha);
|
||||||
|
|
||||||
|
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("Alpha: " + alpha + " Tap Count:" + taps.length + "\n");
|
||||||
|
sb.append("Channel:" + channelCenter);
|
||||||
|
|
||||||
|
mLog.debug(sb.toString());
|
||||||
|
|
||||||
|
PolyphaseChannelizer2 channelizer = new PolyphaseChannelizer2(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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ package dsp.mixer;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.complex.ComplexBuffer;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@@ -111,16 +112,57 @@ public class LowPhaseNoiseOscillator
|
|||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
LowPhaseNoiseOscillator o = new LowPhaseNoiseOscillator(10, 1);
|
int sampleRate = 10000000;
|
||||||
|
int frequency = 24000;
|
||||||
|
int sampleSize = 65536;
|
||||||
|
int warmUpIterations = 100000;
|
||||||
|
int testIterations = 500000;
|
||||||
|
|
||||||
float[] samples = o.generate(20);
|
LowPhaseNoiseOscillator o = new LowPhaseNoiseOscillator(sampleRate, frequency);
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
mLog.debug("Warming up ...");
|
||||||
for(int x = 0; x < samples.length; x += 2)
|
|
||||||
|
for(int x = 0; x < warmUpIterations; x++)
|
||||||
{
|
{
|
||||||
sb.append(samples[x]).append(" ").append(samples[x + 1]).append("\n");
|
float[] samples = o.generate(sampleSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
mLog.debug("Samples:\n" + sb.toString());
|
mLog.debug("Testing ...");
|
||||||
|
|
||||||
|
long start = System.nanoTime();
|
||||||
|
|
||||||
|
for(int x = 0; x < testIterations; x++)
|
||||||
|
{
|
||||||
|
float[] samples = o.generate(sampleSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
long duration = System.nanoTime() - start;
|
||||||
|
|
||||||
|
double durationSeconds = (double)duration / 1000000000.0;
|
||||||
|
mLog.debug("New Took:" + durationSeconds + " seconds to generate " + (testIterations * sampleSize) + " samples");
|
||||||
|
|
||||||
|
Oscillator o2 = new Oscillator(frequency, sampleRate);
|
||||||
|
|
||||||
|
mLog.debug("Warming up ...");
|
||||||
|
|
||||||
|
for(int x = 0; x < warmUpIterations; x++)
|
||||||
|
{
|
||||||
|
ComplexBuffer samples = o2.generateComplexBuffer(sampleSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Testing ...");
|
||||||
|
|
||||||
|
long start2 = System.nanoTime();
|
||||||
|
|
||||||
|
for(int x = 0; x < testIterations; x++)
|
||||||
|
{
|
||||||
|
float[] samples = o.generate(sampleSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
long duration2 = System.nanoTime() - start2;
|
||||||
|
|
||||||
|
double durationSeconds2 = (double)duration2 / 1000000000.0;
|
||||||
|
mLog.debug("New Took:" + durationSeconds2 + " seconds to generate " + (testIterations * sampleSize) + " samples");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user