#1397 Adds channel fft frequency overlay, cursor readout, pll tracking indicator, and channel bandwidth lines. (#1406)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2023-01-13 04:28:00 -05:00
committed by GitHub
parent 8ce8aaddf9
commit f97b2150b9
27 changed files with 1406 additions and 529 deletions

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* 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
@@ -15,7 +14,7 @@
*
* 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.channel.metadata;
@@ -28,10 +27,11 @@ import io.github.dsheirer.module.decode.event.DecodeEventPanel;
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import java.awt.Color;
import net.miginfocom.swing.MigLayout;
import javax.swing.JPanel;
import java.awt.Color;
public class NowPlayingPanel extends JPanel
{
@@ -45,13 +45,14 @@ public class NowPlayingPanel extends JPanel
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
* messages, events, and spectral view.
*/
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences)
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences,
SettingsManager settingsManager)
{
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
mChannelPowerPanel = new ChannelPowerPanel(playlistManager);
mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager);
init();
}
@@ -68,6 +69,10 @@ public class NowPlayingPanel extends JPanel
tabbedPane.setFont(this.getFont());
tabbedPane.setForeground(Color.BLACK);
//Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing
tabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(tabbedPane.getSelectedIndex() == tabbedPane
.indexOfComponent(mChannelPowerPanel)));
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
splitPane.setShowGripper(true);
splitPane.add(mChannelMetadataPanel);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -32,6 +32,8 @@ import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.ui.TunerViewPanel;
import java.awt.Color;
import java.awt.Dimension;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
@@ -41,8 +43,6 @@ import org.slf4j.LoggerFactory;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
public class ControllerPanel extends JPanel
{
@@ -63,7 +63,7 @@ public class ControllerPanel extends JPanel
{
mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager,
playlistManager.getAliasModel());
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences);
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager);
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager);
mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences);

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* 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
@@ -15,7 +14,7 @@
*
* 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.controller.channel;
@@ -33,7 +32,7 @@ public class ChannelSelectionManager implements Listener<ChannelEvent>
public ChannelSelectionManager(ChannelModel channelModel)
{
mChannelEventListener = channelModel;
channelModel.addListener(this::receive);
channelModel.addListener(ChannelSelectionManager.this);
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -427,9 +427,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
source.dispose();
}
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//ignore
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
//Rebroadcast so that the tuner source can process this event
mSourceEventBroadcaster.broadcast(sourceEvent);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -28,6 +28,7 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
@@ -45,9 +46,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection;
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
private double mTunerSampleRate;
private double mTunerCenterFrequency;
/**
* Constructs an instance
*
@@ -66,6 +71,33 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
updateOutputProcessor(channelCalculator, filterManager);
}
/**
* Current output processor indexes.
* @return indexes
*/
public List<Integer> getOutputProcessorIndexes()
{
return mOutputProcessorIndexes;
}
/**
* Sample rate or bandwidth of the tuner providing input to the channelizer.
* @return sample rate
*/
public double getTunerSampleRate()
{
return mTunerSampleRate;
}
/**
* Center tuned frequency of the tuner providing input to the channelizer.
* @return center frequency
*/
public double getTunerCenterFrequency()
{
return mTunerCenterFrequency;
}
@Override
public void start()
{
@@ -125,6 +157,11 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
//calculator will throw an IllegalArgException ... handled below
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
mOutputProcessorIndexes.clear();
mOutputProcessorIndexes.addAll(indexes);
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
mTunerSampleRate = channelCalculator.getSampleRate();
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
@@ -263,59 +300,32 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
}
}
@Override
public long getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Adjusts the frequency correction value that is being applied to the channelized output stream by the
* polyphase channel output processor.
*
* @param value to apply for frequency correction in hertz
*/
protected void setChannelFrequencyCorrection(long value)
{
mChannelFrequencyCorrection = value;
updateFrequencyOffset();
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
/**
* Sets the center frequency for this channel.frequency for the incoming sample stream channel results and resets frequency correction to zero.
* Sets the center frequency for this channel.
* @param frequency in hertz
*/
@Override
public void setFrequency(long frequency)
{
mIndexCenterFrequency = frequency;
}
//Set frequency correction to zero to trigger an update to the mixer and allow downstream monitors to
//recalculate the frequency error correction again
setChannelFrequencyCorrection(0);
/**
* Center frequency from the incoming index channel(s).
* @return frequency in hertz
*/
public long getIndexCenterFrequency()
{
return mIndexCenterFrequency;
}
/**
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
* within the channel
*/
private long getFrequencyOffset()
public long getFrequencyOffset()
{
return mIndexCenterFrequency - getTunerChannel().getFrequency() + mChannelFrequencyCorrection;
}
/**
* Updates the frequency offset being applied by the output processor. Calculates the offset using the center
* frequency of the incoming polyphase channel results and the desired output channel frequency adjusted by any
* requested frequency correction value.
*/
private void updateFrequencyOffset()
{
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
return mIndexCenterFrequency - getTunerChannel().getFrequency();
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -72,6 +72,15 @@ public abstract class ComplexMixer
return mOscillator.hasFrequency();
}
/**
* Current frequency of this oscillator
* @return frequency in hertz
*/
public double getFrequency()
{
return mOscillator.getFrequency();
}
/**
* Sets the sample rate of the underlying oscillator
* @param sampleRate in Hertz

View File

@@ -0,0 +1,117 @@
/*
* *****************************************************************************
* 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.dsp.oscillator;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Random;
/**
* Additive white gaussian noise wrapper around a complex oscillator that mixes in gaussian noise to the complex
* samples generated by the underlying complex oscillator.
*/
public class AWGNOscillator implements IComplexOscillator
{
private final Random mRandom = new Random();
private IComplexOscillator mComplexOscillator;
private float mNoiseScale;
/**
* Constructs an instance
* @param complexOscillator to generate samples
* @param noiseScale to apply to generated samples to reduce the power of the noise and increase the SNR.
*/
public AWGNOscillator(IComplexOscillator complexOscillator, float noiseScale)
{
mComplexOscillator = complexOscillator;
mNoiseScale = noiseScale;
}
/**
* Produce the next scaled noise sample.
* @return next scaled noise sample.
*/
private float nextNoise()
{
return (float)mRandom.nextGaussian() * mNoiseScale;
}
@Override
public float[] generate(int sampleCount)
{
float[] generated = mComplexOscillator.generate(sampleCount);
for(int x = 0; x < generated.length; x++)
{
generated[x] += nextNoise();
}
return generated;
}
@Override
public ComplexSamples generateComplexSamples(int sampleCount, long timestamp)
{
ComplexSamples samples = mComplexOscillator.generateComplexSamples(sampleCount, timestamp);
for(int x = 0; x < samples.i().length; x++)
{
samples.i()[x] += nextNoise();
samples.q()[x] += nextNoise();
}
return samples;
}
@Override
public double getFrequency()
{
return mComplexOscillator.getFrequency();
}
@Override
public void setFrequency(double frequency)
{
mComplexOscillator.setFrequency(frequency);
}
@Override
public boolean hasFrequency()
{
return mComplexOscillator.hasFrequency();
}
@Override
public double getSampleRate()
{
return mComplexOscillator.getSampleRate();
}
@Override
public void setSampleRate(double sampleRate)
{
mComplexOscillator.setSampleRate(sampleRate);
}
@Override
public boolean isEnabled()
{
return mComplexOscillator.isEnabled();
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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/>
* * *****************************************************************************
* 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.psk.pll;
@@ -101,7 +98,9 @@ public class FrequencyCorrectionSyncMonitor implements ISyncDetectListener, IFre
@Override
public void processFrequencyError(long frequencyError)
{
//Only rebroadcast the frequency error if the sync count is more than 2
mFeedbackDecoder.broadcast(SourceEvent.pllFrequencyMeasurement(frequencyError));
//Only rebroadcast as a frequency error measurement if the sync count is more than 2
if(mSyncCount > 2)
{
mFeedbackDecoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -20,6 +20,7 @@ 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;
@@ -36,6 +37,12 @@ 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;
@@ -50,12 +57,6 @@ 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.List;
public class ChannelizerViewer2 extends JFrame
{
@@ -194,9 +195,9 @@ public class ChannelizerViewer2 extends JFrame
getTopChannelArrayPanel().addChannel(tunerChannel);
}
catch(Exception exception)
catch(Exception e1)
{
mLog.error("Can't parse frequency from value: " + value);
mLog.error("Can't parse frequency from value: " + value, e1);
}
}
}
@@ -229,9 +230,9 @@ public class ChannelizerViewer2 extends JFrame
getBottomChannelArrayPanel().addChannel(tunerChannel);
}
catch(Exception exception)
catch(Exception e1)
{
mLog.error("Can't parse frequency from value: " + value);
mLog.error("Can't parse frequency from value: " + value, e1);
}
}
}
@@ -388,6 +389,26 @@ public class ChannelizerViewer2 extends JFrame
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

