diff --git a/build.gradle b/build.gradle
index 5039a823..b89f9ef5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -54,6 +54,8 @@ dependencies {
compile 'org.apache.axis:axis-jaxrpc:1.4'
compile 'org.apache.axis:axis:1.4'
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.3'
+ compile 'org.kordamp.ikonli:ikonli-javafx:2.1.1'
+ compile 'org.kordamp.ikonli:ikonli-fontawesome-pack:2.1.1'
}
task wrapper(type: Wrapper) {
diff --git a/src/main/java/io/github/dsheirer/buffer/ComplexCircularBuffer.java b/src/main/java/io/github/dsheirer/buffer/ComplexCircularBuffer.java
index 7dbbed04..db56da97 100644
--- a/src/main/java/io/github/dsheirer/buffer/ComplexCircularBuffer.java
+++ b/src/main/java/io/github/dsheirer/buffer/ComplexCircularBuffer.java
@@ -1,17 +1,17 @@
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
- *
+ * 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
******************************************************************************/
@@ -25,66 +25,83 @@ import java.util.Arrays;
* Circular sample buffer - allocates a buffer and stores samples in a circular
* fashion, overwriting older samples with newly arrived samples. Initially
* fills buffer with 0-valued samples
- *
+ *
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
*/
public class ComplexCircularBuffer
{
- Complex[] mBuffer;
- int mBufferPointer = 0;
-
- public ComplexCircularBuffer( int size )
- {
- mBuffer = new Complex[ size ];
-
- Arrays.fill( mBuffer, new Complex( 1.0f, 1.0f ) );
- }
+ Complex[] mBuffer;
+ int mBufferPointer = 0;
- /**
- * Returns the sample currently pointed to in the buffer and stores the
- * new sample in its place
- * @param sample
- * @return
- */
- public Complex get( Complex sample )
- {
- Complex retVal = mBuffer[ mBufferPointer ];
+ public ComplexCircularBuffer(int size)
+ {
+ mBuffer = new Complex[size];
- put( sample );
+ Arrays.fill(mBuffer, new Complex(1.0f, 1.0f));
+ }
- return retVal;
- }
-
- public void put( Complex sample )
- {
- mBuffer[ mBufferPointer ] = sample;
+ public int length()
+ {
+ return mBuffer.length;
+ }
- mBufferPointer++;
-
- if( mBufferPointer >= mBuffer.length )
- {
- mBufferPointer = 0;
- }
- }
-
- public Complex[] get( int count )
- {
- Complex[] samples = new Complex[ count ];
-
- int tempCounter = mBufferPointer;
+ /**
+ * Returns the sample currently pointed to in the buffer and stores the
+ * new sample in its place
+ *
+ * @param sample
+ * @return
+ */
+ public Complex get(Complex sample)
+ {
+ Complex retVal = mBuffer[mBufferPointer];
- for( int x = 0; x < count; x++ )
- {
- samples[ x ] = mBuffer[ tempCounter ];
-
- tempCounter++;
-
- if( tempCounter > mBuffer.length - 1 )
- {
- tempCounter = 0;
- }
- }
-
- return samples;
- }
+ put(sample);
+
+ return retVal;
+ }
+
+ public void put(Complex sample)
+ {
+ mBuffer[mBufferPointer] = sample;
+
+ mBufferPointer++;
+
+ if(mBufferPointer >= mBuffer.length)
+ {
+ mBufferPointer = 0;
+ }
+ }
+
+ /**
+ * Returns the (count) oldest samples in a time ordered array
+ */
+ public Complex[] get(int count)
+ {
+ Complex[] samples = new Complex[count];
+
+ int tempCounter = mBufferPointer;
+
+ for(int x = 0; x < count; x++)
+ {
+ samples[x] = mBuffer[tempCounter];
+
+ tempCounter++;
+
+ if(tempCounter > mBuffer.length - 1)
+ {
+ tempCounter = 0;
+ }
+ }
+
+ return samples;
+ }
+
+ /**
+ * Returns the samples in a time-ordered array
+ */
+ public Complex[] getAll()
+ {
+ return get(length());
+ }
}
diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java
index f10b571b..70dcdd0e 100644
--- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java
+++ b/src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java
@@ -5,27 +5,34 @@ import io.github.dsheirer.sample.complex.Complex;
public class ComplexGain implements Listener
{
- private float mGain;
- private Listener mListener;
+ private float mGain;
+ private Listener mListener;
- public ComplexGain( float gain )
- {
- mGain = gain;
- }
-
- @Override
- public void receive( Complex sample )
- {
- if( mListener != null )
- {
- sample.multiply( mGain );
-
- mListener.receive( sample );
- }
- }
+ public ComplexGain(float gain)
+ {
+ mGain = gain;
+ }
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
+ public Complex apply(Complex sample)
+ {
+ sample.multiply(mGain);
+
+ return sample;
+ }
+
+ @Override
+ public void receive(Complex sample)
+ {
+ if(mListener != null)
+ {
+ apply(sample);
+
+ mListener.receive(sample);
+ }
+ }
+
+ public void setListener(Listener listener)
+ {
+ mListener = listener;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/ComplexSampleLineChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/ComplexSampleLineChart.java
new file mode 100644
index 00000000..c141a3d3
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/ComplexSampleLineChart.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * 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
+ *
+ ******************************************************************************/
+package io.github.dsheirer.instrument.gui.viewer;
+
+import io.github.dsheirer.buffer.ComplexCircularBuffer;
+import io.github.dsheirer.dsp.gain.ComplexGain;
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.complex.Complex;
+import io.github.dsheirer.sample.complex.ComplexBuffer;
+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
+{
+ private final static Logger mLog = LoggerFactory.getLogger(ComplexSampleLineChart.class);
+
+ private ComplexCircularBuffer mComplexCircularBuffer;
+ private ComplexGain mComplexGain = new ComplexGain(1000.0f);
+ private ObservableList> mISamples = FXCollections.observableArrayList();
+ private ObservableList> mQSamples = FXCollections.observableArrayList();
+ private IntegerProperty mLengthProperty = new SimpleIntegerProperty(40);
+
+ public ComplexSampleLineChart(int length)
+ {
+ super(new NumberAxis("Sample Number", 1, length, 1),
+ new NumberAxis("Value", -1.0, 1.0, 0.1));
+
+ LineChart.Series iSampleSeries = new LineChart.Series<>("Inphase", mISamples);
+ LineChart.Series qSampleSeries = new LineChart.Series<>("Quadrature", mQSamples);
+ ObservableList> observableList =
+ FXCollections.observableArrayList(iSampleSeries, qSampleSeries);
+
+ setData(observableList);
+ init(length);
+ }
+
+ private void init(int length)
+ {
+ mLengthProperty.addListener(new ChangeListener()
+ {
+ @Override
+ public void changed(ObservableValue extends Number> observable, Number oldValue, Number newValue)
+ {
+ mComplexCircularBuffer = new ComplexCircularBuffer(newValue.intValue());
+ }
+ });
+ mComplexCircularBuffer = new ComplexCircularBuffer(length);
+ 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(ComplexBuffer complexBuffer)
+ {
+ float[] samples = complexBuffer.getSamples();
+
+ Complex sample;
+
+ for(int x = 0; x < samples.length; x += 2)
+ {
+ sample = new Complex(samples[x], samples[x + 1]);
+ mComplexGain.apply(sample);
+ 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)
+ {
+ mISamples.get(x).setYValue(sample.inphase());
+ mQSamples.get(x).setYValue(sample.quadrature());
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/PlaybackController.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/PlaybackController.java
index 6d09b92b..16f5f055 100644
--- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/PlaybackController.java
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/PlaybackController.java
@@ -15,67 +15,263 @@
******************************************************************************/
package io.github.dsheirer.instrument.gui.viewer;
+import io.github.dsheirer.sample.Broadcaster;
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.complex.ComplexBuffer;
+import io.github.dsheirer.source.IControllableFileSource;
+import io.github.dsheirer.source.IFrameLocationListener;
+import io.github.dsheirer.source.wave.ComplexWaveSource;
+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 org.kordamp.ikonli.fontawesome.FontAwesome;
+import org.kordamp.ikonli.javafx.FontIcon;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import java.nio.file.Path;
+import javax.sound.sampled.UnsupportedAudioFileException;
+import java.io.File;
+import java.io.IOException;
-public class PlaybackController extends HBox
+public class PlaybackController extends HBox implements IFrameLocationListener, Listener
{
+ private final static Logger mLog = LoggerFactory.getLogger(PlaybackController.class);
+
+ private Button mRewindButton;
+ private TextField mPlaybackPositionText;
private Button mPlay1Button;
private Button mPlay10Button;
private Button mPlay100Button;
private Button mPlay1000Button;
+ private HBox mControlsBox;
+ private Label mFileLabel;
+ private EventHandler mPlayEventHandler;
+
+ private IControllableFileSource mControllableFileSource;
+ private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>();
public PlaybackController()
{
- getChildren().addAll(getPlay1Button(), getPlay10Button(), getPlay100Button(), getPlay1000Button());
+ super(10);
+ getChildren().addAll(getControlsBox(), getFileLabel());
+
+ setControlsEnabled(false);
}
- public void load(Path path)
+ public void load(File file)
{
+ if(file != null && file.isFile())
+ {
+ try
+ {
+ mControllableFileSource = new ComplexWaveSource(file);
+ mControllableFileSource.setListener(this);
+ ((ComplexWaveSource)mControllableFileSource).setListener((Listener)this);
+ mControllableFileSource.open();
+ }
+ 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") + "]");
+
+ Alert alert = new Alert(Alert.AlertType.ERROR, "There was an error opening the file",
+ ButtonType.OK);
+ alert.show();
+ return;
+ }
+
+ getFileLabel().setText(file.getName());
+ setControlsEnabled(true);
+ }
}
- public Button getPlay1Button()
+ /**
+ * Adds listener to receive complex buffers from this playback
+ */
+ public void addListener(Listener listener)
+ {
+ mComplexBufferBroadcaster.addListener(listener);
+ }
+
+ /**
+ * Removes the listener from receiving complex buffers from this playback
+ */
+ public void removeListener(Listener listener)
+ {
+ mComplexBufferBroadcaster.removeListener(listener);
+ }
+
+ /**
+ * Sets all of the playback controls to the specified enabled state.
+ */
+ private void setControlsEnabled(boolean enabled)
+ {
+ getRewindButton().setDisable(!enabled);
+ getPlaybackPositionText().setDisable(!enabled);
+ getPlay1Button().setDisable(!enabled);
+ getPlay10Button().setDisable(!enabled);
+ getPlay100Button().setDisable(!enabled);
+ getPlay1000Button().setDisable(!enabled);
+ }
+
+ private HBox getControlsBox()
+ {
+ if(mControlsBox == null)
+ {
+ mControlsBox = new HBox();
+
+ mControlsBox.getChildren().addAll(getRewindButton(), getPlaybackPositionText(), getPlay1Button(),
+ getPlay10Button(), getPlay100Button(), getPlay1000Button());
+ }
+
+ return mControlsBox;
+ }
+
+
+ private Button getRewindButton()
+ {
+ if(mRewindButton == null)
+ {
+ mRewindButton = new Button();
+ mRewindButton.setGraphic(new FontIcon(FontAwesome.FAST_BACKWARD));
+ }
+
+ return mRewindButton;
+ }
+
+ private Button getPlay1Button()
{
if(mPlay1Button == null)
{
- mPlay1Button = new Button("> 1");
+ mPlay1Button = new Button("1");
+ mPlay1Button.setUserData(1);
+ mPlay1Button.setOnAction(getPlaybackEventHandler());
+ mPlay1Button.setGraphic(new FontIcon(FontAwesome.PLAY));
}
return mPlay1Button;
}
- public Button getPlay10Button()
+ private Button getPlay10Button()
{
if(mPlay10Button == null)
{
- mPlay10Button = new Button(">> 10");
+ mPlay10Button = new Button("10");
+ mPlay10Button.setUserData(10);
+ mPlay10Button.setOnAction(getPlaybackEventHandler());
+ mPlay10Button.setGraphic(new FontIcon(FontAwesome.FAST_FORWARD));
}
return mPlay10Button;
}
- public Button getPlay100Button()
+ private Button getPlay100Button()
{
if(mPlay100Button == null)
{
- mPlay100Button = new Button(">> 100");
+ mPlay100Button = new Button("100");
+ mPlay100Button.setUserData(100);
+ mPlay100Button.setOnAction(getPlaybackEventHandler());
+ mPlay100Button.setGraphic(new FontIcon(FontAwesome.FAST_FORWARD));
}
return mPlay100Button;
}
- public Button getPlay1000Button()
+ private Button getPlay1000Button()
{
if(mPlay1000Button == null)
{
- mPlay1000Button = new Button(">> 1000");
+ mPlay1000Button = new Button("1000");
+ mPlay1000Button.setUserData(1000);
+ mPlay1000Button.setOnAction(getPlaybackEventHandler());
+ mPlay1000Button.setGraphic(new FontIcon(FontAwesome.FAST_FORWARD));
}
return mPlay1000Button;
}
+ private EventHandler getPlaybackEventHandler()
+ {
+ if(mPlayEventHandler == null)
+ {
+ mPlayEventHandler = new EventHandler()
+ {
+ @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?");
+ }
+
+ @Override
+ public void receive(ComplexBuffer complexBuffer)
+ {
+ mComplexBufferBroadcaster.broadcast(complexBuffer);
+ }
}
diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/ViewerDesktop.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/ViewerDesktop.java
index 85b774cd..2c9449bf 100644
--- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/ViewerDesktop.java
+++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/ViewerDesktop.java
@@ -22,15 +22,18 @@ import java.io.File;
public class ViewerDesktop extends BorderPane
{
private PlaybackController mPlaybackController;
+ private ComplexSampleLineChart mSampleLineChart;
public ViewerDesktop()
{
setBottom(getPlaybackController());
+ getPlaybackController().addListener(getSampleLineChart());
+ setCenter(getSampleLineChart());
}
public void load(File file)
{
-
+ getPlaybackController().load(file);
}
public void close()
@@ -47,4 +50,15 @@ public class ViewerDesktop extends BorderPane
return mPlaybackController;
}
+
+
+ private ComplexSampleLineChart getSampleLineChart()
+ {
+ if(mSampleLineChart == null)
+ {
+ mSampleLineChart = new ComplexSampleLineChart(40);
+ }
+
+ return mSampleLineChart;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/sample/ConversionUtils.java b/src/main/java/io/github/dsheirer/sample/ConversionUtils.java
index 95d72bcb..7ad5c27c 100644
--- a/src/main/java/io/github/dsheirer/sample/ConversionUtils.java
+++ b/src/main/java/io/github/dsheirer/sample/ConversionUtils.java
@@ -21,14 +21,13 @@ public class ConversionUtils
*/
public static float[] convertFromSigned16BitSamples( ByteBuffer buffer )
{
- ShortBuffer byteBuffer =
- buffer.order( ByteOrder.LITTLE_ENDIAN ).asShortBuffer();
+ ShortBuffer byteBuffer = buffer.order( ByteOrder.LITTLE_ENDIAN ).asShortBuffer();
float[] samples = new float[ buffer.limit() / 2 ];
for( int x = 0; x < samples.length; x++ )
{
- samples[ x ] = (float)byteBuffer.get() / 32767.0f;
+ samples[ x ] = (float)byteBuffer.get() / (float)Short.MAX_VALUE;
}
return samples;
diff --git a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java
index 8ece68b6..d227a199 100644
--- a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java
+++ b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java
@@ -239,8 +239,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
buffer = Arrays.copyOf(buffer, samplesRead);
}
- float[] samples = ConversionUtils
- .convertFromSigned16BitSamples(buffer);
+ float[] samples = ConversionUtils.convertFromSigned16BitSamples(buffer);
mListener.receive(new ComplexBuffer(samples));
}