diff --git a/src/dsp/filter/FilterFactory.java b/src/dsp/filter/FilterFactory.java
index 5ca5a078..15b673f2 100644
--- a/src/dsp/filter/FilterFactory.java
+++ b/src/dsp/filter/FilterFactory.java
@@ -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;
diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java
new file mode 100644
index 00000000..94b8fb8c
--- /dev/null
+++ b/src/dsp/filter/channelizer/ChannelizerViewer.java
@@ -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
+ *
+ ******************************************************************************/
+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
+ {
+ 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();
+ }
+ });
+ }
+
+}
diff --git a/src/dsp/filter/channelizer/PolyphaseChannelizer.java b/src/dsp/filter/channelizer/PolyphaseChannelizer.java
index daf257a6..a41e07fb 100644
--- a/src/dsp/filter/channelizer/PolyphaseChannelizer.java
+++ b/src/dsp/filter/channelizer/PolyphaseChannelizer.java
@@ -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
{
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!");
diff --git a/src/dsp/filter/design/FilterViewer.java b/src/dsp/filter/design/FilterViewer.java
index 5526d824..bedc583f 100644
--- a/src/dsp/filter/design/FilterViewer.java
+++ b/src/dsp/filter/design/FilterViewer.java
@@ -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;
}
diff --git a/src/spectrum/SpectrumPanel.java b/src/spectrum/SpectrumPanel.java
index d2295cbe..9e484cbc 100644
--- a/src/spectrum/SpectrumPanel.java
+++ b/src/spectrum/SpectrumPanel.java
@@ -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();