View File

@@ -1,12 +1,52 @@
/*
* *****************************************************************************
* 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.power;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
import io.github.dsheirer.dsp.squelch.ISquelchConfiguration;
import io.github.dsheirer.gui.control.DbPowerMeter;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamplesToNativeBufferModule;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.spectrum.ComplexDftProcessor;
import io.github.dsheirer.spectrum.FrequencyOverlayPanel;
import io.github.dsheirer.spectrum.SpectrumPanel;
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
@@ -16,9 +56,12 @@ import org.slf4j.LoggerFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.MouseInputAdapter;
/**
* Display for channel power and squelch details
@@ -27,28 +70,42 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
{
private static final Logger mLog = LoggerFactory.getLogger(ChannelPowerPanel.class);
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private static final String NOT_AVAILABLE = "Not Available";
private PlaylistManager mPlaylistManager;
private ProcessingChain mProcessingChain;
private ComplexSamplesToNativeBufferModule mSampleStreamTapModule = new ComplexSamplesToNativeBufferModule();
private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor();
private DFTResultsConverter mDFTResultsConverter = new ComplexDecibelConverter();
private JLayeredPane mLayeredPanel;
private SpectrumPanel mSpectrumPanel;
private FrequencyOverlayPanel mFrequencyOverlayPanel;
private DbPowerMeter mPowerMeter = new DbPowerMeter();
private PeakMonitor mPeakMonitor = new PeakMonitor(DbPowerMeter.DEFAULT_MINIMUM_POWER);
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
private SpinnerNumberModel mNoiseFloorSpinnerModel;
private JSpinner mNoiseFloorSpinner;
private JLabel mPowerLabel;
private JLabel mPeakLabel;
private JLabel mSquelchLabel;
private JLabel mSquelchValueLabel;
private JLabel mPllFrequencyLabel;
private JLabel mPllFrequencyValueLabel;
private JButton mSquelchUpButton;
private JButton mSquelchDownButton;
private JButton mLogIndexesButton;
private double mSquelchThreshold;
private boolean mPanelVisible = false;
private boolean mDftProcessing = false;
/**
* Constructs an instance.
*/
public ChannelPowerPanel(PlaylistManager playlistManager)
public ChannelPowerPanel(PlaylistManager playlistManager, SettingsManager settingsManager)
{
mPlaylistManager = playlistManager;
setLayout(new MigLayout("", "[][grow,fill]", "[grow,fill]"));
setLayout(new MigLayout("", "[][][][grow,fill]", "[grow,fill]"));
mPowerMeter.setPeakVisible(true);
mPowerMeter.setSquelchThresholdVisible(true);
add(mPowerMeter);
@@ -85,6 +142,183 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
valuePanel.add(mSquelchDownButton);
add(valuePanel);
add(new JSeparator(JSeparator.VERTICAL));
JPanel fftPanel = new JPanel();
fftPanel.setLayout(new MigLayout("insets 0", "[grow,fill]", "[][grow,fill]"));
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new MigLayout("insets 0", "[grow,fill][right][grow,fill][right][grow,fill][]", ""));
labelPanel.add(new JLabel("Channel Spectrum"));
mPllFrequencyLabel = new JLabel("PLL:");
mPllFrequencyLabel.setEnabled(false);
labelPanel.add(mPllFrequencyLabel);
mPllFrequencyValueLabel = new JLabel("0 Hz");
mPllFrequencyValueLabel.setEnabled(false);
labelPanel.add(mPllFrequencyValueLabel);
mNoiseFloorSpinnerModel = new SpinnerNumberModel(18, 8, 36, 1);
mNoiseFloorSpinnerModel.addChangeListener(e -> {
Number number = mNoiseFloorSpinnerModel.getNumber();
mSpectrumPanel.setSampleSize(number.doubleValue());
});
mNoiseFloorSpinner = new JSpinner(mNoiseFloorSpinnerModel);
labelPanel.add(mNoiseFloorSpinner);
labelPanel.add(new JLabel("Noise Floor"));
mLogIndexesButton = new JButton("Log Settings");
mLogIndexesButton.addActionListener(e -> {
if(mProcessingChain != null)
{
Source source = mProcessingChain.getSource();
if(source instanceof PolyphaseChannelSource pcs)
{
List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sampleRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel - BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
sb.append(" Center/Requested/Mixer: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
sb.append("/").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
sb.append("/").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
sb.append(" Polyphase Indexes: ").append(indexes);
sb.append(" Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
mLog.info(sb.toString());
}
else if(source instanceof HalfBandTunerChannelSource<?> hbtcs)
{
StringBuilder sb = new StringBuilder();
sb.append("Heterodyne Channel - CF:").append(FREQUENCY_FORMAT.format(hbtcs.getFrequency() / 1E6d));
sb.append(" SR:").append(FREQUENCY_FORMAT.format(hbtcs.getSampleRate() / 1E6d));
sb.append(" Mixer:").append(FREQUENCY_FORMAT.format(hbtcs.getMixerFrequency() / 1E6d));
mLog.info(sb.toString());
}
else
{
mLog.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
}
}
});
labelPanel.add(mLogIndexesButton);
fftPanel.add(labelPanel, "wrap");
mFrequencyOverlayPanel = new FrequencyOverlayPanel(settingsManager);
mSpectrumPanel = new SpectrumPanel(settingsManager);
mSpectrumPanel.setSampleSize(18.0);
/**
* The layered pane holds the overlapping spectrum and channel panels
* and manages the sizing of each panel with the resize listener
*/
mLayeredPanel = new JLayeredPane();
mLayeredPanel.addComponentListener(new ResizeListener());
/**
* Create a mouse adapter to handle mouse events over the spectrum
* and waterfall panels
*/
MouseEventProcessor mouser = new MouseEventProcessor();
mFrequencyOverlayPanel.addMouseListener(mouser);
mFrequencyOverlayPanel.addMouseMotionListener(mouser);
mFrequencyOverlayPanel.addMouseWheelListener(mouser);
//Add the spectrum and channel panels to the layered panel
mLayeredPanel.add(mSpectrumPanel, 0, 0);
mLayeredPanel.add(mFrequencyOverlayPanel, 1, 0);
fftPanel.add(mLayeredPanel);
add(fftPanel);
mSampleStreamTapModule.setListener(mComplexDftProcessor);
mComplexDftProcessor.addConverter(mDFTResultsConverter);
mDFTResultsConverter.addListener(mSpectrumPanel);
mSpectrumPanel.clearSpectrum();
}
/**
* Signals this panel to indicate if this panel is visible to turn on the FFT processor when the panel is visible
* and turn off the FFT processor when it's not.
*
* Note: this method is intended to be called by the Swing event thread to ensure that only a single thread is
* invoking either this method, or the receive() method, since there is no thread synchronization between these
* two methods and they each depend on stable access to the mPanelVisible variable.
*
* @param visible true to indicate that this panel is showing/visible.
*/
public void setPanelVisible(boolean visible)
{
mPanelVisible = visible;
updateFFTProcessing();
}
/**
* Updates processing state for the DFT processor. Turns on DFT processing when we have a processing chain and
* when the user has this tab selected and visible. Otherwise, turns off DFT processing.
*/
private void updateFFTProcessing()
{
if(mPanelVisible && mProcessingChain != null)
{
startDftProcessing();
}
else
{
stopDftProcessing();
}
}
/**
* Starts DFT processing
*/
private void startDftProcessing()
{
if(!mDftProcessing)
{
mDftProcessing = true;
mSampleStreamTapModule.setListener(mComplexDftProcessor);
mComplexDftProcessor.start();
}
}
/**
* Stops DFT processing
*/
private void stopDftProcessing()
{
if(mDftProcessing)
{
mSampleStreamTapModule.removeListener();
mComplexDftProcessor.stop();
mSpectrumPanel.clearSpectrum();
mDftProcessing = false;
}
}
/**
* Updates the decoder's PLL frequency on the swing dispatch thread
* @param trackingFrequency that is currently measured.
*/
private void updatePllFrequency(long trackingFrequency)
{
//Note: we flip the sign on the error measurement because the value represents the amount of offset the PLL
//has to apply to move the signal to center/baseband
EventQueue.invokeLater(() -> {
String formattedValue = NumberFormat.getInstance().format(-trackingFrequency);
mPllFrequencyValueLabel.setText(formattedValue + " Hz");
mPllFrequencyValueLabel.setEnabled(true);
mPllFrequencyLabel.setEnabled(true);
});
mFrequencyOverlayPanel.setPllTrackingFrequency(trackingFrequency);
}
/**
@@ -124,6 +358,13 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
mPeakLabel.setText("0");
mPowerLabel.setText("0");
mPllFrequencyLabel.setEnabled(false);
mPllFrequencyValueLabel.setText("0 Hz");
mPllFrequencyValueLabel.setEnabled(false);
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, 0));
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(0));
mFrequencyOverlayPanel.setPllTrackingFrequency(0);
mFrequencyOverlayPanel.setChannelBandwidth(0);
mSquelchLabel.setEnabled(false);
mSquelchValueLabel.setText("Not Available");
@@ -138,9 +379,11 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
@Override
public void receive(ProcessingChain processingChain)
{
//Disconnect the FFT panel
if(mProcessingChain != null)
{
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
mProcessingChain.removeModule(mSampleStreamTapModule);
}
//Invoking reset - we're on the Swing dispatch thread here
@@ -151,11 +394,35 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
if(mProcessingChain != null)
{
mProcessingChain.addSourceEventListener(mSourceEventProcessor);
mProcessingChain.addModule(mSampleStreamTapModule);
Source source = mProcessingChain.getSource();
if(source instanceof TunerChannelSource tcs)
{
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, tcs.getFrequency()));
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(tcs.getSampleRate()));
}
Channel channel = mPlaylistManager.getChannelProcessingManager().getChannel(mProcessingChain);
if(channel != null)
{
TunerChannel tunerChannel = channel.getTunerChannel();
if(tunerChannel != null)
{
mFrequencyOverlayPanel.setChannelBandwidth(tunerChannel.getBandwidth());
}
}
}
updateFFTProcessing();
broadcast(SourceEvent.requestCurrentSquelchThreshold(null));
}
/**
* Processor for source event stream to capture power level and squelch related source events.
*/
@@ -171,17 +438,12 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
final double power = sourceEvent.getValue().doubleValue();
final double peak = mPeakMonitor.process(power);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mPowerMeter.setPower(power);
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
EventQueue.invokeLater(() -> {
mPowerMeter.setPower(power);
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
mPowerMeter.setPeak(peak);
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
}
mPowerMeter.setPeak(peak);
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
});
}
@@ -205,7 +467,70 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
}
});
}
case NOTIFICATION_PLL_FREQUENCY ->
{
if(sourceEvent.hasValue())
{
updatePllFrequency(sourceEvent.getValue().longValue());
}
}
}
}
}
/**
* Monitors the sizing of the layered pane and resizes the spectrum and
* channel panels whenever the layered pane is resized
*/
public class ResizeListener implements ComponentListener
{
@Override public void componentResized(ComponentEvent e)
{
Component c = e.getComponent();
mSpectrumPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
mFrequencyOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
}
@Override public void componentHidden(ComponentEvent arg0) {}
@Override public void componentMoved(ComponentEvent arg0) {}
@Override public void componentShown(ComponentEvent arg0) {}
}
/**
* Mouse event handler for the spectral display panel.
*/
public class MouseEventProcessor extends MouseInputAdapter
{
public MouseEventProcessor()
{
}
@Override public void mouseWheelMoved(MouseWheelEvent e) {}
@Override public void mouseMoved(MouseEvent event)
{
update(event);
}
@Override public void mouseDragged(MouseEvent event) {}
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseClicked(MouseEvent event) {}
/**
* Updates the cursor display while the mouse is performing actions
*/
private void update(MouseEvent event)
{
mFrequencyOverlayPanel.setCursorLocation(event.getPoint());
}
@Override public void mouseEntered(MouseEvent e)
{
mFrequencyOverlayPanel.setCursorVisible(true);
}
@Override public void mouseExited(MouseEvent e)
{
mFrequencyOverlayPanel.setCursorVisible(false);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -72,14 +72,13 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processing chain provides a framework for connecting a complex or real sample
@@ -269,6 +268,15 @@ public class ProcessingChain implements Listener<ChannelEvent>
addModule(mSource);
}
/**
* Source of sample data for this channel.
* @return source, may be null.
*/
public Source getSource()
{
return mSource;
}
/**
* List of current modules for this processing chain
*/

View File

@@ -0,0 +1,90 @@
/*
* *****************************************************************************
* 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.sample.complex;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple module that can be added live to a processing chain to tap into the stream of ComplexSamples buffers and
* convert them to INativeBuffers for relay to a registered native buffer listener.
*/
public class ComplexSamplesToNativeBufferModule extends Module implements IComplexSamplesListener, Listener<ComplexSamples>
{
private static final Logger mLog = LoggerFactory.getLogger(ComplexSamplesToNativeBufferModule.class);
private Listener<INativeBuffer> mNativeBufferListener;
public ComplexSamplesToNativeBufferModule()
{
}
@Override
public Listener<ComplexSamples> getComplexSamplesListener()
{
return this;
}
/**
* Primary receive method to process complex samples buffer by wrapping with a native buffer adapter and sending
* to the registered listener.
*
* Note: this method is synchronized to ensure that multiple calling threads don't step on each other.
*
* @param complexSamples to process and relay.
*/
@Override
public synchronized void receive(ComplexSamples complexSamples)
{
if(mNativeBufferListener != null)
{
mNativeBufferListener.receive(new ComplexSamplesNativeBufferAdapter(complexSamples));
}
}
/**
* Registers the listener to receive native buffers.
* @param listener to register
*/
public void setListener(Listener<INativeBuffer> listener)
{
mNativeBufferListener = listener;
}
/**
* Unregisters a listener from receiving native buffers.
*/
public void removeListener()
{
mNativeBufferListener = null;
}
@Override
public void reset() {}
@Override
public void start() {}
@Override
public void stop() {}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -29,6 +29,7 @@ public class SourceEvent
public enum Event
{
NOTIFICATION_CHANNEL_COUNT_CHANGE,
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION,
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
NOTIFICATION_CHANNEL_POWER,
NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE,
@@ -40,6 +41,7 @@ public class SourceEvent
NOTIFICATION_FREQUENCY_ROTATION_FAILURE,
NOTIFICATION_MEASURED_FREQUENCY_ERROR,
NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED,
NOTIFICATION_PLL_FREQUENCY,
NOTIFICATION_RECORDING_FILE_LOADED,
NOTIFICATION_SAMPLE_RATE_CHANGE,
NOTIFICATION_SQUELCH_THRESHOLD,
@@ -47,7 +49,6 @@ public class SourceEvent
NOTIFICATION_TUNER_SHUTDOWN,
NOTIFICATION_ERROR_STATE,
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
REQUEST_FREQUENCY_CHANGE,
REQUEST_FREQUENCY_ROTATION,
REQUEST_FREQUENCY_SELECTION,
@@ -242,13 +243,13 @@ public class SourceEvent
}
/**
* Creates a new channel frequency correction change event
* Creates a new PLL frequency error measurement notification event.
*
* @param frequencyCorrection in hertz
* @param frequencyError in hertz
*/
public static SourceEvent channelFrequencyCorrectionChange(long frequencyCorrection)
public static SourceEvent pllFrequencyMeasurement(long frequencyError)
{
return new SourceEvent(Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequencyCorrection);
return new SourceEvent(Event.NOTIFICATION_PLL_FREQUENCY, frequencyError);
}
/**
@@ -332,16 +333,6 @@ public class SourceEvent
return new SourceEvent(Event.REQUEST_FREQUENCY_CHANGE, frequency);
}
/**
* Creates a new channel frequency correction change request event
*
* @param frequency requested
*/
public static SourceEvent channelFrequencyCorrectionRequest(long frequency)
{
return new SourceEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequency);
}
/**
* Creates a new start sample stream request event. This method uses the current system time in
* milliseconds as the requested sample start time.

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -50,9 +50,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
private IRealDecimationFilter mIDecimationFilter;
private IRealDecimationFilter mQDecimationFilter;
private double mChannelSampleRate;
private long mChannelFrequencyCorrection = 0;
private long mTunerFrequency;
private Listener<ComplexSamples> mListener;
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
/**
@@ -131,38 +129,25 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
updateMixerFrequencyOffset();
}
/**
* Current frequency correction being applied to the channel sample stream
*
* @return correction in hertz
*/
public long getFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
*
* @param correction current frequency correction value.
*/
public void setFrequencyCorrection(long correction)
{
mChannelFrequencyCorrection = correction;
updateMixerFrequencyOffset();
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
/**
* Calculates the local mixer frequency offset from the tuned frequency,
* channel's requested frequency, and channel frequency correction.
*/
private void updateMixerFrequencyOffset()
{
long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection;
long offset = mTunerFrequency - getTunerChannel().getFrequency();
mFrequencyCorrectionMixer.setFrequency(offset);
}
/**
* Mixer frequency applying offset to the samples.
* @return mixer frequency
*/
public long getMixerFrequency()
{
return (long)mFrequencyCorrectionMixer.getFrequency();
}
/**
* Sets the sample rate of the incoming sample stream from the producer
*
@@ -174,28 +159,6 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
//Not implemented. Sample rate changes are not permitted once sample stream starts
}
/**
* Sets the frequency correction for the outbound sample stream to the consumer
*
* @param correction in hertz
*/
@Override
protected void setChannelFrequencyCorrection(long correction)
{
mChannelFrequencyCorrection = correction;
broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction));
updateMixerFrequencyOffset();
}
/**
* Frequency correction value currently being applied to the outbound sample stream
*/
@Override
public long getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Sets the listener to receive the complex buffer sample output from this channel
*

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -29,13 +29,12 @@ import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Multiple-frequency tuner channel source. Provides a wrapper around a tuner channel source and listens for external
@@ -273,26 +272,6 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource
}
}
@Override
protected void setChannelFrequencyCorrection(long correction)
{
if(mTunerChannelSource != null)
{
mTunerChannelSource.setChannelFrequencyCorrection(correction);
}
}
@Override
public long getChannelFrequencyCorrection()
{
if(mTunerChannelSource != null)
{
return mTunerChannelSource.getChannelFrequencyCorrection();
}
return 0;
}
@Override
public void setListener(Listener<ComplexSamples> complexSamplesListener)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -27,11 +27,10 @@ import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.util.Dispatcher;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
/**
* Pass-through channel source that simply passes complex sample buffers from the tuner controller
* directly to the registered listener
@@ -90,18 +89,6 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
mLog.debug("Request to set sample rate: " + sampleRate);
}
@Override
protected void setChannelFrequencyCorrection(long correction)
{
mLog.debug("Request to set frequency correction: " + correction);
}
@Override
public long getChannelFrequencyCorrection()
{
return 0;
}
@Override
public void setListener(Listener<ComplexSamples> listener)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -78,17 +78,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
*/
protected abstract void setSampleRate(double sampleRate);
/**
* Sets the frequency correction for the outbound sample stream to the consumer
* @param correction in hertz
*/
protected abstract void setChannelFrequencyCorrection(long correction);
/**
* Frequency correction value currently being applied to the outbound sample stream
*/
public abstract long getChannelFrequencyCorrection();
/**
* Sets the listener to receive the complex buffer sample output from this channel
* @param complexSamplesListener to receive complex buffers
@@ -152,11 +141,11 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_FREQUENCY_CHANGE:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_PLL_FREQUENCY:
case NOTIFICATION_STOP_SAMPLE_STREAM:
//no-op
break;
@@ -172,10 +161,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
sourceEvent.setSource(this);
broadcastProducerSourceEvent(sourceEvent);
break;
//Request events from the consumer
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setChannelFrequencyCorrection(sourceEvent.getValue().longValue());
break;
case NOTIFICATION_FREQUENCY_ROTATION_FAILURE:
case NOTIFICATION_FREQUENCY_ROTATION_SUCCESS:
case NOTIFICATION_CHANNEL_POWER:

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -169,7 +169,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
* @return optimal center tuned frequency for the set of currently sourced tuner channels.
* @throws IllegalArgumentException if a center frequency cannot be determined for the set of tuner channels
*/
private long getCenterFrequency(SortedSet<TunerChannel> channels, long currentCenterFrequency)
protected long getCenterFrequency(SortedSet<TunerChannel> channels, long currentCenterFrequency)
throws IllegalArgumentException
{
if(channels.isEmpty())

View File

@@ -0,0 +1,50 @@
/*
* *****************************************************************************
* 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.source.tuner.manager;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import java.util.SortedSet;
public class TestPolyphaseChannelSourceManager extends PolyphaseChannelSourceManager
{
private boolean mLockCenterFrequency = true;
/**
* Constructs an instance
*
* @param tunerController with a center tuned frequency that will be managed by this instance
*/
public TestPolyphaseChannelSourceManager(TunerController tunerController)
{
super(tunerController);
}
@Override
protected long getCenterFrequency(SortedSet<TunerChannel> channels, long currentCenterFrequency) throws IllegalArgumentException
{
if(mLockCenterFrequency)
{
return currentCenterFrequency;
}
return super.getCenterFrequency(channels, currentCenterFrequency);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -20,6 +20,7 @@ package io.github.dsheirer.source.tuner.test;
import io.github.dsheirer.buffer.FloatNativeBuffer;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.dsp.oscillator.AWGNOscillator;
import io.github.dsheirer.dsp.oscillator.IComplexOscillator;
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
import io.github.dsheirer.sample.Broadcaster;
@@ -59,7 +60,9 @@ public class SampleGenerator
throw new IllegalArgumentException("Sweep update rate cannot be greater than sample rate");
}
mComplexOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate);
// mComplexOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate);
IComplexOscillator oscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate);
mComplexOscillator = new AWGNOscillator(oscillator, 0.003f);
mInterval = 1000 / (sampleRate / mSamplesPerInterval);
mSweepUpdateInterval = sweepUpdateRate;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -22,6 +22,7 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.manager.TestPolyphaseChannelSourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,6 +39,7 @@ public class TestTuner extends Tuner
public TestTuner(ITunerErrorListener tunerErrorListener)
{
super(new TestTunerController(), tunerErrorListener);
setChannelSourceManager(new TestPolyphaseChannelSourceManager(getTunerController()));
}
/**

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -33,7 +33,7 @@ public class TestTunerController extends TunerController
private final static Logger mLog = LoggerFactory.getLogger(TestTunerController.class);
public static final long MINIMUM_FREQUENCY = 1l;
public static final long MAXIMUM_FREQUENCY = 1000000000l;
public static final long MAXIMUM_FREQUENCY = 1_000_000_000l;
public static final int SAMPLE_RATE = 2_400_000;
public static final int DC_NOISE_BANDWIDTH = 0;
public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00;
@@ -42,7 +42,7 @@ public class TestTunerController extends TunerController
public static final long SAMPLE_GENERATION_INTERVAL = 1000 / SPECTRAL_FRAME_RATE;
private SampleGenerator mSampleGenerator;
private long mFrequency = 100000000l;
private long mFrequency = 460662500l;
/**
* Tuner controller testing implementation.

View File

@@ -1,268 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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.spectrum;
import io.github.dsheirer.settings.ColorSetting;
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
import io.github.dsheirer.settings.Setting;
import io.github.dsheirer.settings.SettingChangeListener;
import io.github.dsheirer.settings.SettingsManager;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
public class ChannelOverlayPanel extends JPanel implements SettingChangeListener
{
private static final long serialVersionUID = 1L;
private static DecimalFormat sFORMAT = new DecimalFormat( "000.0" );
private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.0" );
private double mBandwidth = 6000.0d;
private Point mCursorLocation = new Point( 0, 0 );
private boolean mCursorVisible = false;
/**
* Colors used by this component
*/
private Color mColorSpectrumBackground;
private Color mColorSpectrumCursor;
private Color mColorSpectrumLine;
//Defines the offset at the bottom of the spectral display to account for
//the frequency labels
private float mSpectrumInset = 20.0f;
private SettingsManager mSettingsManager;
/**
* Translucent overlay panel for displaying channel configurations,
* processing channels, selected channels, frequency labels and lines, and
* a cursor with a frequency readout.
*/
public ChannelOverlayPanel( SettingsManager settingsManager )
{
mSettingsManager = settingsManager;
if( mSettingsManager != null )
{
mSettingsManager.addListener( this );
}
//Set the background transparent, so the spectrum display can be seen
setOpaque( false );
//Fetch color settings from settings manager
setColors();
}
public void dispose()
{
if( mSettingsManager != null )
{
mSettingsManager.removeListener( this );
}
mSettingsManager = null;
}
public void setCursorLocation( Point point )
{
mCursorLocation = point;
repaint();
}
public void setCursorVisible( boolean visible )
{
mCursorVisible = visible;
repaint();
}
/**
* Fetches the color settings from the settings manager
*/
private void setColors()
{
mColorSpectrumCursor = getColor( ColorSettingName.SPECTRUM_CURSOR );
mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE );
mColorSpectrumBackground =
getColor( ColorSettingName.SPECTRUM_BACKGROUND );
}
/**
* Fetches a named color setting from the settings manager. If the setting
* doesn't exist, creates the setting using the defaultColor
*/
private Color getColor( ColorSettingName name )
{
ColorSetting setting = mSettingsManager.getColorSetting( name );
return setting.getColor();
}
/**
* Monitors for setting changes. Colors can be changed by external actions
* and will automatically update in this class
*/
@Override
public void settingChanged( Setting setting )
{
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case SPECTRUM_BACKGROUND:
mColorSpectrumBackground = colorSetting.getColor();
break;
case SPECTRUM_CURSOR:
mColorSpectrumCursor = colorSetting.getColor();
break;
case SPECTRUM_LINE:
mColorSpectrumLine = colorSetting.getColor();
break;
default:
break;
}
}
}
/**
* Renders the channel configs, lines, labels, and cursor
*/
@Override
public void paintComponent( Graphics g )
{
super.paintComponent( g );
Graphics2D graphics = (Graphics2D) g;
graphics.setBackground( mColorSpectrumBackground );
RenderingHints renderHints =
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
renderHints.put( RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY );
graphics.setRenderingHints( renderHints );
drawFrequencies( graphics );
drawCursor( graphics );
}
/**
* Draws a cursor on the panel, whenever the mouse is hovering over the
* panel
*/
private void drawCursor( Graphics2D graphics )
{
if( mCursorVisible )
{
drawFrequencyLine( graphics,
mCursorLocation.x,
mColorSpectrumCursor );
double value = mBandwidth *
( mCursorLocation.getX() / getSize().getWidth() );
String frequency = sCURSOR_FORMAT.format( value );
graphics.drawString( frequency ,
mCursorLocation.x + 5,
mCursorLocation.y );
}
}
/**
* Draws the frequency lines and labels every 10kHz
*/
private void drawFrequencies( Graphics2D graphics )
{
drawFrequencyLine( graphics, getAxisFromFrequency( 1000 ), Color.LIGHT_GRAY );
drawFrequencyLineAndLabel( graphics, 2000 );
drawFrequencyLine( graphics, getAxisFromFrequency( 3000 ), Color.LIGHT_GRAY );
drawFrequencyLineAndLabel( graphics, 4000 );
drawFrequencyLine( graphics, getAxisFromFrequency( 5000 ), Color.LIGHT_GRAY );
// drawFrequencyLineAndLabel( graphics, 6000 );
// drawFrequencyLine( graphics, getAxisFromFrequency( 7000 ), Color.LIGHT_GRAY );
// drawFrequencyLineAndLabel( graphics, 8000 );
// drawFrequencyLine( graphics, getAxisFromFrequency( 9000 ), Color.LIGHT_GRAY );
// drawFrequencyLineAndLabel( graphics, 10000 );
// drawFrequencyLine( graphics, getAxisFromFrequency( 11000 ), Color.LIGHT_GRAY );
}
private float getAxisFromFrequency( long frequency )
{
return (float)( getSize().getWidth() * ( (double)frequency / mBandwidth ) );
}
/**
* Draws a vertical line and a corresponding frequency label at the bottom
*/
private void drawFrequencyLineAndLabel( Graphics2D graphics, long frequency )
{
float xAxis = (float)( getSize().getWidth() *
( (double)frequency / mBandwidth ) );
drawFrequencyLine( graphics, xAxis, mColorSpectrumLine );
graphics.setColor( mColorSpectrumLine );
drawFrequencyLabel( graphics, xAxis, frequency );
}
/**
* Draws a vertical line at the xaxis
*/
private void drawFrequencyLine( Graphics2D graphics, float xaxis, Color color )
{
graphics.setColor( color );
graphics.draw( new Line2D.Float( xaxis, 0,
xaxis, (float)(getSize().getHeight()) - mSpectrumInset ) );
}
/**
* Draws a frequency label at the x-axis position, at the bottom of the panel
*/
private void drawFrequencyLabel( Graphics2D graphics,
float xaxis,
long frequency )
{
String label = sFORMAT.format( (float)frequency );
FontMetrics fontMetrics = graphics.getFontMetrics( this.getFont() );
Rectangle2D rect = fontMetrics.getStringBounds( label, graphics );
float offset = (float)rect.getWidth() / 2;
graphics.drawString( label, xaxis - offset,
(float)getSize().getHeight() - ( mSpectrumInset * 0.2f ) );
}
public void settingDeleted( Setting setting ) {}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -25,10 +25,6 @@ import io.github.dsheirer.dsp.window.WindowType;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
@@ -36,6 +32,9 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processes both complex samples or float samples and dispatches a float array of DFT results, using configurable fft
@@ -141,7 +140,7 @@ public class ComplexDftProcessor<T extends INativeBuffer> implements Listener<T>
//Cancel running DFT calculation task
if(mProcessorTaskHandle != null)
{
mProcessorTaskHandle.cancel(true);
mProcessorTaskHandle.cancel(false);
mProcessorTaskHandle = null;
}
}

