mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
[WIP] Work continues on resolving issues with the 2-channel synthesizer
that is currently up/down converting the signal of interest, but the synthesizer is not correctly combining the original two channels. Some experimentation with the tap count for the channelizer indicates that we can cut the tap count in half and still have decent decoding on the P25 decoder when paired to an aligned channelized output. The 2-channel synthesized output decodes partially, but needs more work. Resolved issue with channel gain in the channelizer that was making the sample values so small that they were registering as zero when converted from float to 16-bit int value in the complex wave recorder.
This commit is contained in:
@@ -121,6 +121,32 @@ public class FilterFactory
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes all filter coefficients to achieve an overall objective gain, by ensuring
|
||||
* that the sum of the absolute value of all coefficients adds up to the objective.
|
||||
*
|
||||
* @param coefficients
|
||||
* @return
|
||||
*/
|
||||
public static float[] normalize(float[] coefficients, float objective)
|
||||
{
|
||||
float accumulator = 0;
|
||||
|
||||
for(int x = 0; x < coefficients.length; x++)
|
||||
{
|
||||
accumulator += Math.abs(coefficients[x]);
|
||||
}
|
||||
|
||||
accumulator /= objective;
|
||||
|
||||
for(int x = 0; x < coefficients.length; x++)
|
||||
{
|
||||
coefficients[x] = coefficients[x] / accumulator;
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an array of unity (1) response values representing the
|
||||
* desired (pre-windowing) frequency response, used by the sync function
|
||||
@@ -740,8 +766,11 @@ public class FilterFactory
|
||||
public static float[] getSincM2Channelizer(double channelBandwidth, int channels, int tapsPerChannel,
|
||||
Window.WindowType windowType, boolean logResults) throws FilterDesignException
|
||||
{
|
||||
return getSincFilter(channelBandwidth * channels, channelBandwidth,
|
||||
float[] filter = getSincFilter(channelBandwidth * channels, channelBandwidth,
|
||||
channels, tapsPerChannel, windowType,logResults);
|
||||
|
||||
//Increase the overall gain of the filter to an objective of 1.0 times the number of channels
|
||||
return normalize(filter, 1.0f * (float)channels);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -759,8 +788,11 @@ public class FilterFactory
|
||||
public static float[] getSincM2Synthesizer(double channelBandwidth, int channels, int tapsPerChannel,
|
||||
Window.WindowType windowType, boolean logResults) throws FilterDesignException
|
||||
{
|
||||
return getSincFilter(channelBandwidth * channels, channelBandwidth, channels,
|
||||
float[] filter = getSincFilter(channelBandwidth * channels, channelBandwidth, channels,
|
||||
tapsPerChannel, windowType,logResults);
|
||||
|
||||
//Decrease the overall gain of the filter to an objective of 1.0 divided by the number of channels
|
||||
return normalize(filter, 1.0f);// / (float)channels);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,589 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* *********************************************************************************************************************
|
||||
* sdr-trunk
|
||||
* 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 io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
|
||||
import io.github.dsheirer.source.tuner.test.TestTuner;
|
||||
import io.github.dsheirer.spectrum.DFTProcessor;
|
||||
import io.github.dsheirer.spectrum.DFTSize;
|
||||
import io.github.dsheirer.spectrum.SpectrumPanel;
|
||||
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer.class);
|
||||
|
||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||
private static final int CHANNEL_FFT_FRAME_RATE = 20;
|
||||
|
||||
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
||||
private JPanel mPrimaryPanel;
|
||||
private JPanel mControlPanel;
|
||||
private JLabel mToneFrequencyLabel;
|
||||
private PrimarySpectrumPanel mPrimarySpectrumPanel;
|
||||
private ChannelArrayPanel mChannelPanel;
|
||||
private DiscreteIndexChannelArrayPanel mDiscreteIndexChannelPanel;
|
||||
private int mChannelCount;
|
||||
private int mChannelsPerRow;
|
||||
private long mBaseFrequency = 100000000; //100 MHz
|
||||
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
||||
private DFTSize mChannelPanelDFTSize = DFTSize.FFT04096;
|
||||
private TestTuner mTestTuner;
|
||||
|
||||
/**
|
||||
* GUI Test utility for researching polyphase channelizers.
|
||||
*
|
||||
* @param channelsPerRow
|
||||
*/
|
||||
public ChannelizerViewer(int channelsPerRow)
|
||||
{
|
||||
mTestTuner = new TestTuner();
|
||||
|
||||
// mChannelCount = (int)(mTestTuner.getTunerController().getUsableBandwidth() / CHANNEL_BANDWIDTH);
|
||||
mChannelCount = (int)(mTestTuner.getTunerController().getBandwidth() / CHANNEL_BANDWIDTH);
|
||||
mChannelsPerRow = channelsPerRow;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setTitle("Polyphase Channelizer Viewer");
|
||||
setSize(1200, 800);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
setLocationRelativeTo(null);
|
||||
add(getPrimaryPanel());
|
||||
}
|
||||
|
||||
private JPanel getPrimaryPanel()
|
||||
{
|
||||
if(mPrimaryPanel == null)
|
||||
{
|
||||
mPrimaryPanel = new JPanel();
|
||||
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][][grow,fill][grow,fill]"));
|
||||
mPrimaryPanel.add(getSpectrumPanel(), "wrap");
|
||||
mPrimaryPanel.add(getControlPanel(), "wrap");
|
||||
mPrimaryPanel.add(getChannelArrayPanel(), "wrap");
|
||||
mPrimaryPanel.add(getDiscreteIndexChannelPanel());
|
||||
}
|
||||
|
||||
return mPrimaryPanel;
|
||||
}
|
||||
|
||||
private PrimarySpectrumPanel getSpectrumPanel()
|
||||
{
|
||||
if(mPrimarySpectrumPanel == null)
|
||||
{
|
||||
mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager,
|
||||
mTestTuner.getTunerController().getSampleRate());
|
||||
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1200, 200));
|
||||
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
|
||||
mTestTuner.getTunerController().addBufferListener(mPrimarySpectrumPanel);
|
||||
}
|
||||
|
||||
return mPrimarySpectrumPanel;
|
||||
}
|
||||
|
||||
private JPanel getControlPanel()
|
||||
{
|
||||
if(mControlPanel == null)
|
||||
{
|
||||
mControlPanel = new JPanel();
|
||||
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
|
||||
|
||||
mControlPanel.add(new JLabel("Tone:"), "align left");
|
||||
long minimumFrequency = -(long)mTestTuner.getTunerController().getSampleRate() / 2 + 1;
|
||||
long maximumFrequency = (long)mTestTuner.getTunerController().getSampleRate() / 2 - 1;
|
||||
long toneFrequency = 0;
|
||||
|
||||
SpinnerNumberModel model = new SpinnerNumberModel(toneFrequency, minimumFrequency, maximumFrequency,
|
||||
100);
|
||||
|
||||
model.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
long toneFrequency = model.getNumber().longValue();
|
||||
mTestTuner.getTunerController().setToneFrequency(toneFrequency);
|
||||
mToneFrequencyLabel.setText(String.valueOf(getToneFrequency()));
|
||||
}
|
||||
});
|
||||
|
||||
JSpinner spinner = new JSpinner(model);
|
||||
|
||||
mControlPanel.add(spinner);
|
||||
mControlPanel.add(new JLabel("Hz"));
|
||||
|
||||
mControlPanel.add(new JLabel("Frequency:"), "push,align right");
|
||||
mToneFrequencyLabel = new JLabel(String.valueOf(getToneFrequency()));
|
||||
mControlPanel.add(mToneFrequencyLabel, "push,align left");
|
||||
|
||||
mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right");
|
||||
}
|
||||
|
||||
return mControlPanel;
|
||||
}
|
||||
|
||||
private long getToneFrequency()
|
||||
{
|
||||
return mTestTuner.getTunerController().getFrequency() + mTestTuner.getTunerController().getToneFrequency();
|
||||
}
|
||||
|
||||
private ChannelArrayPanel getChannelArrayPanel()
|
||||
{
|
||||
if(mChannelPanel == null)
|
||||
{
|
||||
mChannelPanel = new ChannelArrayPanel();
|
||||
}
|
||||
|
||||
return mChannelPanel;
|
||||
}
|
||||
|
||||
private DiscreteIndexChannelArrayPanel getDiscreteIndexChannelPanel()
|
||||
{
|
||||
if(mDiscreteIndexChannelPanel == null)
|
||||
{
|
||||
mDiscreteIndexChannelPanel = new DiscreteIndexChannelArrayPanel();
|
||||
}
|
||||
|
||||
return mDiscreteIndexChannelPanel;
|
||||
}
|
||||
|
||||
public class ChannelArrayPanel extends JPanel
|
||||
{
|
||||
public ChannelArrayPanel()
|
||||
{
|
||||
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
|
||||
if(bufferSize % 2 == 1)
|
||||
{
|
||||
bufferSize++;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill"));
|
||||
|
||||
double spectralBandwidth = mTestTuner.getTunerController().getSampleRate();
|
||||
double halfSpectralBandwidth = spectralBandwidth / 2.0;
|
||||
|
||||
int channelToLog = -1;
|
||||
|
||||
long baseFrequency = mBaseFrequency + (CHANNEL_BANDWIDTH / 2);
|
||||
|
||||
for(int x = 0; x < mChannelCount; x++)
|
||||
{
|
||||
long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH);
|
||||
|
||||
mLog.debug("Channel " + x + "/" + mChannelCount + " Frequency: " + frequency);
|
||||
|
||||
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH, (x == channelToLog));
|
||||
channelPanel.setDFTSize(mChannelPanelDFTSize);
|
||||
|
||||
if(x % mChannelsPerRow == mChannelsPerRow - 1)
|
||||
{
|
||||
add(channelPanel, "grow,push,wrap 2px");
|
||||
}
|
||||
else
|
||||
{
|
||||
add(channelPanel, "grow,push");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DiscreteIndexChannelArrayPanel extends JPanel
|
||||
{
|
||||
public DiscreteIndexChannelArrayPanel()
|
||||
{
|
||||
int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE;
|
||||
if(bufferSize % 2 == 1)
|
||||
{
|
||||
bufferSize++;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill"));
|
||||
|
||||
for(int x = 0; x < mChannelCount; x++)
|
||||
{
|
||||
TunerChannelSource source = mTestTuner.getChannelSourceManager().getTestingSource(x);
|
||||
DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x);
|
||||
channelPanel.setDFTSize(mChannelPanelDFTSize);
|
||||
|
||||
if(x % mChannelsPerRow == mChannelsPerRow - 1)
|
||||
{
|
||||
add(channelPanel, "grow,push,wrap 2px");
|
||||
}
|
||||
else
|
||||
{
|
||||
add(channelPanel, "grow,push");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tuner channels that will fit within the tuner's bandwidth, minus a half channel each at the
|
||||
* lower and upper ends of the spectrum.
|
||||
*
|
||||
* @param tuner to create channels for
|
||||
* @return list of contiguous channels filling the tuner bandwidth
|
||||
*/
|
||||
public static List<TunerChannel> getTunerChannels(Tuner tuner)
|
||||
{
|
||||
List<TunerChannel> tunerChannels = new ArrayList<>();
|
||||
|
||||
long baseFrequency = tuner.getTunerController().getFrequency();
|
||||
baseFrequency -= tuner.getTunerController().getSampleRate() / 2;
|
||||
baseFrequency += (CHANNEL_BANDWIDTH / 2);
|
||||
|
||||
int channelCount = (int)(tuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH) - 1;
|
||||
|
||||
for(int x = 0; x < channelCount; x++)
|
||||
{
|
||||
long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH);
|
||||
TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH);
|
||||
tunerChannels.add(tunerChannel);
|
||||
}
|
||||
|
||||
return tunerChannels;
|
||||
}
|
||||
|
||||
public class PrimarySpectrumPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
|
||||
public PrimarySpectrumPanel(SettingsManager settingsManager, double sampleRate)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
mSpectrumPanel.setSampleSize(16);
|
||||
add(mSpectrumPanel);
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
}
|
||||
|
||||
public void setDFTSize(DFTSize dftSize)
|
||||
{
|
||||
mDFTProcessor.setDFTSize(dftSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
mDFTProcessor.receive(reusableComplexBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event) throws SourceException
|
||||
{
|
||||
mLog.debug("Source Event! Add handler support for this to channelizer viewer");
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private TunerChannelSource mSource;
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private JToggleButton mLoggingButton;
|
||||
private boolean mLoggingEnabled;
|
||||
|
||||
public ChannelPanel(SettingsManager settingsManager, double sampleRate, long frequency, int bandwidth, boolean enableLogging)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[center,grow,fill][]", "[grow,fill][]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
mSpectrumPanel.setSampleSize(16);
|
||||
add(mSpectrumPanel, "span");
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
|
||||
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
|
||||
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
|
||||
|
||||
if(mSource != null)
|
||||
{
|
||||
mSource.setListener(new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer complexBuffer)
|
||||
{
|
||||
if(mLoggingEnabled)
|
||||
{
|
||||
mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples()));
|
||||
}
|
||||
|
||||
complexBuffer.incrementUserCount();
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
complexBuffer.decrementUserCount();
|
||||
}
|
||||
});
|
||||
|
||||
mSource.start();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Couldn't get a source from the tuner for frequency: " + frequency);
|
||||
}
|
||||
|
||||
if(mSource != null)
|
||||
{
|
||||
add(new JLabel("Center:" + frequency));
|
||||
}
|
||||
else
|
||||
{
|
||||
add(new JLabel("NO SRC:" + frequency));
|
||||
}
|
||||
|
||||
mLoggingButton = new JToggleButton("Logging");
|
||||
mLoggingButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
mLoggingEnabled = mLoggingButton.isSelected();
|
||||
}
|
||||
});
|
||||
// add(mLoggingButton);
|
||||
|
||||
}
|
||||
|
||||
public TunerChannelSource getSource()
|
||||
{
|
||||
return mSource;
|
||||
}
|
||||
|
||||
public void setDFTSize(DFTSize dftSize)
|
||||
{
|
||||
mDFTProcessor.setDFTSize(dftSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
reusableComplexBuffer.incrementUserCount();
|
||||
mDFTProcessor.receive(reusableComplexBuffer);
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event) throws SourceException
|
||||
{
|
||||
mLog.debug("Source Event! Add handler support for this to channelizer viewer");
|
||||
}
|
||||
}
|
||||
|
||||
public class DiscreteChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private TunerChannelSource mSource;
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private JToggleButton mLoggingButton;
|
||||
private boolean mLoggingEnabled;
|
||||
|
||||
public DiscreteChannelPanel(SettingsManager settingsManager, TunerChannelSource source, int index)
|
||||
{
|
||||
mSource = source;
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[center,grow,fill][]", "[grow,fill][]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
mSpectrumPanel.setSampleSize(16);
|
||||
add(mSpectrumPanel, "span");
|
||||
add(new JLabel("Index:" + index));
|
||||
|
||||
mLoggingButton = new JToggleButton("Logging");
|
||||
mLoggingButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
mLoggingEnabled = mLoggingButton.isSelected();
|
||||
}
|
||||
});
|
||||
// add(mLoggingButton);
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(25000.0));
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
|
||||
if(mSource != null)
|
||||
{
|
||||
mSource.setListener(new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer complexBuffer)
|
||||
{
|
||||
if(mLoggingEnabled)
|
||||
{
|
||||
mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples()));
|
||||
}
|
||||
|
||||
complexBuffer.incrementUserCount();
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
complexBuffer.decrementUserCount();
|
||||
}
|
||||
});
|
||||
|
||||
mSource.start();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Couldn't get a source from the tuner for index: " + index);
|
||||
}
|
||||
}
|
||||
|
||||
public TunerChannelSource getSource()
|
||||
{
|
||||
return mSource;
|
||||
}
|
||||
|
||||
public void setDFTSize(DFTSize dftSize)
|
||||
{
|
||||
mDFTProcessor.setDFTSize(dftSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
reusableComplexBuffer.incrementUserCount();
|
||||
mDFTProcessor.receive(reusableComplexBuffer);
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event) throws SourceException
|
||||
{
|
||||
mLog.debug("Source Event! Add handler support for this to channelizer viewer");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
boolean useGUI = true;
|
||||
|
||||
if(useGUI)
|
||||
{
|
||||
int channelsPerRow = 16;
|
||||
|
||||
final ChannelizerViewer frame = new ChannelizerViewer(channelsPerRow);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
frame.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
TestTuner tuner = new TestTuner();
|
||||
|
||||
List<TunerChannel> tunerChannels = getTunerChannels(tuner);
|
||||
|
||||
List<TunerChannelSource> sources = new ArrayList<>();
|
||||
|
||||
int maxSourceCount = 30;
|
||||
int sourceCount = 0;
|
||||
for(TunerChannel tunerChannel : tunerChannels)
|
||||
{
|
||||
if(sourceCount < maxSourceCount)
|
||||
{
|
||||
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel);
|
||||
|
||||
if(source != null)
|
||||
{
|
||||
sources.add(source);
|
||||
sourceCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Couldn't get source for: " + tunerChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mLog.debug("Starting [" + sources.size() + "] tuner channel sources");
|
||||
|
||||
for(TunerChannelSource tunerChannelSource : sources)
|
||||
{
|
||||
tunerChannelSource.setListener(new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
});
|
||||
|
||||
tunerChannelSource.start();
|
||||
}
|
||||
|
||||
while(true)
|
||||
{
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||
private static final double MINIMUM_CHANNEL_BANDWIDTH = 12500.0;
|
||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||
private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 16;
|
||||
private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 8;
|
||||
|
||||
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
||||
private IReusableBufferProvider<ReusableComplexBuffer> mReusableBufferProvider;
|
||||
@@ -370,7 +370,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
|
||||
//Broadcast the new channel sample rate
|
||||
double updatedChannelSampleRate = mChannelCalculator.getChannelSampleRate();
|
||||
updateOutputProcessors(SourceEvent.sampleRateChange(updatedChannelSampleRate));
|
||||
updateOutputProcessors(SourceEvent.sampleRateChange(updatedChannelSampleRate, "Channelizer Sample Rate Update"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -514,7 +514,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
if(taps == null)
|
||||
{
|
||||
taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelBandwidth(), channels,
|
||||
POLYPHASE_FILTER_TAPS_PER_CHANNEL, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
POLYPHASE_FILTER_TAPS_PER_CHANNEL + 20, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
|
||||
mOutputProcessorFilters.put(channels, taps);
|
||||
}
|
||||
@@ -530,6 +530,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
mLog.debug("****** Got a source event: " + sourceEvent.toString());
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case REQUEST_START_SAMPLE_STREAM:
|
||||
|
||||
@@ -199,7 +199,6 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
@Override
|
||||
protected void setFrequency(long frequency)
|
||||
{
|
||||
mLog.debug("Index Center Frequency set to: " + frequency + " for channel " + getTunerChannel().toString());
|
||||
mIndexCenterFrequency = frequency;
|
||||
|
||||
//Set frequency correction to zero to trigger an update to the mixer and allow downstream monitors to
|
||||
@@ -218,11 +217,12 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the frequency offset required to mix the incoming signal to the desired output baseband
|
||||
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
|
||||
* within the channel
|
||||
*/
|
||||
private long getFrequencyOffset()
|
||||
{
|
||||
return getTunerChannel().getFrequency() - mIndexCenterFrequency + mChannelFrequencyCorrection;
|
||||
return mIndexCenterFrequency - getTunerChannel().getFrequency() + mChannelFrequencyCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
|
||||
import io.github.dsheirer.spectrum.DFTProcessor;
|
||||
import io.github.dsheirer.spectrum.DFTSize;
|
||||
import io.github.dsheirer.spectrum.SpectrumPanel;
|
||||
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SynthesizerViewer extends JFrame
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(SynthesizerViewer.class);
|
||||
|
||||
private static final int CHANNEL_BANDWIDTH = 12500;
|
||||
private static final int CHANNEL_SAMPLE_RATE = 25000;
|
||||
private static final int CHANNEL_FFT_FRAME_RATE = 20; //frames per second
|
||||
private static final int DATA_GENERATOR_FRAME_RATE = 50; //frames per second
|
||||
|
||||
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
||||
private JPanel mPrimaryPanel;
|
||||
private PrimarySpectrumPanel mPrimarySpectrumPanel;
|
||||
private ChannelPanel mChannel1Panel;
|
||||
private ChannelPanel mChannel2Panel;
|
||||
private ChannelControlPanel mChannel1ControlPanel;
|
||||
private ChannelControlPanel mChannel2ControlPanel;
|
||||
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
||||
private DFTSize mChannelPanelDFTSize = DFTSize.FFT08192;
|
||||
|
||||
/**
|
||||
* GUI Test utility for researching polyphase synthesizers.
|
||||
*/
|
||||
public SynthesizerViewer()
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
ThreadPool.SCHEDULED.scheduleAtFixedRate(new DataGenerationManager(), 0, 1000 / DATA_GENERATOR_FRAME_RATE, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setTitle("Polyphase Synthesizer Viewer");
|
||||
setSize(500, 400);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
setLocationRelativeTo(null);
|
||||
add(getPrimaryPanel());
|
||||
}
|
||||
|
||||
private JPanel getPrimaryPanel()
|
||||
{
|
||||
if(mPrimaryPanel == null)
|
||||
{
|
||||
mPrimaryPanel = new JPanel();
|
||||
mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill]", "[grow,fill][grow,fill]"));
|
||||
mPrimaryPanel.add(getSpectrumPanel(), "span");
|
||||
mPrimaryPanel.add(getChannel1Panel());
|
||||
mPrimaryPanel.add(getChannel2Panel());
|
||||
}
|
||||
|
||||
return mPrimaryPanel;
|
||||
}
|
||||
|
||||
private PrimarySpectrumPanel getSpectrumPanel()
|
||||
{
|
||||
if(mPrimarySpectrumPanel == null)
|
||||
{
|
||||
mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager);
|
||||
mPrimarySpectrumPanel.setPreferredSize(new Dimension(500, 200));
|
||||
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
|
||||
}
|
||||
|
||||
return mPrimarySpectrumPanel;
|
||||
}
|
||||
|
||||
private ChannelPanel getChannel1Panel()
|
||||
{
|
||||
if(mChannel1Panel == null)
|
||||
{
|
||||
mChannel1Panel = new ChannelPanel(mSettingsManager, getChannel1ControlPanel());
|
||||
mChannel1Panel.setPreferredSize(new Dimension(250, 200));
|
||||
mChannel1Panel.setDFTSize(mChannelPanelDFTSize);
|
||||
}
|
||||
|
||||
return mChannel1Panel;
|
||||
}
|
||||
|
||||
private ChannelPanel getChannel2Panel()
|
||||
{
|
||||
if(mChannel2Panel == null)
|
||||
{
|
||||
mChannel2Panel = new ChannelPanel(mSettingsManager, getChannel2ControlPanel());
|
||||
mChannel2Panel.setPreferredSize(new Dimension(250, 200));
|
||||
mChannel2Panel.setDFTSize(mChannelPanelDFTSize);
|
||||
}
|
||||
|
||||
return mChannel2Panel;
|
||||
}
|
||||
|
||||
public class PrimarySpectrumPanel extends JPanel implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
|
||||
public PrimarySpectrumPanel(SettingsManager settingsManager)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
mSpectrumPanel.setSampleSize(22);
|
||||
add(mSpectrumPanel);
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(CHANNEL_SAMPLE_RATE));
|
||||
mDFTProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE);
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
|
||||
mDFTProcessor.setOverflowListener(new IOverflowListener()
|
||||
{
|
||||
@Override
|
||||
public void sourceOverflow(boolean overflow)
|
||||
{
|
||||
mLog.debug("Buffer " + (overflow ? "overflow" : "reset"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setDFTSize(DFTSize dftSize)
|
||||
{
|
||||
mDFTProcessor.setDFTSize(dftSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
mDFTProcessor.receive(reusableComplexBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private boolean mLoggingEnabled = false;
|
||||
|
||||
public ChannelPanel(SettingsManager settingsManager, ChannelControlPanel channelControlPanel)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
mSpectrumPanel.setSampleSize(22);
|
||||
add(mSpectrumPanel, "wrap");
|
||||
add(channelControlPanel);
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(CHANNEL_SAMPLE_RATE));
|
||||
mDFTProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE);
|
||||
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
}
|
||||
|
||||
public void setDFTSize(DFTSize dftSize)
|
||||
{
|
||||
mDFTProcessor.setDFTSize(dftSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
mDFTProcessor.receive(reusableComplexBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private ChannelControlPanel getChannel1ControlPanel()
|
||||
{
|
||||
if(mChannel1ControlPanel == null)
|
||||
{
|
||||
mChannel1ControlPanel = new ChannelControlPanel();
|
||||
}
|
||||
|
||||
return mChannel1ControlPanel;
|
||||
}
|
||||
|
||||
private ChannelControlPanel getChannel2ControlPanel()
|
||||
{
|
||||
if(mChannel2ControlPanel == null)
|
||||
{
|
||||
mChannel2ControlPanel = new ChannelControlPanel();
|
||||
}
|
||||
|
||||
return mChannel2ControlPanel;
|
||||
}
|
||||
|
||||
public class ChannelControlPanel extends JPanel
|
||||
{
|
||||
private static final int MIN_FREQUENCY = -6250;
|
||||
private static final int MAX_FREQUENCY = 6250;
|
||||
private static final int DEFAULT_FREQUENCY = 50;
|
||||
|
||||
private IOscillator mOscillator = new LowPhaseNoiseOscillator(DEFAULT_FREQUENCY, CHANNEL_SAMPLE_RATE);
|
||||
|
||||
public ChannelControlPanel()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
add(new JLabel("Tone:"), "align right");
|
||||
|
||||
SpinnerNumberModel model = new SpinnerNumberModel(DEFAULT_FREQUENCY, MIN_FREQUENCY, MAX_FREQUENCY, 100);
|
||||
model.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
long toneFrequency = model.getNumber().longValue();
|
||||
mOscillator.setFrequency(toneFrequency);
|
||||
}
|
||||
});
|
||||
|
||||
JSpinner spinner = new JSpinner(model);
|
||||
add(spinner);
|
||||
add(new JLabel("Hz"));
|
||||
}
|
||||
|
||||
public IOscillator getOscillator()
|
||||
{
|
||||
return mOscillator;
|
||||
}
|
||||
}
|
||||
|
||||
public class DataGenerationManager implements Runnable
|
||||
{
|
||||
private TwoChannelSynthesizerM2 mSynthesizer;
|
||||
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
|
||||
private int mSamplesPerCycle = CHANNEL_SAMPLE_RATE / DATA_GENERATOR_FRAME_RATE;
|
||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("SynthesizerViewer");
|
||||
|
||||
public DataGenerationManager()
|
||||
{
|
||||
try
|
||||
{
|
||||
float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19,
|
||||
Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
mSynthesizer = new TwoChannelSynthesizerM2(taps);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Filter design error", fde);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
ReusableComplexBuffer channel1Buffer = mReusableComplexBufferQueue.getBuffer(mSamplesPerCycle);
|
||||
getChannel1ControlPanel().getOscillator().generateComplex(channel1Buffer);
|
||||
|
||||
ReusableComplexBuffer channel2Buffer = mReusableComplexBufferQueue.getBuffer(mSamplesPerCycle);
|
||||
getChannel2ControlPanel().getOscillator().generateComplex(channel2Buffer);
|
||||
|
||||
float[] synthesized = mSynthesizer.process(channel1Buffer.getSamples(), channel2Buffer.getSamples());
|
||||
mFS4DownConverter.mixComplex(synthesized);
|
||||
ReusableComplexBuffer synthesizedBuffer = mReusableComplexBufferQueue.getBuffer(synthesized.length);
|
||||
|
||||
channel1Buffer.incrementUserCount();
|
||||
getChannel1Panel().receive(channel1Buffer);
|
||||
channel2Buffer.incrementUserCount();
|
||||
getChannel2Panel().receive(channel2Buffer);
|
||||
synthesizedBuffer.incrementUserCount();
|
||||
getSpectrumPanel().receive(synthesizedBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
final SynthesizerViewer frame = new SynthesizerViewer();
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
frame.setVisible(true);
|
||||
frame.start();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ public class TwoChannelSynthesizerM2
|
||||
|
||||
try
|
||||
{
|
||||
float[] taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
|
||||
TwoChannelSynthesizerM2 synthesizer = new TwoChannelSynthesizerM2(taps);
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||
@@ -26,10 +30,43 @@ import java.util.List;
|
||||
|
||||
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
|
||||
|
||||
|
||||
private static float[] mFilterCoefficients;
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(25000)
|
||||
.gridDensity(16)
|
||||
.oddLength(true)
|
||||
.passBandCutoff(6000)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(6500)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.005) //Approximately 94 dB attenuation
|
||||
.build();
|
||||
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
|
||||
if(designer.isValid())
|
||||
{
|
||||
mFilterCoefficients = designer.getImpulseResponse();
|
||||
}
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Error designing low pass filter for two channel output processor", fde);
|
||||
}
|
||||
}
|
||||
|
||||
private TwoChannelSynthesizerM2 mSynthesizer;
|
||||
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
|
||||
private ComplexFIRFilter mLowPassFilter;
|
||||
|
||||
private int mChannelOffset1;
|
||||
private int mChannelOffset2;
|
||||
@@ -45,9 +82,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
{
|
||||
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
|
||||
//after synthesizing both input channels
|
||||
super(2, sampleRate * 2);
|
||||
super(2, sampleRate);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
setSynthesisFilter(filter);
|
||||
|
||||
mLowPassFilter = new ComplexFIRFilter(mFilterCoefficients, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,6 +151,8 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
|
||||
getFrequencyCorrectionMixer().mixComplex(synthesized);
|
||||
|
||||
mLowPassFilter.filter(synthesized);
|
||||
|
||||
reusableBufferAssembler.receive(synthesized);
|
||||
|
||||
buffer.decrementUserCount();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package io.github.dsheirer.dsp.filter.design;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
@@ -48,7 +49,7 @@ public class FilterViewer extends Application
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(6500)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.028) //Approximately 60 dB attenuation
|
||||
.stopBandRipple(0.005) //Approximately 94 dB attenuation
|
||||
.build();
|
||||
|
||||
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
||||
@@ -73,29 +74,32 @@ public class FilterViewer extends Application
|
||||
|
||||
float[] taps = null;
|
||||
|
||||
// try
|
||||
// {
|
||||
// taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
// }
|
||||
// catch(FilterDesignException fde)
|
||||
// {
|
||||
// mLog.error("Error");
|
||||
// }
|
||||
|
||||
try
|
||||
{
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
|
||||
if(designer.isValid())
|
||||
{
|
||||
taps = designer.getImpulseResponse();
|
||||
}
|
||||
// taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
taps = FilterFactory.getSincFilter(25000.0, 12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Filter design error", fde);
|
||||
mLog.error("Error");
|
||||
}
|
||||
|
||||
// try
|
||||
// {
|
||||
// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
//
|
||||
// if(designer.isValid())
|
||||
// {
|
||||
// taps = designer.getImpulseResponse();
|
||||
// }
|
||||
// }
|
||||
// catch(FilterDesignException fde)
|
||||
// {
|
||||
// mLog.error("Filter design error", fde);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
return taps;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public class ComplexFIRFilter extends FIRFilter
|
||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Complex FIR Filter");
|
||||
private RealFIRFilter mIFilter;
|
||||
private RealFIRFilter mQFilter;
|
||||
private float[] mTemporaryBuffer;
|
||||
|
||||
/**
|
||||
* Complex FIR Filter for processing complex sample pairs. Wraps two real
|
||||
@@ -50,11 +51,21 @@ public class ComplexFIRFilter extends FIRFilter
|
||||
return mIFilter.filter(sample);
|
||||
}
|
||||
|
||||
public float currentInphaseValue()
|
||||
{
|
||||
return mIFilter.currentValue();
|
||||
}
|
||||
|
||||
public float filterQuadrature(float sample)
|
||||
{
|
||||
return mQFilter.filter(sample);
|
||||
}
|
||||
|
||||
public float currentQuadratureValue()
|
||||
{
|
||||
return mQFilter.currentValue();
|
||||
}
|
||||
|
||||
public Complex filter(Complex sample)
|
||||
{
|
||||
float i = filterInphase(sample.inphase());
|
||||
@@ -65,15 +76,20 @@ public class ComplexFIRFilter extends FIRFilter
|
||||
|
||||
public float[] filter(float[] samples)
|
||||
{
|
||||
float[] filteredSamples = new float[samples.length];
|
||||
if(mTemporaryBuffer == null || mTemporaryBuffer.length != samples.length)
|
||||
{
|
||||
mTemporaryBuffer = new float[samples.length];
|
||||
}
|
||||
|
||||
System.arraycopy(samples, 0, mTemporaryBuffer, 0, samples.length);
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
filteredSamples[x] = filterInphase(samples[x]);
|
||||
filteredSamples[x + 1] = filterQuadrature(samples[x + 1]);
|
||||
samples[x] = filterInphase(mTemporaryBuffer[x]);
|
||||
samples[x + 1] = filterQuadrature(mTemporaryBuffer[x + 1]);
|
||||
}
|
||||
|
||||
return filteredSamples;
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,7 @@ public class RealFIRFilter extends FIRFilter
|
||||
|
||||
private float[] mCoefficients;
|
||||
private float mGain;
|
||||
private float mAccumulator;
|
||||
|
||||
/**
|
||||
* Float sample FIR filter base class.
|
||||
@@ -84,12 +85,11 @@ public class RealFIRFilter extends FIRFilter
|
||||
{
|
||||
mBuffer[mBufferPointer] = sample;
|
||||
|
||||
float accumulator = 0.0f;
|
||||
mAccumulator = 0.0f;
|
||||
|
||||
for(int x = 0; x < mBufferSize; x++)
|
||||
{
|
||||
accumulator += mCoefficients[x] *
|
||||
mBuffer[mIndexMap[mBufferPointer][x]];
|
||||
mAccumulator += mCoefficients[x] * mBuffer[mIndexMap[mBufferPointer][x]];
|
||||
}
|
||||
|
||||
mBufferPointer--;
|
||||
@@ -100,7 +100,18 @@ public class RealFIRFilter extends FIRFilter
|
||||
}
|
||||
|
||||
/* Apply gain and return the filtered value */
|
||||
return accumulator * mGain;
|
||||
mAccumulator *= mGain;
|
||||
|
||||
return mAccumulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current output value for the filter after the filter() method has been invoked.
|
||||
* @return
|
||||
*/
|
||||
public float currentValue()
|
||||
{
|
||||
return mAccumulator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,17 +15,13 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.mixer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FS4DownConverter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FS4DownConverter.class);
|
||||
|
||||
private int mPointer = 0;
|
||||
|
||||
/**
|
||||
* Performs complex down-sampling by one-fourth (-FS/4) of the sample rate using simple value assignments.
|
||||
* Performs complex frequency conversion at one-fourth (-FS/4) of the sample rate using simple value
|
||||
* assignments.
|
||||
*
|
||||
* See: Digital Signal Processing 3e, Lyons, p.674-675
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user