Implements a complex sample delay buffer integrated into the tuner

channel manager so that channel source requests can have a start
timestamp specified and the channel can be preloaded with samples from
the delay buffer.  If no timestamp is specified, current time is assumed.

This feature requires corresponding updates to any decoder that wishes
to implement the timestamped sample preloading functionality.
This commit is contained in:
Denny
2018-09-09 08:46:21 -04:00
parent bb0bcd3198
commit 570321da3e
21 changed files with 632 additions and 98 deletions

View File

@@ -34,6 +34,7 @@ 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.
@@ -42,6 +43,9 @@ 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;
@@ -117,9 +121,22 @@ public class DemodulatorViewerFX extends Application
{
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);
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
if(file != null)
{
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
mPreferences.put(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, file.getParent());
}
}
});

View File

@@ -26,6 +26,7 @@ import java.util.prefs.Preferences;
public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
{
private static final String PREFERENCE_RECENT_FILE = ".recent.file.";
private Preferences mPreferences = Preferences.userNodeForPackage(RecentFilesMenu.class);
private IFileSelectionListener mFileSelectionListener;
private String mIdentifier;
@@ -111,7 +112,7 @@ public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
*/
private String getKey(int index)
{
return mIdentifier + ".recent.file." + index;
return mIdentifier + PREFERENCE_RECENT_FILE + index;
}
/**

View File

@@ -21,6 +21,7 @@ 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;
@@ -42,6 +43,24 @@ public class DoubleLineChart extends LineChart implements Listener<Double>
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);

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* 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

@@ -19,6 +19,7 @@ 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.Message;
import io.github.dsheirer.module.decode.DecoderType;
@@ -34,14 +35,14 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class);
private HBox mSampleChartBox;
private ComplexSampleLineChart mSampleLineChart;
private HBox mTopChartBox;
private ComplexSampleLineChart mDifferentialDemodulatedSamplesChartBox;
private EyeDiagramChart mEyeDiagramChart;
private HBox mDecoderChartBox;
private SymbolChart mSymbolChart;
private HBox mBottomChartBox;
private SymbolChart mSymbolConstellationChart;
private PhaseLineChart mPLLPhaseErrorLineChart;
private DoubleLineChart mPLLFrequencyLineChart;
private DoubleLineChart mSamplesPerSymbolLineChart;
private SamplesPerSymbolChart mSamplesPerSymbolLineChart;
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
private P25DecoderLSMInstrumented mDecoder = new P25DecoderLSMInstrumented(null);
@@ -56,16 +57,16 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
addListener(getDecoder());
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
getDecoder().setComplexSymbolListener(getSymbolChart());
getDecoder().setComplexSymbolListener(getSymbolConstellationChart());
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
mFilteredBufferBroadcaster.addListener(getDifferentialDemodulatedSamplesChartBox());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
HBox.setHgrow(getTopChartBox(), Priority.ALWAYS);
HBox.setHgrow(getBottomChartBox(), Priority.ALWAYS);
getChildren().addAll(getTopChartBox(), getBottomChartBox());
mDecoder.setMessageListener(new Listener<Message>()
{
@@ -86,7 +87,8 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
mDecoder.setSampleRate(sampleRate);
double samplesPerSymbol = sampleRate / 4800.0;
getSampleLineChart().setSamplesPerSymbol((int)samplesPerSymbol);
getDifferentialDemodulatedSamplesChartBox().setSamplesPerSymbol((int)samplesPerSymbol);
getSamplesPerSymbolLineChart().setSamplesPerSymbol(samplesPerSymbol);
}
private P25DecoderLSMInstrumented getDecoder()
@@ -94,69 +96,69 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
return mDecoder;
}
private SymbolChart getSymbolChart()
private SymbolChart getSymbolConstellationChart()
{
if(mSymbolChart == null)
if(mSymbolConstellationChart == null)
{
mSymbolChart = new SymbolChart(10);
mSymbolConstellationChart = new SymbolChart(10);
}
return mSymbolChart;
return mSymbolConstellationChart;
}
private HBox getDecoderChartBox()
private HBox getBottomChartBox()
{
if(mDecoderChartBox == null)
if(mBottomChartBox == null)
{
mDecoderChartBox = new HBox();
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
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(getSymbolChart(), Priority.ALWAYS);
HBox.setHgrow(getSymbolConstellationChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
mBottomChartBox.getChildren().addAll(getSymbolConstellationChart(), getPLLPhaseErrorLineChart(),
getPLLFrequencyLineChart());
}
return mDecoderChartBox;
return mBottomChartBox;
}
private HBox getSampleChartBox()
private HBox getTopChartBox()
{
if(mSampleChartBox == null)
if(mTopChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
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(getSampleLineChart(), Priority.ALWAYS);
HBox.setHgrow(getDifferentialDemodulatedSamplesChartBox(), Priority.ALWAYS);
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
mTopChartBox.getChildren().addAll(getDifferentialDemodulatedSamplesChartBox(), getEyeDiagramChart(),
getSamplesPerSymbolLineChart());
}
return mSampleChartBox;
return mTopChartBox;
}
private ComplexSampleLineChart getSampleLineChart()
private ComplexSampleLineChart getDifferentialDemodulatedSamplesChartBox()
{
if(mSampleLineChart == null)
if(mDifferentialDemodulatedSamplesChartBox == null)
{
mSampleLineChart = new ComplexSampleLineChart(100, 10);
mDifferentialDemodulatedSamplesChartBox = new ComplexSampleLineChart(100, 10);
}
return mSampleLineChart;
return mDifferentialDemodulatedSamplesChartBox;
}
private EyeDiagramChart getEyeDiagramChart()
{
if(mEyeDiagramChart == null)
{
mEyeDiagramChart = new EyeDiagramChart(10, "Middle: 3-4 / Symbol: 8-9");
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol/Eye Diagram");
}
return mEyeDiagramChart;
@@ -182,11 +184,11 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
return mPLLFrequencyLineChart;
}
private DoubleLineChart getSamplesPerSymbolLineChart()
private SamplesPerSymbolChart getSamplesPerSymbolLineChart()
{
if(mSamplesPerSymbolLineChart == null)
{
mSamplesPerSymbolLineChart = new DoubleLineChart( "Sample Point", 4.5, 7.0, 0.1, 40);
mSamplesPerSymbolLineChart = new SamplesPerSymbolChart(40, 6.25);
}
return mSamplesPerSymbolLineChart;