View File

@@ -0,0 +1,613 @@
/*
* *****************************************************************************
* 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.spectrum;
import io.github.dsheirer.settings.ColorSetting;
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
import io.github.dsheirer.settings.Setting;
import io.github.dsheirer.settings.SettingChangeListener;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import org.apache.commons.math3.util.FastMath;
import javax.swing.JPanel;
/**
* Frequency overlay panel.
*/
public class FrequencyOverlayPanel extends JPanel implements ISourceEventProcessor, SettingChangeListener
{
private static final long serialVersionUID = 1L;
private final DecimalFormat PPM_FORMATTER = new DecimalFormat( "#.0" );
private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
static
{
RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
private static DecimalFormat CURSOR_FORMAT = new DecimalFormat("000.00000");
private long mFrequency = 0;
private int mBandwidth = 0;
private int mChannelBandwidth = 0;
private long mPllTrackingFrequency = 0;
private Point mCursorLocation = new Point(0, 0);
private boolean mCursorVisible = false;
private DFTSize mDFTSize = DFTSize.FFT04096;
/**
* Colors used by this component
*/
private Color mColorSpectrumBackground;
private Color mColorSpectrumCursor;
private Color mColorSpectrumLine;
//Defines the offset at the bottom of the spectral display to account for
//the frequency labels
private double mSpectrumInset = 20.0d;
private LabelSizeManager mLabelSizeMonitor = new LabelSizeManager();
private SettingsManager mSettingsManager;
/**
* Translucent overlay panel for displaying channel configurations,
* processing channels, selected channels, frequency labels and lines, and
* a cursor with a frequency readout.
*/
public FrequencyOverlayPanel(SettingsManager settingsManager)
{
mSettingsManager = settingsManager;
if(mSettingsManager != null)
{
mSettingsManager.addListener(this);
}
addComponentListener(mLabelSizeMonitor);
//Set the background transparent, so the spectrum display can be seen
setOpaque(false);
//Fetch color settings from settings manager
setColors();
}
/**
* Sets the tracking frequency of the Phase Locked Loop (PLL)
* @param pllTrackingFrequency current setting. Set to 0 to reset.
*/
public void setPllTrackingFrequency(long pllTrackingFrequency)
{
mPllTrackingFrequency = pllTrackingFrequency;
}
/**
* Sets the bandwidth of the channel.
* @param channelBandwidth in Hertz
*/
public void setChannelBandwidth(int channelBandwidth)
{
mChannelBandwidth = channelBandwidth;
}
public void dispose()
{
if(mSettingsManager != null)
{
mSettingsManager.removeListener(this);
}
mSettingsManager = null;
}
/**
* Sets/changes the DFT bin size
*/
public void setDFTSize(DFTSize size)
{
mDFTSize = size;
}
public void setCursorLocation(Point point)
{
mCursorLocation = point;
repaint();
}
public void setCursorVisible(boolean visible)
{
mCursorVisible = visible;
repaint();
}
/**
* Fetches the color settings from the settings manager
*/
private void setColors()
{
mColorSpectrumCursor = getColor(ColorSettingName.SPECTRUM_CURSOR);
mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
mColorSpectrumBackground = getColor(ColorSettingName.SPECTRUM_BACKGROUND);
}
/**
* Fetches a named color setting from the settings manager. If the setting
* doesn't exist, creates the setting using the defaultColor
*/
private Color getColor(ColorSettingName name)
{
ColorSetting setting = mSettingsManager.getColorSetting(name);
return setting.getColor();
}
/**
* Monitors for setting changes. Colors can be changed by external actions
* and will automatically update in this class
*/
@Override
public void settingChanged(Setting setting)
{
if(setting instanceof ColorSetting)
{
ColorSetting colorSetting = (ColorSetting)setting;
switch(colorSetting.getColorSettingName())
{
case SPECTRUM_BACKGROUND:
mColorSpectrumBackground = colorSetting.getColor();
break;
case SPECTRUM_CURSOR:
mColorSpectrumCursor = colorSetting.getColor();
break;
case SPECTRUM_LINE:
mColorSpectrumLine = colorSetting.getColor();
break;
default:
break;
}
}
}
/**
* Renders the channel configs, lines, labels, and cursor
*/
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D graphics = (Graphics2D)g;
graphics.setBackground(mColorSpectrumBackground);
graphics.setRenderingHints(RENDERING_HINTS);
drawFrequencies(graphics);
drawCursor(graphics);
drawPLL(graphics);
drawChannelBandwidth(graphics);
}
/**
* Draws a cursor on the panel, whenever the mouse is hovering over the
* panel
*/
private void drawCursor(Graphics2D graphics)
{
if(mCursorVisible)
{
drawFrequencyLine(graphics, mCursorLocation.x, mColorSpectrumCursor);
String frequency = CURSOR_FORMAT.format(getFrequencyFromAxis(mCursorLocation.getX()) / 1E6D);
FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont());
Rectangle2D rect = fontMetrics.getStringBounds(frequency, graphics);
if(mCursorLocation.y > rect.getHeight())
{
graphics.drawString(frequency, mCursorLocation.x + 5, mCursorLocation.y);
}
}
}
/**
* Draws the frequency lines and labels every 10kHz
*/
private void drawFrequencies(Graphics2D graphics)
{
long minFrequency = getMinDisplayFrequency();
long maxFrequency = getMaxDisplayFrequency();
//Frequency increments for label and tick spacing
int label = mLabelSizeMonitor.getLabelIncrement(graphics);
int major = mLabelSizeMonitor.getMajorTickIncrement(graphics);
int minor = mLabelSizeMonitor.getMinorTickIncrement(graphics);
//Avoid divide by zero error
if(minor == 0)
{
minor = 1;
}
if(label == 0)
{
label = 1;
}
//Adjust the start frequency to a multiple of the minor tick spacing
long frequency = minFrequency - (minFrequency % minor);
while(frequency < maxFrequency)
{
if(frequency % label == 0)
{
drawFrequencyLineAndLabel(graphics, frequency);
}
else if(frequency % major == 0)
{
drawTickLine(graphics, frequency, true);
}
else
{
drawTickLine(graphics, frequency, false);
}
frequency += minor;
}
}
/**
* Draws a vertical line and a corresponding frequency label at the bottom
*/
private void drawFrequencyLineAndLabel(Graphics2D graphics, long frequency)
{
double xAxis = getAxisFromFrequency(frequency);
drawFrequencyLine(graphics, xAxis, mColorSpectrumLine);
drawTickLine(graphics, frequency, false);
graphics.setColor(mColorSpectrumLine);
drawFrequencyLabel(graphics, xAxis, frequency);
}
/**
* Draws a vertical line at the xaxis
*/
private void drawTickLine(Graphics2D graphics, long frequency, boolean major)
{
graphics.setColor(mColorSpectrumLine);
double xAxis = getAxisFromFrequency(frequency);
double start = getSize().getHeight() - mSpectrumInset;
double end = start + (major ? 9.0d : 3.0d);
graphics.draw(new Line2D.Double(xAxis, start, xAxis, end));
}
/**
* Draws a vertical line at the xaxis
*/
private void drawFrequencyLine(Graphics2D graphics, double xaxis, Color color)
{
graphics.setColor(color);
graphics.draw(new Line2D.Double(xaxis, 0.0d, xaxis, getSize().getHeight() - mSpectrumInset));
}
/**
* Draws a vertical line at the xaxis
*/
private void drawChannelCenterLine(Graphics2D graphics, double xaxis)
{
double height = getSize().getHeight() - mSpectrumInset;
graphics.setColor(Color.LIGHT_GRAY);
graphics.draw(new Line2D.Double(xaxis, height * 0.65d, xaxis, height - 1.0d));
}
/**
* Draws the Phase Locked Loop tracking frequency, offset from channel center
*/
private void drawPLL(Graphics2D graphics)
{
if(mPllTrackingFrequency == 0)
{
return;
}
long frequency = mFrequency - mPllTrackingFrequency;
double xAxis = getAxisFromFrequency(frequency);
double height = getSize().getHeight() - mSpectrumInset;
double verticalAxisTop = height * 0.02d;
double verticalAxisBottom = height * 0.98d;
graphics.setColor(Color.YELLOW);
//Vertical band edge lines
graphics.draw(new Line2D.Double(xAxis, verticalAxisTop, xAxis, verticalAxisBottom));
}
/**
* Draws the Phase Locked Loop tracking frequency, offset from channel center
*/
private void drawChannelBandwidth(Graphics2D graphics)
{
if(mChannelBandwidth == 0)
{
return;
}
long minFrequency = mFrequency - (mChannelBandwidth / 2);
long maxFrequency = mFrequency + (mChannelBandwidth / 2);
double height = getSize().getHeight() - mSpectrumInset;
double verticalAxisTop = height * 0.8d;
double verticalAxisBottom = height * 0.98d;
double minXAxis = getAxisFromFrequency(minFrequency);
double maxXAxis = getAxisFromFrequency(maxFrequency);
graphics.setColor(Color.GREEN);
graphics.draw(new Line2D.Double(minXAxis, verticalAxisTop, minXAxis, verticalAxisBottom));
graphics.draw(new Line2D.Double(maxXAxis, verticalAxisTop, maxXAxis, verticalAxisBottom));
}
/**
* Returns the x-axis value corresponding to the frequency
*/
private double getAxisFromFrequency(long frequency)
{
double screenWidth = (double)getSize().getWidth();
double pixelsPerBin = screenWidth / (double)mDFTSize.getSize();
double pixelOffsetToMinDisplayFrequency = pixelsPerBin * 2.0d;
//Calculate frequency offset from the min frequency
double frequencyOffset = (double)(frequency - getMinDisplayFrequency());
//Determine ratio of frequency offset to overall bandwidth
double ratio = frequencyOffset / (double)getDisplayBandwidth();
//Apply the ratio to the screen width minus 1 bin width
double screenOffset = screenWidth * ratio;
return pixelOffsetToMinDisplayFrequency + screenOffset;
}
/**
* Returns the frequency corresponding to the x-axis value using the current
* zoom level.
*/
public long getFrequencyFromAxis(double xAxis)
{
double width = getSize().getWidth();
double offset = xAxis / width;
long frequency = getMinDisplayFrequency() + FastMath.round((double)getDisplayBandwidth() * offset);
if(frequency > (getMaxFrequency()))
{
frequency = getMaxFrequency();
}
return frequency;
}
/**
* Draws a frequency label at the x-axis position, at the bottom of the panel
*/
private void drawFrequencyLabel(Graphics2D graphics, double xaxis, long frequency)
{
String label = mLabelSizeMonitor.format(frequency);
FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont());
Rectangle2D rect = fontMetrics.getStringBounds(label, graphics);
float xOffset = (float)rect.getWidth() / 2;
graphics.drawString(label, (float)(xaxis - xOffset), (float)(getSize().getHeight() - 2.0f));
}
/**
* Frequency change event handler
*/
@Override
public void process(SourceEvent event)
{
switch(event.getEvent())
{
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mBandwidth = event.getValue().intValue();
mLabelSizeMonitor.update();
break;
case NOTIFICATION_FREQUENCY_CHANGE:
mFrequency = event.getValue().longValue();
mLabelSizeMonitor.update();
break;
default:
break;
}
}
public int getBandwidth()
{
return mBandwidth;
}
/**
* Currently displayed minimum frequency
*/
public long getMinFrequency()
{
return mFrequency - (mBandwidth / 2);
}
/**
* Currently displayed maximum frequency
*/
public long getMaxFrequency()
{
return mFrequency + (mBandwidth / 2);
}
public boolean containsFrequency(long frequency)
{
return FastMath.abs(mFrequency - frequency) <= (mBandwidth / 2);
}
private long getMinDisplayFrequency()
{
double bandwidthPerBin = (double)mBandwidth / (double)(mDFTSize.getSize());
return getMinFrequency();
// return getMinFrequency() + (int)((mDFTZoomWindowOffset) * bandwidthPerBin);
}
private long getMaxDisplayFrequency()
{
return getMinDisplayFrequency() + getDisplayBandwidth();
}
private int getDisplayBandwidth()
{
return mBandwidth;
}
@Override
public void settingDeleted(Setting setting) { /* not implemented */ }
/**
* Calculates correct spacing and format for frequency labels and major/minor
* tick lines based on current frequency, bandwidth, zoom and screen size.
*/
public class LabelSizeManager implements ComponentListener
{
private static final double LABEL_FILL_THRESHOLD = 0.5d;
private DecimalFormat mFrequencyFormat = new DecimalFormat("0.0");
private boolean mUpdateRequired = true;
private int mLabelIncrement = 1;
private int mMajorTickIncrement = 1;
private int mMinorTickIncrement = 1;
public String format(long frequency)
{
return mFrequencyFormat.format((double)frequency / 1E6D);
}
private void setPrecision(int precision)
{
if(precision < 1)
{
precision = 1;
}
if(precision > 5)
{
precision = 5;
}
mFrequencyFormat.setMinimumFractionDigits(precision);
mFrequencyFormat.setMaximumFractionDigits(precision);
}
private void update(Graphics2D graphics)
{
if(mUpdateRequired)
{
//Set maximum precision as a starting point
setPrecision(5);
FontMetrics fontMetrics =
graphics.getFontMetrics(FrequencyOverlayPanel.this.getFont());
int maxLabelWidth = fontMetrics.stringWidth(format(getMaxDisplayFrequency()));
double maxLabels = ((double) FrequencyOverlayPanel.this.getWidth() * LABEL_FILL_THRESHOLD) / (double)maxLabelWidth;
//Calculate the next smallest base 10 value for the major increment
int power = (int)FastMath.log10((double)getDisplayBandwidth() / maxLabels);
//Set the number of decimal places to display in frequency labels
int precision = 5 - power;
int start = (int)FastMath.pow(10.0, power + 1);
int minimum = (int)FastMath.pow(10.0, power);
int labelIncrement = start;
while(((double)getDisplayBandwidth() / (double)labelIncrement) < maxLabels && labelIncrement >= minimum)
{
labelIncrement /= 2;
precision++;
}
if(labelIncrement == minimum)
{
precision = 5 - power;
}
setPrecision(precision);
mLabelIncrement = labelIncrement;
mMajorTickIncrement = labelIncrement / 2;
mMinorTickIncrement = labelIncrement / 10;
mUpdateRequired = false;
}
}
/**
* Forces the display to update the label and frequency display
* calculations
*/
public void update()
{
mUpdateRequired = true;
}
public int getMajorTickIncrement(Graphics2D graphics)
{
//Check to see if a calculation update is scheduled
update(graphics);
return mMajorTickIncrement;
}
public int getMinorTickIncrement(Graphics2D graphics)
{
return mMinorTickIncrement;
}
public int getLabelIncrement(Graphics2D graphics)
{
return mLabelIncrement;
}
@Override
public void componentResized(ComponentEvent arg0)
{
update();
}
public void componentHidden(ComponentEvent arg0) {}
public void componentMoved(ComponentEvent arg0) {}
public void componentShown(ComponentEvent arg0) {}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* 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
@@ -15,7 +14,7 @@
*
* 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.spectrum;
@@ -33,9 +32,6 @@ import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import org.apache.commons.math3.util.FastMath;
import javax.swing.JPanel;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
@@ -53,6 +49,9 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.math3.util.FastMath;
import javax.swing.JPanel;
public class OverlayPanel extends JPanel implements Listener<ChannelEvent>, ISourceEventProcessor, SettingChangeListener
{
@@ -226,15 +225,10 @@ public class OverlayPanel extends JPanel implements Listener<ChannelEvent>, ISou
private void setColors()
{
mColorChannelConfig = getColor(ColorSettingName.CHANNEL_CONFIG);
mColorChannelConfigProcessing = getColor(ColorSettingName.CHANNEL_CONFIG_PROCESSING);
mColorChannelConfigSelected = getColor(ColorSettingName.CHANNEL_CONFIG_SELECTED);
mColorSpectrumCursor = getColor(ColorSettingName.SPECTRUM_CURSOR);
mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
mColorSpectrumBackground = getColor(ColorSettingName.SPECTRUM_BACKGROUND);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -29,12 +29,6 @@ import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
import io.github.dsheirer.settings.Setting;
import io.github.dsheirer.settings.SettingChangeListener;
import io.github.dsheirer.settings.SettingsManager;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
@@ -45,6 +39,12 @@ import java.awt.RenderingHints;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.util.Arrays;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JPanel;
public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster
{
@@ -82,7 +82,6 @@ public class SpectrumPanel extends JPanel implements DFTResultsListener, Setting
private int mZoom = 0;
private int mZoomWindowOffset = 0;
;
private SettingsManager mSettingsManager;