diff --git a/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java b/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java index 3c7886ea..7108c0e7 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java @@ -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 - * ***************************************************************************** + * **************************************************************************** */ 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); diff --git a/src/main/java/io/github/dsheirer/controller/ControllerPanel.java b/src/main/java/io/github/dsheirer/controller/ControllerPanel.java index 54ce7164..66748334 100644 --- a/src/main/java/io/github/dsheirer/controller/ControllerPanel.java +++ b/src/main/java/io/github/dsheirer/controller/ControllerPanel.java @@ -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); diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelSelectionManager.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelSelectionManager.java index 5303e0c2..744e9a95 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelSelectionManager.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelSelectionManager.java @@ -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 - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.controller.channel; @@ -33,7 +32,7 @@ public class ChannelSelectionManager implements Listener public ChannelSelectionManager(ChannelModel channelModel) { mChannelEventListener = channelModel; - channelModel.addListener(this::receive); + channelModel.addListener(ChannelSelectionManager.this); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java index 7df40059..382c936d 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java @@ -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); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java index 94c4b197..a2b00c7b 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -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 mStreamHeartbeatProcessor; private double mChannelSampleRate; private long mIndexCenterFrequency; - private long mChannelFrequencyCorrection; private ReentrantLock mOutputProcessorLock = new ReentrantLock(); + private List 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 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 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 diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java index 8f34fce7..ac98cb08 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java +++ b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/AWGNOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/AWGNOscillator.java new file mode 100644 index 00000000..a6d32018 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/AWGNOscillator.java @@ -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 + * **************************************************************************** + */ + +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(); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/pll/FrequencyCorrectionSyncMonitor.java b/src/main/java/io/github/dsheirer/dsp/psk/pll/FrequencyCorrectionSyncMonitor.java index b12ec5de..6dc2a727 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/pll/FrequencyCorrectionSyncMonitor.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/pll/FrequencyCorrectionSyncMonitor.java @@ -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 - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** */ package 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)); diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java index 99f7c113..60324c7a 100644 --- a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java @@ -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 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 diff --git a/src/main/java/io/github/dsheirer/gui/power/ChannelPowerPanel.java b/src/main/java/io/github/dsheirer/gui/power/ChannelPowerPanel.java index 9e350898..85fce2eb 100644 --- a/src/main/java/io/github/dsheirer/gui/power/ChannelPowerPanel.java +++ b/src/main/java/io/github/dsheirer/gui/power/ChannelPowerPanel.java @@ -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 + * **************************************************************************** + */ + 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 { + 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 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 { + 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 + { + 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); + } + } } diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index 21a2534c..78cdb12e 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -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 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 */ diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesToNativeBufferModule.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesToNativeBufferModule.java new file mode 100644 index 00000000..df359c86 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesToNativeBufferModule.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final Logger mLog = LoggerFactory.getLogger(ComplexSamplesToNativeBufferModule.class); + private Listener mNativeBufferListener; + + public ComplexSamplesToNativeBufferModule() + { + } + + @Override + public Listener 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 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() {} + +} diff --git a/src/main/java/io/github/dsheirer/source/SourceEvent.java b/src/main/java/io/github/dsheirer/source/SourceEvent.java index e12ded96..2d6ed305 100644 --- a/src/main/java/io/github/dsheirer/source/SourceEvent.java +++ b/src/main/java/io/github/dsheirer/source/SourceEvent.java @@ -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. diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java index 2df83923..ab3e344e 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java @@ -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 extends TunerCh private IRealDecimationFilter mIDecimationFilter; private IRealDecimationFilter mQDecimationFilter; private double mChannelSampleRate; - private long mChannelFrequencyCorrection = 0; private long mTunerFrequency; - private Listener mListener; private StreamProcessorWithHeartbeat mStreamHeartbeatProcessor; /** @@ -131,38 +129,25 @@ public class HalfBandTunerChannelSource 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 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 * diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java index 1fbf2b07..cb6f3dfd 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java @@ -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 complexSamplesListener) { diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java index 0b25176d..1491211a 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java @@ -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 listener) { diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java index 0f121b61..242859ca 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java @@ -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: diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java index 32306208..fc490b53 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java @@ -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 channels, long currentCenterFrequency) + protected long getCenterFrequency(SortedSet channels, long currentCenterFrequency) throws IllegalArgumentException { if(channels.isEmpty()) diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/TestPolyphaseChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/TestPolyphaseChannelSourceManager.java new file mode 100644 index 00000000..5857d321 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/TestPolyphaseChannelSourceManager.java @@ -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 + * **************************************************************************** + */ + +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 channels, long currentCenterFrequency) throws IllegalArgumentException + { + if(mLockCenterFrequency) + { + return currentCenterFrequency; + } + + return super.getCenterFrequency(channels, currentCenterFrequency); + } +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java index 69e39893..aa31aa91 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java @@ -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; diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/TestTuner.java b/src/main/java/io/github/dsheirer/source/tuner/test/TestTuner.java index 631c78fc..ff241a5e 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/TestTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/TestTuner.java @@ -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())); } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java index c1830d50..508578c0 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java @@ -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. diff --git a/src/main/java/io/github/dsheirer/spectrum/ChannelOverlayPanel.java b/src/main/java/io/github/dsheirer/spectrum/ChannelOverlayPanel.java deleted file mode 100644 index 5a74fa04..00000000 --- a/src/main/java/io/github/dsheirer/spectrum/ChannelOverlayPanel.java +++ /dev/null @@ -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 - ******************************************************************************/ -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 ) {} -} diff --git a/src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java b/src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java index 092fa521..81ffbb72 100644 --- a/src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java +++ b/src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java @@ -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 implements Listener //Cancel running DFT calculation task if(mProcessorTaskHandle != null) { - mProcessorTaskHandle.cancel(true); + mProcessorTaskHandle.cancel(false); mProcessorTaskHandle = null; } } diff --git a/src/main/java/io/github/dsheirer/spectrum/FrequencyOverlayPanel.java b/src/main/java/io/github/dsheirer/spectrum/FrequencyOverlayPanel.java new file mode 100644 index 00000000..34108a45 --- /dev/null +++ b/src/main/java/io/github/dsheirer/spectrum/FrequencyOverlayPanel.java @@ -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 + * **************************************************************************** + */ +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) {} + } +} diff --git a/src/main/java/io/github/dsheirer/spectrum/OverlayPanel.java b/src/main/java/io/github/dsheirer/spectrum/OverlayPanel.java index 20f2b2cd..5a462892 100644 --- a/src/main/java/io/github/dsheirer/spectrum/OverlayPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/OverlayPanel.java @@ -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 - * ***************************************************************************** + * **************************************************************************** */ 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, ISourceEventProcessor, SettingChangeListener { @@ -226,15 +225,10 @@ public class OverlayPanel extends JPanel implements Listener, 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); } diff --git a/src/main/java/io/github/dsheirer/spectrum/SpectrumPanel.java b/src/main/java/io/github/dsheirer/spectrum/SpectrumPanel.java index c4262a0b..56e977f6 100644 --- a/src/main/java/io/github/dsheirer/spectrum/SpectrumPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/SpectrumPanel.java @@ -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;