#1067 P25 Phase 1 C4FM/LSM decoder redesign. (#2294)

C4FM now uses SIMD differential decoding and soft sync detection.
LSM updated with soft sync detector and retains original Gardner error detector.
Adds new QPSK symbol view in the now playing channel tab.
Restores PLL error driven tuner Auto-PPM feature.
Deletes the demod viewer GUI application and all instrumented decoders
Updates DMR to send soft symbols to new symbol view in Now Playing.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2025-11-16 06:52:17 -05:00
committed by GitHub
parent 48202da9e4
commit 0d886c272e
149 changed files with 5990 additions and 8984 deletions

View File

@@ -1,29 +1,31 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui;
import java.io.File;
import java.util.prefs.Preferences;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import java.io.File;
import java.util.prefs.Preferences;
public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
{
private static final String PREFERENCE_RECENT_FILE = ".recent.file.";

View File

@@ -23,7 +23,9 @@ import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
import io.github.dsheirer.gui.power.SignalPowerView;
import io.github.dsheirer.gui.squelch.NoiseSquelchView;
import io.github.dsheirer.gui.symbol.SymbolView;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.module.decode.FeedbackDecoder;
import io.github.dsheirer.module.decode.PrimaryDecoder;
import io.github.dsheirer.module.decode.am.AMDecoder;
import io.github.dsheirer.module.decode.nbfm.NBFMDecoder;
@@ -46,13 +48,14 @@ import java.awt.EventQueue;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -70,7 +73,7 @@ import javax.swing.event.MouseInputAdapter;
*/
public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingChain>
{
private static final Logger mLog = LoggerFactory.getLogger(ChannelSpectrumPanel.class);
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelSpectrumPanel.class);
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private final PlaylistManager mPlaylistManager;
private ProcessingChain mProcessingChain;
@@ -80,13 +83,14 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
private final FrequencyOverlayPanel mFrequencyOverlayPanel;
private final SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
private final SpinnerNumberModel mNoiseFloorSpinnerModel;
private final JLabel mEstimatedCarrierOffsetFrequencyLabel;
private final JLabel mEstimatedCarrierOffsetFrequencyValueLabel;
private boolean mPanelVisible = false;
private boolean mDftProcessing = false;
private final NoiseSquelchView mNoiseSquelchView;
private final SignalPowerView mSignalPowerView;
private final SymbolView mSymbolView = new SymbolView();
private final JFXPanel mNoiseSquelchPanel;
private final JFXPanel mSymbolPanel;
private JSplitPane mSplitPane;
/**
@@ -103,13 +107,10 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
fftPanel.setLayout(new MigLayout("insets 0", "[grow,fill]", "[][grow,fill]"));
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new MigLayout("insets 0", "[grow,fill][right][grow,fill][right][][]", ""));
labelPanel.add(new JLabel("Channel Spectrum"));
labelPanel.setLayout(new MigLayout("insets 2", "[grow,fill][grow,fill,left][right][][]", ""));
labelPanel.add(new JLabel("Channel Spectrum "));
mEstimatedCarrierOffsetFrequencyLabel = new JLabel("Carrier Offset:");
mEstimatedCarrierOffsetFrequencyLabel.setEnabled(false);
labelPanel.add(mEstimatedCarrierOffsetFrequencyLabel);
mEstimatedCarrierOffsetFrequencyValueLabel = new JLabel("0 Hz");
mEstimatedCarrierOffsetFrequencyValueLabel = new JLabel(getPaddedCarrierOffsetLabel(0));
mEstimatedCarrierOffsetFrequencyValueLabel.setEnabled(false);
labelPanel.add(mEstimatedCarrierOffsetFrequencyValueLabel);
@@ -120,7 +121,7 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
});
JSpinner noiseFloorSpinner = new JSpinner(mNoiseFloorSpinnerModel);
labelPanel.add(noiseFloorSpinner);
labelPanel.add(new JLabel("Spectral Display Noise Floor"));
labelPanel.add(new JLabel("Noise Floor"));
JButton logIndexesButton = new JButton("Log Settings");
logIndexesButton.addActionListener(e -> {
@@ -144,8 +145,8 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
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());
mLog.info("Output Processor: " + pcs.getStateDescription());
LOGGER.info(sb.toString());
LOGGER.info("Output Processor: " + pcs.getStateDescription());
}
else if(source instanceof HalfBandTunerChannelSource<?> hbtcs)
{
@@ -153,11 +154,11 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
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());
LOGGER.info(sb.toString());
}
else
{
mLog.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
LOGGER.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
}
}
});
@@ -194,18 +195,32 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
fftPanel.add(layeredPanel);
mNoiseSquelchPanel = new JFXPanel();
mSymbolPanel = new JFXPanel();
//Spin noise squelch panel construction off onto the JavafX UI thread.
Platform.runLater(() -> {
Scene scene = new Scene(mNoiseSquelchView);
mNoiseSquelchPanel.setScene(scene);
Scene scene2 = new Scene(mSymbolView);
URL resource = getClass().getResource("/sdrtrunk_style.css");
if(resource != null)
{
scene2.getStylesheets().add(resource.toExternalForm());
}
else
{
LOGGER.warn("Can't find stylesheet resource for sdrtrunk");
}
mSymbolPanel.setScene(scene2);
});
mSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
mSplitPane.add(fftPanel, JSplitPane.LEFT);
mSplitPane.add(mNoiseSquelchPanel, JSplitPane.RIGHT);
mSplitPane.setDividerLocation(0.5);
add(mSplitPane);
// mSplitPane.setDividerLocation(0.5);
mSampleStreamTapModule.setListener(mComplexDftProcessor);
DFTResultsConverter DFTResultsConverter = new ComplexDecibelConverter();
@@ -214,6 +229,13 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
mSpectrumPanel.clearSpectrum();
}
private String getPaddedCarrierOffsetLabel(long value)
{
String paddedValue = StringUtils.leftPad(String.valueOf(value), 5);
String paddedText = StringUtils.rightPad(paddedValue + " Hz", 20);
return "Carrier Offset: " + paddedText;
}
/**
* 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.
@@ -229,6 +251,7 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
mPanelVisible = visible;
updateFFTProcessing();
mNoiseSquelchView.setShowing(visible);
mSymbolView.setShowing(visible);
}
/**
@@ -283,10 +306,8 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
//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(carrierOffsetFrequency);
mEstimatedCarrierOffsetFrequencyValueLabel.setText(formattedValue + " Hz");
mEstimatedCarrierOffsetFrequencyValueLabel.setText(getPaddedCarrierOffsetLabel(carrierOffsetFrequency));
mEstimatedCarrierOffsetFrequencyValueLabel.setEnabled(true);
mEstimatedCarrierOffsetFrequencyLabel.setEnabled(true);
});
mFrequencyOverlayPanel.setEstimatedCarrierOffsetFrequency(carrierOffsetFrequency);
@@ -306,8 +327,7 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
*/
private void reset()
{
mEstimatedCarrierOffsetFrequencyLabel.setEnabled(false);
mEstimatedCarrierOffsetFrequencyValueLabel.setText("0 Hz");
mEstimatedCarrierOffsetFrequencyValueLabel.setText(getPaddedCarrierOffsetLabel(0));
mEstimatedCarrierOffsetFrequencyValueLabel.setEnabled(false);
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, 0));
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(0));
@@ -321,10 +341,13 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
@Override
public void receive(ProcessingChain processingChain)
{
//Disconnect the FFT panel
//Disconnect the previous processing chain.
if(mProcessingChain != null)
{
mNoiseSquelchView.setController(null);
mSignalPowerView.setProcessingChain(null);
mSymbolView.removeSymbolProvider();
mSymbolView.setProtocol("");
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
mProcessingChain.removeModule(mSampleStreamTapModule);
}
@@ -341,34 +364,19 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
PrimaryDecoder primaryDecoder = mProcessingChain.getPrimaryDecoder();
if(primaryDecoder instanceof NBFMDecoder nbfmDecoder)
{
Component rightComponent = mSplitPane.getRightComponent();
if(rightComponent != mNoiseSquelchPanel)
{
mSplitPane.remove(rightComponent);
mSplitPane.setRightComponent(mNoiseSquelchPanel);
}
setRightComponent(mNoiseSquelchPanel);
mNoiseSquelchView.setController(nbfmDecoder);
mSignalPowerView.setProcessingChain(null);
}
else if(primaryDecoder instanceof AMDecoder)
{
Component rightComponent = mSplitPane.getRightComponent();
if(rightComponent != mSignalPowerView)
{
mSplitPane.remove(rightComponent);
mSplitPane.setRightComponent(mSignalPowerView);
}
mNoiseSquelchView.setController(null);
setRightComponent(mSignalPowerView);
mSignalPowerView.setProcessingChain(mProcessingChain);
}
else
else if(primaryDecoder instanceof FeedbackDecoder feedbackDecoder)
{
mNoiseSquelchView.setController(null);
mSignalPowerView.setProcessingChain(null);
setRightComponent(mSymbolPanel);
mSymbolView.setSymbolProvider(feedbackDecoder);
mSymbolView.setProtocol(feedbackDecoder.getProtocolDescription());
}
mProcessingChain.addModule(mSampleStreamTapModule);
@@ -392,15 +400,25 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
}
}
}
else
{
mNoiseSquelchView.setController(null);
mSignalPowerView.setProcessingChain(null);
}
updateFFTProcessing();
}
/**
* Shows the component on the right side of the split pane.
* @param component to show.
*/
private void setRightComponent(Component component)
{
Component rightComponent = mSplitPane.getRightComponent();
if(rightComponent != component)
{
mSplitPane.remove(rightComponent);
mSplitPane.setRightComponent(component);
mSplitPane.setDividerLocation(0.4);
}
}
/**
* Processor for source event stream to capture power level and squelch related source events.
@@ -410,7 +428,7 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
@Override
public void receive(SourceEvent sourceEvent)
{
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_CARRIER_OFFSET_FREQUENCY)
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED)
{
updateEstimatedCarrierOffsetFrequency(sourceEvent.getValue().longValue());
}

View File

@@ -1,257 +0,0 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.gui.instrument;
import io.github.dsheirer.gui.instrument.decoder.DecoderPaneFactory;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.prefs.Preferences;
/**
* Application for viewing and working with demodulators and recording files.
*/
public class DemodulatorViewerFX extends Application
{
private final static Logger mLog = LoggerFactory.getLogger(DemodulatorViewerFX.class);
private static final String PREFERENCE_FILE_OPEN_DEFAULT_FOLDER = "default.folder";
private Preferences mPreferences = Preferences.userNodeForPackage(DemodulatorViewerFX.class);
private Stage mStage;
private MenuBar mMenuBar;
private RecentFilesMenu mRecentFilesMenu;
private ViewerDesktop mViewerDesktop;
@Override
public void start(Stage primaryStage) throws Exception
{
mStage = primaryStage;
setTitle();
BorderPane borderPane = new BorderPane();
borderPane.setTop(getMenuBar());
borderPane.setCenter(getViewerDesktop());
Scene scene = new Scene(borderPane, 1800, 900);
mStage.setScene(scene);
mStage.show();
}
private void setTitle()
{
mStage.setTitle("Demodulator Viewer FX");
}
private void setTitle(DecoderType decoderType)
{
if(decoderType != null)
{
mStage.setTitle("Demodulator Viewer FX - " + decoderType.getDisplayString());
}
else
{
setTitle();
}
}
private void setTitle(P25P1Decoder.Modulation modulation)
{
if(modulation != null)
{
mStage.setTitle("Demodulator Viewer FX - P25 " + modulation.getLabel());
}
else
{
setTitle();
}
}
private ViewerDesktop getViewerDesktop()
{
if(mViewerDesktop == null)
{
mViewerDesktop = new ViewerDesktop();
}
return mViewerDesktop;
}
private MenuBar getMenuBar()
{
if(mMenuBar == null)
{
mMenuBar = new MenuBar();
Menu fileMenu = new Menu("File");
MenuItem openMenuItem = new MenuItem("Open ...");
openMenuItem.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open an I/Q recording file");
String directory = mPreferences.get(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, null);
if(directory != null)
{
fileChooser.setInitialDirectory(new File(directory));
}
File file = fileChooser.showOpenDialog(mStage);
if(file != null)
{
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
mPreferences.put(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, file.getParent());
}
}
});
MenuItem closeMenuItem = new MenuItem("Close");
closeMenuItem.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
getViewerDesktop().close();
}
});
MenuItem exitMenuItem = new MenuItem("Exit");
exitMenuItem.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
Platform.exit();
}
});
fileMenu.getItems().addAll(openMenuItem, getRecentFilesMenu(), closeMenuItem, new SeparatorMenuItem(),
exitMenuItem);
mMenuBar.getMenus().add(fileMenu);
Menu decoderMenu = new Menu("Decoder");
for(DecoderType decoderType: DecoderType.values())
{
if(DecoderPaneFactory.isSupported(decoderType))
{
if(decoderType == DecoderType.P25_PHASE1)
{
MenuItem c4fmDecoderMenuItem = new MenuItem("P25 Phase 1 C4FM");
c4fmDecoderMenuItem.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
getViewerDesktop().setP25Phase1Decoder(P25P1Decoder.Modulation.C4FM);
setTitle(decoderType);
}
});
decoderMenu.getItems().add(c4fmDecoderMenuItem);
MenuItem lsmDecoderMenuItem = new MenuItem("P25 Phase 1 LSM");
lsmDecoderMenuItem.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
getViewerDesktop().setP25Phase1Decoder(P25P1Decoder.Modulation.CQPSK);
setTitle(decoderType);
}
});
decoderMenu.getItems().add(lsmDecoderMenuItem);
}
else
{
MenuItem decoderMenuItem = new MenuItem(decoderType.getDisplayString());
decoderMenuItem.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
getViewerDesktop().setDecoder(decoderType);
setTitle(decoderType);
}
});
decoderMenu.getItems().add(decoderMenuItem);
}
}
}
mMenuBar.getMenus().add(decoderMenu);
}
return mMenuBar;
}
private RecentFilesMenu getRecentFilesMenu()
{
if(mRecentFilesMenu == null)
{
mRecentFilesMenu = new RecentFilesMenu("Recent","demodulator.viewer", 5);
mRecentFilesMenu.setFileSelectionListener(new RecentFilesMenu.IFileSelectionListener()
{
@Override
public void fileSelected(File file)
{
getViewerDesktop().load(file);
}
});
}
return mRecentFilesMenu;
}
public static void main(String[] args)
{
launch(args);
}
}

View File

