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;