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();