@@ -1,423 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.IControllableFileSource;
import io.github.dsheirer.source.IFrameLocationListener;
import io.github.dsheirer.source.wave.ComplexWaveSource;
import io.github.dsheirer.source.wave.RealWaveSource;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.File;
import java.io.IOException;
public class PlaybackController extends HBox implements IFrameLocationListener
{
private final static Logger mLog = LoggerFactory.getLogger(PlaybackController.class);
private Button mRewindButton;
private TextField mPlaybackPositionText;
private Button mPlay1Button;
private Button mPlay10Button;
private Button mPlay30Button;
private Button mPlay100Button;
private Button mPlay1000Button;
private Button mPlay2000Button;
private HBox mControlsBox;
private Label mFileLabel;
private EventHandler<ActionEvent> mPlayEventHandler;
private AbstractDecoderPane mSampleRateListener;
private IControllableFileSource mControllableFileSource;
private Broadcaster<float[]> mRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<INativeBuffer> mNativeBufferBroadcaster = new Broadcaster<>();
public PlaybackController()
{
super(10);
getChildren().addAll(getControlsBox(), getFileLabel());
disableControls();
}
public void setSampleRateListener(AbstractDecoderPane decoderPane)
{
mSampleRateListener = decoderPane;
if(mSampleRateListener != null && mControllableFileSource != null)
{
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
}
}
public void load(File file)
{
if(file != null && file.isFile())
{
if(ComplexWaveSource.supports(file))
{
try
{
mControllableFileSource = new ComplexWaveSource(file);
mControllableFileSource.setListener(this);
((ComplexWaveSource)mControllableFileSource).setListener(mNativeBufferBroadcaster);
mControllableFileSource.open();
if(mSampleRateListener != null)
{
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
}
}
catch(UnsupportedAudioFileException e)
{
mLog.error("Unsupported Audio File Type [" + (file != null ? file.getAbsolutePath() : "null") + "]");
Alert alert = new Alert(Alert.AlertType.ERROR, "The file type is unsupported.", ButtonType.OK);
alert.show();
return;
}
catch(IOException ioe)
{
mLog.error("Error opening file [" + (file != null ? file.getAbsolutePath() : "null") + "]", ioe);
Alert alert = new Alert(Alert.AlertType.ERROR, "There was an error opening the file",
ButtonType.OK);
alert.show();
return;
}
enableControls();
}
else if(RealWaveSource.supports(file))
{
try
{
mControllableFileSource = new RealWaveSource(file);
mControllableFileSource.setListener(this);
((RealWaveSource)mControllableFileSource).setListener(mRealBufferBroadcaster);
mControllableFileSource.open();
if(mSampleRateListener != null)
{
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
}
}
catch(UnsupportedAudioFileException e)
{
mLog.error("Unsupported Audio File Type [" + (file != null ? file.getAbsolutePath() : "null") + "]");
Alert alert = new Alert(Alert.AlertType.ERROR, "The file type is unsupported.", ButtonType.OK);
alert.show();
return;
}
catch(IOException ioe)
{
mLog.error("Error opening file [" + (file != null ? file.getAbsolutePath() : "null") + "]", ioe);
Alert alert = new Alert(Alert.AlertType.ERROR, "There was an error opening the file",
ButtonType.OK);
alert.show();
return;
}
enableControls();
}
getFileLabel().setText(file.getName());
}
}
public void close()
{
if(mControllableFileSource != null)
{
try
{
mControllableFileSource.close();
}
catch(IOException ioe)
{
mLog.error("Error closing file", ioe);
}
}
disableControls();
}
/**
* Adds listener to receive real buffers from this playback
*/
public void addRealListener(Listener<float[]> listener)
{
mRealBufferBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving real buffers from this playback
*/
public void removeRealListener(Listener<float[]> listener)
{
mRealBufferBroadcaster.removeListener(listener);
}
/**
* Adds listener to receive complex buffers from this playback
*/
public void addComplexListener(Listener<INativeBuffer> listener)
{
mNativeBufferBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving compex buffers from this playback
*/
public void removeComplexListener(Listener<INativeBuffer> listener)
{
mNativeBufferBroadcaster.removeListener(listener);
}
/**
* Sets all of the playback controls to the specified enabled state.
*/
private void disableControls()
{
getRewindButton().setDisable(true);
getPlaybackPositionText().setDisable(true);
getPlay1Button().setDisable(true);
getPlay10Button().setDisable(true);
getPlay30Button().setDisable(true);
getPlay100Button().setDisable(true);
getPlay1000Button().setDisable(true);
getPlay2000Button().setDisable(true);
}
/**
* Sets all of the playback controls to the specified enabled state.
*/
private void enableControls()
{
getRewindButton().setDisable(false);
getPlaybackPositionText().setDisable(false);
getPlay1Button().setDisable(false);
getPlay10Button().setDisable(false);
getPlay30Button().setDisable(false);
getPlay100Button().setDisable(false);
getPlay1000Button().setDisable(false);
getPlay2000Button().setDisable(false);
}
private HBox getControlsBox()
{
if(mControlsBox == null)
{
mControlsBox = new HBox();
mControlsBox.getChildren().addAll(getRewindButton(), getPlaybackPositionText(), getPlay1Button(),
getPlay10Button(), getPlay30Button(), getPlay100Button(), getPlay1000Button(), getPlay2000Button());
}
return mControlsBox;
}
private Button getRewindButton()
{
if(mRewindButton == null)
{
mRewindButton = new Button();
IconNode iconNode = new IconNode(FontAwesome.FAST_BACKWARD);
iconNode.setIconSize(10);
mRewindButton.setGraphic(iconNode);
}
return mRewindButton;
}
private Button getPlay1Button()
{
if(mPlay1Button == null)
{
mPlay1Button = new Button("1");
mPlay1Button.setUserData(1);
mPlay1Button.setOnAction(getPlaybackEventHandler());
IconNode iconNode = new IconNode(FontAwesome.PLAY);
iconNode.setIconSize(10);
mPlay1Button.setGraphic(iconNode);
}
return mPlay1Button;
}
private Button getPlay10Button()
{
if(mPlay10Button == null)
{
mPlay10Button = new Button("10");
mPlay10Button.setUserData(10);
mPlay10Button.setOnAction(getPlaybackEventHandler());
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
iconNode.setIconSize(10);
mPlay10Button.setGraphic(iconNode);
}
return mPlay10Button;
}
private Button getPlay30Button()
{
if(mPlay30Button == null)
{
mPlay30Button = new Button("30");
mPlay30Button.setUserData(30);
mPlay30Button.setOnAction(getPlaybackEventHandler());
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
iconNode.setIconSize(10);
mPlay30Button.setGraphic(iconNode);
}
return mPlay30Button;
}
private Button getPlay100Button()
{
if(mPlay100Button == null)
{
mPlay100Button = new Button("100");
mPlay100Button.setUserData(100);
mPlay100Button.setOnAction(getPlaybackEventHandler());
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
iconNode.setIconSize(10);
mPlay100Button.setGraphic(iconNode);
}
return mPlay100Button;
}
private Button getPlay1000Button()
{
if(mPlay1000Button == null)
{
mPlay1000Button = new Button("1000");
mPlay1000Button.setUserData(1000);
mPlay1000Button.setOnAction(getPlaybackEventHandler());
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
iconNode.setIconSize(10);
mPlay1000Button.setGraphic(iconNode);
}
return mPlay1000Button;
}
private Button getPlay2000Button()
{
if(mPlay2000Button == null)
{
mPlay2000Button = new Button("2000");
mPlay2000Button.setUserData(2000);
mPlay2000Button.setOnAction(getPlaybackEventHandler());
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
iconNode.setIconSize(10);
mPlay2000Button.setGraphic(iconNode);
}
return mPlay2000Button;
}
private EventHandler<ActionEvent> getPlaybackEventHandler()
{
if(mPlayEventHandler == null)
{
mPlayEventHandler = new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
int count = (int)((Button)event.getSource()).getUserData();
if(mControllableFileSource != null)
{
try
{
mControllableFileSource.next(count, true);
}
catch(IOException ioe)
{
mLog.error("Error while playing samples");
}
}
}
};
}
return mPlayEventHandler;
}
private TextField getPlaybackPositionText()
{
if(mPlaybackPositionText == null)
{
mPlaybackPositionText = new TextField("0");
mPlaybackPositionText.setAlignment(Pos.CENTER);
mPlaybackPositionText.setPrefWidth(80);
}
return mPlaybackPositionText;
}
private Label getFileLabel()
{
if(mFileLabel == null)
{
mFileLabel = new Label();
}
return mFileLabel;
}
@Override
public void frameLocationUpdated(int location)
{
getPlaybackPositionText().setText(String.valueOf(location));
}
@Override
public void frameLocationReset()
{
mLog.info("Frame location reset ... ignoring?");
}
}

View File

@@ -1,109 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument;
import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane;
import io.github.dsheirer.gui.instrument.decoder.DecoderPaneFactory;
import io.github.dsheirer.gui.instrument.decoder.RealDecoderPane;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import javafx.scene.layout.BorderPane;
import java.io.File;
public class ViewerDesktop extends BorderPane
{
private PlaybackController mPlaybackController;
private AbstractDecoderPane mDecoderPane = DecoderPaneFactory.getDefaultPane();
public ViewerDesktop()
{
setBottom(getPlaybackController());
setCenter(getDecoderPane());
}
public void load(File file)
{
close();
getPlaybackController().load(file);
}
public void setDecoder(DecoderType decoderType)
{
setDecoderPane(DecoderPaneFactory.getDecoderPane(decoderType));
}
public void setP25Phase1Decoder(P25P1Decoder.Modulation modulation)
{
setDecoderPane(DecoderPaneFactory.getP25P1DecoderPane(modulation));
}
private void setDecoderPane(AbstractDecoderPane decoderPane)
{
if(getDecoderPane() instanceof RealDecoderPane)
{
getPlaybackController().removeRealListener(getDecoderPane());
}
else
{
getPlaybackController().removeComplexListener(getDecoderPane());
}
getChildren().remove(getDecoderPane());
mDecoderPane = decoderPane;
setCenter(getDecoderPane());
if(getDecoderPane() instanceof RealDecoderPane)
{
getPlaybackController().addRealListener(getDecoderPane());
}
else
{
getPlaybackController().addComplexListener(getDecoderPane());
}
getPlaybackController().setSampleRateListener(getDecoderPane());
}
/**
* Decoder pane
*/
public AbstractDecoderPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = DecoderPaneFactory.getDefaultPane();
}
return mDecoderPane;
}
public void close()
{
getPlaybackController().close();
}
private PlaybackController getPlaybackController()
{
if(mPlaybackController == null)
{
mPlaybackController = new PlaybackController();
}
return mPlaybackController;
}
}

View File

@@ -1,169 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.layout.StackPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AFSK1200SampleBufferChart extends LineChart
{
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200SampleBufferChart.class);
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
private Series<Integer,Float> mSampleSeries = new Series<>("Samples", mSamples);
private ObservableList<Data<Number,Number>> mSamplePoints = FXCollections.observableArrayList();
private Series<Number,Number> mSamplePointSeries = new Series<>("Pointers", mSamplePoints);
private ObservableList<Data<Number,Number>> mSymbolSamples = FXCollections.observableArrayList();
private Series<Number,Number> mSymbolSamplesSeries = new Series<>("Next Symbol Samples", mSymbolSamples);
private ObservableList<Data<Number,Number>> mPreviousSymbol = FXCollections.observableArrayList();
private Series<Number,Number> mPreviousSymbolSeries = new Series<>("Previous Symbol", mPreviousSymbol);
private ObservableList<Data<Number,Number>> mZeroCrossing = FXCollections.observableArrayList();
private Series<Number,Number> mZeroCrossingSeries = new Series<>("Zero Crossing", mZeroCrossing);
private IInstrumentedAFSK1200Decoder mIInstrumentedAFSK1200Decoder;
public AFSK1200SampleBufferChart(IInstrumentedAFSK1200Decoder decoder, int length)
{
super(new NumberAxis("Samples", 0, length, 5),
new NumberAxis("Value", -1.0, 1.0, 0.25));
ObservableList<Series> observableList = FXCollections.observableArrayList(mSampleSeries, mSamplePointSeries,
mSymbolSamplesSeries, mPreviousSymbolSeries, mZeroCrossingSeries);
setData(observableList);
mIInstrumentedAFSK1200Decoder = decoder;
decoder.getBufferCountProperty().addListener(new BufferChangeListener());
decoder.getAFSK1200Decoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
}
public void setBuffer(boolean[] buffer)
{
while(mSamples.size() < buffer.length)
{
Data<Integer,Float> sample = new Data<>(mSamples.size(), 0.0f);
mSamples.add(sample);
//Turn off the data point marker symbols - yes I know this is inefficient
for(Data data: mSampleSeries.getData())
{
StackPane stackPane = (StackPane)data.getNode();
stackPane.setVisible(false);
}
}
for(int x = 0; x < buffer.length; x++)
{
Data<Integer,Float> sample = mSamples.get(x);
sample.setYValue(buffer[x] ? 0.5f : -0.5f);
}
}
public class BufferChangeListener implements ChangeListener
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
setBuffer(mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLine());
while(mPreviousSymbol.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.55);
mPreviousSymbol.add(sample);
}
while(mZeroCrossing.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.65);
mZeroCrossing.add(sample);
}
while(mSamplePoints.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.75);
mSamplePoints.add(sample);
}
while(mSymbolSamples.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.85);
mSymbolSamples.add(sample);
}
int pointer1 = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLinePointer();
int pointer2 = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLineSecondPointer();
mSamplePoints.get(0).setXValue(pointer1);
mSamplePoints.get(1).setXValue(pointer2);
float samplesPerSymbol = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getSamplesPerSymbol();
boolean symbol = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getLastSymbol();
int symbolStart = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getSymbolStart();
int symbolEnd = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getSymbolEnd();
float start = pointer1 + samplesPerSymbol - 1;
float end = symbolEnd;
mSymbolSamples.get(0).setXValue(start);
mSymbolSamples.get(1).setXValue(end);
mPreviousSymbol.get(0).setXValue(symbolStart);
mPreviousSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
mPreviousSymbol.get(1).setXValue(symbolEnd);
mPreviousSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
}
}
public class ErrorChangeListener implements ChangeListener
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
while(mZeroCrossing.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.65);
mZeroCrossing.add(sample);
}
int pointer1 = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLinePointer();
int pointer2 = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLineSecondPointer();
int detectedZeroCrossing = ((Number)newValue).intValue();
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
// reference--;
//
// float start = reference - zeroCrossingIdeal;
// float end = reference - detectedZeroCrossing;
//
// mZeroCrossing.get(0).setXValue(start);
// mZeroCrossing.get(1).setXValue(end);
}
}
}

View File

