[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:
Denny
2018-04-29 06:19:39 -04:00
parent e395d737f8
commit 488b2e02ce
19 changed files with 764 additions and 159 deletions

View File

@@ -0,0 +1,588 @@
/*******************************************************************************
* 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.gui.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)
{
;
}
}
}
}

View File

@@ -0,0 +1,467 @@
/*******************************************************************************
* 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.gui.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.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
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 ChannelizerViewer2 extends JFrame
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelizerViewer2.class);
private static final int CHANNEL_BANDWIDTH = 12500;
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
private JPanel mPrimaryPanel;
private JPanel mControlPanel;
private JButton mTopFrameAddChannelButton;
private JButton mBottomFrameAddChannelButton;
private JLabel mToneFrequencyLabel;
private JLabel mCenterFrequencyLabel;
private PrimarySpectrumPanel mPrimarySpectrumPanel;
private ChannelArrayPanel mTopChannelArrayPanel;
private ChannelArrayPanel mBottomChannelArrayPanel;
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
private DFTSize mChannelPanelDFTSize = DFTSize.FFT04096;
private TestTuner mTestTuner = new TestTuner();
/**
* GUI Test utility for researching polyphase channelizers.
*/
public ChannelizerViewer2()
{
init();
}
private void init()
{
setTitle("Polyphase Channelizer Viewer");
setSize(1200, 700);
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(getTopChannelArrayPanel(), "wrap");
mPrimaryPanel.add(getBottomChannelArrayPanel());
}
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()));
mCenterFrequencyLabel.setText(String.valueOf(getCenterFrequency()));
}
});
JSpinner spinner = new JSpinner(model);
mControlPanel.add(spinner);
mControlPanel.add(new JLabel("Hz"));
mControlPanel.add(new JLabel("Tone Frequency:"), "push,align right");
mToneFrequencyLabel = new JLabel(String.valueOf(getToneFrequency()));
mControlPanel.add(mToneFrequencyLabel, "push,align left");
mControlPanel.add(new JLabel("Center Frequency:"), "push,align right");
mCenterFrequencyLabel = new JLabel(String.valueOf(getCenterFrequency()));
mControlPanel.add(mCenterFrequencyLabel, "push,align left");
mControlPanel.add(getBottomFrameAddChannelButton(), "push,align right");
mControlPanel.add(getTopFrameAddChannelButton(), "push,align right");
}
return mControlPanel;
}
private JButton getTopFrameAddChannelButton()
{
if(mTopFrameAddChannelButton == null)
{
mTopFrameAddChannelButton = new JButton("Top - Add Channel");
mTopFrameAddChannelButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
String value = JOptionPane.showInputDialog(ChannelizerViewer2.this, "Frequency?");
if(value != null && !value.isEmpty())
{
try
{
int frequency = Integer.parseInt(value);
TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH);
getTopChannelArrayPanel().addChannel(tunerChannel);
}
catch(Exception exception)
{
mLog.error("Can't parse frequency from value: " + value);
}
}
}
});
}
return mTopFrameAddChannelButton;
}
private JButton getBottomFrameAddChannelButton()
{
if(mBottomFrameAddChannelButton == null)
{
mBottomFrameAddChannelButton = new JButton("Bottom - Add Channel");
mBottomFrameAddChannelButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
String value = JOptionPane.showInputDialog(ChannelizerViewer2.this, "Frequency?");
if(value != null && !value.isEmpty())
{
try
{
int frequency = Integer.parseInt(value);
TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH);
getBottomChannelArrayPanel().addChannel(tunerChannel);
}
catch(Exception exception)
{
mLog.error("Can't parse frequency from value: " + value);
}
}
}
});
}
return mBottomFrameAddChannelButton;
}
private long getToneFrequency()
{
return mTestTuner.getTunerController().getFrequency() + mTestTuner.getTunerController().getToneFrequency();
}
private long getCenterFrequency()
{
return mTestTuner.getTunerController().getFrequency();
}
private ChannelArrayPanel getTopChannelArrayPanel()
{
if(mTopChannelArrayPanel == null)
{
mTopChannelArrayPanel = new ChannelArrayPanel();
}
return mTopChannelArrayPanel;
}
private ChannelArrayPanel getBottomChannelArrayPanel()
{
if(mBottomChannelArrayPanel == null)
{
mBottomChannelArrayPanel = new ChannelArrayPanel();
}
return mBottomChannelArrayPanel;
}
public class ChannelArrayPanel extends JPanel
{
public ChannelArrayPanel()
{
init();
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill"));
}
public void addChannel(TunerChannel tunerChannel)
{
ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2,
tunerChannel.getFrequency(), CHANNEL_BANDWIDTH);
channelPanel.setDFTSize(mChannelPanelDFTSize);
add(channelPanel, "grow,push");
validate();
repaint();
}
}
/**
* 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)
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][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)
{
int half = (int)(sampleRate / 2.0f);
add(new JLabel("Min:" + (frequency - half)), "align left");
add(new JLabel("Center:" + frequency));
add(new JLabel("Max:" + (frequency + half)), "align right");
}
else
{
add(new JLabel("NO SRC:" + frequency));
}
mLoggingButton = new JToggleButton("Logging");
mLoggingButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mLoggingEnabled = mLoggingButton.isSelected();
}
});
}
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)
{
final ChannelizerViewer2 frame = new ChannelizerViewer2();
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
frame.setVisible(true);
}
});
}
}

View File

@@ -0,0 +1,326 @@
/*******************************************************************************
* 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.gui.channelizer;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
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();
});
}
}