Files
sdrtrunk/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java

471 lines
17 KiB
Java

/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.buffer.FloatNativeBuffer;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
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.LoggingTunerErrorListener;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.test.TestTuner;
import io.github.dsheirer.spectrum.ComplexDftProcessor;
import io.github.dsheirer.spectrum.DFTSize;
import io.github.dsheirer.spectrum.SpectrumPanel;
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
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.List;
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;
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();
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(new LoggingTunerErrorListener());
/**
* 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 e1)
{
mLog.error("Can't parse frequency from value: " + value, e1);
}
}
}
});
}
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 e1)
{
mLog.error("Can't parse frequency from value: " + value, e1);
}
}
}
});
}
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<INativeBuffer>, ISourceEventProcessor
{
private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor();
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);
mComplexDftProcessor.addConverter(mComplexDecibelConverter);
mComplexDecibelConverter.addListener(mSpectrumPanel);
}
public void setDFTSize(DFTSize dftSize)
{
mComplexDftProcessor.setDFTSize(dftSize);
}
@Override
public void receive(INativeBuffer nativeBuffer)
{
mComplexDftProcessor.receive(nativeBuffer);
}
@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<INativeBuffer>, ISourceEventProcessor
{
private TunerChannelSource mSource;
private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor();
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");
mComplexDftProcessor.addConverter(mComplexDecibelConverter);
mComplexDecibelConverter.addListener(mSpectrumPanel);
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, new ChannelSpecification(50000, 12500, 6000, 7000));
if(mSource != null)
{
mLog.debug("Channel: " + mSource.getTunerChannel() + " Rate:" + mSource.getSampleRate());
mSource.setListener((Listener<ComplexSamples>) complexSamples ->
{
mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()));
});
if(mSource instanceof PolyphaseChannelSource pcs)
{ List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel - Bandwidth: ").append(sRate);
sb.append(" Channel/Requested/Offset: ").append(indexCenterFrequency);
sb.append("/").append(requestedCenterFrequency);
sb.append("/").append(appliedFrequencyOffset);
sb.append(" Indexes: ").append(indexes);
sb.append(" Tuner SR:").append(pcs.getTunerSampleRate());
sb.append(" Tuner CF:").append(pcs.getTunerCenterFrequency());
mLog.info(sb.toString());
}
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(e -> mLoggingEnabled = mLoggingButton.isSelected());
}
public TunerChannelSource getSource()
{
return mSource;
}
public void setDFTSize(DFTSize dftSize)
{
mComplexDftProcessor.setDFTSize(dftSize);
}
@Override
public void receive(INativeBuffer nativeBuffer)
{
mComplexDftProcessor.receive(nativeBuffer);
}
@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);
}
});
}
}