@@ -1,118 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AFSK1200ZeroCrossingErrorDetectorChart extends LineChart
{
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200ZeroCrossingErrorDetectorChart.class);
private ObservableList<Data<Number,Number>> mCurrentSamples = FXCollections.observableArrayList();
private Series<Number,Number> mCurrentSampleSeries = new Series<>("Samples", mCurrentSamples);
private ObservableList<Data<Number,Number>> mPreviousSamples = FXCollections.observableArrayList();
private Series<Number,Number> mPreviousSampleSeries = new Series<>("Samples", mPreviousSamples);
private ObservableList<Data<Number,Number>> mDetected = FXCollections.observableArrayList();
private Series<Number,Number> mDetectedSeries = new Series<>("Detected", mDetected);
private IInstrumentedAFSK1200Decoder mIInstrumentedAFSK1200Decoder;
public AFSK1200ZeroCrossingErrorDetectorChart(IInstrumentedAFSK1200Decoder decoder, int length)
{
super(new NumberAxis("Samples", 0, length, 5),
new NumberAxis("Value", -1.0, 1.0, 0.25));
ObservableList<Series> observableList = FXCollections.observableArrayList(mCurrentSampleSeries,
mPreviousSampleSeries, mDetectedSeries);
setData(observableList);
mIInstrumentedAFSK1200Decoder = decoder;
decoder.getAFSK1200Decoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
decoder.getBufferCountProperty().addListener(new ChangeListener()
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
updateBuffer();
}
});
for(int x = 0; x < length; x++)
{
Data<Number,Number> sample = new Data<>(x, 0.0f);
mCurrentSamples.add(sample);
Data<Number,Number> sample2 = new Data<>(x, 0.0f);
mPreviousSamples.add(sample2);
}
Data<Number,Number> detected1 = new Data<>(5, -0.25f);
mDetected.add(detected1);
Data<Number,Number> detected2 = new Data<>(5, -0.75f);
mDetected.add(detected2);
}
private void updateBuffer()
{
boolean[] samples = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getBuffer();
for(int x = 0; x < samples.length; x++)
{
Data<Number,Number> sample = mCurrentSamples.get(x);
sample.setYValue(samples[x] ? 0.7f : 0.3f);
}
}
public class ErrorChangeListener implements ChangeListener
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
float error = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getError();
boolean[] samples = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getBuffer();
for(int x = 0; x < samples.length; x++)
{
Data<Number,Number> sample = mPreviousSamples.get(x);
sample.setYValue(samples[x] ? -0.4f : -0.6f);
}
if(error == 0.0f)
{
Data<Number,Number> detected1 = mDetected.get(0);
detected1.setXValue(0.0f);
Data<Number,Number> detected2 = mDetected.get(1);
detected2.setXValue(0.0f);
}
else
{
Data<Number,Number> detected1 = mDetected.get(0);
detected1.setXValue(mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getDetectedZeroCrossing());
Data<Number,Number> detected2 = mDetected.get(1);
detected2.setXValue(mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getDetectedZeroCrossing());
}
}
}
}

View File

@@ -1,132 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.buffer.ComplexCircularBuffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexSampleListener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ComplexSampleLineChart extends LineChart implements Listener<ComplexSamples>, ComplexSampleListener
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexSampleLineChart.class);
private ComplexCircularBuffer mComplexCircularBuffer;
private ComplexCircularBuffer mDemodulationCircularBuffer;
private ObservableList<XYChart.Data<Integer,Float>> mISamples = FXCollections.observableArrayList();
private ObservableList<XYChart.Data<Integer,Float>> mQSamples = FXCollections.observableArrayList();
private IntegerProperty mLengthProperty = new SimpleIntegerProperty(40);
public ComplexSampleLineChart(String label, int length, int samplesPerSymbol)
{
super(new NumberAxis(label, 0, length, 10),
new NumberAxis("Value", -1.0, 1.0, 0.25));
LineChart.Series<Integer,Float> iSampleSeries = new LineChart.Series<>("Inphase", mISamples);
LineChart.Series<Integer,Float> qSampleSeries = new LineChart.Series<>("Quadrature", mQSamples);
ObservableList<XYChart.Series<Integer,Float>> observableList =
FXCollections.observableArrayList(iSampleSeries, qSampleSeries);
setData(observableList);
init(length, samplesPerSymbol);
}
public void setSamplesPerSymbol(int samplesPerSymbol)
{
mDemodulationCircularBuffer = new ComplexCircularBuffer(samplesPerSymbol);
}
private void init(int length, int samplesPerSymbol)
{
mLengthProperty.addListener(new ChangeListener<Number>()
{
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue)
{
mComplexCircularBuffer = new ComplexCircularBuffer(newValue.intValue());
}
});
mComplexCircularBuffer = new ComplexCircularBuffer(length);
mDemodulationCircularBuffer = new ComplexCircularBuffer(samplesPerSymbol);
length().setValue(length);
for(int x = 1; x <= length; x++)
{
mISamples.add(new Data(x, 0.0f));
mQSamples.add(new Data(x, 0.0f));
}
}
/**
* Length of the x-axis for displaying samples
*/
public IntegerProperty length()
{
return mLengthProperty;
}
@Override
public void receive(ComplexSamples samples)
{
for(int x = 0; x < samples.i().length; x++)
{
receive(samples.i()[x], samples.q()[x]);
}
}
@Override
public void receive(float i, float q)
{
Complex sample = new Complex(i,q);
sample.normalize();
Complex previous = mDemodulationCircularBuffer.get(sample.copy());
sample.multiply(previous.conjugate());
mComplexCircularBuffer.put(sample);
updateChart();
}
private void updateChart()
{
Complex[] samples = mComplexCircularBuffer.getAll();
for(int x = 0; x < samples.length; x++)
{
Complex sample = samples[x];
if(sample != null)
{
sample.normalize();
mISamples.get(x).setYValue(sample.inphase());
mQSamples.get(x).setYValue(sample.quadrature());
}
}
}
}

View File

@@ -1,73 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DecodedSymbolChart extends LineChart implements Listener<Boolean>
{
private final static Logger mLog = LoggerFactory.getLogger(DecodedSymbolChart.class);
private ObservableList<Data<Number,Number>> mSymbols = FXCollections.observableArrayList();
private Series<Number,Number> mSymbolSeries = new Series<>("Samples", mSymbols);
private boolean[] mSymbolBuffer;
private int mSymbolBufferPointer;
public DecodedSymbolChart(int length)
{
super(new NumberAxis("Symbols", 0, length, 5),
new NumberAxis("Value", -1.0, 1.0, 0.25));
ObservableList<Series> observableList = FXCollections.observableArrayList(mSymbolSeries);
setData(observableList);
for(int x = 0; x < length; x++)
{
Data<Number,Number> symbol = new Data<>(x, -0.5f);
mSymbols.add(symbol);
}
mSymbolBuffer = new boolean[length];
}
@Override
public void receive(Boolean symbol)
{
mSymbolBuffer[mSymbolBufferPointer++] = symbol;
mSymbolBufferPointer %= mSymbolBuffer.length;
update();
}
private void update()
{
for(int x = 0; x < mSymbolBuffer.length; x++)
{
Data<Number,Number> symbol = mSymbols.get(x);
int offset = x + mSymbolBufferPointer;
offset %= mSymbolBuffer.length;
symbol.setYValue(mSymbolBuffer[offset] ? 0.5f : -0.5f);
}
}
}

View File

@@ -1,91 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.buffer.DoubleCircularBuffer;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ValueAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DoubleLineChart extends LineChart implements Listener<Double>
{
private final static Logger mLog = LoggerFactory.getLogger(DoubleLineChart.class);
private DoubleCircularBuffer mDoubleCircularBuffer;
private ObservableList<Data<Integer,Double>> mPhaseValues = FXCollections.observableArrayList();
public DoubleLineChart(String seriesLabel, double min, double max, double tick, int length)
{
super(new NumberAxis(1, length, 2), new NumberAxis(min, max, tick));
Series<Integer,Double> phaseSeries = new Series<>(seriesLabel, mPhaseValues);
ObservableList<Series<Integer,Double>> observableList = FXCollections.observableArrayList(phaseSeries);
setData(observableList);
init(length);
}
/**
* Sets the minimum displayable value for the y axis
* @param min
*/
public void setMin(double min)
{
((ValueAxis)getYAxis()).setLowerBound(min);
}
/**
* Sets the maximum displayable value for the y axis
* @param max
*/
public void setMax(double max)
{
((ValueAxis)getYAxis()).setUpperBound(max);
}
private void init(int length)
{
mDoubleCircularBuffer = new DoubleCircularBuffer(length);
for(int x = 1; x <= length; x++)
{
mPhaseValues.add(new Data(x, 0.0));
}
}
@Override
public void receive(Double phase)
{
mDoubleCircularBuffer.put(phase);
updateChart();
}
private void updateChart()
{
double[] values = mDoubleCircularBuffer.getAll();
for(int x = 0; x < values.length; x++)
{
mPhaseValues.get(x).setYValue(values[x]);
}
}
}

View File

@@ -1,149 +0,0 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.dsp.psk.SymbolDecisionData;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.Complex;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EyeDiagramChart extends LineChart implements Listener<SymbolDecisionData>
{
private final static Logger mLog = LoggerFactory.getLogger(EyeDiagramChart.class);
private ObservableList<Series<Double,Float>> mData = FXCollections.observableArrayList();
private int mSeriesCount;
private int mSeriesPointer;
private int mSeriesLength;
public EyeDiagramChart(int seriesCount, String legend)
{
super(new NumberAxis("Sample Point = 3", -1.0, 7.0, 1.0),
new NumberAxis("Radians", -5.0, 5.0, 0.5));
mSeriesCount = seriesCount;
init();
}
private void init()
{
for(int x = 0; x < mSeriesCount; x++)
{
ObservableList<Data<Double,Double>> series = FXCollections.observableArrayList();
mData.add(new Series(series));
}
setData(mData);
}
private ObservableList<Data<Double,Float>> getSeries(int series, int length)
{
ObservableList<Data<Double,Float>> seriesData = mData.get(series).getData();
while(seriesData.size() > length)
{
seriesData.remove(0);
}
while(seriesData.size() < length)
{
seriesData.add(new Data<>(0.0,0.0f));
}
return seriesData;
}
private void checkChartLength(int length)
{
if(mSeriesLength != length)
{
((NumberAxis)getXAxis()).setUpperBound(length);
mSeriesLength = length;
}
}
private float[] conditionData(Complex[] samples)
{
Float previousAngle = null;
float[] corrected = new float[samples.length];
int phaseRolloverIndex = -1;
for(int x = 0; x < samples.length; x++)
{
corrected[x] = samples[x].angle();
if(x > 0 && (FastMath.abs(corrected[x] - corrected[x - 1]) > FastMath.PI))
{
phaseRolloverIndex = x;
}
}
if(phaseRolloverIndex >= 0)
{
for(int x = 0; x < phaseRolloverIndex; x++)
{
if(corrected[x] > 0)
{
corrected[x] -= 2 * FastMath.PI;
}
else
{
corrected[x] += 2 * FastMath.PI;
}
}
}
return corrected;
}
@Override
public void receive(SymbolDecisionData symbolDecisionData)
{
Complex[] samples = symbolDecisionData.getSamples();
int length = samples.length;
ObservableList<Data<Double,Float>> iSeries = getSeries(mSeriesPointer++, length);
// checkChartLength(length);
if(mSeriesPointer >= mSeriesCount)
{
mSeriesPointer = 0;
}
float[] angles = conditionData(samples);
for(int x = 0; x < samples.length; x++)
{
Data<Double,Float> iDataPoint = iSeries.get(x);
Complex sample = samples[x];
iDataPoint.setXValue((double)x - symbolDecisionData.getSamplingPoint());
iDataPoint.setYValue(angles[x]);
}
}
}

View File

@@ -1,30 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.sample.Listener;
import javafx.beans.property.SimpleIntegerProperty;
public interface IInstrumentedAFSK1200Decoder
{
SimpleIntegerProperty getBufferCountProperty();
AFSK1200DecoderInstrumented getAFSK1200Decoder();
void setMessageListener(Listener<IMessage> listener);
}

View File

@@ -1,174 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.layout.StackPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LTRNetSampleBufferChart extends LineChart
{
private final static Logger mLog = LoggerFactory.getLogger(LTRNetSampleBufferChart.class);
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
private Series<Integer,Float> mSampleSeries = new Series<>("Samples", mSamples);
private ObservableList<Data<Number,Number>> mSamplePoints = FXCollections.observableArrayList();
private Series<Number,Number> mSamplePointSeries = new Series<>("Pointers", mSamplePoints);
private ObservableList<Data<Number,Number>> mSymbolSamples = FXCollections.observableArrayList();
private Series<Number,Number> mSymbolSamplesSeries = new Series<>("Next Symbol Samples", mSymbolSamples);
private ObservableList<Data<Number,Number>> mSymbol = FXCollections.observableArrayList();
private Series<Number,Number> mSymbolSeries = new Series<>("Previous Symbol", mSymbol);
private ObservableList<Data<Number,Number>> mZeroCrossing = FXCollections.observableArrayList();
private Series<Number,Number> mZeroCrossingSeries = new Series<>("Zero Crossing", mZeroCrossing);
private LTRNetDecoderInstrumented mLTRNetDecoderInstrumented;
public LTRNetSampleBufferChart(LTRNetDecoderInstrumented decoder, int length)
{
super(new NumberAxis("Samples", 0, length, 5),
new NumberAxis("Value", -1.0, 1.0, 0.25));
ObservableList<Series> observableList = FXCollections.observableArrayList(mSampleSeries, mSamplePointSeries,
mSymbolSamplesSeries, mSymbolSeries, mZeroCrossingSeries);
setData(observableList);
mLTRNetDecoderInstrumented = decoder;
decoder.bufferCount.addListener(new BufferChangeListener());
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
}
public void setBuffer(boolean[] buffer)
{
while(mSamples.size() < buffer.length)
{
Data<Integer,Float> sample = new Data<>(mSamples.size(), 0.0f);
mSamples.add(sample);
//Turn off the data point marker symbols - yes I know this is inefficient
for(Data data: mSampleSeries.getData())
{
StackPane stackPane = (StackPane)data.getNode();
stackPane.setVisible(false);
}
}
for(int x = 0; x < buffer.length; x++)
{
Data<Integer,Float> sample = mSamples.get(x);
sample.setYValue(buffer[x] ? 0.5f : -0.5f);
}
}
public class BufferChangeListener implements ChangeListener
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
// setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
while(mSymbol.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.55);
mSymbol.add(sample);
}
while(mZeroCrossing.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.65);
mZeroCrossing.add(sample);
}
while(mSamplePoints.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.75);
mSamplePoints.add(sample);
}
while(mSymbolSamples.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.85);
mSymbolSamples.add(sample);
}
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
// mSamplePoints.get(0).setXValue(pointer1);
// mSamplePoints.get(1).setXValue(pointer2);
// float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
// float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
// float start = pointer1 + samplesRemaining;
// float end = start + samplesPerSymbol;
// mSymbolSamples.get(0).setXValue(start);
// mSymbolSamples.get(1).setXValue(end);
// boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
// int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
// int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
// mSymbol.get(0).setXValue(symbolStart);
// mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
// mSymbol.get(1).setXValue(symbolEnd);
// mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
}
}
public class ErrorChangeListener implements ChangeListener
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
while(mZeroCrossing.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.65);
mZeroCrossing.add(sample);
}
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
// float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
// float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
//
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
// reference--;
//
// float start = reference - zeroCrossingIdeal;
// float end = reference - detectedZeroCrossing;
//
// mZeroCrossing.get(0).setXValue(start);
// mZeroCrossing.get(1).setXValue(end);
}
}
}

