mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
[WIP] working on polyphase channelizer and testing viewer
This commit is contained in:
@@ -662,16 +662,22 @@ public class FilterFactory
|
||||
*
|
||||
* Symbols = -44 * alpha + 33
|
||||
*
|
||||
* Polyphase Channelizer notes:
|
||||
* -Set samples Per Symbol at 2 (or more) * number of channels
|
||||
* -Set symbolCount to sufficient size to produce requireded attenuation
|
||||
* -Set symbol rate in hertz
|
||||
* -Set alpha = (desired channel bandwidth / (symbol rate * samples per symbol) - 1.0
|
||||
* -Channel bandwidth must be larger than symbol rate * samples per symbol, in order for there to be a positive alpha
|
||||
*
|
||||
* @param samplesPerSymbol - number of samples per symbol
|
||||
* @param symbols - number of symbols - must be even
|
||||
* @param alpha - roll-off factor
|
||||
* @param symbolCount - number of symbol intervals for the filter. This directly impacts the filter size
|
||||
* @param alpha - roll-off factor.
|
||||
*
|
||||
* @return - filter coefficients
|
||||
*/
|
||||
public static float[] getRootRaisedCosine(int samplesPerSymbol,
|
||||
int symbols,
|
||||
float alpha)
|
||||
public static float[] getRootRaisedCosine(int samplesPerSymbol, int symbolCount, float alpha)
|
||||
{
|
||||
int taps = samplesPerSymbol * symbols + 1;
|
||||
int taps = samplesPerSymbol * symbolCount;
|
||||
|
||||
float scale = 0;
|
||||
|
||||
|
||||
234
src/dsp/filter/channelizer/ChannelizerViewer.java
Normal file
234
src/dsp/filter/channelizer/ChannelizerViewer.java
Normal file
@@ -0,0 +1,234 @@
|
||||
/*******************************************************************************
|
||||
* 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.mixer.Oscillator;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.SampleType;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import settings.SettingsManager;
|
||||
import source.tuner.configuration.TunerConfigurationModel;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import spectrum.DFTProcessor;
|
||||
import spectrum.DFTSize;
|
||||
import spectrum.SpectrumPanel;
|
||||
import spectrum.converter.ComplexDecibelConverter;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer.class);
|
||||
|
||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||
|
||||
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
||||
private JPanel mPrimaryPanel;
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ChannelPanel mChannelPanel;
|
||||
private Oscillator mOscillator;
|
||||
private int mChannelCount;
|
||||
private int mSampleRate;
|
||||
private int mToneFrequency;
|
||||
private DFTSize mDFTSize = DFTSize.FFT04096;
|
||||
|
||||
public ChannelizerViewer(int channelCount)
|
||||
{
|
||||
mChannelCount = channelCount;
|
||||
mSampleRate = mChannelCount * CHANNEL_BANDWIDTH;
|
||||
mToneFrequency = 37500; //Set to channel 1 as default
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setTitle("Channelizer Viewer");
|
||||
setSize(1000, 800);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
setLocationRelativeTo(null);
|
||||
add(getPrimaryPanel());
|
||||
|
||||
mOscillator = new Oscillator(mToneFrequency, mSampleRate);
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
|
||||
scheduledExecutorService.scheduleAtFixedRate(new SampleGenerationTask(), 0, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void setToneFrequency(int frequency)
|
||||
{
|
||||
mOscillator.setFrequency(frequency);
|
||||
mToneFrequency = frequency;
|
||||
}
|
||||
|
||||
private void generateSamples()
|
||||
{
|
||||
int size = mDFTSize.getSize();
|
||||
|
||||
float[] samples = new float[size * 2];
|
||||
|
||||
for(int x = 0; x < size; x += 2)
|
||||
{
|
||||
samples[x] = mOscillator.getComplex().inphase();
|
||||
samples[x + 1] = mOscillator.getComplex().quadrature();
|
||||
|
||||
mOscillator.rotate();
|
||||
}
|
||||
|
||||
ComplexBuffer buffer = new ComplexBuffer(samples);
|
||||
|
||||
getChannelPanel().receive(buffer);
|
||||
mDFTProcessor.receive(buffer);
|
||||
}
|
||||
|
||||
private JPanel getPrimaryPanel()
|
||||
{
|
||||
if(mPrimaryPanel == null)
|
||||
{
|
||||
mPrimaryPanel = new JPanel();
|
||||
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][grow,fill]"));
|
||||
mPrimaryPanel.add(getSpectrumPanel(), "wrap");
|
||||
mPrimaryPanel.add(getChannelPanel());
|
||||
}
|
||||
|
||||
return mPrimaryPanel;
|
||||
}
|
||||
|
||||
private SpectrumPanel getSpectrumPanel()
|
||||
{
|
||||
if(mSpectrumPanel == null)
|
||||
{
|
||||
mSpectrumPanel = new SpectrumPanel(mSettingsManager);
|
||||
mSpectrumPanel.setPreferredSize(new Dimension(1000, 200));
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.frequencyChanged(new FrequencyChangeEvent(
|
||||
FrequencyChangeEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mSampleRate));
|
||||
}
|
||||
|
||||
return mSpectrumPanel;
|
||||
}
|
||||
|
||||
private ChannelPanel getChannelPanel()
|
||||
{
|
||||
if(mChannelPanel == null)
|
||||
{
|
||||
mChannelPanel = new ChannelPanel(getFilter());
|
||||
}
|
||||
|
||||
return mChannelPanel;
|
||||
}
|
||||
|
||||
private float[] getFilter()
|
||||
{
|
||||
int symbolRate = 4800;
|
||||
int samplesPerSymbol = 2;
|
||||
int symbolCount = 8;
|
||||
|
||||
//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[] taps = FilterFactory.getRootRaisedCosine(samplesPerSymbol * mChannelCount, symbolCount, alpha);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\nPolyphase Channelizer\n");
|
||||
sb.append("Sample Rate:" + mSampleRate + " Channels:" + mChannelCount + " Channel Rate:" + CHANNEL_BANDWIDTH + "\n");
|
||||
sb.append("Alpha: " + alpha + " Tap Count:" + taps.length + "\n");
|
||||
sb.append("Channel:" + mToneFrequency);
|
||||
mLog.debug(sb.toString());
|
||||
|
||||
return taps;
|
||||
}
|
||||
|
||||
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>
|
||||
{
|
||||
private PolyphaseChannelizer mPolyphaseChannelizer;
|
||||
|
||||
public ChannelPanel(float[] taps)
|
||||
{
|
||||
mPolyphaseChannelizer = new PolyphaseChannelizer(taps, mChannelCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
mPolyphaseChannelizer.receive(complexBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public class SampleGenerator
|
||||
{
|
||||
public SampleGenerator()
|
||||
{
|
||||
mLog.debug("Starting ...");
|
||||
|
||||
|
||||
PolyphaseChannelizer channelizer = new PolyphaseChannelizer(getFilter(), mChannelCount);
|
||||
|
||||
Oscillator oscillator = new Oscillator(mToneFrequency, mSampleRate);
|
||||
|
||||
for(int x = 0; x < 2000; x++)
|
||||
{
|
||||
channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature());
|
||||
oscillator.rotate();
|
||||
}
|
||||
|
||||
mLog.debug("Finished!");
|
||||
}
|
||||
}
|
||||
|
||||
public class SampleGenerationTask implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
generateSamples();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
final ChannelizerViewer frame = new ChannelizerViewer(16);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
frame.setVisible(true);
|
||||
frame.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
******************************************************************************/
|
||||
package dsp.filter.channelizer;
|
||||
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.design.FilterDesignException;
|
||||
import dsp.filter.fir.FIRFilterSpecification;
|
||||
import dsp.filter.fir.real.RealFIRFilter;
|
||||
@@ -26,11 +27,12 @@ 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 PolyphaseChannelizer
|
||||
public class PolyphaseChannelizer implements Listener<ComplexBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelizer.class);
|
||||
|
||||
@@ -90,9 +92,10 @@ public class PolyphaseChannelizer
|
||||
}
|
||||
}
|
||||
|
||||
public void filter(ComplexBuffer buffer)
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
float[] samples = complexBuffer.getSamples();
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
@@ -115,22 +118,22 @@ public class PolyphaseChannelizer
|
||||
}
|
||||
|
||||
//FFT is executed in-place where the output overwrites the input
|
||||
mFFT.complexInverse(samples, true);
|
||||
mFFT.complexForward(samples);
|
||||
|
||||
dispatch(samples);
|
||||
}
|
||||
|
||||
private void dispatch(float[] channels)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(int x = 0; x < channels.length; x += 2)
|
||||
{
|
||||
sb.append((x/2)).append("-")
|
||||
.append(DECIMAL_FORMAT.format(magnitude(channels[x], channels[x + 1], mChannelCount))).append(" ");
|
||||
}
|
||||
|
||||
mLog.debug("Out: " + sb.toString());
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
//
|
||||
// for(int x = 0; x < channels.length; x += 2)
|
||||
// {
|
||||
// sb.append((x/2)).append("[")
|
||||
// .append(DECIMAL_FORMAT.format(magnitude(channels[x], channels[x + 1], mChannelCount))).append("] ");
|
||||
// }
|
||||
//
|
||||
// mLog.debug("Out: " + sb.toString());
|
||||
}
|
||||
|
||||
private double magnitude(float real, float imaginary, int fftSize)
|
||||
@@ -183,39 +186,36 @@ public class PolyphaseChannelizer
|
||||
{
|
||||
mLog.debug("Starting ...");
|
||||
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(4000)
|
||||
.gridDensity(16)
|
||||
.passBandCutoff(950)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(1050)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.005)
|
||||
.build();
|
||||
int symbolRate = 4800;
|
||||
int samplesPerSymbol = 2;
|
||||
int symbolCount = 4;
|
||||
int channels = 10;
|
||||
int channelBandwidth = 12500;
|
||||
int sampleRate = channels * channelBandwidth;
|
||||
|
||||
RemezFIRFilterDesigner filterDesigner = new RemezFIRFilterDesigner(specification);
|
||||
//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;
|
||||
|
||||
if(filterDesigner.isValid())
|
||||
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());
|
||||
|
||||
PolyphaseChannelizer channelizer = new PolyphaseChannelizer(taps, 8);
|
||||
|
||||
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
|
||||
|
||||
for(int x = 0; x < 2000; x++)
|
||||
{
|
||||
try
|
||||
{
|
||||
float[] taps = filterDesigner.getImpulseResponse();
|
||||
|
||||
PolyphaseChannelizer channelizer = new PolyphaseChannelizer(taps, 8);
|
||||
|
||||
Oscillator oscillator = new Oscillator(-1000, 8000);
|
||||
|
||||
for(int x = 0; x < 2000; x++)
|
||||
{
|
||||
channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature());
|
||||
oscillator.rotate();
|
||||
}
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Couldn't design filter to specification");
|
||||
}
|
||||
channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature());
|
||||
oscillator.rotate();
|
||||
}
|
||||
|
||||
mLog.debug("Finished!");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dsp.filter.design;
|
||||
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.fir.FIRFilterSpecification;
|
||||
import dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import javafx.application.Application;
|
||||
@@ -39,16 +40,16 @@ public class FilterViewer extends Application
|
||||
*/
|
||||
private float[] getFilter()
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(4000)
|
||||
.gridDensity(16)
|
||||
.passBandCutoff(950)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(1050)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.005)
|
||||
.build();
|
||||
// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
// .sampleRate(4000)
|
||||
// .gridDensity(16)
|
||||
// .passBandCutoff(950)
|
||||
// .passBandAmplitude(1.0)
|
||||
// .passBandRipple(0.01)
|
||||
// .stopBandStart(1050)
|
||||
// .stopBandAmplitude(0.0)
|
||||
// .stopBandRipple(0.005)
|
||||
// .build();
|
||||
|
||||
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
||||
// .sampleRate( 24000 )
|
||||
@@ -74,19 +75,21 @@ public class FilterViewer extends Application
|
||||
|
||||
// taps = FilterFactory.getLowPass( 16000, 2400, 73, WindowType.HANNING );
|
||||
|
||||
try
|
||||
{
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
taps = FilterFactory.getRootRaisedCosine(2 * 8, 16, (12500.0f / 9600.0f - 1.0f));
|
||||
|
||||
if(designer.isValid())
|
||||
{
|
||||
taps = designer.getImpulseResponse();
|
||||
}
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Filter design error", fde);
|
||||
}
|
||||
// try
|
||||
// {
|
||||
// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
//
|
||||
// if(designer.isValid())
|
||||
// {
|
||||
// taps = designer.getImpulseResponse();
|
||||
// }
|
||||
// }
|
||||
// catch(FilterDesignException fde)
|
||||
// {
|
||||
// mLog.error("Filter design error", fde);
|
||||
// }
|
||||
|
||||
return taps;
|
||||
}
|
||||
|
||||
@@ -45,24 +45,18 @@ import dsp.filter.smoothing.SmoothingFilter;
|
||||
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
import dsp.filter.smoothing.TriangularSmoothingFilter;
|
||||
|
||||
public class SpectrumPanel extends JPanel
|
||||
implements DFTResultsListener,
|
||||
SettingChangeListener,
|
||||
SpectralDisplayAdjuster
|
||||
public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( SpectrumPanel.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger( SpectrumPanel.class );
|
||||
|
||||
private static final RenderingHints RENDERING_HINTS =
|
||||
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON );
|
||||
new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
|
||||
|
||||
static
|
||||
{
|
||||
RENDERING_HINTS.put( RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY );
|
||||
RENDERING_HINTS.put( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
|
||||
}
|
||||
|
||||
private Color mColorSpectrumBackground;
|
||||
@@ -77,7 +71,7 @@ public class SpectrumPanel extends JPanel
|
||||
private float[] mDisplayFFTBins = new float[ 1 ];
|
||||
|
||||
//Averaging across multiple DFT result sets
|
||||
private int mAveraging = 4;
|
||||
private int mAveraging = 1;
|
||||
|
||||
//Smoothing across bins in the same DFT result set
|
||||
private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
|
||||
|
||||
Reference in New Issue
Block a user