View File

@@ -1,73 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.buffer.DoubleCircularBuffer;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PhaseLineChart extends LineChart implements Listener<Double>
{
private final static Logger mLog = LoggerFactory.getLogger(PhaseLineChart.class);
private DoubleCircularBuffer mDoubleCircularBuffer;
private ObservableList<Data<Integer,Double>> mPhaseValues = FXCollections.observableArrayList();
public PhaseLineChart(int length)
{
super(new NumberAxis("Time", 1, length, 2),
new NumberAxis("Phase", -0.8, 0.8, 0.1));
Series<Integer,Double> phaseSeries = new Series<>("PLL Phase Error", mPhaseValues);
ObservableList<Series<Integer,Double>> observableList = FXCollections.observableArrayList(phaseSeries);
setData(observableList);
init(length);
}
private void init(int length)
{
mDoubleCircularBuffer = new DoubleCircularBuffer(length);
for(int x = 1; x <= length; x++)
{
mPhaseValues.add(new Data(x, 0.0));
}
}
@Override
public void receive(Double phase)
{
mDoubleCircularBuffer.put(phase);
updateChart();
}
private void updateChart()
{
double[] values = mDoubleCircularBuffer.getAll();
for(int x = 0; x < values.length; x++)
{
mPhaseValues.get(x).setYValue(values[x]);
}
}
}

View File

@@ -1,85 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.buffer.FloatCircularBuffer;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RealSampleLineChart extends LineChart implements Listener<float[]>
{
private final static Logger mLog = LoggerFactory.getLogger(RealSampleLineChart.class);
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
private Series<Integer,Float> mSampleSeries = new Series<>("Samples", mSamples);
private FloatCircularBuffer mRealCircularBuffer;
public RealSampleLineChart(int length, double tickUnit)
{
super(new NumberAxis("Samples", 0, length, tickUnit),
new NumberAxis("Value", -1.0, 1.0, 0.25));
ObservableList<Series<Integer,Float>> observableList = FXCollections.observableArrayList(mSampleSeries);
setData(observableList);
init(length);
}
private void init(int length)
{
mRealCircularBuffer = new FloatCircularBuffer(length);
for(int x = 0; x < length; x++)
{
Data<Integer,Float> sample = new Data<>(x, 0.0f);
mSamples.add(sample);
}
//Turn off the data point marker symbols
for(XYChart.Data data: mSampleSeries.getData())
{
StackPane stackPane = (StackPane)data.getNode();
stackPane.setVisible(false);
}
}
@Override
public void receive(float[] buffer)
{
for(float sample: buffer)
{
mRealCircularBuffer.put(sample);
}
float[] bufferSamples = mRealCircularBuffer.getAll();
for(int x = 0; x < bufferSamples.length; x++)
{
Data<Integer,Float> sample = mSamples.get(x);
sample.setYValue(bufferSamples[x]);
}
}
}

View File

@@ -1,87 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.chart;
import eu.hansolo.fx.charts.ChartType;
import eu.hansolo.fx.charts.PolarChart;
import eu.hansolo.fx.charts.XYPane;
import eu.hansolo.fx.charts.data.XYChartItem;
import eu.hansolo.fx.charts.series.XYSeries;
import io.github.dsheirer.buffer.ComplexCircularBuffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexSamples;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SampleXYChart extends PolarChart<XYChartItem> implements Listener<ComplexSamples>
{
private static ObservableList<XYChartItem> sChartItemList = FXCollections.observableArrayList();
static
{
sChartItemList.add(new XYChartItem(1, 1));
sChartItemList.add(new XYChartItem(-1, -1));
}
private final static Logger mLog = LoggerFactory.getLogger(SampleXYChart.class);
private ComplexCircularBuffer mCircularBuffer;
private int mSampleCount;
public SampleXYChart(int sampleCount, String title)
{
super(new XYPane<>(new XYSeries<>(sChartItemList, ChartType.POLAR)));
mSampleCount = sampleCount;
mCircularBuffer = new ComplexCircularBuffer(mSampleCount);
getXYPane().setLowerBoundX(0);
getXYPane().setUpperBoundX(1.0);
getXYPane().setLowerBoundY(0);
getXYPane().setUpperBoundY(1.0);
}
private void init()
{
}
@Override
public void receive(ComplexSamples buffer)
{
for(int x = 0; x < buffer.i().length; x++)
{
mCircularBuffer.put(new Complex(buffer.i()[x], buffer.q()[x]));
}
Complex[] complexSamples = mCircularBuffer.getAll();
XYSeries<XYChartItem> series = getXYPane().getListOfSeries().get(0);
series.setStroke(Color.BLUE);
series.getItems().clear();
for (Complex sample : complexSamples) {
series.getItems().add(new XYChartItem(sample.polarAngleDegrees(), sample.magnitude(), Color.BLUE));
}
refresh();
}
}

View File

@@ -1,59 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import javafx.collections.ObservableList;
public class SamplesPerSymbolChart extends DoubleLineChart
{
private double mFractionalSamplesPerSymbol;
private double mPreviousSamplesPerSymbol;
public SamplesPerSymbolChart(int length, double samplesPerSymbol)
{
super("Samples Per Symbol", -1.0, 1.0, 0.1, length);
setSamplesPerSymbol(samplesPerSymbol);
}
public void setSamplesPerSymbol(double samplesPerSymbol)
{
mFractionalSamplesPerSymbol = samplesPerSymbol - ((int)samplesPerSymbol);
mPreviousSamplesPerSymbol = samplesPerSymbol;
ObservableList<Series> seriesList = getData();
seriesList.get(0).setName("Samples Per Symbol:" + samplesPerSymbol);
}
@Override
public void receive(Double samplesPerSymbol)
{
double delta = samplesPerSymbol - mPreviousSamplesPerSymbol - mFractionalSamplesPerSymbol;
while(delta < (-1.0 + mFractionalSamplesPerSymbol))
{
delta++;
}
while(delta > 1.0)
{
delta--;
}
super.receive(delta);
mPreviousSamplesPerSymbol = samplesPerSymbol;
}
}

View File

@@ -1,90 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.Complex;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SymbolChart extends ScatterChart implements Listener<Complex>
{
private final static Logger mLog = LoggerFactory.getLogger(SymbolChart.class);
private ObservableList<ScatterChart.Data> mCurrentPoints = FXCollections.observableArrayList();
// private ObservableList<ScatterChart.Data> mPreviousPoints = FXCollections.observableArrayList();
// private ObservableList<ScatterChart.Data> mOldestPoints = FXCollections.observableArrayList();
private int mDataPointer;
private int mHistory;
/**
* Symbol chart for plotting a series of constellation symbols over time. Maintains three history sets for
* current, previous, and oldest symbols.
*
* @param history size for each of the history sets
*/
public SymbolChart(int history)
{
super(new NumberAxis("I", -1.1, 1.1, 0.2),
new NumberAxis("Q", -1.1, 1.1, 0.2));
mHistory = history;
for(int x = 0; x < mHistory; x++)
{
mCurrentPoints.add(new XYChart.Data<>(0.0, 0.0));
// mPreviousPoints.add(new XYChart.Data<>(0.0, 0.0));
// mOldestPoints.add(new XYChart.Data<>(0.0, 0.0));
}
ObservableList<XYChart.Series> data = FXCollections.observableArrayList();
data.add(new ScatterChart.Series("Constellation", mCurrentPoints));
// data.add(new ScatterChart.Series("Previous", mPreviousPoints));
// data.add(new ScatterChart.Series("Oldest", mOldestPoints));
this.setData(data);
}
@Override
public void receive(Complex complex)
{
//Transfer previous to oldest
// XYChart.Data oldest = mOldestPoints.get(mDataPointer);
// XYChart.Data previous = mPreviousPoints.get(mDataPointer);
XYChart.Data current = mCurrentPoints.get(mDataPointer);
// oldest.setXValue(previous.getXValue());
// oldest.setYValue(previous.getYValue());
//
// previous.setXValue(current.getXValue());
// previous.setYValue(current.getYValue());
current.setXValue(complex.inphase());
current.setYValue(complex.quadrature());
mDataPointer++;
if(mDataPointer >= mHistory)
{
mDataPointer = 0;
}
}
}

View File

@@ -1,135 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZeroCrossingErrorDetectorChart extends LineChart
{
private final static Logger mLog = LoggerFactory.getLogger(ZeroCrossingErrorDetectorChart.class);
private ObservableList<Data<Number,Number>> mCurrentSamples = FXCollections.observableArrayList();
private Series<Number,Number> mCurrentSampleSeries = new Series<>("Samples", mCurrentSamples);
private ObservableList<Data<Number,Number>> mPreviousSamples = FXCollections.observableArrayList();
private Series<Number,Number> mPreviousSampleSeries = new Series<>("Samples", mPreviousSamples);
private ObservableList<Data<Number,Number>> mIdeal = FXCollections.observableArrayList();
private Series<Number,Number> mIdealSeries = new Series<>("Ideal", mIdeal);
private ObservableList<Data<Number,Number>> mDetected = FXCollections.observableArrayList();
private Series<Number,Number> mDetectedSeries = new Series<>("Detected", mDetected);
private LTRNetDecoderInstrumented mLTRNetDecoderInstrumented;
public ZeroCrossingErrorDetectorChart(LTRNetDecoderInstrumented decoder, int length)
{
super(new NumberAxis("Samples", 0, length, 5),
new NumberAxis("Value", -1.0, 1.0, 0.25));
ObservableList<Series> observableList = FXCollections.observableArrayList(mCurrentSampleSeries,
mPreviousSampleSeries, mIdealSeries, mDetectedSeries);
setData(observableList);
mLTRNetDecoderInstrumented = decoder;
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
decoder.bufferCount.addListener(new ChangeListener()
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
updateBuffer();
}
});
for(int x = 0; x < length; x++)
{
Data<Number,Number> sample = new Data<>(x, 0.0f);
mCurrentSamples.add(sample);
Data<Number,Number> sample2 = new Data<>(x, 0.0f);
mPreviousSamples.add(sample2);
}
Data<Number,Number> ideal1 = new Data<>(5, -0.1f);
mIdeal.add(ideal1);
Data<Number,Number> ideal2 = new Data<>(5, -0.9f);
mIdeal.add(ideal2);
Data<Number,Number> detected1 = new Data<>(5, -0.25f);
mDetected.add(detected1);
Data<Number,Number> detected2 = new Data<>(5, -0.75f);
mDetected.add(detected2);
}
private void updateBuffer()
{
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
//
// for(int x = 0; x < samples.length; x++)
// {
// Data<Number,Number> sample = mCurrentSamples.get(x);
// sample.setYValue(samples[x] ? 0.7f : 0.3f);
// }
}
public class ErrorChangeListener implements ChangeListener
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
// float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
//
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
//
// for(int x = 0; x < samples.length; x++)
// {
// Data<Number,Number> sample = mPreviousSamples.get(x);
// sample.setYValue(samples[x] ? -0.4f : -0.6f);
// }
//
// Data<Number,Number> ideal1 = mIdeal.get(0);
// ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
// Data<Number,Number> ideal2 = mIdeal.get(1);
// ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
//
// if(error == 0.0f)
// {
// Data<Number,Number> detected1 = mDetected.get(0);
// detected1.setXValue(0.0f);
// Data<Number,Number> detected2 = mDetected.get(1);
// detected2.setXValue(0.0f);
// }
// else
// {
// Data<Number,Number> detected1 = mDetected.get(0);
// detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
// Data<Number,Number> detected2 = mDetected.get(1);
// detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
// }
}
}
}

View File

@@ -1,164 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.AFSK1200SampleBufferChart;
import io.github.dsheirer.gui.instrument.chart.AFSK1200ZeroCrossingErrorDetectorChart;
import io.github.dsheirer.gui.instrument.chart.DecodedSymbolChart;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractAFSK1200Pane extends RealDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractAFSK1200Pane.class);
private HBox mSampleChartBox;
private RealSampleLineChart mRealSampleLineChart;
private HBox mSymbolDecoderBox;
private AFSK1200SampleBufferChart mSampleBufferChart;
private AFSK1200ZeroCrossingErrorDetectorChart mZeroCrossingErrorDetectorChart;
private DecodedSymbolChart mDecodedSymbolChart;
private IInstrumentedAFSK1200Decoder mDecoder;
public AbstractAFSK1200Pane(DecoderType decoderType)
{
super(decoderType);
init();
}
private void init()
{
//This is force-cast without type checking ....
addListener((Listener<float[]>) getDecoder());
addListener(getSampleLineChart());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getSymbolDecoderBox(), Priority.ALWAYS);
VBox.setVgrow(getSampleChartBox(), Priority.ALWAYS);
VBox.setVgrow(getSymbolDecoderBox(), Priority.ALWAYS);
getSampleChartBox().setMaxWidth(Double.MAX_VALUE);
getSymbolDecoderBox().setMaxWidth(Double.MAX_VALUE);
getChildren().addAll(getSampleChartBox(), getSymbolDecoderBox());
getDecoder().getAFSK1200Decoder().addListener(getDecodedSymbolChart());
getDecoder().setMessageListener(new Listener<IMessage>()
{
@Override
public void receive(IMessage message)
{
mLog.debug((message.isValid() ? "PASS " : "FAIL ") + message.toString());
}
});
}
protected abstract IInstrumentedAFSK1200Decoder getDecoder();
private HBox getSampleChartBox()
{
if(mSampleChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
getDecodedSymbolChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
HBox.setHgrow(getDecodedSymbolChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getDecodedSymbolChart());
}
return mSampleChartBox;
}
private RealSampleLineChart getSampleLineChart()
{
if(mRealSampleLineChart == null)
{
mRealSampleLineChart = new RealSampleLineChart(200, (10.0));
}
return mRealSampleLineChart;
}
private HBox getSymbolDecoderBox()
{
if(mSymbolDecoderBox == null)
{
mSymbolDecoderBox = new HBox();
mSymbolDecoderBox.setMaxWidth(Double.MAX_VALUE);
getSampleBufferChart().setMaxWidth(Double.MAX_VALUE);
getZeroCrossingErrorDetectorChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleBufferChart(), Priority.ALWAYS);
HBox.setHgrow(getZeroCrossingErrorDetectorChart(), Priority.ALWAYS);
mSymbolDecoderBox.getChildren().addAll(getSampleBufferChart(), getZeroCrossingErrorDetectorChart());
}
return mSymbolDecoderBox;
}
private AFSK1200SampleBufferChart getSampleBufferChart()
{
if(mSampleBufferChart == null)
{
mSampleBufferChart = new AFSK1200SampleBufferChart(getDecoder(), 106);
}
return mSampleBufferChart;
}
private AFSK1200ZeroCrossingErrorDetectorChart getZeroCrossingErrorDetectorChart()
{
if(mZeroCrossingErrorDetectorChart == null)
{
mZeroCrossingErrorDetectorChart = new AFSK1200ZeroCrossingErrorDetectorChart(getDecoder(),
getDecoder().getAFSK1200Decoder().getErrorDetector().getBuffer().length);
}
return mZeroCrossingErrorDetectorChart;
}
private DecodedSymbolChart getDecodedSymbolChart()
{
if(mDecodedSymbolChart == null)
{
mDecodedSymbolChart = new DecodedSymbolChart(50);
}
return mDecodedSymbolChart;
}
}

View File

@@ -1,67 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import javafx.scene.text.Text;
public class ComplexDecoderPane extends AbstractDecoderPane<INativeBuffer>
{
private Broadcaster<INativeBuffer> mBufferBroadcaster = new Broadcaster();
private DecoderType mDecoderType;
public ComplexDecoderPane(DecoderType decoderType)
{
mDecoderType = decoderType;
}
/**
* Package private - used by factory class to create an empty pane
*/
ComplexDecoderPane()
{
getChildren().add(new Text("Please select a decoder from the menu"));
}
@Override
SampleType getSampleType()
{
return SampleType.COMPLEX;
}
@Override
public void receive(INativeBuffer nativeBuffer)
{
mBufferBroadcaster.broadcast(nativeBuffer);
}
public void addListener(Listener<INativeBuffer> listener)
{
mBufferBroadcaster.addListener(listener);
}
public void removeListener(Listener<INativeBuffer> listener)
{
mBufferBroadcaster.removeListener(listener);
}
}

View File

@@ -1,96 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import java.util.EnumSet;
public class DecoderPaneFactory
{
private static final EnumSet<DecoderType> SUPPORTED_DECODER_TYPES = EnumSet.of(
DecoderType.P25_PHASE1,
DecoderType.P25_PHASE2,
DecoderType.FLEETSYNC2,
DecoderType.LJ_1200,
DecoderType.LTR_NET,
DecoderType.MDC1200,
DecoderType.MPT1327,
DecoderType.TAIT_1200);
/**
* Creates a decoder pane for the decoder type
*/
public static AbstractDecoderPane getDecoderPane(DecoderType decoderType)
{
switch(decoderType)
{
case FLEETSYNC2:
return new Fleetsync2Pane();
case LJ_1200:
return new LJ1200Pane();
case LTR_NET:
return new LTRNetPane();
case MDC1200:
return new MDC1200Pane();
case MPT1327:
return new MPT1327Pane();
case TAIT_1200:
return new Tait1200Pane();
case P25_PHASE1:
throw new IllegalArgumentException("Use the getP25P1DecoderPane() method for P25 decoder type");
case P25_PHASE2:
return new P25Phase2HDQPSKPane();
}
return getDefaultPane();
}
/**
* Creates a decoder pane for the P25 decoder type
*/
public static AbstractDecoderPane getP25P1DecoderPane(P25P1Decoder.Modulation modulation)
{
switch(modulation)
{
case C4FM:
return new P25Phase1C4FMPane();
case CQPSK:
return new P25Phase1LSMPane();
}
return getDefaultPane();
}
/**
* Creates an empty decoder pane
*/
public static AbstractDecoderPane getDefaultPane()
{
return new ComplexDecoderPane();
}
/**
* Indicates if the decoder type is supported by this factory
*/
public static boolean isSupported(DecoderType decoderType)
{
return SUPPORTED_DECODER_TYPES.contains(decoderType);
}
}

View File

@@ -1,43 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderInstrumented;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Fleetsync2Pane extends AbstractAFSK1200Pane
{
private final static Logger mLog = LoggerFactory.getLogger(Fleetsync2Pane.class);
private Fleetsync2DecoderInstrumented mDecoder;
public Fleetsync2Pane()
{
super(DecoderType.FLEETSYNC2);
}
protected Fleetsync2DecoderInstrumented getDecoder()
{
if(mDecoder == null)
{
mDecoder = new Fleetsync2DecoderInstrumented();
}
return mDecoder;
}
}

View File

@@ -1,78 +0,0 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.lj1200.LJ1200DecoderInstrumented;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LJ1200Pane extends AbstractAFSK1200Pane
{
private final static Logger mLog = LoggerFactory.getLogger(LJ1200Pane.class);
private LJ1200DecoderInstrumented mDecoder;
public LJ1200Pane()
{
super(DecoderType.LJ_1200);
}
protected LJ1200DecoderInstrumented getDecoder()
{
if(mDecoder == null)
{
mDecoder = new LJ1200DecoderInstrumented();
mDecoder.getTowerMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@Override
public void syncDetected(int bitErrors)
{
mLog.debug("Tower Sync Detected!");
}
@Override
public void syncLost(int bitsProcessed)
{
//no-op
}
});
mDecoder.getTransponderMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@Override
public void syncDetected(int bitErrors)
{
mLog.debug("Transponder Sync Detected!");
}
@Override
public void syncLost(int bitsProcessed)
{
//no-op
}
});
}
return mDecoder;
}
}

View File

@@ -1,154 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.LTRNetSampleBufferChart;
import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart;
import io.github.dsheirer.gui.instrument.chart.ZeroCrossingErrorDetectorChart;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet;
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
import io.github.dsheirer.sample.Listener;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LTRNetPane extends RealDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(LTRNetPane.class);
private HBox mSampleChartBox;
private RealSampleLineChart mRealSampleLineChart;
private HBox mSymbolDecoderBox;
private LTRNetSampleBufferChart mLTRNetSampleBufferChart;
private ZeroCrossingErrorDetectorChart mZeroCrossingErrorDetectorChart;
private LTRNetDecoderInstrumented mDecoder;
public LTRNetPane()
{
super(DecoderType.LTR_NET);
init();
}
private void init()
{
addListener(getDecoder());
addListener(getSampleLineChart());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getSymbolDecoderBox(), Priority.ALWAYS);
VBox.setVgrow(getSampleChartBox(), Priority.ALWAYS);
VBox.setVgrow(getSymbolDecoderBox(), Priority.ALWAYS);
getSampleChartBox().setMaxWidth(Double.MAX_VALUE);
getSymbolDecoderBox().setMaxWidth(Double.MAX_VALUE);
getChildren().addAll(getSampleChartBox(), getSymbolDecoderBox());
}
private LTRNetDecoderInstrumented getDecoder()
{
if(mDecoder == null)
{
DecodeConfigLTRNet config = new DecodeConfigLTRNet();
config.setMessageDirection(MessageDirection.OSW);
mDecoder = new LTRNetDecoderInstrumented(config);
mDecoder.setMessageListener(new Listener<IMessage>()
{
@Override
public void receive(IMessage message)
{
mLog.debug(message.toString());
}
});
}
return mDecoder;
}
private HBox getSampleChartBox()
{
if(mSampleChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChart());
}
return mSampleChartBox;
}
private RealSampleLineChart getSampleLineChart()
{
if(mRealSampleLineChart == null)
{
mRealSampleLineChart = new RealSampleLineChart(500, (8000.0 / 300.0));
}
return mRealSampleLineChart;
}
private HBox getSymbolDecoderBox()
{
if(mSymbolDecoderBox == null)
{
mSymbolDecoderBox = new HBox();
mSymbolDecoderBox.setMaxWidth(Double.MAX_VALUE);
getSampleBufferChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleBufferChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleBufferChart(), getZeroCrossingErrorDetectorChart());
}
return mSymbolDecoderBox;
}
private LTRNetSampleBufferChart getSampleBufferChart()
{
if(mLTRNetSampleBufferChart == null)
{
mLTRNetSampleBufferChart = new LTRNetSampleBufferChart(getDecoder(), 106);
}
return mLTRNetSampleBufferChart;
}
private ZeroCrossingErrorDetectorChart getZeroCrossingErrorDetectorChart()
{
if(mZeroCrossingErrorDetectorChart == null)
{
// mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
// getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
}
return mZeroCrossingErrorDetectorChart;
}
}

View File

@@ -1,64 +0,0 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.mdc1200.MDCDecoderInstrumented;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MDC1200Pane extends AbstractAFSK1200Pane
{
private final static Logger mLog = LoggerFactory.getLogger(MDC1200Pane.class);
private MDCDecoderInstrumented mDecoder;
public MDC1200Pane()
{
super(DecoderType.MDC1200);
}
protected MDCDecoderInstrumented getDecoder()
{
if(mDecoder == null)
{
mDecoder = new MDCDecoderInstrumented();
mDecoder.getMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@Override
public void syncDetected(int bitErrors)
{
mLog.debug("Sync Detected!");
}
@Override
public void syncLost(int bitsProcessed)
{
mLog.debug("Sync Lost!");
}
});
}
return mDecoder;
}
}

View File

@@ -1,78 +0,0 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.mpt1327.MPT1327DecoderInstrumented;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MPT1327Pane extends AbstractAFSK1200Pane
{
private final static Logger mLog = LoggerFactory.getLogger(MPT1327Pane.class);
private MPT1327DecoderInstrumented mDecoder;
public MPT1327Pane()
{
super(DecoderType.MPT1327);
}
protected MPT1327DecoderInstrumented getDecoder()
{
if(mDecoder == null)
{
mDecoder = new MPT1327DecoderInstrumented();
mDecoder.getControlMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@Override
public void syncDetected(int bitErrors)
{
// mLog.debug("Control Sync Detected!");
}
@Override
public void syncLost(int bitsProcessed)
{
//no-op
}
});
mDecoder.getTrafficMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@Override
public void syncDetected(int bitErrors)
{
// mLog.debug("Traffic Sync Detected!");
}
@Override
public void syncLost(int bitsProcessed)
{
//no-op
}
});
}
return mDecoder;
}
}

View File

@@ -1,208 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FMInstrumented;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
public class P25Phase1C4FMPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1C4FMPane.class);
private HBox mSampleChartBox;
private ComplexSampleLineChart mSampleLineChart;
private EyeDiagramChart mEyeDiagramChart;
private HBox mDecoderChartBox;
private SymbolChart mSymbolChart;
private PhaseLineChart mPLLPhaseErrorLineChart;
private DoubleLineChart mPLLFrequencyLineChart;
private DoubleLineChart mSamplesPerSymbolLineChart;
private Broadcaster<ComplexSamples> mFilteredBufferBroadcaster = new Broadcaster();
private P25P1DecoderC4FMInstrumented mDecoder = new P25P1DecoderC4FMInstrumented();
public P25Phase1C4FMPane()
{
super(DecoderType.P25_PHASE1);
init();
}
private void init()
{
addListener(nativeBuffer ->
{
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
while(iterator.hasNext())
{
getDecoder().receive(iterator.next());
}
});
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
getDecoder().setComplexSymbolListener(getSymbolChart());
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
mDecoder.setMessageListener(new Listener<IMessage>()
{
@Override
public void receive(IMessage message)
{
mLog.debug(message.toString());
}
});
}
@Override
public void setSampleRate(double sampleRate)
{
mLog.debug("Configuring for sample rate: " + sampleRate);
mDecoder.setSampleRate(sampleRate);
double samplesPerSymbol = sampleRate / 4800.0;
getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol);
}
private P25P1DecoderC4FMInstrumented getDecoder()
{
return mDecoder;
}
private SymbolChart getSymbolChart()
{
if(mSymbolChart == null)
{
mSymbolChart = new SymbolChart(10);
}
return mSymbolChart;
}
private HBox getDecoderChartBox()
{
if(mDecoderChartBox == null)
{
mDecoderChartBox = new HBox();
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
getPLLFrequencyLineChart());
}
return mDecoderChartBox;
}
private HBox getSampleChartBox()
{
if(mSampleChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
getSamplesPerSymbolLineChart());
}
return mSampleChartBox;
}
private ComplexSampleLineChart getSampleLineChart()
{
if(mSampleLineChart == null)
{
mSampleLineChart = new ComplexSampleLineChart("Raw Samples", 100, 10);
}
return mSampleLineChart;
}
private EyeDiagramChart getEyeDiagramChart()
{
if(mEyeDiagramChart == null)
{
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
}
return mEyeDiagramChart;
}
private PhaseLineChart getPLLPhaseErrorLineChart()
{
if(mPLLPhaseErrorLineChart == null)
{
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
}
return mPLLPhaseErrorLineChart;
}
private DoubleLineChart getPLLFrequencyLineChart()
{
if(mPLLFrequencyLineChart == null)
{
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
}
return mPLLFrequencyLineChart;
}
private DoubleLineChart getSamplesPerSymbolLineChart()
{
if(mSamplesPerSymbolLineChart == null)
{
mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 9.5, 11.5, 0.1, 40);
}
return mSamplesPerSymbolLineChart;
}
}

View File

@@ -1,210 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
import io.github.dsheirer.gui.instrument.chart.SamplesPerSymbolChart;
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSMInstrumented;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
public class P25Phase1LSMPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class);
private HBox mTopChartBox;
private ComplexSampleLineChart mDifferentialDemodulatedSamplesChartBox;
private EyeDiagramChart mEyeDiagramChart;
private HBox mBottomChartBox;
private SymbolChart mSymbolConstellationChart;
private PhaseLineChart mPLLPhaseErrorLineChart;
private DoubleLineChart mPLLFrequencyLineChart;
private SamplesPerSymbolChart mSamplesPerSymbolLineChart;
private Broadcaster<ComplexSamples> mFilteredBufferBroadcaster = new Broadcaster();
private P25P1DecoderLSMInstrumented mDecoder = new P25P1DecoderLSMInstrumented();
public P25Phase1LSMPane()
{
super(DecoderType.P25_PHASE1);
init();
}
private void init()
{
addListener(nativeBuffer ->
{
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
while(iterator.hasNext())
{
getDecoder().receive(iterator.next());
}
});
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
getDecoder().setComplexSymbolListener(getSymbolConstellationChart());
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
mFilteredBufferBroadcaster.addListener(getDifferentialDemodulatedSamplesChartBox());
HBox.setHgrow(getTopChartBox(), Priority.ALWAYS);
HBox.setHgrow(getBottomChartBox(), Priority.ALWAYS);
getChildren().addAll(getTopChartBox(), getBottomChartBox());
mDecoder.setMessageListener(new Listener<IMessage>()
{
@Override
public void receive(IMessage message)
{
mLog.debug(message.toString());
}
});
}
@Override
public void setSampleRate(double sampleRate)
{
mLog.debug("Configuring for sample rate: " + sampleRate);
mDecoder.setSampleRate(sampleRate);
double samplesPerSymbol = sampleRate / 4800.0;
getDifferentialDemodulatedSamplesChartBox().setSamplesPerSymbol((int) samplesPerSymbol);
getSamplesPerSymbolLineChart().setSamplesPerSymbol(samplesPerSymbol);
}
private P25P1DecoderLSMInstrumented getDecoder()
{
return mDecoder;
}
private SymbolChart getSymbolConstellationChart()
{
if(mSymbolConstellationChart == null)
{
mSymbolConstellationChart = new SymbolChart(10);
}
return mSymbolConstellationChart;
}
private HBox getBottomChartBox()
{
if(mBottomChartBox == null)
{
mBottomChartBox = new HBox();
mBottomChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolConstellationChart().setMaxWidth(Double.MAX_VALUE);
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSymbolConstellationChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
mBottomChartBox.getChildren().addAll(getSymbolConstellationChart(), getPLLPhaseErrorLineChart(),
getPLLFrequencyLineChart());
}
return mBottomChartBox;
}
private HBox getTopChartBox()
{
if(mTopChartBox == null)
{
mTopChartBox = new HBox();
mTopChartBox.setMaxHeight(Double.MAX_VALUE);
getDifferentialDemodulatedSamplesChartBox().setMaxWidth(Double.MAX_VALUE);
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getDifferentialDemodulatedSamplesChartBox(), Priority.ALWAYS);
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
mTopChartBox.getChildren().addAll(getDifferentialDemodulatedSamplesChartBox(), getEyeDiagramChart(),
getSamplesPerSymbolLineChart());
}
return mTopChartBox;
}
private ComplexSampleLineChart getDifferentialDemodulatedSamplesChartBox()
{
if(mDifferentialDemodulatedSamplesChartBox == null)
{
mDifferentialDemodulatedSamplesChartBox = new ComplexSampleLineChart("Raw Samples", 100, 10);
}
return mDifferentialDemodulatedSamplesChartBox;
}
private EyeDiagramChart getEyeDiagramChart()
{
if(mEyeDiagramChart == null)
{
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol/Eye Diagram");
}
return mEyeDiagramChart;
}
private PhaseLineChart getPLLPhaseErrorLineChart()
{
if(mPLLPhaseErrorLineChart == null)
{
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
}
return mPLLPhaseErrorLineChart;
}
private DoubleLineChart getPLLFrequencyLineChart()
{
if(mPLLFrequencyLineChart == null)
{
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -2500, 2500, 200, 40);
}
return mPLLFrequencyLineChart;
}
private SamplesPerSymbolChart getSamplesPerSymbolLineChart()
{
if(mSamplesPerSymbolLineChart == null)
{
mSamplesPerSymbolLineChart = new SamplesPerSymbolChart(40, 6.25);
}
return mSamplesPerSymbolLineChart;
}
}

View File

@@ -1,250 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
import io.github.dsheirer.gui.instrument.chart.SampleXYChart;
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSKInstrumented;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
public class P25Phase2HDQPSKPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase2HDQPSKPane.class);
private HBox mSampleChartBox;
private HBox mDecoderChartBox;
private ComplexSampleLineChart mSampleLineChartRaw;
private ComplexSampleLineChart mSampleLineChartPllCorrected;
private EyeDiagramChart mEyeDiagramChart;
private DoubleLineChart mSamplesPerSymbolLineChart;
private SymbolChart mSymbolChart;
private PhaseLineChart mPLLPhaseErrorLineChart;
private DoubleLineChart mPLLFrequencyLineChart;
private SampleXYChart mSampleXYChart;
private Broadcaster<ComplexSamples> mFilteredBufferBroadcaster = new Broadcaster();
private P25P2DecoderHDQPSKInstrumented mDecoder = new P25P2DecoderHDQPSKInstrumented(new DecodeConfigP25Phase2());
public P25Phase2HDQPSKPane()
{
super(DecoderType.P25_PHASE2);
init();
}
private void init()
{
addListener(nativeBuffer ->
{
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
while(iterator.hasNext())
{
getDecoder().receive(iterator.next());
}
});
getDecoder().getDemodulator().setFilteredGainAppliedComplexBufferListener(getSampleXYChart());
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
getDecoder().setComplexSymbolListener(getSymbolChart());
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
//Listen for raw (uncorrected) samples
mFilteredBufferBroadcaster.addListener(getSampleLineChartRaw());
//Listen for PLL corrected samples
getDecoder().getSampleBuffer().setSampleListener(getSampleLineChartPllCorrected());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
mDecoder.setMessageListener(new Listener<IMessage>()
{
@Override
public void receive(IMessage message)
{
mLog.debug(message.toString());
}
});
}
@Override
public void setSampleRate(double sampleRate)
{
mLog.debug("Configuring for sample rate: " + sampleRate);
mDecoder.setSampleRate(sampleRate);
double samplesPerSymbol = sampleRate / 4800.0;
getSampleLineChartRaw().setSamplesPerSymbol((int) samplesPerSymbol);
getDecoder().getSampleBuffer().setSampleListener(getSampleLineChartPllCorrected());
getDecoder().getDemodulator().setFilteredGainAppliedComplexBufferListener(getSampleXYChart());
}
private P25P2DecoderHDQPSKInstrumented getDecoder()
{
return mDecoder;
}
private SymbolChart getSymbolChart()
{
if(mSymbolChart == null)
{
mSymbolChart = new SymbolChart(10);
}
return mSymbolChart;
}
private HBox getDecoderChartBox()
{
if(mDecoderChartBox == null)
{
mDecoderChartBox = new HBox();
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
getSampleXYChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
HBox.setHgrow(getSampleXYChart(), Priority.ALWAYS);
mDecoderChartBox.getChildren().addAll(getPLLPhaseErrorLineChart(), getPLLFrequencyLineChart(),
getSymbolChart(), getSampleXYChart());
}
return mDecoderChartBox;
}
private HBox getSampleChartBox()
{
if(mSampleChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChartRaw().setMaxWidth(Double.MAX_VALUE);
getSampleLineChartPllCorrected().setMaxWidth(Double.MAX_VALUE);
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleLineChartRaw(), Priority.ALWAYS);
HBox.setHgrow(getSampleLineChartPllCorrected(), Priority.ALWAYS);
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChartRaw(), getSampleLineChartPllCorrected(),
getEyeDiagramChart(), getSamplesPerSymbolLineChart());
}
return mSampleChartBox;
}
private ComplexSampleLineChart getSampleLineChartRaw()
{
if(mSampleLineChartRaw == null)
{
mSampleLineChartRaw = new ComplexSampleLineChart("Raw Samples", 100, 10);
}
return mSampleLineChartRaw;
}
private ComplexSampleLineChart getSampleLineChartPllCorrected()
{
if(mSampleLineChartPllCorrected == null)
{
mSampleLineChartPllCorrected = new ComplexSampleLineChart("PLL Corrected Samples", 100, 10);
}
return mSampleLineChartPllCorrected;
}
private EyeDiagramChart getEyeDiagramChart()
{
if(mEyeDiagramChart == null)
{
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
}
return mEyeDiagramChart;
}
private PhaseLineChart getPLLPhaseErrorLineChart()
{
if(mPLLPhaseErrorLineChart == null)
{
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
}
return mPLLPhaseErrorLineChart;
}
private DoubleLineChart getPLLFrequencyLineChart()
{
if(mPLLFrequencyLineChart == null)
{
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
}
return mPLLFrequencyLineChart;
}
private SampleXYChart getSampleXYChart()
{
if(mSampleXYChart == null)
{
mSampleXYChart = new SampleXYChart(100, "Samples");
}
return mSampleXYChart;
}
private DoubleLineChart getSamplesPerSymbolLineChart()
{
if(mSamplesPerSymbolLineChart == null)
{
mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 8.0, 10.0, 0.1, 40);
}
return mSamplesPerSymbolLineChart;
}
}

View File

@@ -1,70 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import javafx.scene.text.Text;
public class RealDecoderPane extends AbstractDecoderPane<float[]>
{
private Broadcaster<float[]> mBufferBroadcaster = new Broadcaster();
private DecoderType mDecoderType;
public RealDecoderPane(DecoderType decoderType)
{
mDecoderType = decoderType;
}
/**
* Package private - used by factory class to create an empty pane
*/
RealDecoderPane()
{
getChildren().add(new Text("Please select a decoder from the menu"));
}
@Override
SampleType getSampleType()
{
return SampleType.COMPLEX;
}
@Override
public void receive(float[] buffer)
{
mBufferBroadcaster.broadcast(buffer);
}
public void addListener(Listener<float[]> listener)
{
mBufferBroadcaster.addListener(listener);
}
public void removeListener(Listener<float[]> listener)
{
mBufferBroadcaster.removeListener(listener);
}
public void setSampleRate(double sampleRate)
{
}
}

View File

@@ -1,78 +0,0 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.tait.Tait1200DecoderInstrumented;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Tait1200Pane extends AbstractAFSK1200Pane
{
private final static Logger mLog = LoggerFactory.getLogger(Tait1200Pane.class);
private Tait1200DecoderInstrumented mDecoder;
public Tait1200Pane()
{
super(DecoderType.TAIT_1200);
}
protected Tait1200DecoderInstrumented getDecoder()
{
if(mDecoder == null)
{
mDecoder = new Tait1200DecoderInstrumented();
mDecoder.getANIMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@Override
public void syncDetected(int bitErrors)
{
mLog.debug("ANI Sync Detected!");
}
@Override
public void syncLost(int bitsProcessed)
{
}
});
mDecoder.getGPSMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@Override
public void syncDetected(int bitErrors)
{
mLog.debug("GPS Sync Detected!");
}
@Override
public void syncLost(int bitsProcessed)
{
}
});
}
return mDecoder;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 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,7 +27,7 @@ import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import io.github.dsheirer.module.decode.p25.phase1.Modulation;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
@@ -332,7 +332,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
DecodeConfigP25Phase1 decodeConfig = (DecodeConfigP25Phase1)config;
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
if(decodeConfig.getModulation() == P25P1Decoder.Modulation.C4FM)
if(decodeConfig.getModulation() == Modulation.C4FM)
{
getC4FMToggleButton().setSelected(true);
getLSMToggleButton().setSelected(false);
@@ -366,7 +366,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
config.setModulation(getC4FMToggleButton().isSelected() ? P25P1Decoder.Modulation.C4FM : P25P1Decoder.Modulation.CQPSK);
config.setModulation(getC4FMToggleButton().isSelected() ? Modulation.C4FM : Modulation.CQPSK);
getItem().setDecodeConfiguration(config);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 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
@@ -26,7 +26,7 @@ import io.github.dsheirer.module.decode.DecoderFactory;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import io.github.dsheirer.module.decode.p25.phase1.Modulation;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.Category;
@@ -36,6 +36,7 @@ import io.github.dsheirer.rrapi.type.SubCategory;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.util.ThreadPool;
import java.text.DecimalFormat;
import javafx.animation.FadeTransition;
import javafx.application.Platform;
import javafx.geometry.HPos;
@@ -53,8 +54,6 @@ import javafx.util.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
public class FrequencyEditor extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(FrequencyEditor.class);
@@ -421,7 +420,7 @@ public class FrequencyEditor extends VBox
}
else if(decodeConfiguration instanceof DecodeConfigP25Phase1)
{
((DecodeConfigP25Phase1)decodeConfiguration).setModulation(P25P1Decoder.Modulation.C4FM);
((DecodeConfigP25Phase1)decodeConfiguration).setModulation(Modulation.C4FM);
}
channel.setDecodeConfiguration(decodeConfiguration);
return channel;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 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
@@ -30,7 +30,7 @@ import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import io.github.dsheirer.module.decode.p25.phase1.Modulation;
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
import io.github.dsheirer.playlist.PlaylistManager;
@@ -257,7 +257,7 @@ public class SiteEditor extends GridPane
{
if(p1config instanceof DecodeConfigP25Phase1)
{
((DecodeConfigP25Phase1)p1config).setModulation(P25P1Decoder.Modulation.CQPSK);
((DecodeConfigP25Phase1)p1config).setModulation(Modulation.CQPSK);
}
}
return p1config;

View File

@@ -22,6 +22,7 @@ package io.github.dsheirer.gui.squelch;
import io.github.dsheirer.dsp.squelch.INoiseSquelchController;
import io.github.dsheirer.dsp.squelch.NoiseSquelch;
import io.github.dsheirer.dsp.squelch.NoiseSquelchState;
import io.github.dsheirer.gui.symbol.ChannelView;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
@@ -72,7 +73,7 @@ import org.slf4j.LoggerFactory;
* 1 for an operating range of 1-6. The close slider uses a range of 0-5 that is combined with the open slider for a
* range of (open + 0) to (open + 5).
*/
public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState>
public class NoiseSquelchView extends ChannelView implements Listener<NoiseSquelchState>
{
private static final Logger LOGGER = LoggerFactory.getLogger(NoiseSquelchView.class);
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.00");
@@ -129,7 +130,6 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
private Label mHysteresisValueLabel;
private Button mResetButton;
private boolean mShowing = false;
private boolean mControlsUpdated = true;
/**
@@ -235,9 +235,10 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
* Sets this view as showing and starts the chart update timer, or sets this view as hidden and stops the update timer
* @param showing to indicate if this view is selected by the user and showing.
*/
@Override
public void setShowing(boolean showing)
{
mShowing = showing;
super.setShowing(showing);
updateTimer();
}
@@ -258,12 +259,12 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
*/
private synchronized void updateTimer()
{
if(mShowing && mController != null && mTimerFuture == null)
if(isShowing() && mController != null && mTimerFuture == null)
{
//Start the timer
mTimerFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::updateChart, 0, 50, TimeUnit.MILLISECONDS);
}
else if((!mShowing || mController == null) && mTimerFuture != null)
else if((!isShowing() || mController == null) && mTimerFuture != null)
{
cancelTimer();
}
@@ -604,7 +605,7 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
*/
private void updateControllerNoise()
{
if(mController != null && mShowing)
if(mController != null && isShowing())
{
float open = getOpenNoiseThresholdFromSliderValue();
float close = getCloseNoiseThresholdFromSliderValue();
@@ -669,7 +670,7 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
*/
private void updateControllerHysteresis()
{
if(mController != null && mShowing)
if(mController != null && isShowing())
{
int open = (int)getOpenHysteresisSlider().getValue();
int close = (int)getCloseHysteresisSlider().getValue() + open;

View File

@@ -0,0 +1,55 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.symbol;
import javafx.scene.layout.VBox;
/**
* Base class for channel viewer panel
*/
public class ChannelView extends VBox
{
private boolean mShowing = false;
/**
* Constructs an instance.
*/
public ChannelView()
{
}
/**
* Indicates if this panel is currently showing.
* @return true if showing.
*/
protected boolean isShowing()
{
return mShowing;
}
/**
* Toggle the showing state to indicate when the user has selected to show this panel.
* @param showing true or false to disable the panel.
*/
public void setShowing(boolean showing)
{
mShowing = showing;
}
}

View File

@@ -0,0 +1,133 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.symbol;
import io.github.dsheirer.module.decode.FeedbackDecoder;
import io.github.dsheirer.sample.Listener;
import java.util.Arrays;
import javafx.application.Platform;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
/**
* Chart display for received QPSK symbols
*/
public class SymbolView extends ChannelView implements Listener<Float>
{
private static final int SYMBOL_DISPLAY_COUNT = 4800;
private static final int SYMBOL_QUEUE_SIZE = SYMBOL_DISPLAY_COUNT / 40;
private final float[] mSymbolQueue = new float[SYMBOL_QUEUE_SIZE];
private int mSymbolQueuePointer = 0;
private int mChartSymbolPointer = 0;
private final NumberAxis mSymbolTiming = new NumberAxis();
private final NumberAxis mSymbolPhase = new NumberAxis(-Math.PI, Math.PI, Math.PI / 2);
private final XYChart.Series<Number, Number> mSymbolSeries = new XYChart.Series<>();
private final ScatterChart<Number, Number> mSymbolChart = new ScatterChart<>(mSymbolTiming, mSymbolPhase);
private FeedbackDecoder mFeedbackDecoder;
/**
* Constructs an instance
*/
public SymbolView()
{
mSymbolSeries.setName("xx Demodulated Symbols");
for(int x = 0; x < SYMBOL_DISPLAY_COUNT; x++)
{
mSymbolSeries.getData().add(new XYChart.Data<>(x, 0));
}
mSymbolChart.getData().add(mSymbolSeries);
mSymbolChart.setMaxHeight(Double.MAX_VALUE);
mSymbolChart.setMaxWidth(Double.MAX_VALUE);
mSymbolChart.setAnimated(false);
VBox.setVgrow(mSymbolChart, Priority.ALWAYS);
getChildren().add(mSymbolChart);
}
/**
* Updates the chart axis label with the currently displayed protocol
* @param protocol that is being processed/displayed.
*/
public void setProtocol(String protocol)
{
Platform.runLater(() -> mSymbolSeries.setName(protocol + " Demodulated Symbols"));
}
/**
* Registers the decoder with this view and this view then registers as a symbol listener on the decoder.
* @param feedbackDecoder for this view
*/
public void setSymbolProvider(FeedbackDecoder feedbackDecoder)
{
removeSymbolProvider();
mFeedbackDecoder = feedbackDecoder;
if(mFeedbackDecoder != null)
{
mFeedbackDecoder.setSymbolListener(this);
}
}
/**
* Removes current symbol provider and unregisters this view from that provider.
*/
public void removeSymbolProvider()
{
if(mFeedbackDecoder != null)
{
mFeedbackDecoder.removeSymbolListener();
}
mFeedbackDecoder = null;
}
@Override
public void receive(Float symbol)
{
if(isShowing())
{
mSymbolQueue[mSymbolQueuePointer++] = symbol;
//Flush the queued symbols to the chart when the queue is full
if(mSymbolQueuePointer == SYMBOL_QUEUE_SIZE)
{
float[] symbolDataCopy = Arrays.copyOf(mSymbolQueue, mSymbolQueue.length);
//Execute the chart data update on the JavaFX UI thread
Platform.runLater(() ->
{
for(float v : symbolDataCopy)
{
mSymbolSeries.getData().get(mChartSymbolPointer++).setYValue(v);
mChartSymbolPointer %= SYMBOL_DISPLAY_COUNT;
}
});
mSymbolQueuePointer = 0;
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 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,6 +25,7 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.channel.state.DecoderStateEvent;
import io.github.dsheirer.channel.state.SingleChannelState;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
import io.github.dsheirer.message.IMessage;
@@ -164,8 +165,9 @@ public class P25P1Viewer extends VBox
ThreadPool.CACHED.submit(() -> {
List<MessagePackage> messagePackages = new ArrayList<>();
P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600);
P25P1MessageFramer messageFramer = new P25P1MessageFramer();
P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor();
Broadcaster<IMessage> messageBroadcaster = new Broadcaster<>();
messageFramer.setListener(messageBroadcaster);
messageBroadcaster.addListener(messageProcessor);
@@ -229,7 +231,13 @@ public class P25P1Viewer extends VBox
while(reader.hasNext())
{
ByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
for(byte value : buffer.array())
{
for(int x = 0; x <= 3; x++)
{
messageFramer.processWithHardSyncDetect(Dibit.parse(value, x));
}
}
}
}
catch(Exception ioe)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 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
@@ -16,17 +16,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import javafx.scene.layout.VBox;
package io.github.dsheirer.gui.viewer.symbol;
public abstract class AbstractDecoderPane<T> extends VBox implements Listener<T>
import java.util.concurrent.CountDownLatch;
public interface ISymbolResultsListener
{
abstract SampleType getSampleType();
public void setSampleRate(double sampleRate)
{
}
void receive(double samplesPerSymbol, float[] rawI, float[] rawQ, float sampleGain, float pll,
double[] points, float[] symbols, CountDownLatch release);
}

View File

@@ -0,0 +1,255 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.viewer.symbol;
import io.github.dsheirer.dsp.filter.interpolator.LinearInterpolator;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
/**
* JavaFX viewer application for visualizing sample buffers and detected sync patterns
*/
public class SymbolViewPanel extends VBox implements ISymbolResultsListener
{
private static final double TWO_PI = Math.PI * 2.0;
private final NumberAxis mRawTiming = new NumberAxis();
private final NumberAxis mRawValue = new NumberAxis(-1.1, 1.1, .1);
private final NumberAxis mDemodulatedTiming = new NumberAxis();
private final NumberAxis mDemodulatedValue = new NumberAxis(-1.1, 1.1, .1);
private final NumberAxis mSymbolTiming = new NumberAxis();
private final NumberAxis mSymbolValue = new NumberAxis(-Math.PI, Math.PI, Math.PI / 4.0);
private final LineChart<Number, Number> mRawChart = new LineChart<>(mRawTiming, mRawValue);
private final LineChart<Number, Number> mDemodulatedChart = new LineChart<>(mDemodulatedTiming, mDemodulatedValue);
private final LineChart<Number, Number> mSymbolChart = new LineChart<>(mSymbolTiming, mSymbolValue);
private final Button mReleaseButton = new Button("Next Symbol");
private final Label mDisplayText = new Label();
private CountDownLatch mRelease;
/**
* Constructs an instance
*/
public SymbolViewPanel()
{
setPadding(new Insets(5));
mRawTiming.setLabel("Raw Sample Timing");
mRawValue.setLabel("Raw Sample Value (+/- 1.0)");
mDemodulatedTiming.setLabel("Demodulated Sample Timing");
mDemodulatedValue.setLabel("Demodulated Sample Value (+/- 1.0)");
mSymbolTiming.setLabel("Symbol Timing");
mSymbolValue.setLabel("Symbol Value (+/- PI)");
mReleaseButton.setOnAction(e -> {
if(mRelease != null)
{
mRelease.countDown();
mReleaseButton.setDisable(true);
}
});
mRawChart.setAnimated(false);
mDemodulatedChart.setAnimated(false);
mSymbolChart.setAnimated(false);
VBox.setVgrow(mRawChart, Priority.ALWAYS);
VBox.setVgrow(mDemodulatedChart, Priority.ALWAYS);
VBox.setVgrow(mSymbolChart, Priority.ALWAYS);
HBox buttons = new HBox();
buttons.setAlignment(Pos.BASELINE_LEFT);
buttons.setSpacing(10);
buttons.getChildren().addAll(mReleaseButton, mDisplayText);
VBox.setVgrow(buttons, Priority.NEVER);
getChildren().addAll(buttons, mRawChart, mDemodulatedChart, mSymbolChart);
}
@Override
public void receive(double samplesPerSymbol, float[] rawI, float[] rawQ, float sampleGain, float pll,
double[] points, float[] symbolSequence, CountDownLatch release)
{
mRelease = release;
mReleaseButton.setDisable(release == null);
Platform.runLater(() -> {
XYChart.Series<Number, Number> rawSeriesI = new XYChart.Series<>();
rawSeriesI.setName("Raw Samples");
XYChart.Series<Number, Number> rawSeriesQ = new XYChart.Series<>();
XYChart.Series<Number, Number> rawTiming = new XYChart.Series<>();
XYChart.Series<Number, Number> rawDecisionI = new XYChart.Series<>();
XYChart.Series<Number, Number> rawDecisionQ = new XYChart.Series<>();
XYChart.Series<Number, Number> demodulatedSeriesI = new XYChart.Series<>();
XYChart.Series<Number, Number> demodulatedSeriesQ = new XYChart.Series<>();
XYChart.Series<Number, Number> demodulatedTiming = new XYChart.Series<>();
XYChart.Series<Number, Number> demodulatedDecisionI = new XYChart.Series<>();
XYChart.Series<Number, Number> demodulatedDecisionQ = new XYChart.Series<>();
XYChart.Series<Number, Number> symbolsRaw = new XYChart.Series<>();
symbolsRaw.setName("Symbols Raw");
XYChart.Series<Number, Number> symbolsPLL = new XYChart.Series<>();
symbolsPLL.setName("Symbols PLL");
XYChart.Series<Number, Number> symbolTiming = new XYChart.Series<>();
XYChart.Series<Number, Number> symbolP3 = new XYChart.Series<>();
XYChart.Series<Number, Number> symbolP1 = new XYChart.Series<>();
XYChart.Series<Number, Number> symbolM1 = new XYChart.Series<>();
XYChart.Series<Number, Number> symbolM3 = new XYChart.Series<>();
XYChart.Series<Number, Number> symbolsWithPLL = new XYChart.Series<>();
XYChart.Series<Number, Number> symbolsWithoutPLL = new XYChart.Series<>();
float pllI = (float)Math.cos(pll);
float pllQ = (float)Math.sin(pll);
float pllTemp = 0;
double spsMax = Math.ceil(samplesPerSymbol);
double fractional = spsMax - samplesPerSymbol;
for(int x = 0; x < rawI.length; x++)
{
rawSeriesI.getData().add(new XYChart.Data<>(x, rawI[x] * sampleGain));
rawSeriesQ.getData().add(new XYChart.Data<>(x, rawQ[x] * sampleGain));
if(x >= spsMax)
{
int offset = x - (int)spsMax;
float previousI = LinearInterpolator.calculate(rawI[offset] * sampleGain, rawI[offset + 1] * sampleGain, fractional);
float previousQ = LinearInterpolator.calculate(rawQ[offset] * sampleGain, rawQ[offset + 1] * sampleGain, fractional);
float currentI = rawI[x] * sampleGain;
float currentQ = rawQ[x] * sampleGain;
float demodulatedI = (previousI * currentI) - (-previousQ * currentQ);
float demodulatedQ = (previousI * currentQ) + (-previousQ * currentI);
//Rotate by the PLL
pllTemp = (demodulatedI * pllI) - (demodulatedQ * pllQ);
demodulatedQ = (demodulatedQ * pllI) + (demodulatedI * pllQ);
demodulatedI = pllTemp;
demodulatedSeriesI.getData().add(new XYChart.Data<>(x, demodulatedI));
demodulatedSeriesQ.getData().add(new XYChart.Data<>(x, demodulatedQ));
symbolsRaw.getData().add(new XYChart.Data<>(x, Math.atan2(demodulatedQ, demodulatedI)));
// symbolsPLL.getData().add(new XYChart.Data<>(x, normalize(Math.atan2(demodulatedQ, demodulatedI) + pll)));
}
}
rawTiming.getData().add(new XYChart.Data<>(0, -1));
demodulatedTiming.getData().add(new XYChart.Data<>(0, -1));
symbolTiming.getData().add(new XYChart.Data<>(0, -Math.PI));
for(int x = 0; x < 3; x++)
{
rawTiming.getData().add(new XYChart.Data<>(points[x], -1));
rawTiming.getData().add(new XYChart.Data<>(points[x], 1));
rawTiming.getData().add(new XYChart.Data<>(points[x], -1));
demodulatedTiming.getData().add(new XYChart.Data<>(points[x], -1));
demodulatedTiming.getData().add(new XYChart.Data<>(points[x], 1));
demodulatedTiming.getData().add(new XYChart.Data<>(points[x], -1));
symbolTiming.getData().add(new XYChart.Data<>(points[x], -Math.PI));
symbolTiming.getData().add(new XYChart.Data<>(points[x], Math.PI));
symbolTiming.getData().add(new XYChart.Data<>(points[x], -Math.PI));
}
rawTiming.getData().add(new XYChart.Data<>(rawI.length - 1, -1));
demodulatedTiming.getData().add(new XYChart.Data<>(rawI.length - 1, -1));
symbolTiming.getData().add(new XYChart.Data<>(rawI.length - 1, -Math.PI));
rawDecisionI.getData().add(new XYChart.Data<>(points[0], points[3]));
rawDecisionI.getData().add(new XYChart.Data<>(points[1], points[4]));
rawDecisionI.getData().add(new XYChart.Data<>(points[2], points[5]));
rawDecisionQ.getData().add(new XYChart.Data<>(points[0], points[6]));
rawDecisionQ.getData().add(new XYChart.Data<>(points[1], points[7]));
rawDecisionQ.getData().add(new XYChart.Data<>(points[2], points[8]));
demodulatedDecisionI.getData().add(new XYChart.Data<>(points[0], points[9]));
demodulatedDecisionI.getData().add(new XYChart.Data<>(points[1], points[10]));
demodulatedDecisionI.getData().add(new XYChart.Data<>(points[2], points[11]));
demodulatedDecisionQ.getData().add(new XYChart.Data<>(points[0], points[12]));
demodulatedDecisionQ.getData().add(new XYChart.Data<>(points[1], points[13]));
demodulatedDecisionQ.getData().add(new XYChart.Data<>(points[2], points[14]));
symbolP3.getData().add(new XYChart.Data<>(0, 3 * Math.PI / 4));
symbolP1.getData().add(new XYChart.Data<>(0, Math.PI / 4));
symbolM1.getData().add(new XYChart.Data<>(0, -Math.PI / 4));
symbolM3.getData().add(new XYChart.Data<>(0, -3 * Math.PI / 4));
symbolP3.getData().add(new XYChart.Data<>(rawI.length - 1, 3 * Math.PI / 4));
symbolP1.getData().add(new XYChart.Data<>(rawI.length - 1, Math.PI / 4));
symbolM1.getData().add(new XYChart.Data<>(rawI.length - 1, -Math.PI / 4));
symbolM3.getData().add(new XYChart.Data<>(rawI.length - 1, -3 * Math.PI / 4));
symbolsWithPLL.getData().add((new XYChart.Data<>(points[0], symbolSequence[0])));
// symbolsWithPLL.getData().add((new XYChart.Data<>(points[1], symbolSequence[1])));
symbolsWithPLL.getData().add((new XYChart.Data<>(points[2], symbolSequence[2])));
// symbolsWithoutPLL.getData().add((new XYChart.Data<>(points[0], normalize(symbolSequence[0] - pll))));
// symbolsWithoutPLL.getData().add((new XYChart.Data<>(points[1], normalize(symbolSequence[1] - pll))));
// symbolsWithoutPLL.getData().add((new XYChart.Data<>(points[2], normalize(symbolSequence[2] - pll))));
mRawChart.getData().clear();
mRawChart.getData().add(rawSeriesI);
mRawChart.getData().add(rawSeriesQ);
mRawChart.getData().add(rawTiming);
mRawChart.getData().add(rawDecisionI);
mRawChart.getData().add(rawDecisionQ);
mDemodulatedChart.getData().clear();
mDemodulatedChart.getData().add(demodulatedSeriesI);
mDemodulatedChart.getData().add(demodulatedSeriesQ);
mDemodulatedChart.getData().add(demodulatedTiming);
mDemodulatedChart.getData().add(demodulatedDecisionI);
mDemodulatedChart.getData().add(demodulatedDecisionQ);
mSymbolChart.getData().clear();
mSymbolChart.getData().add(symbolsRaw);
// mSymbolChart.getData().add(symbolsPLL);
mSymbolChart.getData().add(symbolTiming);
mSymbolChart.getData().add(symbolP3);
mSymbolChart.getData().add(symbolP1);
mSymbolChart.getData().add(symbolM1);
mSymbolChart.getData().add(symbolM3);
mSymbolChart.getData().add(symbolsWithPLL);
// mSymbolChart.getData().add(symbolsWithoutPLL);
mDisplayText.setText("PLL: " + pll + " Sample Gain: " + sampleGain);
});
}
/**
* Normalizes the value in radians to the range of +/-(2 * PI)
* @param value to normalize
* @return normalized value.
*/
private static double normalize(double value)
{
if(value > Math.PI)
{
return value - TWO_PI;
}
else if(value < -Math.PI)
{
return value + TWO_PI;
}
return value;
}
}

View File

@@ -0,0 +1,104 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.viewer.symbol;
import io.github.dsheirer.util.SwingUtils;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JFrame;
/**
* Utility for viewing sync detection results
*/
public class SymbolViewerFX implements ISymbolResultsListener
{
private static final Logger LOGGER = LoggerFactory.getLogger(SymbolViewerFX.class.getName());
private ISymbolResultsListener mListener;
public SymbolViewerFX()
{
initUI();
}
@Override
public void receive(double samplesPerSymbol, float[] rawI, float[] rawQ, float sampleGain, float pll,
double[] points, float[] symbols, CountDownLatch releasee)
{
if(mListener != null)
{
mListener.receive(samplesPerSymbol, rawI, rawQ, sampleGain, pll, points, symbols, releasee);
}
}
/**
* Initializes the viewer UI
*/
private void initUI()
{
CountDownLatch latch = new CountDownLatch(1);
final JFXPanel fxPanel = new JFXPanel();
Platform.runLater(() -> {
Platform.setImplicitExit(false);
SymbolViewPanel viewer = new SymbolViewPanel();
mListener = viewer;
Scene scene = new Scene(viewer, 1400, 1000);
// URL resource = getClass().getResource("/sdrtrunk_style.css");
//
// if(resource != null)
// {
// scene.getStylesheets().add(resource.toExternalForm());
// }
// else
// {
// LOGGER.warn("Can't find stylesheet resource for sdrtrunk");
// }
fxPanel.setScene(scene);
fxPanel.setVisible(true);
JFrame frame = new JFrame();
frame.setTitle("Symbol Results Viewer");
frame.setContentPane(fxPanel);
frame.setSize(1400, 1400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
SwingUtils.run(() -> {
frame.setVisible(true);
latch.countDown();
});
});
try
{
latch.await();
}
catch(InterruptedException e)
{
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.viewer.sync;
import java.util.concurrent.CountDownLatch;
/**
* Interface for receiving sync detection results from a demodulated I/Q stream
*/
public interface ISyncResultsListener
{
/**
* Receive results
* @param symbols for the detected sync
* @param sync pattern symbols (ideal)
* @param samples demodulated samples
* @param syncIntervals for timing of each symbol in the samples array
* @param equalizerBalance balance for the equalizer
* @param equalizerGain gain for the equalizer
* @param label to display in the UI
* @param release that pauses execution until the user acknowledges the displayed sync results. Decrement the latch to release.
*/
void receive(float[] symbols, float[] sync, float[] samples, float[] syncIntervals, float equalizerBalance,
float equalizerGain, String label, CountDownLatch release);
/**
* Post a symbol value to the listener
* @param symbol value in range +/- PI
*/
void symbol(float symbol);
}

View File

@@ -0,0 +1,123 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.viewer.sync;
import io.github.dsheirer.util.SwingUtils;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JFrame;
/**
* Utility for viewing sync detection results
*/
public class SyncResultsViewer implements ISyncResultsListener
{
private static final Logger LOGGER = LoggerFactory.getLogger(SyncResultsViewer.class.getName());
private ISyncResultsListener mListener;
public SyncResultsViewer()
{
initUI();
}
/**
* View sync detection results.
* @param symbols that were detected
* @param sync that was detected
* @param samples for the symbols
* @param syncIntervals symbol timing interval pointers into the I/Q sample arrays
* @param label to display to the user
* @param release latch to release via the UI when done viewing these results
*/
@Override
public void receive(float[] symbols, float[] sync, float[] samples, float[] syncIntervals, float equalizerBalance,
float equalizerGain, String label, CountDownLatch release)
{
if(mListener != null)
{
mListener.receive(symbols, samples, sync, syncIntervals, equalizerBalance, equalizerGain, label, release);
}
}
@Override
public void symbol(float symbol)
{
if(mListener != null)
{
mListener.symbol(symbol);
}
}
/**
* Initializes the viewer UI
*/
private void initUI()
{
CountDownLatch latch = new CountDownLatch(1);
final JFXPanel fxPanel = new JFXPanel();
Platform.runLater(() -> {
Platform.setImplicitExit(false);
SyncVisualizer syncVisualizer = new SyncVisualizer();
mListener = syncVisualizer;
Scene scene = new Scene(syncVisualizer, 1400, 1000);
// URL resource = getClass().getResource("/sdrtrunk_style.css");
//
// if(resource != null)
// {
// scene.getStylesheets().add(resource.toExternalForm());
// }
// else
// {
// LOGGER.warn("Can't find stylesheet resource for sdrtrunk");
// }
fxPanel.setScene(scene);
fxPanel.setVisible(true);
JFrame frame = new JFrame();
frame.setTitle("Sync Results Viewer");
frame.setContentPane(fxPanel);
frame.setSize(1400, 1400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
SwingUtils.run(() -> {
frame.setVisible(true);
latch.countDown();
});
});
try
{
latch.await();
}
catch(InterruptedException e)
{
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,218 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.viewer.sync;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
/**
* JavaFX viewer application for visualizing sample buffers and detected sync patterns
*/
public class SyncVisualizer extends VBox implements ISyncResultsListener
{
private static final int SYMBOL_CHART_SERIES_COUNT = 2000;
private static final float SYMBOL = (float) (Math.PI / 4.0);
private final NumberAxis mConstellationI = new NumberAxis();
private final NumberAxis mConstellationQ = new NumberAxis();
private final NumberAxis mSampleTiming = new NumberAxis();
private final NumberAxis mSamplePhase = new NumberAxis(-3.5, 3.5, .5);
private final NumberAxis mSymbolTiming = new NumberAxis();
private final NumberAxis mSymbolPhase = new NumberAxis(-Math.PI, Math.PI, Math.PI / 2);
private final XYChart.Series<Number, Number> mSymbolSeries = new XYChart.Series<>();
private final XYChart.Series<Number, Number> mSymbolSeriesMarker = new XYChart.Series<>();
private final ScatterChart<Number, Number> mConstellationChart = new ScatterChart<>(mConstellationI, mConstellationQ);
private final LineChart<Number, Number> mSampleChart = new LineChart<>(mSampleTiming, mSamplePhase);
private final ScatterChart<Number, Number> mSymbolChart = new ScatterChart<>(mSymbolTiming, mSymbolPhase);
private final NumberAxis mEqualizerHistoryAxis = new NumberAxis();
private final NumberAxis mEqualizerValueAxis = new NumberAxis(-1.5, 1.5, .1);
private final LineChart<Number, Number> mEqualizerChart = new LineChart<>(mEqualizerHistoryAxis, mEqualizerValueAxis);
private int mEqualizerPointer = 0;
private int mEqualizerHistorySize = 40;
private float[] mEqualizerBalanceHistory = new float[mEqualizerHistorySize];
private float[] mEqualizerGainHistory = new float[mEqualizerHistorySize];
private final Button mReleaseButton = new Button("Next Sync");
private final Label mPatternLabel = new Label();
private CountDownLatch mRelease;
private int mSymbolPointer = 0;
/**
* Constructs an instance
*/
public SyncVisualizer()
{
setPadding(new Insets(5));
mConstellationI.setLabel("Constellation - Inphase");
mConstellationQ.setLabel("Quadrature");
mSampleTiming.setLabel("Sample Timing");
mSamplePhase.setLabel("Sample Phase (+/- PI)");
mReleaseButton.setOnAction(e -> {
if(mRelease != null)
{
mRelease.countDown();
mReleaseButton.setDisable(true);
}
});
mConstellationChart.setPrefSize(400, 400);
mConstellationChart.setAnimated(false);
mSampleChart.setAnimated(false);
mSymbolChart.setAnimated(false);
mSymbolChart.setId("symbol-chart");
mEqualizerChart.setAnimated(false);
mEqualizerValueAxis.setLabel("Value");
mEqualizerHistoryAxis.setLabel("Equalizer");
HBox hbox = new HBox();
hbox.getChildren().add(mConstellationChart);
HBox.setHgrow(mEqualizerChart, Priority.ALWAYS);
hbox.getChildren().add(mEqualizerChart);
// VBox.setVgrow(hbox, Priority.ALWAYS);
VBox.setVgrow(mSampleChart, Priority.ALWAYS);
VBox.setVgrow(mSymbolChart, Priority.ALWAYS);
HBox buttons = new HBox();
buttons.setAlignment(Pos.BASELINE_LEFT);
buttons.setSpacing(10);
buttons.getChildren().addAll(mReleaseButton, mPatternLabel);
VBox.setVgrow(buttons, Priority.NEVER);
//Load the symbol chart with symbols
mSymbolSeries.setName("Symbols");
for(int x = 0; x < SYMBOL_CHART_SERIES_COUNT; x++)
{
mSymbolSeries.getData().add(new XYChart.Data<>(x, 0));
}
mSymbolChart.getData().add(mSymbolSeries);
float pi_4 = (float)Math.PI / 4f;
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, 3f * pi_4));
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, pi_4));
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, 0));
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, -pi_4));
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, -3 * pi_4));
mSymbolChart.getData().add(mSymbolSeriesMarker);
getChildren().addAll(buttons, hbox, mSampleChart, mSymbolChart);
}
@Override
public void symbol(float symbol)
{
for(XYChart.Data<Number, Number> data : mSymbolSeriesMarker.getData())
{
data.setXValue(mSymbolPointer);
}
mSymbolSeries.getData().get(mSymbolPointer++).setYValue(symbol);
mSymbolPointer %= SYMBOL_CHART_SERIES_COUNT;
}
@Override
public void receive(float[] symbols, float[] samples, float[] sync, float[] syncIntervals, float equalizerBalance, float equalizerGain, String label, CountDownLatch latch)
{
mRelease = latch;
mReleaseButton.setDisable(latch == null);
mEqualizerBalanceHistory[mEqualizerPointer] = equalizerBalance;
mEqualizerGainHistory[mEqualizerPointer++] = (equalizerGain - 1.0f);
mEqualizerPointer %= mEqualizerHistorySize;
//Clear the next/upcoming values to give visual indication of where we're at.
mEqualizerBalanceHistory[mEqualizerPointer] = 0f;
mEqualizerGainHistory[mEqualizerPointer] = 0f;
Platform.runLater(() -> {
XYChart.Series<Number, Number> constellationIdeal = new XYChart.Series<>();
constellationIdeal.setName("Ideal");
float PI_4 = (float) (Math.PI / 4);
constellationIdeal.getData().add(new XYChart.Data<>(PI_4, PI_4));
constellationIdeal.getData().add(new XYChart.Data<>(PI_4, -PI_4));
constellationIdeal.getData().add(new XYChart.Data<>(-PI_4, PI_4));
constellationIdeal.getData().add(new XYChart.Data<>(-PI_4, -PI_4));
constellationIdeal.getData().add(new XYChart.Data<>(1, 0));
constellationIdeal.getData().add(new XYChart.Data<>(-1, 0));
constellationIdeal.getData().add(new XYChart.Data<>(0, 1));
constellationIdeal.getData().add(new XYChart.Data<>(0, -1));
mConstellationChart.getData().clear();
mConstellationChart.getData().add(constellationIdeal);
XYChart.Series<Number, Number> constellation = new XYChart.Series<>();
constellation.setName("Decoded");
for(int x = 0; x < symbols.length; x++)
{
constellation.getData().add(new XYChart.Data<>(Math.cos(symbols[x]), Math.sin(symbols[x])));
}
mConstellationChart.getData().add(constellation);
XYChart.Series<Number, Number> sampleSeries = new XYChart.Series<>();
sampleSeries.setName("Demodulated Samples");
for(int x = 0; x < samples.length; x++)
{
sampleSeries.getData().add(new XYChart.Data<>(x, samples[x]));
}
mSampleChart.getData().clear();
mSampleChart.getData().add(sampleSeries);
XYChart.Series<Number, Number> patternSeries = new XYChart.Series<>();
patternSeries.setName("Ideal Sync Pattern");
for(int x = 0; x < sync.length; x++)
{
patternSeries.getData().add(new XYChart.Data<>(syncIntervals[x], sync[x]));
}
mSampleChart.getData().add(patternSeries);
mPatternLabel.setText(label);
mEqualizerChart.getData().clear();
XYChart.Series<Number, Number> balanceSeries = new XYChart.Series<>();
balanceSeries.setName("Balance");
XYChart.Series<Number, Number> gainSeries = new XYChart.Series<>();
gainSeries.setName("Gain (Value minus 1.0)");
for(int x = 0; x < mEqualizerHistorySize; x++)
{
balanceSeries.getData().add(new XYChart.Data<>(x, mEqualizerBalanceHistory[x]));
gainSeries.getData().add(new XYChart.Data<>(x, mEqualizerGainHistory[x]));
}
mEqualizerChart.getData().add(balanceSeries);
mEqualizerChart.getData().add(gainSeries);
});
}
}