mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Compare commits
47 Commits
v0.3.3-bet
...
v0.3.4-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfaf506e23 | ||
|
|
81c9a352a8 | ||
|
|
e6937764c3 | ||
|
|
5bfc5bf456 | ||
|
|
ce83273393 | ||
|
|
95aa9f8803 | ||
|
|
3619b480f8 | ||
|
|
830b2a2d34 | ||
|
|
78bb5ffe43 | ||
|
|
5dd1664844 | ||
|
|
8868c1f954 | ||
|
|
646bd78037 | ||
|
|
5af4cb8706 | ||
|
|
45a6c5a0b6 | ||
|
|
b563f2d11b | ||
|
|
148ddd0bd5 | ||
|
|
a0630102e6 | ||
|
|
3d1ac30646 | ||
|
|
dda37a1198 | ||
|
|
eaf7c7d11e | ||
|
|
80c5ad1bc8 | ||
|
|
956bf2d76a | ||
|
|
1406d5576a | ||
|
|
03e39f3343 | ||
|
|
28f351287c | ||
|
|
54227703a2 | ||
|
|
bb078de0a9 | ||
|
|
ec30cd6305 | ||
|
|
5a88ac0200 | ||
|
|
05eea44f7e | ||
|
|
030fc1617c | ||
|
|
594a77a9d0 | ||
|
|
c2fcc9e85d | ||
|
|
1c06c4382f | ||
|
|
3d9b38bef5 | ||
|
|
d5b377b1af | ||
|
|
69e9bed2af | ||
|
|
ddad4b6fb0 | ||
|
|
b65dd7e414 | ||
|
|
b076587f30 | ||
|
|
336669f1d5 | ||
|
|
46cf7f4193 | ||
|
|
e9aeb09011 | ||
|
|
c7f00ea124 | ||
|
|
1759f4a804 | ||
|
|
3d9044802e | ||
|
|
42aec490bd |
@@ -1,5 +1,12 @@
|
|||||||
0.4.0 ........ (in progress)
|
0.3.4 20180418
|
||||||
|
- New P25 C4FM decoder that uses Differential QPSK decoder instead of FM demodulation with 4FSK.
|
||||||
|
- Adds channel auto-start and ordering capability with startup dialog allowing user to invoke or bypass channel
|
||||||
|
auto-start. Provides user with more control over startup configuration.
|
||||||
|
- Adds preferred tuner attribute to channel configuration allowing user to select a preferred tuner for the channel
|
||||||
|
|
||||||
|
0.3.3 20171225
|
||||||
- Changes build system from ant to gradle. Thanks to: Roman Tsypuk https://github.com/tsypuk
|
- Changes build system from ant to gradle. Thanks to: Roman Tsypuk https://github.com/tsypuk
|
||||||
|
- Adds LIST and ID3 metadata to wav audio recordings
|
||||||
|
|
||||||
0.3.2 20171105
|
0.3.2 20171105
|
||||||
- Resolves issue with tuner frequency adjustments that was causing channel processing
|
- Resolves issue with tuner frequency adjustments that was causing channel processing
|
||||||
|
|||||||
58
README.md
58
README.md
@@ -1,17 +1,52 @@
|
|||||||
# sdrtrunk
|
# sdrtrunk
|
||||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||||
|
|
||||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
|
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted_V0.3.0)
|
||||||
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_Version0.3.0)
|
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
|
||||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||||
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||||
|
|
||||||

|

|
||||||
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
|
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
|
||||||
|
|
||||||
# Instructions:
|
# End User Instructions:
|
||||||
|
|
||||||
|
If you simply want to download and run the program, please follow these instructions.
|
||||||
|
|
||||||
|
## Install Java 8 (or newer)
|
||||||
|
|
||||||
|
## Download the latest sdrtrunk release
|
||||||
|
|
||||||
|
All release versions of sdrtrunk are available from the [releases](https://github.com/DSheirer/sdrtrunk/releases) tab.
|
||||||
|
|
||||||
|
**(final)** These versions have been tested and are the current release version.
|
||||||
|
|
||||||
|
**(beta)** These versions are currently being tested for bugs and functionality.
|
||||||
|
|
||||||
|
## Run the application
|
||||||
|
|
||||||
|
Either double-click on the downloaded file (if supported on your operating system) or open a terminal/command window
|
||||||
|
and change to the directory where you downloaded the release file and type:
|
||||||
|
|
||||||
|
```
|
||||||
|
java -jar downloaded-jar-filename
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: replace _downloaded-jar-filename_ with the actual name of the sdrtrunk release version that you downloaded
|
||||||
|
|
||||||
|
## Optional - P25 Audio
|
||||||
|
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions
|
||||||
|
for downloading the JMBE audio library source code and compiling the JMBE library. Copy the resulting JMBE audio library
|
||||||
|
jar file to the same folder containing the sdrtrunk application to use the library with sdrtrunk.
|
||||||
|
|
||||||
|
# Developer Instructions:
|
||||||
|
|
||||||
|
If you're interested in modifying and/or compiling the source code, please follow these instructions to use gradle to compile the code.
|
||||||
|
|
||||||
## Build the project
|
## Build the project
|
||||||
Project uses gradle as build system. You can build it with locally installed gradle or if you do not have/want install gradle you can use preconfigured linux/windows wrapper.
|
sdrtrunk uses gradle as the build system. You can build it with locally installed gradle or if you do not have/want
|
||||||
|
to install gradle you can use preconfigured linux/windows wrapper.
|
||||||
|
|
||||||
### Build with locally installed gradle 4.3.1
|
### Build with locally installed gradle 4.3.1
|
||||||
```
|
```
|
||||||
gradle clean buildSdr
|
gradle clean buildSdr
|
||||||
@@ -35,20 +70,9 @@ version = '0.3.2'
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Run SDRTrunk
|
## Run SDRTrunk
|
||||||
SDRTrunk is packed into single uber-jar file. There is need to have folders with dependecies and
|
SDRTrunk is packed into single uber-jar file. There is need to have folders with dependencies and
|
||||||
adding them with classpath. Just run with java.
|
adding them with classpath. Just run with java.
|
||||||
### Use Java8
|
### Use Java8
|
||||||
```
|
```
|
||||||
java -jar build/libs/sdr-trunk-all-0.3.2.jar
|
java -jar build/libs/sdr-trunk-all-0.3.2.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
## TODO ITEMS
|
|
||||||
|
|
||||||
- [x] Move project to gradle build
|
|
||||||
- [x] Changed folders structure, based on java project
|
|
||||||
- [x] Dependencies are moved to gradle and versions are updated
|
|
||||||
- [x] Create uberJAR with all dependencies
|
|
||||||
- [ ] Some dependencies left in imports folder. Find them and include into gradle
|
|
||||||
- [ ] Move images into resources of project
|
|
||||||
- [ ] Add tests to project. This would guarantee that after refactoring part of functionality is not broken
|
|
||||||
- [ ] Create modular OOP system (radio-module, gui, web-ui, etc). This would allow to use RADIO modules to call from CLI, GUI or Web UI. And use SDRTrunk as dependency for other projects.
|
|
||||||
@@ -15,7 +15,7 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.3.2'
|
version = '0.3.4-beta.2'
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -54,6 +54,8 @@ dependencies {
|
|||||||
compile 'org.apache.axis:axis-jaxrpc:1.4'
|
compile 'org.apache.axis:axis-jaxrpc:1.4'
|
||||||
compile 'org.apache.axis:axis:1.4'
|
compile 'org.apache.axis:axis:1.4'
|
||||||
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.3'
|
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) {
|
task wrapper(type: Wrapper) {
|
||||||
|
|||||||
104
sdr-trunk.ipr
Normal file
104
sdr-trunk.ipr
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<option name="DEFAULT_COMPILER" value="Javac"/>
|
||||||
|
<resourceExtensions>
|
||||||
|
<entry name=".+\.(properties|xml|html|dtd|tld)"/>
|
||||||
|
<entry name=".+\.(gif|png|jpeg|jpg)"/>
|
||||||
|
</resourceExtensions>
|
||||||
|
<wildcardResourcePatterns>
|
||||||
|
<entry name="!?*.class"/>
|
||||||
|
<entry name="!?*.scala"/>
|
||||||
|
<entry name="!?*.java"/>
|
||||||
|
<entry name="!?*.groovy"/>
|
||||||
|
</wildcardResourcePatterns>
|
||||||
|
<annotationProcessing enabled="false" useClasspath="true"/>
|
||||||
|
<bytecodeTargetLevel target="1.8"/>
|
||||||
|
</component>
|
||||||
|
<component name="CopyrightManager" default="">
|
||||||
|
<module2copyright/>
|
||||||
|
</component>
|
||||||
|
<component name="DependencyValidationManager">
|
||||||
|
<option name="SKIP_IMPORT_STATEMENTS" value="false"/>
|
||||||
|
</component>
|
||||||
|
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false"/>
|
||||||
|
<component name="GradleUISettings">
|
||||||
|
<setting name="root"/>
|
||||||
|
</component>
|
||||||
|
<component name="GradleUISettings2">
|
||||||
|
<setting name="root"/>
|
||||||
|
</component>
|
||||||
|
<component name="IdProvider" IDEtalkID="11DA1DB66DD62DDA1ED602B7079FE97C"/>
|
||||||
|
<component name="JavadocGenerationManager">
|
||||||
|
<option name="OUTPUT_DIRECTORY"/>
|
||||||
|
<option name="OPTION_SCOPE" value="protected"/>
|
||||||
|
<option name="OPTION_HIERARCHY" value="true"/>
|
||||||
|
<option name="OPTION_NAVIGATOR" value="true"/>
|
||||||
|
<option name="OPTION_INDEX" value="true"/>
|
||||||
|
<option name="OPTION_SEPARATE_INDEX" value="true"/>
|
||||||
|
<option name="OPTION_DOCUMENT_TAG_USE" value="false"/>
|
||||||
|
<option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false"/>
|
||||||
|
<option name="OPTION_DOCUMENT_TAG_VERSION" value="false"/>
|
||||||
|
<option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true"/>
|
||||||
|
<option name="OPTION_DEPRECATED_LIST" value="true"/>
|
||||||
|
<option name="OTHER_OPTIONS" value=""/>
|
||||||
|
<option name="HEAP_SIZE"/>
|
||||||
|
<option name="LOCALE"/>
|
||||||
|
<option name="OPEN_IN_BROWSER" value="true"/>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module filepath="$PROJECT_DIR$/sdr-trunk.iml" fileurl="file://$PROJECT_DIR$/sdr-trunk.iml"/>
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" assert-keyword="true" jdk-15="true" project-jdk-type="JavaSDK" assert-jdk-15="true" project-jdk-name="1.8">
|
||||||
|
<output url="file://$PROJECT_DIR$/out"/>
|
||||||
|
</component>
|
||||||
|
<component name="SvnBranchConfigurationManager">
|
||||||
|
<option name="mySupportsUserInfoFilter" value="true"/>
|
||||||
|
</component>
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs=""/>
|
||||||
|
</component>
|
||||||
|
<component name="masterDetails">
|
||||||
|
<states>
|
||||||
|
<state key="ArtifactsStructureConfigurable.UI">
|
||||||
|
<UIState>
|
||||||
|
<splitter-proportions>
|
||||||
|
<SplitterProportionsDataImpl/>
|
||||||
|
</splitter-proportions>
|
||||||
|
<settings/>
|
||||||
|
</UIState>
|
||||||
|
</state>
|
||||||
|
<state key="Copyright.UI">
|
||||||
|
<UIState>
|
||||||
|
<splitter-proportions>
|
||||||
|
<SplitterProportionsDataImpl/>
|
||||||
|
</splitter-proportions>
|
||||||
|
</UIState>
|
||||||
|
</state>
|
||||||
|
<state key="ProjectJDKs.UI">
|
||||||
|
<UIState>
|
||||||
|
<splitter-proportions>
|
||||||
|
<SplitterProportionsDataImpl>
|
||||||
|
<option name="proportions">
|
||||||
|
<list>
|
||||||
|
<option value="0.2"/>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</SplitterProportionsDataImpl>
|
||||||
|
</splitter-proportions>
|
||||||
|
<last-edited>1.6</last-edited>
|
||||||
|
</UIState>
|
||||||
|
</state>
|
||||||
|
<state key="ScopeChooserConfigurable.UI">
|
||||||
|
<UIState>
|
||||||
|
<splitter-proportions>
|
||||||
|
<SplitterProportionsDataImpl/>
|
||||||
|
</splitter-proportions>
|
||||||
|
<settings/>
|
||||||
|
</UIState>
|
||||||
|
</state>
|
||||||
|
</states>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -29,10 +29,19 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.sound.sampled.FloatControl;
|
import javax.sound.sampled.FloatControl;
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JMenu;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JPopupMenu;
|
||||||
|
import javax.swing.JSlider;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.*;
|
import java.awt.Color;
|
||||||
|
import java.awt.EventQueue;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
@@ -44,10 +53,8 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
|||||||
|
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AudioPanel.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AudioPanel.class);
|
||||||
|
|
||||||
private static ImageIcon MUTED_ICON = IconManager.getScaledIcon(
|
private static ImageIcon MUTED_ICON = IconManager.getScaledIcon("/images/audio_muted.png", 20);
|
||||||
new ImageIcon("images/audio_muted.png"), 20);
|
private static ImageIcon UNMUTED_ICON = IconManager.getScaledIcon("/images/audio_unmuted.png", 20);
|
||||||
private static ImageIcon UNMUTED_ICON = IconManager.getScaledIcon(
|
|
||||||
new ImageIcon("images/audio_unmuted.png"), 20);
|
|
||||||
|
|
||||||
private IconManager mIconManager;
|
private IconManager mIconManager;
|
||||||
private SettingsManager mSettingsManager;
|
private SettingsManager mSettingsManager;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import io.github.dsheirer.util.ThreadPool;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
@@ -519,7 +519,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
|
|
||||||
if(iconPath != null && mIconManager != null)
|
if(iconPath != null && mIconManager != null)
|
||||||
{
|
{
|
||||||
return mIconManager.getScaledIcon(new ImageIcon(iconPath), 14);
|
return mIconManager.getScaledIcon(iconPath, 14);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case COLUMN_STREAM_NAME:
|
case COLUMN_STREAM_NAME:
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ import net.miginfocom.swing.MigLayout;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JPopupMenu;
|
||||||
|
import javax.swing.ListSelectionModel;
|
||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.event.ListSelectionEvent;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
@@ -227,7 +232,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
|
|||||||
|
|
||||||
public AddBroadcastConfigurationItem(BroadcastServerType type)
|
public AddBroadcastConfigurationItem(BroadcastServerType type)
|
||||||
{
|
{
|
||||||
super(type.toString(), mIconManager.getScaledIcon(new ImageIcon(type.getIconPath()), 14));
|
super(type.toString(), mIconManager.getScaledIcon(type.getIconPath(), 14));
|
||||||
|
|
||||||
mBroadcastServerType = type;
|
mBroadcastServerType = type;
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ package io.github.dsheirer.audio.broadcast;
|
|||||||
|
|
||||||
public enum BroadcastServerType
|
public enum BroadcastServerType
|
||||||
{
|
{
|
||||||
BROADCASTIFY("Broadcastify", "images/broadcastify.png"), //Icecast Server 2.3.2
|
BROADCASTIFY("Broadcastify", "/images/broadcastify.png"), //Icecast Server 2.3.2
|
||||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
|
||||||
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
|
||||||
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
|
||||||
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
|
||||||
UNKNOWN("Unknown", null);
|
UNKNOWN("Unknown", null);
|
||||||
|
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
|
|||||||
@@ -31,10 +31,17 @@ import net.miginfocom.swing.MigLayout;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JSlider;
|
||||||
|
import javax.swing.JTextField;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.*;
|
import java.awt.Dimension;
|
||||||
|
import java.awt.EventQueue;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
@@ -74,8 +81,7 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
|
|||||||
|
|
||||||
JLabel channelLabel = new JLabel("Broadcastify Stream");
|
JLabel channelLabel = new JLabel("Broadcastify Stream");
|
||||||
|
|
||||||
ImageIcon broadcastifyIcon = mIconManager.getScaledIcon(
|
ImageIcon broadcastifyIcon = mIconManager.getScaledIcon(BroadcastServerType.BROADCASTIFY.getIconPath(), 25);
|
||||||
new ImageIcon(BroadcastServerType.BROADCASTIFY.getIconPath()), 25);
|
|
||||||
channelLabel.setIcon(broadcastifyIcon);
|
channelLabel.setIcon(broadcastifyIcon);
|
||||||
add(channelLabel, "span, align center");
|
add(channelLabel, "span, align center");
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,16 @@ import net.miginfocom.swing.MigLayout;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JSlider;
|
||||||
|
import javax.swing.JTextField;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.*;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
@@ -73,7 +79,7 @@ public class IcecastHTTPConfigurationEditor extends BroadcastConfigurationEditor
|
|||||||
|
|
||||||
JLabel channelLabel = new JLabel("Icecast 2 (v2.4+) Stream");
|
JLabel channelLabel = new JLabel("Icecast 2 (v2.4+) Stream");
|
||||||
|
|
||||||
ImageIcon icon = mIconManager.getScaledIcon(new ImageIcon(BroadcastServerType.ICECAST_HTTP.getIconPath()), 25);
|
ImageIcon icon = mIconManager.getScaledIcon(BroadcastServerType.ICECAST_HTTP.getIconPath(), 25);
|
||||||
channelLabel.setIcon(icon);
|
channelLabel.setIcon(icon);
|
||||||
|
|
||||||
add(channelLabel, "span, align center");
|
add(channelLabel, "span, align center");
|
||||||
|
|||||||
@@ -29,10 +29,16 @@ import net.miginfocom.swing.MigLayout;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JSlider;
|
||||||
|
import javax.swing.JTextField;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.*;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
@@ -72,7 +78,7 @@ public class IcecastTCPConfigurationEditor extends BroadcastConfigurationEditor
|
|||||||
|
|
||||||
JLabel channelLabel = new JLabel("Icecast (v2.3) Stream");
|
JLabel channelLabel = new JLabel("Icecast (v2.3) Stream");
|
||||||
|
|
||||||
ImageIcon icon = mIconManager.getScaledIcon(new ImageIcon(BroadcastServerType.ICECAST_TCP.getIconPath()), 25);
|
ImageIcon icon = mIconManager.getScaledIcon(BroadcastServerType.ICECAST_TCP.getIconPath(), 25);
|
||||||
channelLabel.setIcon(icon);
|
channelLabel.setIcon(icon);
|
||||||
|
|
||||||
add(channelLabel, "span, align center");
|
add(channelLabel, "span, align center");
|
||||||
|
|||||||
@@ -29,10 +29,16 @@ import net.miginfocom.swing.MigLayout;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JSlider;
|
||||||
|
import javax.swing.JTextField;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.*;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
@@ -71,7 +77,7 @@ public class ShoutcastV1ConfigurationEditor extends BroadcastConfigurationEditor
|
|||||||
|
|
||||||
JLabel channelLabel = new JLabel("Shoutcast (v1.x) Stream");
|
JLabel channelLabel = new JLabel("Shoutcast (v1.x) Stream");
|
||||||
|
|
||||||
ImageIcon icon = mIconManager.getScaledIcon(new ImageIcon(BroadcastServerType.SHOUTCAST_V1.getIconPath()), 25);
|
ImageIcon icon = mIconManager.getScaledIcon(BroadcastServerType.SHOUTCAST_V1.getIconPath(), 25);
|
||||||
channelLabel.setIcon(icon);
|
channelLabel.setIcon(icon);
|
||||||
|
|
||||||
add(channelLabel, "span, align center");
|
add(channelLabel, "span, align center");
|
||||||
|
|||||||
@@ -29,10 +29,16 @@ import net.miginfocom.swing.MigLayout;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JSlider;
|
||||||
|
import javax.swing.JTextField;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.*;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
@@ -72,7 +78,7 @@ public class ShoutcastV2ConfigurationEditor extends BroadcastConfigurationEditor
|
|||||||
|
|
||||||
JLabel channelLabel = new JLabel("Shoutcast (v2) Stream");
|
JLabel channelLabel = new JLabel("Shoutcast (v2) Stream");
|
||||||
|
|
||||||
ImageIcon icon = mIconManager.getScaledIcon(new ImageIcon(BroadcastServerType.SHOUTCAST_V2.getIconPath()), 25);
|
ImageIcon icon = mIconManager.getScaledIcon(BroadcastServerType.SHOUTCAST_V2.getIconPath(), 25);
|
||||||
channelLabel.setIcon(icon);
|
channelLabel.setIcon(icon);
|
||||||
|
|
||||||
add(channelLabel, "span, align center");
|
add(channelLabel, "span, align center");
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -25,66 +25,83 @@ import java.util.Arrays;
|
|||||||
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
||||||
* fashion, overwriting older samples with newly arrived samples. Initially
|
* fashion, overwriting older samples with newly arrived samples. Initially
|
||||||
* fills buffer with 0-valued samples
|
* fills buffer with 0-valued samples
|
||||||
*
|
*
|
||||||
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
||||||
*/
|
*/
|
||||||
public class ComplexCircularBuffer
|
public class ComplexCircularBuffer
|
||||||
{
|
{
|
||||||
Complex[] mBuffer;
|
Complex[] mBuffer;
|
||||||
int mBufferPointer = 0;
|
int mBufferPointer = 0;
|
||||||
|
|
||||||
public ComplexCircularBuffer( int size )
|
|
||||||
{
|
|
||||||
mBuffer = new Complex[ size ];
|
|
||||||
|
|
||||||
Arrays.fill( mBuffer, new Complex( 1.0f, 1.0f ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public ComplexCircularBuffer(int size)
|
||||||
* Returns the sample currently pointed to in the buffer and stores the
|
{
|
||||||
* new sample in its place
|
mBuffer = new Complex[size];
|
||||||
* @param sample
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Complex get( Complex sample )
|
|
||||||
{
|
|
||||||
Complex retVal = mBuffer[ mBufferPointer ];
|
|
||||||
|
|
||||||
put( sample );
|
Arrays.fill(mBuffer, new Complex(1.0f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
return retVal;
|
public int length()
|
||||||
}
|
{
|
||||||
|
return mBuffer.length;
|
||||||
public void put( Complex sample )
|
}
|
||||||
{
|
|
||||||
mBuffer[ mBufferPointer ] = sample;
|
|
||||||
|
|
||||||
mBufferPointer++;
|
/**
|
||||||
|
* Returns the sample currently pointed to in the buffer and stores the
|
||||||
if( mBufferPointer >= mBuffer.length )
|
* new sample in its place
|
||||||
{
|
*
|
||||||
mBufferPointer = 0;
|
* @param sample
|
||||||
}
|
* @return
|
||||||
}
|
*/
|
||||||
|
public Complex get(Complex sample)
|
||||||
public Complex[] get( int count )
|
{
|
||||||
{
|
Complex retVal = mBuffer[mBufferPointer];
|
||||||
Complex[] samples = new Complex[ count ];
|
|
||||||
|
|
||||||
int tempCounter = mBufferPointer;
|
|
||||||
|
|
||||||
for( int x = 0; x < count; x++ )
|
put(sample);
|
||||||
{
|
|
||||||
samples[ x ] = mBuffer[ tempCounter ];
|
return retVal;
|
||||||
|
}
|
||||||
tempCounter++;
|
|
||||||
|
public void put(Complex sample)
|
||||||
if( tempCounter > mBuffer.length - 1 )
|
{
|
||||||
{
|
mBuffer[mBufferPointer] = sample;
|
||||||
tempCounter = 0;
|
|
||||||
}
|
mBufferPointer++;
|
||||||
}
|
|
||||||
|
if(mBufferPointer >= mBuffer.length)
|
||||||
return samples;
|
{
|
||||||
}
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +1,180 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
* Copyright (C) 2014 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.buffer;
|
package io.github.dsheirer.buffer;
|
||||||
|
|
||||||
|
import org.apache.commons.math3.stat.descriptive.moment.Mean;
|
||||||
|
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
|
||||||
|
import org.apache.commons.math3.stat.descriptive.moment.Variance;
|
||||||
|
import org.apache.commons.math3.stat.descriptive.rank.Max;
|
||||||
|
import org.apache.commons.math3.stat.descriptive.rank.Min;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
* Circular sample buffer - allocates a buffer and stores samples in a circular fashion, overwriting older samples with
|
||||||
* fashion, overwriting older samples with newly arrived samples. Initially
|
* newly arrived samples. Initially fills buffer with 0-valued (default) samples or with a specified value.
|
||||||
* fills buffer with 0-valued samples
|
|
||||||
*
|
|
||||||
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
|
||||||
*/
|
*/
|
||||||
public class DoubleCircularBuffer
|
public class DoubleCircularBuffer
|
||||||
{
|
{
|
||||||
double[] mBuffer;
|
private StandardDeviation mStandardDeviation;
|
||||||
int mBufferPointer = 0;
|
private Variance mVariance;
|
||||||
|
private Max mMax;
|
||||||
public DoubleCircularBuffer( int size )
|
private Mean mMean;
|
||||||
{
|
private Min mMin;
|
||||||
mBuffer = new double[ size ];
|
private double[] mBuffer;
|
||||||
|
private int mBufferPointer = 0;
|
||||||
Arrays.fill( mBuffer, 0.0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts the new value into the buffer and returns the oldest buffer value
|
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
|
||||||
* that it replaced
|
* @param size of the buffer
|
||||||
* @param newValue
|
* @param initialFillValue to fill the buffer entries
|
||||||
* @return
|
*/
|
||||||
*/
|
public DoubleCircularBuffer(int size, double initialFillValue)
|
||||||
public double get( double newValue )
|
{
|
||||||
{
|
mBuffer = new double[size];
|
||||||
double oldestSample = mBuffer[ mBufferPointer ];
|
Arrays.fill(mBuffer, initialFillValue);
|
||||||
|
}
|
||||||
|
|
||||||
mBuffer[ mBufferPointer ] = newValue;
|
/**
|
||||||
|
* Creates a circular double buffer of the specified size and all entries filled with an initial value of zero.
|
||||||
mBufferPointer++;
|
* @param size of the buffer
|
||||||
|
*/
|
||||||
if( mBufferPointer >= mBuffer.length )
|
public DoubleCircularBuffer(int size)
|
||||||
{
|
{
|
||||||
mBufferPointer = 0;
|
this(size, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return oldestSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the maximum from the values currently in the buffer
|
* Puts the new value into the buffer and returns the oldest buffer value that it replaced
|
||||||
*/
|
*
|
||||||
public double max()
|
* @param newValue to add to the buffer
|
||||||
{
|
* @return oldest value that was overwritten by the new value
|
||||||
double max = 0.0d;
|
*/
|
||||||
|
public double get(double newValue)
|
||||||
for( int x = 0; x < mBuffer.length; x++ )
|
{
|
||||||
{
|
double oldestSample = mBuffer[mBufferPointer];
|
||||||
if( mBuffer[ x ] > max )
|
|
||||||
{
|
mBuffer[mBufferPointer] = newValue;
|
||||||
max = mBuffer[ x ];
|
|
||||||
}
|
mBufferPointer++;
|
||||||
}
|
|
||||||
|
if(mBufferPointer >= mBuffer.length)
|
||||||
return max;
|
{
|
||||||
}
|
mBufferPointer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldestSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[] getAll()
|
||||||
|
{
|
||||||
|
int valuePointer = 0;
|
||||||
|
double[] values = new double[mBuffer.length];
|
||||||
|
int bufferPointer = mBufferPointer;
|
||||||
|
|
||||||
|
while(valuePointer < values.length)
|
||||||
|
{
|
||||||
|
values[valuePointer++] = mBuffer[bufferPointer++];
|
||||||
|
|
||||||
|
if(bufferPointer >= mBuffer.length)
|
||||||
|
{
|
||||||
|
bufferPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Places the new value into the buffer, overwriting the oldest value
|
||||||
|
* @param newValue to add to the buffer
|
||||||
|
*/
|
||||||
|
public void put(double newValue)
|
||||||
|
{
|
||||||
|
mBuffer[mBufferPointer] = newValue;
|
||||||
|
|
||||||
|
mBufferPointer++;
|
||||||
|
|
||||||
|
if(mBufferPointer >= mBuffer.length)
|
||||||
|
{
|
||||||
|
mBufferPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum from the values currently in the buffer
|
||||||
|
*/
|
||||||
|
public double max()
|
||||||
|
{
|
||||||
|
if(mMax == null)
|
||||||
|
{
|
||||||
|
mMax = new Max();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMax.evaluate(mBuffer, 0, mBuffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum from the values currently in the buffer
|
||||||
|
*/
|
||||||
|
public double min()
|
||||||
|
{
|
||||||
|
if(mMin == null)
|
||||||
|
{
|
||||||
|
mMin = new Min();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMin.evaluate(mBuffer, 0, mBuffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double standardDeviation()
|
||||||
|
{
|
||||||
|
if(mStandardDeviation == null)
|
||||||
|
{
|
||||||
|
mStandardDeviation = new StandardDeviation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mStandardDeviation.evaluate(mBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the variance of the values contained in this buffer
|
||||||
|
*/
|
||||||
|
public double variance()
|
||||||
|
{
|
||||||
|
if(mVariance == null)
|
||||||
|
{
|
||||||
|
mVariance = new Variance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mVariance.evaluate(mBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the average/mean of the values contained in this buffer
|
||||||
|
*/
|
||||||
|
public double mean()
|
||||||
|
{
|
||||||
|
if(mMean == null)
|
||||||
|
{
|
||||||
|
mMean = new Mean();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMean.evaluate(mBuffer, 0, mBuffer.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
* Copyright (C) 2014 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -23,96 +23,98 @@ import java.util.Arrays;
|
|||||||
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
||||||
* fashion, overwriting older samples with newly arrived samples. Initially
|
* fashion, overwriting older samples with newly arrived samples. Initially
|
||||||
* fills buffer with 0-valued samples
|
* fills buffer with 0-valued samples
|
||||||
*
|
|
||||||
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
|
||||||
* @author denny
|
|
||||||
*
|
*
|
||||||
|
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
||||||
|
*
|
||||||
|
* @author denny
|
||||||
*/
|
*/
|
||||||
public class FloatCircularBuffer
|
public class FloatCircularBuffer
|
||||||
{
|
{
|
||||||
float[] mBuffer;
|
float[] mBuffer;
|
||||||
int mBufferPointer = 0;
|
int mBufferPointer = 0;
|
||||||
|
|
||||||
public FloatCircularBuffer( int size )
|
|
||||||
{
|
|
||||||
mBuffer = new float[ size ];
|
|
||||||
|
|
||||||
Arrays.fill( mBuffer, (float)0.0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSize()
|
|
||||||
{
|
|
||||||
return mBuffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getBuffer()
|
|
||||||
{
|
|
||||||
return mBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts the new value into the buffer and returns the oldest buffer value
|
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
|
||||||
* that it replaced
|
*
|
||||||
* @param newValue
|
* @param size of the buffer
|
||||||
* @return
|
* @param initialFillValue to fill the buffer entries
|
||||||
*/
|
*/
|
||||||
public float get( float newValue )
|
public FloatCircularBuffer(int size, float initialFillValue)
|
||||||
{
|
{
|
||||||
float oldestSample = mBuffer[ mBufferPointer ];
|
mBuffer = new float[size];
|
||||||
|
Arrays.fill(mBuffer, initialFillValue);
|
||||||
|
}
|
||||||
|
|
||||||
mBuffer[ mBufferPointer ] = newValue;
|
/**
|
||||||
|
* Creates a circular double buffer of the specified size and all entries filled with an initial value of zero.
|
||||||
mBufferPointer++;
|
*
|
||||||
|
* @param size of the buffer
|
||||||
if( mBufferPointer >= mBuffer.length )
|
*/
|
||||||
{
|
public FloatCircularBuffer(int size)
|
||||||
mBufferPointer = 0;
|
{
|
||||||
}
|
this(size, 0.0f);
|
||||||
|
}
|
||||||
return oldestSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public float[] getBuffer()
|
||||||
* Convenience method to put a new value in the buffer, while discarding
|
{
|
||||||
* the old value
|
return mBuffer;
|
||||||
*/
|
}
|
||||||
public void put( float newValue )
|
|
||||||
{
|
|
||||||
get( newValue );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of arraySize, loaded with sampleCount of the
|
* Puts the new value into the buffer and returns the oldest buffer value that it replaced
|
||||||
* oldest samples, loaded into the array starting at index 0.
|
*
|
||||||
*
|
* @param newValue to add to the buffer
|
||||||
* This is useful for fetching an array filled half way with samples to
|
* @return oldest value that was overwritten by the new value
|
||||||
* use in FFT processing.
|
*/
|
||||||
* @param arraySize - size of the array (must be larger than sampleCount)
|
public float get(float newValue)
|
||||||
* @param sampleCount - number of the oldest samples in the buffer to load
|
{
|
||||||
* @return
|
float oldestSample = mBuffer[mBufferPointer];
|
||||||
*/
|
|
||||||
public float[] get( int arraySize, int sampleCount )
|
|
||||||
{
|
|
||||||
float[] samples = new float[ arraySize ];
|
|
||||||
|
|
||||||
if( arraySize > sampleCount )
|
mBuffer[mBufferPointer] = newValue;
|
||||||
{
|
|
||||||
int tempCounter = mBufferPointer;
|
|
||||||
|
|
||||||
for( int x = 0; x < sampleCount; x++ )
|
mBufferPointer++;
|
||||||
{
|
|
||||||
samples[ x ] = mBuffer[ tempCounter ];
|
if(mBufferPointer >= mBuffer.length)
|
||||||
|
{
|
||||||
tempCounter++;
|
mBufferPointer = 0;
|
||||||
|
}
|
||||||
if( tempCounter > mBuffer.length )
|
|
||||||
{
|
return oldestSample;
|
||||||
tempCounter = 0;
|
}
|
||||||
}
|
|
||||||
}
|
public float[] getAll()
|
||||||
}
|
{
|
||||||
|
int valuePointer = 0;
|
||||||
return samples;
|
float[] values = new float[mBuffer.length];
|
||||||
}
|
int bufferPointer = mBufferPointer;
|
||||||
|
|
||||||
|
while(valuePointer < values.length)
|
||||||
|
{
|
||||||
|
values[valuePointer++] = mBuffer[bufferPointer++];
|
||||||
|
|
||||||
|
if(bufferPointer >= mBuffer.length)
|
||||||
|
{
|
||||||
|
bufferPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Places the new value into the buffer, overwriting the oldest value
|
||||||
|
*
|
||||||
|
* @param newValue to add to the buffer
|
||||||
|
*/
|
||||||
|
public void put(float newValue)
|
||||||
|
{
|
||||||
|
mBuffer[mBufferPointer] = newValue;
|
||||||
|
|
||||||
|
mBufferPointer++;
|
||||||
|
|
||||||
|
if(mBufferPointer >= mBuffer.length)
|
||||||
|
{
|
||||||
|
mBufferPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
|||||||
{
|
{
|
||||||
for(Channel configuredChannel : mTrafficChannelPool)
|
for(Channel configuredChannel : mTrafficChannelPool)
|
||||||
{
|
{
|
||||||
if(!configuredChannel.getEnabled())
|
if(!configuredChannel.isProcessing())
|
||||||
{
|
{
|
||||||
channel = configuredChannel;
|
channel = configuredChannel;
|
||||||
break;
|
break;
|
||||||
@@ -207,7 +207,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
|||||||
//Request to enable the channel
|
//Request to enable the channel
|
||||||
mChannelModel.broadcast(trafficChannelEvent);
|
mChannelModel.broadcast(trafficChannelEvent);
|
||||||
|
|
||||||
if(channel.getEnabled())
|
if(channel.isProcessing())
|
||||||
{
|
{
|
||||||
mTrafficChannelsInUse.put(callEvent.getChannel(), channel);
|
mTrafficChannelsInUse.put(callEvent.getChannel(), channel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.controller.channel;
|
||||||
|
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-Start Channel Model
|
||||||
|
*/
|
||||||
|
public class AutoStartChannelModel extends AbstractTableModel
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public static final int COLUMN_ORDER = 0;
|
||||||
|
public static final int COLUMN_SYSTEM = 1;
|
||||||
|
public static final int COLUMN_SITE = 2;
|
||||||
|
public static final int COLUMN_NAME = 3;
|
||||||
|
public static final int COLUMN_DECODER = 4;
|
||||||
|
|
||||||
|
private static final String[] COLUMN_NAMES = new String[] {"Order", "System", "Site", "Name", "Decoder"};
|
||||||
|
|
||||||
|
private List<Channel> mChannels;
|
||||||
|
|
||||||
|
public AutoStartChannelModel(List<Channel> autoStartChannels)
|
||||||
|
{
|
||||||
|
mChannels = autoStartChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount()
|
||||||
|
{
|
||||||
|
return mChannels.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount()
|
||||||
|
{
|
||||||
|
return COLUMN_NAMES.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int columnIndex)
|
||||||
|
{
|
||||||
|
if(columnIndex < COLUMN_NAMES.length)
|
||||||
|
{
|
||||||
|
return COLUMN_NAMES[columnIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int columnIndex)
|
||||||
|
{
|
||||||
|
return String.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int rowIndex, int columnIndex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int rowIndex, int columnIndex)
|
||||||
|
{
|
||||||
|
Channel channel = mChannels.get(rowIndex);
|
||||||
|
|
||||||
|
switch(columnIndex)
|
||||||
|
{
|
||||||
|
case COLUMN_ORDER:
|
||||||
|
return channel.getAutoStartOrder();
|
||||||
|
case COLUMN_SYSTEM:
|
||||||
|
return channel.getSystem();
|
||||||
|
case COLUMN_SITE:
|
||||||
|
return channel.getSite();
|
||||||
|
case COLUMN_NAME:
|
||||||
|
return channel.getName();
|
||||||
|
case COLUMN_DECODER:
|
||||||
|
return channel.getDecodeConfiguration().getDecoderType().getShortDisplayString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Not yet implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* sdrtrunk
|
* sdrtrunk
|
||||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -45,18 +45,18 @@ import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
|
|||||||
public class Channel extends Configuration implements Listener<FrequencyChangeEvent>
|
public class Channel extends Configuration implements Listener<FrequencyChangeEvent>
|
||||||
{
|
{
|
||||||
// Standard channels are persisted and traffic channels are temporary
|
// Standard channels are persisted and traffic channels are temporary
|
||||||
public enum ChannelType {STANDARD, TRAFFIC}
|
public enum ChannelType
|
||||||
|
{
|
||||||
|
STANDARD, TRAFFIC
|
||||||
|
}
|
||||||
|
|
||||||
// Static unique channel identifier tracking
|
// Static unique channel identifier tracking
|
||||||
private static int UNIQUE_ID = 0;
|
private static int UNIQUE_ID = 0;
|
||||||
|
|
||||||
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
|
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
|
||||||
private AuxDecodeConfiguration mAuxDecodeConfiguration =
|
private AuxDecodeConfiguration mAuxDecodeConfiguration = new AuxDecodeConfiguration();
|
||||||
new AuxDecodeConfiguration();
|
private SourceConfiguration mSourceConfiguration = SourceConfigFactory.getDefaultSourceConfiguration();
|
||||||
private SourceConfiguration mSourceConfiguration =
|
private EventLogConfiguration mEventLogConfiguration = new EventLogConfiguration();
|
||||||
SourceConfigFactory.getDefaultSourceConfiguration();
|
|
||||||
private EventLogConfiguration mEventLogConfiguration =
|
|
||||||
new EventLogConfiguration();
|
|
||||||
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
|
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
|
||||||
|
|
||||||
private String mAliasListName;
|
private String mAliasListName;
|
||||||
@@ -64,7 +64,9 @@ public class Channel extends Configuration implements Listener<FrequencyChangeEv
|
|||||||
private String mSite = "Site";
|
private String mSite = "Site";
|
||||||
private String mName = "Channel";
|
private String mName = "Channel";
|
||||||
|
|
||||||
private boolean mEnabled;
|
private boolean mProcessing;
|
||||||
|
private boolean mAutoStart;
|
||||||
|
private Integer mAutoStartOrder;
|
||||||
private boolean mSelected;
|
private boolean mSelected;
|
||||||
private TunerChannel mTunerChannel = null;
|
private TunerChannel mTunerChannel = null;
|
||||||
|
|
||||||
@@ -113,6 +115,8 @@ public class Channel extends Configuration implements Listener<FrequencyChangeEv
|
|||||||
channel.setSystem(mSystem);
|
channel.setSystem(mSystem);
|
||||||
channel.setSite(mSite);
|
channel.setSite(mSite);
|
||||||
channel.setAliasListName(mAliasListName);
|
channel.setAliasListName(mAliasListName);
|
||||||
|
channel.setAutoStart(mAutoStart);
|
||||||
|
channel.setAutoStartOrder(mAutoStartOrder);
|
||||||
|
|
||||||
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
|
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
|
||||||
|
|
||||||
@@ -258,27 +262,90 @@ public class Channel extends Configuration implements Listener<FrequencyChangeEv
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this channel is enabled for processing.
|
* Indicates if this channel is currently started and processing.
|
||||||
*/
|
*/
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "enabled")
|
@JsonIgnore
|
||||||
public boolean getEnabled()
|
public boolean isProcessing()
|
||||||
{
|
{
|
||||||
return mEnabled;
|
return mProcessing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this channel will automatically start processing on
|
* Indicates if this channel is currently started and processing.
|
||||||
* application startup or if this channel is currently processing after
|
|
||||||
* application startup.
|
|
||||||
*
|
*
|
||||||
* Note: this method is package private and is intended to be managed
|
* Note: this method is package private and is intended to be managed by a channel processing manager instance
|
||||||
* by a co-package central processing manager
|
|
||||||
*
|
*
|
||||||
* @see ChannelProcessingManager
|
* @see ChannelProcessingManager
|
||||||
*/
|
*/
|
||||||
void setEnabled(boolean enabled)
|
void setProcessing(boolean processing)
|
||||||
{
|
{
|
||||||
mEnabled = enabled;
|
mProcessing = processing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this channel has auto-start enabled.
|
||||||
|
*
|
||||||
|
* Note: the Json (de)serialization tag for auto-start is 'enabled'. We've re-purposed the now
|
||||||
|
* transient enabled property for the auto-start persisted value to maintain backward compatibility.
|
||||||
|
*
|
||||||
|
* @return true if this channel should be auto-started on application startup
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "enabled")
|
||||||
|
public boolean getAutoStart()
|
||||||
|
{
|
||||||
|
return mAutoStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this channel is set for auto-start
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isAutoStart()
|
||||||
|
{
|
||||||
|
return mAutoStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the auto-start status for this channel.
|
||||||
|
*
|
||||||
|
* @param autoStart
|
||||||
|
*/
|
||||||
|
public void setAutoStart(boolean autoStart)
|
||||||
|
{
|
||||||
|
mAutoStart = autoStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-start order. Specifies an optional ordering for channels so that when the application starts up,
|
||||||
|
* the channels can be started in a user-specified order.
|
||||||
|
*
|
||||||
|
* @return auto-start order or null
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "order")
|
||||||
|
public Integer getAutoStartOrder()
|
||||||
|
{
|
||||||
|
return mAutoStartOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the auto-start order for this channel
|
||||||
|
*
|
||||||
|
* @param order
|
||||||
|
*/
|
||||||
|
public void setAutoStartOrder(Integer order)
|
||||||
|
{
|
||||||
|
mAutoStartOrder = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this channel has an auto-start order specified (ie non-null).
|
||||||
|
*
|
||||||
|
* @return true if this channel has an auto-start order
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean hasAutoStartOrder()
|
||||||
|
{
|
||||||
|
return mAutoStartOrder != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.controller.channel;
|
||||||
|
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class ChannelAutoStartFrame extends JFrame
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelAutoStartFrame.class);
|
||||||
|
|
||||||
|
private static final int COUNTDOWN_SECONDS = 10;
|
||||||
|
private ChannelEventListener mChannelEventListener;
|
||||||
|
private List<Channel> mChannels;
|
||||||
|
|
||||||
|
private JLabel mCountdownLabel;
|
||||||
|
private JButton mStartButton;
|
||||||
|
private JButton mCancelButton;
|
||||||
|
private JTable mChannelTable;
|
||||||
|
private AtomicBoolean mChannelsStarted = new AtomicBoolean();
|
||||||
|
|
||||||
|
private ScheduledFuture<?> mTimerFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and displays a channel auto-start gui for presenting the user with a list of channels that
|
||||||
|
* will be automatically started once the countdown timer reaches zero, or the user chooses to start
|
||||||
|
* now or cancel.
|
||||||
|
*
|
||||||
|
* @param listener to receive channel start/enable request(s)
|
||||||
|
* @param channels to auto-start
|
||||||
|
*/
|
||||||
|
public ChannelAutoStartFrame(ChannelEventListener listener, List<Channel> channels)
|
||||||
|
{
|
||||||
|
mChannelEventListener = listener;
|
||||||
|
mChannels = channels;
|
||||||
|
init();
|
||||||
|
|
||||||
|
EventQueue.invokeLater(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
setVisible(true);
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
setTitle("Auto-Start Channels");
|
||||||
|
setSize(new Dimension(400,300));
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
addWindowListener(new WindowAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent event)
|
||||||
|
{
|
||||||
|
mLog.info("Channel auto-start canceled by user - window closed");
|
||||||
|
stopTimer();
|
||||||
|
super.windowClosing(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JPanel panel = new JPanel();
|
||||||
|
panel.setLayout(new MigLayout("","[grow,fill][grow,fill]",
|
||||||
|
"[][][grow,fill][]"));
|
||||||
|
panel.add(new JLabel("The following channels will be automatically"), "span");
|
||||||
|
|
||||||
|
mCountdownLabel = new JLabel(getCountdownText(COUNTDOWN_SECONDS));
|
||||||
|
panel.add(mCountdownLabel, "span");
|
||||||
|
|
||||||
|
panel.add(new JScrollPane(getChannelTable()), "span");
|
||||||
|
|
||||||
|
mStartButton = new JButton("Start Now");
|
||||||
|
mStartButton.addActionListener(new ActionListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
|
{
|
||||||
|
mLog.info("Starting [" + mChannels.size() + "] channels now - user invoked");
|
||||||
|
startChannels();
|
||||||
|
stopTimer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
panel.add(mStartButton);
|
||||||
|
|
||||||
|
mCancelButton = new JButton("Cancel");
|
||||||
|
mCancelButton.addActionListener(new ActionListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
|
{
|
||||||
|
mLog.info("Channel auto-start canceled by user");
|
||||||
|
stopTimer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
panel.add(mCancelButton);
|
||||||
|
setContentPane(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCountdownText(int value)
|
||||||
|
{
|
||||||
|
return "started in: " + value + " seconds.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the countdown text label on the swing event thread
|
||||||
|
* @param value for the countdown
|
||||||
|
*/
|
||||||
|
private void updateCountdownText(final int value)
|
||||||
|
{
|
||||||
|
EventQueue.invokeLater(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
mCountdownLabel.setText(getCountdownText(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates (once) a table of auto-start channels
|
||||||
|
*/
|
||||||
|
private JTable getChannelTable()
|
||||||
|
{
|
||||||
|
if(mChannelTable == null)
|
||||||
|
{
|
||||||
|
AutoStartChannelModel model = new AutoStartChannelModel(mChannels);
|
||||||
|
mChannelTable = new JTable(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannelTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an enable (ie start) request for each of the auto start channels.
|
||||||
|
*
|
||||||
|
* This method is thread-safe and will only be executed once.
|
||||||
|
*
|
||||||
|
* Although the redundant channel-start request would be ignored, this once-only will prevent the chance
|
||||||
|
* that both the timer and the user would attempt to start the channels at the same time.
|
||||||
|
*/
|
||||||
|
private void startChannels()
|
||||||
|
{
|
||||||
|
if(mChannelsStarted.compareAndSet(false, true))
|
||||||
|
{
|
||||||
|
if(mChannelEventListener != null)
|
||||||
|
{
|
||||||
|
for(Channel channel: mChannels)
|
||||||
|
{
|
||||||
|
mChannelEventListener.channelChanged(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the countdown timer to fire once a second.
|
||||||
|
*/
|
||||||
|
private void startTimer()
|
||||||
|
{
|
||||||
|
if(mTimerFuture == null)
|
||||||
|
{
|
||||||
|
mTimerFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new CountdownTimer(), 0, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the countdown timer and disposes this frame
|
||||||
|
*/
|
||||||
|
private void stopTimer()
|
||||||
|
{
|
||||||
|
if(mTimerFuture != null)
|
||||||
|
{
|
||||||
|
mTimerFuture.cancel(true);
|
||||||
|
mTimerFuture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventQueue.invokeLater(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the countdown text and auto-starts the channels once the timer falls below zero.
|
||||||
|
*/
|
||||||
|
public class CountdownTimer implements Runnable
|
||||||
|
{
|
||||||
|
private int mCount = COUNTDOWN_SECONDS;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
if(mCount >= 0)
|
||||||
|
{
|
||||||
|
updateCountdownText(mCount);
|
||||||
|
mCount--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("Starting [" + mChannels.size() + "] now - timer invoked");
|
||||||
|
startChannels();
|
||||||
|
stopTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,11 +8,19 @@ import net.coderazzi.filters.gui.AutoChoices;
|
|||||||
import net.coderazzi.filters.gui.TableFilterHeader;
|
import net.coderazzi.filters.gui.TableFilterHeader;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.ListSelectionModel;
|
||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.event.ListSelectionEvent;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
import java.awt.*;
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Rectangle;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
@@ -236,7 +244,7 @@ public class ChannelController extends JPanel
|
|||||||
|
|
||||||
Channel channel = model.getChannelAtIndex( index );
|
Channel channel = model.getChannelAtIndex( index );
|
||||||
|
|
||||||
boolean enabled = channel.getEnabled();
|
boolean enabled = channel.isProcessing();
|
||||||
|
|
||||||
if( isSelected )
|
if( isSelected )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -30,221 +30,227 @@ import io.github.dsheirer.source.SourceConfigurationEditor;
|
|||||||
import io.github.dsheirer.source.SourceManager;
|
import io.github.dsheirer.source.SourceManager;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JButton;
|
||||||
import java.awt.*;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
public class ChannelEditor extends Editor<Channel> implements ActionListener, ChannelEventListener
|
public class ChannelEditor extends Editor<Channel> implements ActionListener, ChannelEventListener
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final String ACTION_START = "Start";
|
||||||
|
private static final String ACTION_STOP = "Stop";
|
||||||
|
private static final String ACTION_SAVE = "Save";
|
||||||
|
private static final String ACTION_RESET = "Reset";
|
||||||
|
|
||||||
private NameConfigurationEditor mNameConfigurationEditor;
|
private NameConfigurationEditor mNameConfigurationEditor;
|
||||||
private SourceConfigurationEditor mSourceConfigurationEditor;
|
private SourceConfigurationEditor mSourceConfigurationEditor;
|
||||||
private DecodeConfigurationEditor mDecodeConfigurationEditor;
|
private DecodeConfigurationEditor mDecodeConfigurationEditor;
|
||||||
private AuxDecodeConfigurationEditor mAuxDecodeConfigurationEditor;
|
private AuxDecodeConfigurationEditor mAuxDecodeConfigurationEditor;
|
||||||
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
||||||
private RecordConfigurationEditor mRecordConfigurationEditor;
|
private RecordConfigurationEditor mRecordConfigurationEditor;
|
||||||
|
|
||||||
private JButton mEnableButton = new JButton( "Enable" );
|
private JButton mProcessingStateButton = new JButton(ACTION_START);
|
||||||
private JLabel mChannelName = new JLabel( "Channel:" );
|
private JLabel mChannelName = new JLabel("Channel:");
|
||||||
|
|
||||||
private ChannelModel mChannelModel;
|
private ChannelModel mChannelModel;
|
||||||
private ChannelMapModel mChannelMapModel;
|
private ChannelMapModel mChannelMapModel;
|
||||||
private SourceManager mSourceManager;
|
private SourceManager mSourceManager;
|
||||||
|
|
||||||
private boolean mChannelEnableRequested = false;
|
|
||||||
|
|
||||||
public ChannelEditor( ChannelModel channelModel,
|
|
||||||
ChannelMapModel channelMapModel,
|
|
||||||
SourceManager sourceManager,
|
|
||||||
AliasModel aliasModel )
|
|
||||||
{
|
|
||||||
mChannelModel = channelModel;
|
|
||||||
mChannelMapModel = channelMapModel;
|
|
||||||
mSourceManager = sourceManager;
|
|
||||||
mNameConfigurationEditor = new NameConfigurationEditor( aliasModel, mChannelModel );
|
|
||||||
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init()
|
|
||||||
{
|
|
||||||
setLayout( new MigLayout( "fill,wrap 3", "[grow,fill][grow,fill][grow,fill]", "[grow,fill][]" ) );
|
|
||||||
|
|
||||||
JideTabbedPane tabs = new JideTabbedPane();
|
|
||||||
tabs.setFont( this.getFont() );
|
|
||||||
tabs.setForeground( Color.BLACK );
|
|
||||||
|
|
||||||
tabs.setTabTrailingComponent( mChannelName );
|
private boolean mChannelStartRequested = false;
|
||||||
|
|
||||||
mNameConfigurationEditor.setSaveRequestListener( this );
|
|
||||||
tabs.addTab( "Name/Alias", mNameConfigurationEditor );
|
|
||||||
|
|
||||||
mSourceConfigurationEditor = new SourceConfigurationEditor( mSourceManager );
|
|
||||||
mSourceConfigurationEditor.setSaveRequestListener( this );
|
|
||||||
tabs.addTab( "Source", mSourceConfigurationEditor );
|
|
||||||
|
|
||||||
mDecodeConfigurationEditor = new DecodeConfigurationEditor( mChannelMapModel );
|
|
||||||
mDecodeConfigurationEditor.setSaveRequestListener( this );
|
|
||||||
tabs.addTab( "Decoder", mDecodeConfigurationEditor );
|
|
||||||
|
|
||||||
mAuxDecodeConfigurationEditor = new AuxDecodeConfigurationEditor();
|
public ChannelEditor(ChannelModel channelModel,
|
||||||
mAuxDecodeConfigurationEditor.setSaveRequestListener( this );
|
ChannelMapModel channelMapModel,
|
||||||
tabs.addTab( "Aux Decoders", mAuxDecodeConfigurationEditor );
|
SourceManager sourceManager,
|
||||||
|
AliasModel aliasModel)
|
||||||
mEventLogConfigurationEditor = new EventLogConfigurationEditor();
|
|
||||||
mEventLogConfigurationEditor.setSaveRequestListener( this );
|
|
||||||
tabs.addTab( "Logging", mEventLogConfigurationEditor );
|
|
||||||
|
|
||||||
mRecordConfigurationEditor = new RecordConfigurationEditor();
|
|
||||||
mRecordConfigurationEditor.setSaveRequestListener( this );
|
|
||||||
tabs.addTab( "Recording", mRecordConfigurationEditor );
|
|
||||||
|
|
||||||
add( tabs, "span" );
|
|
||||||
|
|
||||||
mEnableButton.addActionListener( this );
|
|
||||||
mEnableButton.setEnabled( false );
|
|
||||||
mEnableButton.setToolTipText( "Start the currently selected channel running/decoding" );
|
|
||||||
add( mEnableButton );
|
|
||||||
|
|
||||||
JButton btnSave = new JButton( "Save" );
|
|
||||||
btnSave.setToolTipText( "Save changes to the currently selected channel" );
|
|
||||||
btnSave.addActionListener( ChannelEditor.this );
|
|
||||||
add( btnSave );
|
|
||||||
|
|
||||||
JButton btnReset = new JButton( "Reset" );
|
|
||||||
btnReset.setToolTipText( "Reload the currently selected channel" );
|
|
||||||
btnReset.addActionListener( ChannelEditor.this );
|
|
||||||
add( btnReset );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelChanged( ChannelEvent event )
|
|
||||||
{
|
|
||||||
if( hasItem() && getItem() == event.getChannel() )
|
|
||||||
{
|
|
||||||
switch( event.getEvent() )
|
|
||||||
{
|
|
||||||
case NOTIFICATION_CONFIGURATION_CHANGE:
|
|
||||||
case NOTIFICATION_PROCESSING_START:
|
|
||||||
case NOTIFICATION_PROCESSING_STOP:
|
|
||||||
setItem( getItem() );
|
|
||||||
mChannelEnableRequested = false;
|
|
||||||
break;
|
|
||||||
case NOTIFICATION_DELETE:
|
|
||||||
setItem( null );
|
|
||||||
break;
|
|
||||||
case NOTIFICATION_ENABLE_REJECTED:
|
|
||||||
if( mChannelEnableRequested )
|
|
||||||
{
|
|
||||||
JOptionPane.showMessageDialog( ChannelEditor.this, "Channel could not be "
|
|
||||||
+ "enabled. This is likely because there are no tuner channels "
|
|
||||||
+ "available", "Couldn't enable channel", JOptionPane.INFORMATION_MESSAGE );
|
|
||||||
|
|
||||||
mChannelEnableRequested = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e )
|
|
||||||
{
|
{
|
||||||
String command = e.getActionCommand();
|
mChannelModel = channelModel;
|
||||||
|
mChannelMapModel = channelMapModel;
|
||||||
if( command.contentEquals( "Enable" ) )
|
mSourceManager = sourceManager;
|
||||||
{
|
mNameConfigurationEditor = new NameConfigurationEditor(aliasModel, mChannelModel);
|
||||||
if( hasItem() )
|
|
||||||
{
|
init();
|
||||||
save();
|
}
|
||||||
|
|
||||||
mChannelEnableRequested = true;
|
private void init()
|
||||||
mChannelModel.broadcast( new ChannelEvent( getItem(), ChannelEvent.Event.REQUEST_ENABLE ) );
|
{
|
||||||
}
|
setLayout(new MigLayout("fill,wrap 3", "[grow,fill][grow,fill][grow,fill]", "[grow,fill][]"));
|
||||||
}
|
|
||||||
else if( command.contentEquals( "Disable" ) )
|
JideTabbedPane tabs = new JideTabbedPane();
|
||||||
{
|
tabs.setFont(this.getFont());
|
||||||
if( hasItem() )
|
tabs.setForeground(Color.BLACK);
|
||||||
{
|
|
||||||
mChannelModel.broadcast( new ChannelEvent( getItem(), ChannelEvent.Event.REQUEST_DISABLE ) );
|
tabs.setTabTrailingComponent(mChannelName);
|
||||||
}
|
|
||||||
}
|
mNameConfigurationEditor.setSaveRequestListener(this);
|
||||||
else if( command.contentEquals( "Save" ) )
|
tabs.addTab("Name/Alias", mNameConfigurationEditor);
|
||||||
{
|
|
||||||
save();
|
mSourceConfigurationEditor = new SourceConfigurationEditor(mSourceManager);
|
||||||
}
|
mSourceConfigurationEditor.setSaveRequestListener(this);
|
||||||
else if( command.contentEquals( "Reset" ) )
|
tabs.addTab("Source", mSourceConfigurationEditor);
|
||||||
{
|
|
||||||
setItem( getItem() );
|
mDecodeConfigurationEditor = new DecodeConfigurationEditor(mChannelMapModel);
|
||||||
}
|
mDecodeConfigurationEditor.setSaveRequestListener(this);
|
||||||
|
tabs.addTab("Decoder", mDecodeConfigurationEditor);
|
||||||
|
|
||||||
|
mAuxDecodeConfigurationEditor = new AuxDecodeConfigurationEditor();
|
||||||
|
mAuxDecodeConfigurationEditor.setSaveRequestListener(this);
|
||||||
|
tabs.addTab("Aux Decoders", mAuxDecodeConfigurationEditor);
|
||||||
|
|
||||||
|
mEventLogConfigurationEditor = new EventLogConfigurationEditor();
|
||||||
|
mEventLogConfigurationEditor.setSaveRequestListener(this);
|
||||||
|
tabs.addTab("Logging", mEventLogConfigurationEditor);
|
||||||
|
|
||||||
|
mRecordConfigurationEditor = new RecordConfigurationEditor();
|
||||||
|
mRecordConfigurationEditor.setSaveRequestListener(this);
|
||||||
|
tabs.addTab("Recording", mRecordConfigurationEditor);
|
||||||
|
|
||||||
|
add(tabs, "span");
|
||||||
|
|
||||||
|
mProcessingStateButton.addActionListener(this);
|
||||||
|
mProcessingStateButton.setEnabled(false);
|
||||||
|
mProcessingStateButton.setToolTipText("Start/Stop the currently selected channel");
|
||||||
|
add(mProcessingStateButton);
|
||||||
|
|
||||||
|
JButton btnSave = new JButton(ACTION_SAVE);
|
||||||
|
btnSave.setToolTipText("Save changes to the currently selected channel");
|
||||||
|
btnSave.addActionListener(ChannelEditor.this);
|
||||||
|
add(btnSave);
|
||||||
|
|
||||||
|
JButton btnReset = new JButton(ACTION_RESET);
|
||||||
|
btnReset.setToolTipText("Reload the currently selected channel");
|
||||||
|
btnReset.addActionListener(ChannelEditor.this);
|
||||||
|
add(btnReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelChanged(ChannelEvent event)
|
||||||
|
{
|
||||||
|
if(hasItem() && getItem() == event.getChannel())
|
||||||
|
{
|
||||||
|
switch(event.getEvent())
|
||||||
|
{
|
||||||
|
case NOTIFICATION_CONFIGURATION_CHANGE:
|
||||||
|
case NOTIFICATION_PROCESSING_START:
|
||||||
|
case NOTIFICATION_PROCESSING_STOP:
|
||||||
|
setItem(getItem());
|
||||||
|
mChannelStartRequested = false;
|
||||||
|
break;
|
||||||
|
case NOTIFICATION_DELETE:
|
||||||
|
setItem(null);
|
||||||
|
break;
|
||||||
|
case NOTIFICATION_START_PROCESSING_REJECTED:
|
||||||
|
if(mChannelStartRequested)
|
||||||
|
{
|
||||||
|
JOptionPane.showMessageDialog(ChannelEditor.this, "Channel could not be "
|
||||||
|
+ "started. This is likely because there are no tuner channels "
|
||||||
|
+ "available", "Couldn't start channel", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
|
||||||
|
mChannelStartRequested = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
|
{
|
||||||
|
String command = e.getActionCommand();
|
||||||
|
|
||||||
|
if(command.contentEquals(ACTION_START))
|
||||||
|
{
|
||||||
|
if(hasItem())
|
||||||
|
{
|
||||||
|
save();
|
||||||
|
|
||||||
|
mChannelStartRequested = true;
|
||||||
|
mChannelModel.broadcast(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_ENABLE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(command.contentEquals(ACTION_STOP))
|
||||||
|
{
|
||||||
|
if(hasItem())
|
||||||
|
{
|
||||||
|
mChannelModel.broadcast(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_DISABLE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(command.contentEquals(ACTION_SAVE))
|
||||||
|
{
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
else if(command.contentEquals(ACTION_RESET))
|
||||||
|
{
|
||||||
|
setItem(getItem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the channel configuration in each of the channel editor components
|
||||||
|
* Note: this method must be invoked on the swing event dispatch thread
|
||||||
|
*/
|
||||||
|
public void setItem(final Channel channel)
|
||||||
|
{
|
||||||
|
super.setItem(channel);
|
||||||
|
|
||||||
|
mNameConfigurationEditor.setItem(channel);
|
||||||
|
mSourceConfigurationEditor.setItem(channel);
|
||||||
|
mDecodeConfigurationEditor.setItem(channel);
|
||||||
|
mAuxDecodeConfigurationEditor.setItem(channel);
|
||||||
|
mEventLogConfigurationEditor.setItem(channel);
|
||||||
|
mRecordConfigurationEditor.setItem(channel);
|
||||||
|
|
||||||
|
if(channel != null)
|
||||||
|
{
|
||||||
|
mChannelName.setText("Channel: " + channel.getName());
|
||||||
|
mProcessingStateButton.setText(channel.isProcessing() ? ACTION_STOP : ACTION_START);
|
||||||
|
mProcessingStateButton.setEnabled(true);
|
||||||
|
mProcessingStateButton.setBackground(channel.isProcessing() ? Color.GREEN : getBackground());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mChannelName.setText("Channel: ");
|
||||||
|
mProcessingStateButton.setText(ACTION_START);
|
||||||
|
mProcessingStateButton.setEnabled(false);
|
||||||
|
mProcessingStateButton.setBackground(getBackground());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the channel configuration in each of the channel editor components
|
|
||||||
* Note: this method must be invoked on the swing event dispatch thread
|
|
||||||
*/
|
|
||||||
public void setItem( final Channel channel )
|
|
||||||
{
|
|
||||||
super.setItem( channel );
|
|
||||||
|
|
||||||
mNameConfigurationEditor.setItem( channel );
|
|
||||||
mSourceConfigurationEditor.setItem( channel );
|
|
||||||
mDecodeConfigurationEditor.setItem( channel );
|
|
||||||
mAuxDecodeConfigurationEditor.setItem( channel );
|
|
||||||
mEventLogConfigurationEditor.setItem( channel );
|
|
||||||
mRecordConfigurationEditor.setItem( channel );
|
|
||||||
|
|
||||||
if( channel != null )
|
|
||||||
{
|
|
||||||
mChannelName.setText( "Channel: " + channel.getName() );
|
|
||||||
mEnableButton.setText( channel.getEnabled() ? "Disable" : "Enable" );
|
|
||||||
mEnableButton.setEnabled( true );
|
|
||||||
mEnableButton.setBackground( channel.getEnabled() ? Color.GREEN : getBackground() );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mChannelName.setText( "Channel: " );
|
|
||||||
mEnableButton.setText( "Enable" );
|
|
||||||
mEnableButton.setEnabled( false );
|
|
||||||
mEnableButton.setBackground( getBackground() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save()
|
public void save()
|
||||||
{
|
{
|
||||||
if( hasItem() )
|
if(hasItem())
|
||||||
{
|
{
|
||||||
mNameConfigurationEditor.save();
|
mNameConfigurationEditor.save();
|
||||||
mSourceConfigurationEditor.save();
|
mSourceConfigurationEditor.save();
|
||||||
mDecodeConfigurationEditor.save();
|
mDecodeConfigurationEditor.save();
|
||||||
mAuxDecodeConfigurationEditor.save();
|
mAuxDecodeConfigurationEditor.save();
|
||||||
mEventLogConfigurationEditor.save();
|
mEventLogConfigurationEditor.save();
|
||||||
mRecordConfigurationEditor.save();
|
mRecordConfigurationEditor.save();
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mDecodeConfigurationEditor.validate( mSourceConfigurationEditor );
|
|
||||||
mDecodeConfigurationEditor.validate( mAuxDecodeConfigurationEditor );
|
|
||||||
}
|
|
||||||
catch( EditorValidationException e )
|
|
||||||
{
|
|
||||||
e.getEditor().requestFocus();
|
|
||||||
|
|
||||||
JOptionPane.showMessageDialog( e.getEditor(), e.getReason(),
|
|
||||||
"Configuration Error", JOptionPane.ERROR_MESSAGE );
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mChannelModel.broadcast( new ChannelEvent( getItem(),
|
try
|
||||||
ChannelEvent.Event.NOTIFICATION_CONFIGURATION_CHANGE ) );
|
{
|
||||||
}
|
mDecodeConfigurationEditor.validate(mSourceConfigurationEditor);
|
||||||
|
mDecodeConfigurationEditor.validate(mAuxDecodeConfigurationEditor);
|
||||||
|
}
|
||||||
|
catch(EditorValidationException e)
|
||||||
|
{
|
||||||
|
e.getEditor().requestFocus();
|
||||||
|
|
||||||
setModified( false );
|
JOptionPane.showMessageDialog(e.getEditor(), e.getReason(),
|
||||||
|
"Configuration Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mChannelModel.broadcast(new ChannelEvent(getItem(),
|
||||||
|
ChannelEvent.Event.NOTIFICATION_CONFIGURATION_CHANGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
setModified(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -42,6 +42,11 @@ public class ChannelEvent
|
|||||||
{
|
{
|
||||||
return mEvent;
|
return mEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getEvent().name() + " Channel:" + getChannel().getName();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel events to describe the specific event
|
* Channel events to describe the specific event
|
||||||
@@ -55,7 +60,7 @@ public class ChannelEvent
|
|||||||
//Channel is deleted/removed
|
//Channel is deleted/removed
|
||||||
NOTIFICATION_DELETE,
|
NOTIFICATION_DELETE,
|
||||||
//Channel enable request was rejected
|
//Channel enable request was rejected
|
||||||
NOTIFICATION_ENABLE_REJECTED,
|
NOTIFICATION_START_PROCESSING_REJECTED,
|
||||||
//Channel has started processing/decoding
|
//Channel has started processing/decoding
|
||||||
NOTIFICATION_PROCESSING_START,
|
NOTIFICATION_PROCESSING_START,
|
||||||
//Channel has stopped processing/decoding
|
//Channel has stopped processing/decoding
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import io.github.dsheirer.module.decode.DecoderType;
|
|||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
@@ -42,9 +43,11 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro
|
|||||||
public static final int COLUMN_ALIAS_LIST = 4;
|
public static final int COLUMN_ALIAS_LIST = 4;
|
||||||
public static final int COLUMN_SOURCE = 5;
|
public static final int COLUMN_SOURCE = 5;
|
||||||
public static final int COLUMN_DECODER = 6;
|
public static final int COLUMN_DECODER = 6;
|
||||||
|
public static final int COLUMN_AUTO_START = 7;
|
||||||
|
|
||||||
private static final String[] COLUMN_NAMES = new String[] {"Enabled", "System", "Site", "Name", "Alias List",
|
private static final String[] COLUMN_NAMES = new String[] {"Playing", "System", "Site", "Name", "Alias List",
|
||||||
"Source", "Decoder"};
|
"Source", "Decoder", "Auto-Start"};
|
||||||
|
private static final String VALUE_YES = "Yes";
|
||||||
|
|
||||||
private List<Channel> mChannels = new CopyOnWriteArrayList<>();
|
private List<Channel> mChannels = new CopyOnWriteArrayList<>();
|
||||||
private List<Channel> mTrafficChannels = new CopyOnWriteArrayList<>();
|
private List<Channel> mTrafficChannels = new CopyOnWriteArrayList<>();
|
||||||
@@ -209,11 +212,6 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro
|
|||||||
|
|
||||||
broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD));
|
broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD));
|
||||||
|
|
||||||
if(channel.getEnabled())
|
|
||||||
{
|
|
||||||
broadcast(new ChannelEvent(channel, Event.REQUEST_ENABLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +245,48 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of channels that have the auto-start flag set.
|
||||||
|
*
|
||||||
|
* @return auto-start channels sorted by the channel order value.
|
||||||
|
*/
|
||||||
|
public List<Channel> getAutoStartChannels()
|
||||||
|
{
|
||||||
|
List<Channel> autoStartChannels = new ArrayList<>();
|
||||||
|
|
||||||
|
for(Channel channel: getChannels())
|
||||||
|
{
|
||||||
|
if(channel.isAutoStart())
|
||||||
|
{
|
||||||
|
autoStartChannels.add(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(autoStartChannels, new Comparator<Channel>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public int compare(Channel channel1, Channel channel2)
|
||||||
|
{
|
||||||
|
if(channel1.hasAutoStartOrder() && channel2.hasAutoStartOrder())
|
||||||
|
{
|
||||||
|
return Integer.compare(channel1.getAutoStartOrder(), channel2.getAutoStartOrder());
|
||||||
|
}
|
||||||
|
else if(channel1.hasAutoStartOrder())
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if(channel2.hasAutoStartOrder())
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return autoStartChannels;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of channels that fall within the frequency range
|
* Returns a list of channels that fall within the frequency range
|
||||||
*
|
*
|
||||||
@@ -331,7 +371,7 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro
|
|||||||
switch(columnIndex)
|
switch(columnIndex)
|
||||||
{
|
{
|
||||||
case COLUMN_ENABLED:
|
case COLUMN_ENABLED:
|
||||||
return channel.getEnabled() ? "Enabled" : null;
|
return channel.isProcessing() ? VALUE_YES : null;
|
||||||
case COLUMN_SYSTEM:
|
case COLUMN_SYSTEM:
|
||||||
return channel.getSystem();
|
return channel.getSystem();
|
||||||
case COLUMN_SITE:
|
case COLUMN_SITE:
|
||||||
@@ -344,6 +384,19 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro
|
|||||||
return channel.getSourceConfiguration().getDescription();
|
return channel.getSourceConfiguration().getDescription();
|
||||||
case COLUMN_DECODER:
|
case COLUMN_DECODER:
|
||||||
return channel.getDecodeConfiguration().getDecoderType().getShortDisplayString();
|
return channel.getDecodeConfiguration().getDecoderType().getShortDisplayString();
|
||||||
|
case COLUMN_AUTO_START:
|
||||||
|
if(channel.isAutoStart())
|
||||||
|
{
|
||||||
|
if(channel.hasAutoStartOrder())
|
||||||
|
{
|
||||||
|
return channel.getAutoStartOrder();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return VALUE_YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class ChannelProcessingManager implements ChannelEventListener
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case REQUEST_DISABLE:
|
case REQUEST_DISABLE:
|
||||||
if(channel.getEnabled())
|
if(channel.isProcessing())
|
||||||
{
|
{
|
||||||
switch(channel.getChannelType())
|
switch(channel.getChannelType())
|
||||||
{
|
{
|
||||||
@@ -176,7 +176,7 @@ public class ChannelProcessingManager implements ChannelEventListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case NOTIFICATION_DELETE:
|
case NOTIFICATION_DELETE:
|
||||||
if(channel.getEnabled())
|
if(channel.isProcessing())
|
||||||
{
|
{
|
||||||
stopProcessing(channel, true);
|
stopProcessing(channel, true);
|
||||||
}
|
}
|
||||||
@@ -220,9 +220,9 @@ public class ChannelProcessingManager implements ChannelEventListener
|
|||||||
|
|
||||||
if(source == null)
|
if(source == null)
|
||||||
{
|
{
|
||||||
channel.setEnabled(false);
|
channel.setProcessing(false);
|
||||||
|
|
||||||
mChannelModel.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_ENABLE_REJECTED));
|
mChannelModel.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_START_PROCESSING_REJECTED));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ public class ChannelProcessingManager implements ChannelEventListener
|
|||||||
|
|
||||||
getChannelMetadataModel().add(processingChain.getChannelState().getMutableMetadata(), channel);
|
getChannelMetadataModel().add(processingChain.getChannelState().getMutableMetadata(), channel);
|
||||||
|
|
||||||
channel.setEnabled(true);
|
channel.setProcessing(true);
|
||||||
|
|
||||||
mProcessingChains.put(channel.getChannelID(), processingChain);
|
mProcessingChains.put(channel.getChannelID(), processingChain);
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ public class ChannelProcessingManager implements ChannelEventListener
|
|||||||
|
|
||||||
private void stopProcessing(Channel channel, boolean remove)
|
private void stopProcessing(Channel channel, boolean remove)
|
||||||
{
|
{
|
||||||
channel.setEnabled(false);
|
channel.setProcessing(false);
|
||||||
|
|
||||||
if(mProcessingChains.containsKey(channel.getChannelID()))
|
if(mProcessingChains.containsKey(channel.getChannelID()))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import io.github.dsheirer.channel.state.DecoderState;
|
|||||||
import io.github.dsheirer.module.ProcessingChain;
|
import io.github.dsheirer.module.ProcessingChain;
|
||||||
import io.github.dsheirer.module.decode.event.ActivitySummaryFrame;
|
import io.github.dsheirer.module.decode.event.ActivitySummaryFrame;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JMenu;
|
||||||
import java.awt.*;
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JSeparator;
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ public class ChannelUtils
|
|||||||
{
|
{
|
||||||
JMenu menu = new JMenu( "Channel: " + channel.getName() );
|
JMenu menu = new JMenu( "Channel: " + channel.getName() );
|
||||||
|
|
||||||
if( channel.getEnabled() )
|
if( channel.isProcessing() )
|
||||||
{
|
{
|
||||||
JMenuItem disable = new JMenuItem( "Disable" );
|
JMenuItem disable = new JMenuItem( "Disable" );
|
||||||
disable.addActionListener( new ActionListener()
|
disable.addActionListener( new ActionListener()
|
||||||
|
|||||||
@@ -23,9 +23,21 @@ import net.miginfocom.swing.MigLayout;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ComboBoxModel;
|
||||||
|
import javax.swing.DefaultComboBoxModel;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JSpinner;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.SpinnerModel;
|
||||||
|
import javax.swing.SpinnerNumberModel;
|
||||||
|
import javax.swing.event.ChangeEvent;
|
||||||
|
import javax.swing.event.ChangeListener;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -48,6 +60,8 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
|
|||||||
private JComboBox<String> mSystemNameCombo;
|
private JComboBox<String> mSystemNameCombo;
|
||||||
private JComboBox<String> mSiteNameCombo;
|
private JComboBox<String> mSiteNameCombo;
|
||||||
private JComboBox<String> mAliasListCombo;
|
private JComboBox<String> mAliasListCombo;
|
||||||
|
private JCheckBox mAutoStartCheckBox;
|
||||||
|
private JSpinner mAutoStartOrder;
|
||||||
|
|
||||||
public NameConfigurationEditor(AliasModel aliasModel, ChannelModel model)
|
public NameConfigurationEditor(AliasModel aliasModel, ChannelModel model)
|
||||||
{
|
{
|
||||||
@@ -139,6 +153,40 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
|
|||||||
|
|
||||||
add(new JLabel("Site:"));
|
add(new JLabel("Site:"));
|
||||||
add(mSiteNameCombo);
|
add(mSiteNameCombo);
|
||||||
|
|
||||||
|
JPanel autoStartPanel = new JPanel();
|
||||||
|
|
||||||
|
mAutoStartCheckBox = new JCheckBox("Auto-Start");
|
||||||
|
mAutoStartCheckBox.setToolTipText("Automatically start this channel each time the application runs.");
|
||||||
|
mAutoStartCheckBox.addActionListener(new ActionListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
|
{
|
||||||
|
mAutoStartOrder.setEnabled(mAutoStartCheckBox.isSelected());
|
||||||
|
setModified(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
autoStartPanel.add(mAutoStartCheckBox);
|
||||||
|
|
||||||
|
SpinnerModel model = new SpinnerNumberModel(0,0,500,1);
|
||||||
|
mAutoStartOrder = new JSpinner(model);
|
||||||
|
mAutoStartOrder.setEnabled(false);
|
||||||
|
mAutoStartOrder.setPreferredSize(new Dimension(60, mAutoStartOrder.getPreferredSize().height));
|
||||||
|
mAutoStartOrder.setToolTipText("Auto-start order: 1 (high) <> 500 (low). 0 = no ordering");
|
||||||
|
mAutoStartOrder.addChangeListener(new ChangeListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void stateChanged(ChangeEvent e)
|
||||||
|
{
|
||||||
|
setModified(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
autoStartPanel.add(new JLabel("Order:"));
|
||||||
|
autoStartPanel.add(mAutoStartOrder);
|
||||||
|
add(new JLabel()); //Empty space holder
|
||||||
|
add(autoStartPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -156,6 +204,11 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
|
|||||||
|
|
||||||
Object aliasList = mAliasListCombo.getSelectedItem();
|
Object aliasList = mAliasListCombo.getSelectedItem();
|
||||||
getItem().setAliasListName(aliasList == null ? null : (String)aliasList);
|
getItem().setAliasListName(aliasList == null ? null : (String)aliasList);
|
||||||
|
|
||||||
|
getItem().setAutoStart(mAutoStartCheckBox.isSelected());
|
||||||
|
|
||||||
|
int order = ((Number)mAutoStartOrder.getValue()).intValue();
|
||||||
|
getItem().setAutoStartOrder((order > 0 ? order : null));
|
||||||
}
|
}
|
||||||
|
|
||||||
setModified(false);
|
setModified(false);
|
||||||
@@ -182,6 +235,16 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
|
|||||||
{
|
{
|
||||||
mSiteNameCombo.setEnabled(enabled);
|
mSiteNameCombo.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(mAutoStartCheckBox.isEnabled() != enabled)
|
||||||
|
{
|
||||||
|
mAutoStartCheckBox.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mAutoStartOrder.isEnabled() != enabled)
|
||||||
|
{
|
||||||
|
mAutoStartOrder.setEnabled(enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -237,6 +300,19 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
|
|||||||
{
|
{
|
||||||
mAliasListCombo.setSelectedItem(channel.getAliasListName());
|
mAliasListCombo.setSelectedItem(channel.getAliasListName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(channel.isAutoStart())
|
||||||
|
{
|
||||||
|
mAutoStartCheckBox.setSelected(channel.isAutoStart());
|
||||||
|
mAutoStartOrder.setEnabled(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mAutoStartCheckBox.setSelected(false);
|
||||||
|
mAutoStartOrder.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
mAutoStartOrder.setValue(channel.hasAutoStartOrder() ? channel.getAutoStartOrder() : 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -244,6 +320,7 @@ public class NameConfigurationEditor extends Editor<Channel> implements Document
|
|||||||
mChannelName.setText(null);
|
mChannelName.setText(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setModified(false);
|
setModified(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -666,11 +666,11 @@ public class FilterFactory
|
|||||||
* @param alpha - roll-off factor
|
* @param alpha - roll-off factor
|
||||||
* @return - filter coefficients
|
* @return - filter coefficients
|
||||||
*/
|
*/
|
||||||
public static float[] getRootRaisedCosine(int samplesPerSymbol,
|
public static float[] getRootRaisedCosine(float samplesPerSymbol,
|
||||||
int symbols,
|
int symbols,
|
||||||
float alpha)
|
float alpha)
|
||||||
{
|
{
|
||||||
int taps = samplesPerSymbol * symbols + 1;
|
int taps = (int)(samplesPerSymbol * (float)symbols + 1.0f);
|
||||||
|
|
||||||
float scale = 0;
|
float scale = 0;
|
||||||
|
|
||||||
@@ -680,8 +680,8 @@ public class FilterFactory
|
|||||||
{
|
{
|
||||||
float index = (float) x - ((float) taps / 2.0f);
|
float index = (float) x - ((float) taps / 2.0f);
|
||||||
|
|
||||||
float x1 = (float) Math.PI * index / (float) samplesPerSymbol;
|
float x1 = (float) Math.PI * index / samplesPerSymbol;
|
||||||
float x2 = 4.0f * alpha * index / (float) samplesPerSymbol;
|
float x2 = 4.0f * alpha * index / samplesPerSymbol;
|
||||||
float x3 = x2 * x2 - 1.0f;
|
float x3 = x2 * x2 - 1.0f;
|
||||||
|
|
||||||
float numerator, denominator;
|
float numerator, denominator;
|
||||||
@@ -692,7 +692,7 @@ public class FilterFactory
|
|||||||
{
|
{
|
||||||
numerator = (float) Math.cos((1.0 + alpha) * x1) +
|
numerator = (float) Math.cos((1.0 + alpha) * x1) +
|
||||||
(float) Math.sin((1.0f - alpha) * x1) /
|
(float) Math.sin((1.0f - alpha) * x1) /
|
||||||
(4.0f * alpha * index / (float) samplesPerSymbol);
|
(4.0f * alpha * index / samplesPerSymbol);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -713,9 +713,9 @@ public class FilterFactory
|
|||||||
x3 = (1.0f - alpha) * x1;
|
x3 = (1.0f - alpha) * x1;
|
||||||
x2 = (1.0f + alpha) * x1;
|
x2 = (1.0f + alpha) * x1;
|
||||||
|
|
||||||
numerator = (float) (Math.sin(x2) * (1.0f + alpha) * Math.PI -
|
numerator = (float) (Math.sin(x2) * (1.0 + alpha) * Math.PI -
|
||||||
Math.cos(x3) * ((1.0 - alpha) * Math.PI *
|
Math.cos(x3) * ((1.0 - alpha) * Math.PI *
|
||||||
(double) samplesPerSymbol) / (4 * alpha * index) +
|
(double) samplesPerSymbol) / (4.0 * alpha * index) +
|
||||||
Math.sin(x3) * (double) samplesPerSymbol *
|
Math.sin(x3) * (double) samplesPerSymbol *
|
||||||
(double) samplesPerSymbol / (4.0 * alpha * index * index));
|
(double) samplesPerSymbol / (4.0 * alpha * index * index));
|
||||||
|
|
||||||
|
|||||||
@@ -12,155 +12,169 @@ import org.jtransforms.fft.FloatFFT_1D;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
public class FilterView extends BorderPane
|
public class FilterView extends BorderPane
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( FilterView.class );
|
private final static Logger mLog = LoggerFactory.getLogger(FilterView.class);
|
||||||
|
|
||||||
private CurveFittedAreaChart mImpulseChart;
|
private CurveFittedAreaChart mImpulseChart;
|
||||||
private NumberAxis mImpulseXAxis;
|
private NumberAxis mImpulseXAxis;
|
||||||
private NumberAxis mImpulseYAxis;
|
private NumberAxis mImpulseYAxis;
|
||||||
private CurveFittedAreaChart mDFTChart;
|
private CurveFittedAreaChart mDFTChart;
|
||||||
private NumberAxis mDFTXAxis;
|
private NumberAxis mDFTXAxis;
|
||||||
private NumberAxis mDFTYAxis;
|
private NumberAxis mDFTYAxis;
|
||||||
private static final int FFT_SIZE = 4096;
|
private static final int FFT_SIZE = 4096;
|
||||||
private FloatFFT_1D mFFT = new FloatFFT_1D( FFT_SIZE );
|
private FloatFFT_1D mFFT = new FloatFFT_1D(FFT_SIZE);
|
||||||
|
|
||||||
public FilterView( float[] taps )
|
|
||||||
{
|
|
||||||
init();
|
|
||||||
|
|
||||||
if( taps != null )
|
public FilterView(float[] taps)
|
||||||
{
|
{
|
||||||
setImpulseTaps( taps );
|
init();
|
||||||
|
|
||||||
float[] dft = new float[ FFT_SIZE ];
|
if(taps != null)
|
||||||
System.arraycopy( taps, 0, dft, 0, taps.length );
|
{
|
||||||
|
setImpulseTaps(taps);
|
||||||
mFFT.realForward( dft );
|
|
||||||
|
float[] dft = new float[FFT_SIZE];
|
||||||
setDFTTaps( convertDFTToDecibel( dft, taps.length ) );
|
System.arraycopy(taps, 0, dft, 0, taps.length);
|
||||||
}
|
|
||||||
}
|
mFFT.realForward(dft);
|
||||||
|
|
||||||
private void init()
|
setDFTTaps(convertDFTToDecibel(dft, taps.length));
|
||||||
{
|
}
|
||||||
HBox hbox = new HBox();
|
}
|
||||||
|
|
||||||
mImpulseXAxis = new NumberAxis( 0.0, 0.5, 1.0 );
|
private void init()
|
||||||
mImpulseYAxis = new NumberAxis( -0.2, 1.2, .1 );
|
{
|
||||||
|
HBox hbox = new HBox();
|
||||||
mImpulseChart = new CurveFittedAreaChart( mImpulseXAxis, mImpulseYAxis );
|
|
||||||
mImpulseChart.setLegendVisible( false );
|
mImpulseXAxis = new NumberAxis(0.0, 0.5, 1.0);
|
||||||
mImpulseChart.setHorizontalGridLinesVisible( false );
|
mImpulseYAxis = new NumberAxis(-0.2, 1.2, .1);
|
||||||
mImpulseChart.setVerticalGridLinesVisible(false);
|
|
||||||
|
mImpulseChart = new CurveFittedAreaChart(mImpulseXAxis, mImpulseYAxis);
|
||||||
|
mImpulseChart.setLegendVisible(false);
|
||||||
|
mImpulseChart.setHorizontalGridLinesVisible(false);
|
||||||
|
mImpulseChart.setVerticalGridLinesVisible(false);
|
||||||
mImpulseChart.setAlternativeColumnFillVisible(false);
|
mImpulseChart.setAlternativeColumnFillVisible(false);
|
||||||
mImpulseChart.setAlternativeRowFillVisible(false);
|
mImpulseChart.setAlternativeRowFillVisible(false);
|
||||||
|
|
||||||
String css = FilterView.class.getResource( "FilterView.css" ).toExternalForm();
|
|
||||||
mImpulseChart.getStylesheets().add( css );
|
|
||||||
|
|
||||||
hbox.getChildren().add( mImpulseChart );
|
URL cssURL = FilterView.class.getResource("FilterView.css");
|
||||||
HBox.setHgrow( mImpulseChart, Priority.ALWAYS );
|
|
||||||
|
String css = null;
|
||||||
mDFTXAxis = new NumberAxis( 0.0, FFT_SIZE / 2, FFT_SIZE / 8 );
|
|
||||||
mDFTYAxis = new NumberAxis( -200.0, 10.0, 10.0 );
|
if(cssURL != null)
|
||||||
|
{
|
||||||
mDFTChart = new CurveFittedAreaChart( mDFTXAxis, mDFTYAxis );
|
css = cssURL.toExternalForm();
|
||||||
mDFTChart.setLegendVisible( false );
|
|
||||||
mDFTChart.setHorizontalGridLinesVisible( false );
|
}
|
||||||
mDFTChart.setVerticalGridLinesVisible(false);
|
|
||||||
|
hbox.getChildren().add(mImpulseChart);
|
||||||
|
HBox.setHgrow(mImpulseChart, Priority.ALWAYS);
|
||||||
|
|
||||||
|
mDFTXAxis = new NumberAxis(0.0, FFT_SIZE / 2, FFT_SIZE / 8);
|
||||||
|
mDFTYAxis = new NumberAxis(-200.0, 10.0, 10.0);
|
||||||
|
|
||||||
|
mDFTChart = new CurveFittedAreaChart(mDFTXAxis, mDFTYAxis);
|
||||||
|
mDFTChart.setLegendVisible(false);
|
||||||
|
mDFTChart.setHorizontalGridLinesVisible(false);
|
||||||
|
mDFTChart.setVerticalGridLinesVisible(false);
|
||||||
mDFTChart.setAlternativeColumnFillVisible(false);
|
mDFTChart.setAlternativeColumnFillVisible(false);
|
||||||
mDFTChart.setAlternativeRowFillVisible(false);
|
mDFTChart.setAlternativeRowFillVisible(false);
|
||||||
mDFTChart.getStylesheets().add( css );
|
|
||||||
|
|
||||||
hbox.getChildren().add( mDFTChart );
|
|
||||||
HBox.setHgrow( mDFTChart, Priority.ALWAYS );
|
|
||||||
|
|
||||||
setCenter( hbox );
|
if(css != null)
|
||||||
}
|
|
||||||
|
|
||||||
public void setImpulseTaps( double[] taps )
|
|
||||||
{
|
|
||||||
float[] convertedTaps = new float[ taps.length ];
|
|
||||||
|
|
||||||
for( int x = 0; x < taps.length; x++ )
|
|
||||||
{
|
|
||||||
convertedTaps[ x ] = (float)taps[ x ];
|
|
||||||
}
|
|
||||||
|
|
||||||
setImpulseTaps( convertedTaps );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setImpulseTaps( float[] taps )
|
|
||||||
{
|
|
||||||
double smallest = 0.0;
|
|
||||||
double largest = 0.0;
|
|
||||||
|
|
||||||
final XYChart.Series<Number, Number> series = new XYChart.Series<>();
|
|
||||||
|
|
||||||
for( int x = 0; x < taps.length; x++ )
|
|
||||||
{
|
{
|
||||||
if( taps[ x ] < smallest )
|
mImpulseChart.getStylesheets().add(css);
|
||||||
{
|
mDFTChart.getStylesheets().add(css);
|
||||||
smallest = taps[ x ];
|
|
||||||
}
|
|
||||||
|
|
||||||
if( taps[ x ] > largest )
|
|
||||||
{
|
|
||||||
largest = taps[ x ];
|
|
||||||
}
|
|
||||||
|
|
||||||
series.getData().add( new XYChart.Data<Number, Number>( x, taps[ x ] ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mImpulseXAxis.setLowerBound( 0.0 );
|
hbox.getChildren().add(mDFTChart);
|
||||||
mImpulseXAxis.setUpperBound( taps.length - 1 );
|
HBox.setHgrow(mDFTChart, Priority.ALWAYS);
|
||||||
mImpulseXAxis.setTickUnit( 1.0 );
|
|
||||||
|
setCenter(hbox);
|
||||||
double padding = Math.abs( largest - smallest ) * .1;
|
}
|
||||||
|
|
||||||
mImpulseYAxis.setLowerBound( smallest - padding );
|
public void setImpulseTaps(double[] taps)
|
||||||
mImpulseYAxis.setUpperBound( largest + padding );
|
{
|
||||||
mImpulseYAxis.setTickUnit( padding );
|
float[] convertedTaps = new float[taps.length];
|
||||||
|
|
||||||
|
for(int x = 0; x < taps.length; x++)
|
||||||
|
{
|
||||||
|
convertedTaps[x] = (float)taps[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
setImpulseTaps(convertedTaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImpulseTaps(float[] taps)
|
||||||
|
{
|
||||||
|
double smallest = 0.0;
|
||||||
|
double largest = 0.0;
|
||||||
|
|
||||||
|
final XYChart.Series<Number,Number> series = new XYChart.Series<>();
|
||||||
|
|
||||||
|
for(int x = 0; x < taps.length; x++)
|
||||||
|
{
|
||||||
|
if(taps[x] < smallest)
|
||||||
|
{
|
||||||
|
smallest = taps[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(taps[x] > largest)
|
||||||
|
{
|
||||||
|
largest = taps[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
series.getData().add(new XYChart.Data<Number,Number>(x, taps[x]));
|
||||||
|
}
|
||||||
|
|
||||||
|
mImpulseXAxis.setLowerBound(0.0);
|
||||||
|
mImpulseXAxis.setUpperBound(taps.length - 1);
|
||||||
|
mImpulseXAxis.setTickUnit(1.0);
|
||||||
|
|
||||||
|
double padding = Math.abs(largest - smallest) * .1;
|
||||||
|
|
||||||
|
mImpulseYAxis.setLowerBound(smallest - padding);
|
||||||
|
mImpulseYAxis.setUpperBound(largest + padding);
|
||||||
|
mImpulseYAxis.setTickUnit(padding);
|
||||||
|
|
||||||
ObservableList<Series<Number,Number>> list = mImpulseChart.getData();
|
ObservableList<Series<Number,Number>> list = mImpulseChart.getData();
|
||||||
list.add( series );
|
list.add(series);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDFTTaps( float[] taps )
|
public void setDFTTaps(float[] taps)
|
||||||
{
|
|
||||||
double smallest = 0.0;
|
|
||||||
|
|
||||||
final XYChart.Series<Number, Number> series = new XYChart.Series<>();
|
|
||||||
|
|
||||||
for( int x = 0; x < taps.length; x++ )
|
|
||||||
{
|
|
||||||
if( taps[ x ] < smallest )
|
|
||||||
{
|
|
||||||
smallest = taps[ x ];
|
|
||||||
}
|
|
||||||
|
|
||||||
series.getData().add( new XYChart.Data<Number, Number>( x, taps[ x ] ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
ObservableList<Series<Number,Number>> list = mDFTChart.getData();
|
|
||||||
list.add( series );
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] convertDFTToDecibel( float[] dft, int tapLength )
|
|
||||||
{
|
{
|
||||||
float[] decibels = new float[ dft.length / 2 ];
|
double smallest = 0.0;
|
||||||
|
|
||||||
int index = 0;
|
final XYChart.Series<Number,Number> series = new XYChart.Series<>();
|
||||||
|
|
||||||
for( int x = 0; x < decibels.length; x ++ )
|
|
||||||
{
|
|
||||||
index = x * 2;
|
|
||||||
|
|
||||||
decibels[ x ] = 20.0f * (float)Math.log10( ( ( dft[ index ] * dft[ index ] ) +
|
|
||||||
( dft[ index + 1 ] * dft[ index + 1 ] ) ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return decibels;
|
for(int x = 0; x < taps.length; x++)
|
||||||
|
{
|
||||||
|
if(taps[x] < smallest)
|
||||||
|
{
|
||||||
|
smallest = taps[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
series.getData().add(new XYChart.Data<Number,Number>(x, taps[x]));
|
||||||
|
}
|
||||||
|
|
||||||
|
ObservableList<Series<Number,Number>> list = mDFTChart.getData();
|
||||||
|
list.add(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] convertDFTToDecibel(float[] dft, int tapLength)
|
||||||
|
{
|
||||||
|
float[] decibels = new float[dft.length / 2];
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
for(int x = 0; x < decibels.length; x++)
|
||||||
|
{
|
||||||
|
index = x * 2;
|
||||||
|
|
||||||
|
decibels[x] = 20.0f * (float)Math.log10(((dft[index] * dft[index]) +
|
||||||
|
(dft[index + 1] * dft[index + 1])));
|
||||||
|
}
|
||||||
|
|
||||||
|
return decibels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,15 +39,24 @@ public class FilterViewer extends Application
|
|||||||
*/
|
*/
|
||||||
private float[] getFilter()
|
private float[] getFilter()
|
||||||
{
|
{
|
||||||
|
// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||||
|
// .sampleRate(48000)
|
||||||
|
// .gridDensity(16)
|
||||||
|
// .passBandCutoff(2500)
|
||||||
|
// .passBandAmplitude(1.0)
|
||||||
|
// .passBandRipple(0.01)
|
||||||
|
// .stopBandStart(4000)
|
||||||
|
// .stopBandAmplitude(0.0)
|
||||||
|
// .stopBandRipple(0.008)
|
||||||
|
// .build();
|
||||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||||
.sampleRate(48000)
|
.sampleRate(48000)
|
||||||
.gridDensity(16)
|
.passBandCutoff(5100)
|
||||||
.passBandCutoff(2500)
|
|
||||||
.passBandAmplitude(1.0)
|
.passBandAmplitude(1.0)
|
||||||
.passBandRipple(0.01)
|
.passBandRipple(0.01)
|
||||||
.stopBandStart(4000)
|
|
||||||
.stopBandAmplitude(0.0)
|
.stopBandAmplitude(0.0)
|
||||||
.stopBandRipple(0.008)
|
.stopBandStart(6500)
|
||||||
|
.stopBandRipple(0.01)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
||||||
@@ -72,6 +81,9 @@ public class FilterViewer extends Application
|
|||||||
|
|
||||||
float[] taps = null;
|
float[] taps = null;
|
||||||
|
|
||||||
|
// taps = FilterFactory.getSinc(6000, 1920, 47, Window.WindowType.BLACKMAN);
|
||||||
|
|
||||||
|
// taps = FilterFactory.getRootRaisedCosine(10, 10, 0.2f);
|
||||||
// taps = FilterFactory.getLowPass(48000, 6750, 7500, 60,
|
// taps = FilterFactory.getLowPass(48000, 6750, 7500, 60,
|
||||||
// Window.WindowType.HAMMING, true);
|
// Window.WindowType.HAMMING, true);
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,90 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.filter.fir.complex;
|
package io.github.dsheirer.dsp.filter.fir.complex;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
|
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
|
||||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
|
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
|
||||||
import io.github.dsheirer.sample.complex.Complex;
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
|
|
||||||
public class ComplexFIRFilter extends FIRFilter
|
public class ComplexFIRFilter extends FIRFilter
|
||||||
{
|
{
|
||||||
private RealFIRFilter mIFilter;
|
private RealFIRFilter mIFilter;
|
||||||
private RealFIRFilter mQFilter;
|
private RealFIRFilter mQFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complex FIR Filter for processing complex sample pairs. Wraps two real
|
* Complex FIR Filter for processing complex sample pairs. Wraps two real
|
||||||
* FIR filters for processing each of the inphase and quadrature samples.
|
* FIR filters for processing each of the inphase and quadrature samples.
|
||||||
*
|
*
|
||||||
* @param coefficients - filter taps
|
* @param coefficients - filter taps
|
||||||
* @param gain - gain to apply to filtered outputs - use 1.0f for no gain
|
* @param gain - gain to apply to filtered outputs - use 1.0f for no gain
|
||||||
*/
|
*/
|
||||||
public ComplexFIRFilter( float[] coefficients, float gain )
|
public ComplexFIRFilter(float[] coefficients, float gain)
|
||||||
{
|
{
|
||||||
mIFilter = new RealFIRFilter( coefficients, gain );
|
mIFilter = new RealFIRFilter(coefficients, gain);
|
||||||
mQFilter = new RealFIRFilter( coefficients, gain );
|
mQFilter = new RealFIRFilter(coefficients, gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] getCoefficients()
|
public float[] getCoefficients()
|
||||||
{
|
{
|
||||||
return mIFilter.getCoefficients();
|
return mIFilter.getCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
public float filterInphase( float sample )
|
public float filterInphase(float sample)
|
||||||
{
|
{
|
||||||
return mIFilter.filter( sample );
|
return mIFilter.filter(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float filterQuadrature( float sample )
|
public float filterQuadrature(float sample)
|
||||||
{
|
{
|
||||||
return mQFilter.filter( sample );
|
return mQFilter.filter(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Complex filter( Complex sample )
|
public Complex filter(Complex sample)
|
||||||
{
|
{
|
||||||
float i = filterInphase( sample.inphase() );
|
float i = filterInphase(sample.inphase());
|
||||||
float q = filterQuadrature( sample.quadrature() );
|
float q = filterQuadrature(sample.quadrature());
|
||||||
|
|
||||||
return new Complex( i, q );
|
return new Complex(i, q);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void dispose()
|
* Filters the complex samples from the buffer and returns a new complex buffer with the filtered output
|
||||||
{
|
*
|
||||||
mIFilter.dispose();
|
* @param buffer with complex samples to filter
|
||||||
mQFilter.dispose();
|
* @return new buffer containing filtered complex samples
|
||||||
}
|
*/
|
||||||
|
public ComplexBuffer filter(ComplexBuffer buffer)
|
||||||
|
{
|
||||||
|
float[] samples = buffer.getSamples();
|
||||||
|
float[] filtered = new float[samples.length];
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
|
{
|
||||||
|
filtered[x] = filterInphase(samples[x]);
|
||||||
|
filtered[x + 1] = filterQuadrature(samples[x + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ComplexBuffer(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
mIFilter.dispose();
|
||||||
|
mQFilter.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ package io.github.dsheirer.dsp.filter.fir.complex;
|
|||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
|
|
||||||
public class ComplexFIRFilter_CB_CB extends ComplexFIRFilter
|
public class ComplexFIRFilter_CB_CB extends ComplexFIRFilter implements Listener<ComplexBuffer>
|
||||||
implements Listener<ComplexBuffer>
|
|
||||||
{
|
{
|
||||||
private Listener<ComplexBuffer> mListener;
|
private Listener<ComplexBuffer> mListener;
|
||||||
|
|
||||||
|
|||||||
@@ -5,54 +5,86 @@ import org.apache.commons.lang3.Validate;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class RealInterpolator extends Interpolator
|
public class RealInterpolator extends Interpolator
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( RealInterpolator.class );
|
private final static Logger mLog = LoggerFactory.getLogger(RealInterpolator.class);
|
||||||
|
|
||||||
private float mGain;
|
private float mGain;
|
||||||
|
|
||||||
public RealInterpolator( float gain )
|
|
||||||
{
|
|
||||||
mGain = gain;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Calculates an interpolated value from the 8 samples beginning at the
|
|
||||||
* offset index. Uses the mu value to determine which of 128 filter kernels
|
|
||||||
* to use in order to closely approximate the ideal frequency response for
|
|
||||||
* the value of mu, ranging from 0.0 to 1.0, indicating the interpolated
|
|
||||||
* position in the sample set ranging from offset to offset + 7.
|
|
||||||
*
|
|
||||||
* @param samples - sample array of length at least offset + 7
|
|
||||||
*
|
|
||||||
* @param mu - interpolated sample position between 0 and 1.0
|
|
||||||
*
|
|
||||||
* @return - interpolated sample value
|
|
||||||
*/
|
|
||||||
public float filter( float[] samples, int offset, float mu )
|
|
||||||
{
|
|
||||||
/* Ensure we have enough samples in the array */
|
|
||||||
Validate.isTrue(samples.length >= offset + 7);
|
|
||||||
|
|
||||||
/* Identify the filter bank that corresponds to mu */
|
/**
|
||||||
int index = (int)( NSTEPS * mu );
|
* Provides an interpolated sample point along an eight-sample waveform representation using 128 filters to
|
||||||
|
* provide approximate interpolation with a resolution of 1/128th of a sample.
|
||||||
float accumulator = ( TAPS[ index ][ 7 ] * samples[ offset ] );
|
*
|
||||||
accumulator += ( TAPS[ index ][ 6 ] * samples[ offset + 1 ] );
|
* @param gain to apply to the interpolated sample
|
||||||
accumulator += ( TAPS[ index ][ 5 ] * samples[ offset + 2 ] );
|
*/
|
||||||
accumulator += ( TAPS[ index ][ 4 ] * samples[ offset + 3 ] );
|
public RealInterpolator(float gain)
|
||||||
accumulator += ( TAPS[ index ][ 3 ] * samples[ offset + 4 ] );
|
{
|
||||||
accumulator += ( TAPS[ index ][ 2 ] * samples[ offset + 5 ] );
|
mGain = gain;
|
||||||
accumulator += ( TAPS[ index ][ 1 ] * samples[ offset + 6 ] );
|
}
|
||||||
accumulator += ( TAPS[ index ][ 0 ] * samples[ offset + 7 ] );
|
|
||||||
|
/**
|
||||||
return accumulator * mGain;
|
* Calculates an interpolated value from eight samples that start at the offset into the sample array. The
|
||||||
}
|
* interpolated sample will fall within the middle of the eight sample array, between indexes offset+3 and
|
||||||
|
* offset+4. The mu argument is translated into an index position between 0 and 128, where a mu of 0.0 will be
|
||||||
public Complex filter( float[] iSamples, float[] qSamples, int offset, float mu )
|
* converted to index zero and will be equal to the sample at index offset+3 and a mu of 1.0 will be equal to
|
||||||
{
|
* the sample at offset+4. All mu values between 0.0 and 1.0 will be converted to a 1 - 127 index and will
|
||||||
float i = filter( iSamples, offset, mu );
|
* produce an approximated value from among 127 interpolated sample points between indexes offset+3 and offset+4.
|
||||||
float q = filter( qSamples, offset, mu );
|
*
|
||||||
|
* @param samples - sample array of length at least offset + 7
|
||||||
return new Complex( i, q );
|
* @param mu - interpolated sample position between 0.0 and 1.0
|
||||||
}
|
* @return - interpolated sample value
|
||||||
|
*/
|
||||||
|
public float filter(float[] samples, int offset, float mu)
|
||||||
|
{
|
||||||
|
/* Ensure we have enough samples in the array */
|
||||||
|
Validate.isTrue(samples.length >= offset + 7);
|
||||||
|
|
||||||
|
/* Identify the filter bank that corresponds to mu */
|
||||||
|
int index = (int)(NSTEPS * mu);
|
||||||
|
|
||||||
|
float accumulator = (TAPS[index][7] * samples[offset]);
|
||||||
|
accumulator += (TAPS[index][6] * samples[offset + 1]);
|
||||||
|
accumulator += (TAPS[index][5] * samples[offset + 2]);
|
||||||
|
accumulator += (TAPS[index][4] * samples[offset + 3]);
|
||||||
|
accumulator += (TAPS[index][3] * samples[offset + 4]);
|
||||||
|
accumulator += (TAPS[index][2] * samples[offset + 5]);
|
||||||
|
accumulator += (TAPS[index][1] * samples[offset + 6]);
|
||||||
|
accumulator += (TAPS[index][0] * samples[offset + 7]);
|
||||||
|
|
||||||
|
return accumulator * mGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Complex filter(float[] iSamples, float[] qSamples, int offset, float mu)
|
||||||
|
{
|
||||||
|
float i = filter(iSamples, offset, mu);
|
||||||
|
float q = filter(qSamples, offset, mu);
|
||||||
|
|
||||||
|
return new Complex(i, q);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
RealInterpolator interpolator = new RealInterpolator(1.0f);
|
||||||
|
DecimalFormat decimalFormat = new DecimalFormat("0.0000");
|
||||||
|
|
||||||
|
double TWO_PI = Math.PI * 2.0;
|
||||||
|
|
||||||
|
float[] samples = new float[16];
|
||||||
|
|
||||||
|
for(int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
samples[x] = (float)Math.sin(TWO_PI * (double)x / 8.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Samples: " + Arrays.toString(samples));
|
||||||
|
|
||||||
|
for(float x = 0.0f; x <= 1.01f; x += 0.1f)
|
||||||
|
{
|
||||||
|
mLog.debug(decimalFormat.format(x) + ": " + decimalFormat.format(interpolator.filter(samples, 1, x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,103 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.dsp.fm;
|
package io.github.dsheirer.dsp.fm;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
public abstract class FMDemodulator
|
public abstract class FMDemodulator
|
||||||
{
|
{
|
||||||
private float mPreviousI = 0.0f;
|
private float mPreviousI = 0.0f;
|
||||||
private float mPreviousQ = 0.0f;
|
private float mPreviousQ = 0.0f;
|
||||||
protected float mGain;
|
protected float mGain;
|
||||||
|
|
||||||
public FMDemodulator( float gain )
|
public FMDemodulator(float gain)
|
||||||
{
|
{
|
||||||
mGain = gain;
|
mGain = gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float demodulate( float currentI, float currentQ )
|
public float demodulate(float currentI, float currentQ)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Multiply the current sample against the complex conjugate of the
|
* Multiply the current sample against the complex conjugate of the
|
||||||
* previous sample to derive the phase delta between the two samples
|
* previous sample to derive the phase delta between the two samples
|
||||||
*
|
*
|
||||||
* Negating the previous sample quadrature produces the conjugate
|
* Negating the previous sample quadrature produces the conjugate
|
||||||
*/
|
*/
|
||||||
double inphase = ( currentI * mPreviousI ) - ( currentQ * -mPreviousQ );
|
double inphase = (currentI * mPreviousI) - (currentQ * -mPreviousQ);
|
||||||
double quadrature = ( currentQ * mPreviousI ) + ( currentI * -mPreviousQ );
|
double quadrature = (currentQ * mPreviousI) + (currentI * -mPreviousQ);
|
||||||
|
|
||||||
double angle = 0.0f;
|
|
||||||
|
|
||||||
//Check for divide by zero
|
|
||||||
if( inphase != 0 )
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Use the arc-tangent of quadrature divided by inphase to
|
|
||||||
* get the phase angle (+/-) which was directly manipulated by the
|
|
||||||
* original message waveform during the modulation. This value now
|
|
||||||
* serves as the instantaneous amplitude of the demodulated signal
|
|
||||||
*/
|
|
||||||
double denominator = 1.0d / inphase;
|
|
||||||
angle = Math.atan( (double)quadrature * denominator );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the current sample to use during the next iteration
|
|
||||||
*/
|
|
||||||
mPreviousI = currentI;
|
|
||||||
mPreviousQ = currentQ;
|
|
||||||
|
|
||||||
return (float)( angle * mGain );
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void dispose();
|
double angle = 0.0f;
|
||||||
|
|
||||||
public void reset()
|
//Check for divide by zero
|
||||||
{
|
if(inphase != 0)
|
||||||
mPreviousI = 0.0f;
|
{
|
||||||
mPreviousQ = 0.0f;
|
/**
|
||||||
}
|
* Use the arc-tangent of quadrature divided by inphase to
|
||||||
|
* get the phase angle (+/-) which was directly manipulated by the
|
||||||
|
* original message waveform during the modulation. This value now
|
||||||
|
* serves as the instantaneous amplitude of the demodulated signal
|
||||||
|
*/
|
||||||
|
double denominator = 1.0d / inphase;
|
||||||
|
angle = Math.atan((double)quadrature * denominator);
|
||||||
|
}
|
||||||
|
|
||||||
public void setGain( float gain )
|
/**
|
||||||
{
|
* Store the current sample to use during the next iteration
|
||||||
mGain = gain;
|
*/
|
||||||
}
|
mPreviousI = currentI;
|
||||||
|
mPreviousQ = currentQ;
|
||||||
|
|
||||||
|
return (float)(angle * mGain);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double demodulate(Complex previous, Complex current)
|
||||||
|
{
|
||||||
|
double inphase = (current.inphase() * previous.inphase()) - (current.quadrature() * -previous.quadrature());
|
||||||
|
double quadrature = (current.quadrature() * previous.inphase()) + (current.inphase() * -previous.quadrature());
|
||||||
|
|
||||||
|
double angle = 0.0f;
|
||||||
|
|
||||||
|
//Check for divide by zero
|
||||||
|
if(inphase != 0)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Use the arc-tangent of quadrature divided by inphase to
|
||||||
|
* get the phase angle (+/-) which was directly manipulated by the
|
||||||
|
* original message waveform during the modulation. This value now
|
||||||
|
* serves as the instantaneous amplitude of the demodulated signal
|
||||||
|
*/
|
||||||
|
double denominator = 1.0d / inphase;
|
||||||
|
angle = Math.atan((double)quadrature * denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void dispose();
|
||||||
|
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
mPreviousI = 0.0f;
|
||||||
|
mPreviousQ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGain(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
public class FMDemodulator_CB extends FMDemodulator implements Listener<ComplexBuffer>
|
public class FMDemodulator_CB extends FMDemodulator implements Listener<ComplexBuffer>
|
||||||
{
|
{
|
||||||
private final static Logger mLog =
|
private final static Logger mLog = LoggerFactory.getLogger( FMDemodulator_CB.class );
|
||||||
LoggerFactory.getLogger( FMDemodulator_CB.class );
|
|
||||||
|
|
||||||
private Listener<RealBuffer> mListener;
|
private Listener<RealBuffer> mListener;
|
||||||
|
|
||||||
|
|||||||
@@ -1,112 +1,125 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.gain;
|
package io.github.dsheirer.dsp.gain;
|
||||||
|
|
||||||
import io.github.dsheirer.buffer.FloatCircularBuffer;
|
import io.github.dsheirer.buffer.FloatCircularBuffer;
|
||||||
import io.github.dsheirer.sample.complex.Complex;
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2015 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/>
|
|
||||||
*
|
|
||||||
* This class is modeled using concepts detailed in the GNURadio
|
|
||||||
* implementation of feed forward AGC class located at:
|
|
||||||
* https://github.com/gnuradio/gnuradio/blob/master/gr-analog/lib/
|
|
||||||
* feedforward_agc_cc_impl.cc
|
|
||||||
*
|
|
||||||
******************************************************************************/
|
|
||||||
public class ComplexFeedForwardGainControl implements ComplexSampleListener
|
public class ComplexFeedForwardGainControl implements ComplexSampleListener
|
||||||
{
|
{
|
||||||
public static final float OBJECTIVE_ENVELOPE = 1.0f;
|
public static final float OBJECTIVE_ENVELOPE = 1.0f;
|
||||||
public static final float MINIMUM_ENVELOPE = 0.0001f;
|
public static final float MINIMUM_ENVELOPE = 0.0001f;
|
||||||
|
|
||||||
private ComplexSampleListener mListener;
|
private ComplexSampleListener mListener;
|
||||||
|
|
||||||
private FloatCircularBuffer mEnvelopeHistory;
|
private FloatCircularBuffer mEnvelopeHistory;
|
||||||
|
|
||||||
private float mMaxEnvelope = 0.0f;
|
|
||||||
private float mGain = 1.0f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamic gain control for incoming sample stream to amplify or attenuate
|
|
||||||
* all samples toward an objective unity)gain, using the maximum envelope
|
|
||||||
* value detected in the stream history window.
|
|
||||||
*
|
|
||||||
* Uses the specified damping factor to limit gain swings. Damping factor
|
|
||||||
* is applied against the delta between current gain value and a recalculated
|
|
||||||
* gain value to limit how quickly the gain value will increase or decrease.
|
|
||||||
*
|
|
||||||
* @param window - history size to use in detecting maximum envelope value
|
|
||||||
* @param damping - a value between 0 < damping <= 1.0;
|
|
||||||
*/
|
|
||||||
public ComplexFeedForwardGainControl( int window )
|
|
||||||
{
|
|
||||||
mEnvelopeHistory = new FloatCircularBuffer( window );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
mListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private float mMaxEnvelope = 0.0f;
|
||||||
public void receive( float inphase, float quadrature )
|
private float mGain = 1.0f;
|
||||||
{
|
|
||||||
float envelope = Complex.envelope( inphase, quadrature );
|
|
||||||
|
|
||||||
if( envelope > mMaxEnvelope )
|
/**
|
||||||
{
|
* Dynamic gain control for incoming sample stream to amplify or attenuate
|
||||||
mMaxEnvelope = envelope;
|
* all samples toward an objective unity)gain, using the maximum envelope
|
||||||
|
* value detected in the stream history window.
|
||||||
adjustGain();
|
*
|
||||||
}
|
* Uses the specified damping factor to limit gain swings. Damping factor
|
||||||
|
* is applied against the delta between current gain value and a recalculated
|
||||||
|
* gain value to limit how quickly the gain value will increase or decrease.
|
||||||
|
*
|
||||||
|
* @param window - history size to use in detecting maximum envelope value
|
||||||
|
*/
|
||||||
|
public ComplexFeedForwardGainControl(int window)
|
||||||
|
{
|
||||||
|
mEnvelopeHistory = new FloatCircularBuffer(window);
|
||||||
|
}
|
||||||
|
|
||||||
/* Replace oldest envelope value with current envelope value */
|
public void dispose()
|
||||||
float oldestEnvelope = mEnvelopeHistory.get( envelope );
|
{
|
||||||
|
mListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
/* If the oldest envelope value was the max envelope value, then we
|
@Override
|
||||||
* have to rediscover the max value from the envelope history */
|
public void receive(float inphase, float quadrature)
|
||||||
if( mMaxEnvelope == oldestEnvelope && mMaxEnvelope != envelope )
|
{
|
||||||
{
|
process(inphase, quadrature);
|
||||||
mMaxEnvelope = MINIMUM_ENVELOPE;
|
|
||||||
|
|
||||||
for( float value: mEnvelopeHistory.getBuffer() )
|
|
||||||
{
|
|
||||||
if( value > mMaxEnvelope )
|
|
||||||
{
|
|
||||||
mMaxEnvelope = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
adjustGain();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Apply current gain value to the sample and send to the listener */
|
|
||||||
if( mListener != null )
|
|
||||||
{
|
|
||||||
mListener.receive( inphase *= mGain, quadrature *= mGain );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void adjustGain()
|
|
||||||
{
|
|
||||||
mGain = OBJECTIVE_ENVELOPE / mMaxEnvelope;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setListener( ComplexSampleListener listener )
|
/* Apply current gain value to the sample and send to the listener */
|
||||||
{
|
if(mListener != null)
|
||||||
mListener = listener;
|
{
|
||||||
}
|
mListener.receive(inphase *= mGain, quadrature *= mGain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
float envelope = Complex.envelope(inphase, quadrature);
|
||||||
|
|
||||||
|
if(envelope > mMaxEnvelope)
|
||||||
|
{
|
||||||
|
mMaxEnvelope = envelope;
|
||||||
|
|
||||||
|
adjustGain();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Replace oldest envelope value with current envelope value */
|
||||||
|
float oldestEnvelope = mEnvelopeHistory.get(envelope);
|
||||||
|
|
||||||
|
/* If the oldest envelope value was the max envelope value, then we
|
||||||
|
* have to rediscover the max value from the envelope history */
|
||||||
|
if(mMaxEnvelope == oldestEnvelope && mMaxEnvelope != envelope)
|
||||||
|
{
|
||||||
|
mMaxEnvelope = MINIMUM_ENVELOPE;
|
||||||
|
|
||||||
|
for(float value : mEnvelopeHistory.getBuffer())
|
||||||
|
{
|
||||||
|
if(value > mMaxEnvelope)
|
||||||
|
{
|
||||||
|
mMaxEnvelope = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustGain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustGain()
|
||||||
|
{
|
||||||
|
mGain = OBJECTIVE_ENVELOPE / mMaxEnvelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(ComplexSampleListener listener)
|
||||||
|
{
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComplexBuffer filter(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
float[] samples = complexBuffer.getSamples();
|
||||||
|
float[] processed = new float[samples.length];
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
|
{
|
||||||
|
process(samples[x], samples[x + 1]);
|
||||||
|
|
||||||
|
processed[x] = samples[x] * mGain;
|
||||||
|
processed[x + 1] = samples[x + 1] * mGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ComplexBuffer(processed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,27 +5,34 @@ import io.github.dsheirer.sample.complex.Complex;
|
|||||||
|
|
||||||
public class ComplexGain implements Listener<Complex>
|
public class ComplexGain implements Listener<Complex>
|
||||||
{
|
{
|
||||||
private float mGain;
|
private float mGain;
|
||||||
private Listener<Complex> mListener;
|
private Listener<Complex> mListener;
|
||||||
|
|
||||||
public ComplexGain( float gain )
|
public ComplexGain(float gain)
|
||||||
{
|
{
|
||||||
mGain = gain;
|
mGain = gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive( Complex sample )
|
|
||||||
{
|
|
||||||
if( mListener != null )
|
|
||||||
{
|
|
||||||
sample.multiply( mGain );
|
|
||||||
|
|
||||||
mListener.receive( sample );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setListener( Listener<Complex> listener )
|
public Complex apply(Complex sample)
|
||||||
{
|
{
|
||||||
mListener = listener;
|
sample.multiply(mGain);
|
||||||
}
|
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(Complex sample)
|
||||||
|
{
|
||||||
|
if(mListener != null)
|
||||||
|
{
|
||||||
|
apply(sample);
|
||||||
|
|
||||||
|
mListener.receive(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(Listener<Complex> listener)
|
||||||
|
{
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ public class Oscillator
|
|||||||
*/
|
*/
|
||||||
private void update()
|
private void update()
|
||||||
{
|
{
|
||||||
float anglePerSample =
|
float anglePerSample = (float)( 2.0d * Math.PI * mFrequency / mSampleRate );
|
||||||
(float)( 2.0d * Math.PI * mFrequency / mSampleRate );
|
|
||||||
|
|
||||||
mAnglePerSample = Complex.fromAngle( anglePerSample );
|
mAnglePerSample = Complex.fromAngle( anglePerSample );
|
||||||
}
|
}
|
||||||
|
|||||||
162
src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java
Normal file
162
src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
||||||
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class DQPSKDemodulator implements ComplexSampleListener
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(DQPSKDemodulator.class);
|
||||||
|
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||||
|
protected IPhaseLockedLoop mPLL;
|
||||||
|
protected DQPSKSymbolEvaluator mSymbolEvaluator = new DQPSKSymbolEvaluator();
|
||||||
|
private Complex mReceivedSample = new Complex(0, 0);
|
||||||
|
private Complex mPreviousPrecedingSample = new Complex(0, 0);
|
||||||
|
private Complex mPreviousCurrentSample = new Complex(0, 0);
|
||||||
|
private Complex mPrecedingSample = new Complex(0, 0);
|
||||||
|
private Complex mCurrentSample = new Complex(0, 0);
|
||||||
|
private Complex mPrecedingSymbol = new Complex(0, 0);
|
||||||
|
protected Complex mCurrentSymbol = new Complex(0, 0);
|
||||||
|
private Listener<Dibit> mDibitListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder for Differential Quaternary Phase Shift Keying (DQPSK). This decoder uses both a Costas Loop (PLL) and
|
||||||
|
* a custom DQPSK symbol timing error detector to automatically align to the incoming carrier frequency and to
|
||||||
|
* adjust for any changes in symbol timing.
|
||||||
|
*
|
||||||
|
* This detector is optimized for constant amplitude DQPSK symbols like C4FM.
|
||||||
|
*
|
||||||
|
* @param phaseLockedLoop for tracking carrier frequency error
|
||||||
|
* @param interpolatingSampleBuffer to hold samples for interpolating a symbol
|
||||||
|
*/
|
||||||
|
public DQPSKDemodulator(IPhaseLockedLoop phaseLockedLoop, InterpolatingSampleBuffer interpolatingSampleBuffer)
|
||||||
|
{
|
||||||
|
mPLL = phaseLockedLoop;
|
||||||
|
mInterpolatingSampleBuffer = interpolatingSampleBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a (filtered) buffer containing complex samples for decoding
|
||||||
|
* @param complexBuffer with complex samples
|
||||||
|
*/
|
||||||
|
public void receive(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
float[] samples = complexBuffer.getSamples();
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
|
{
|
||||||
|
receive(samples[x], samples[x + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a complex sample for decoding. Once sufficient samples are buffered, a symbol decision is made.
|
||||||
|
* @param inphase value for the sample
|
||||||
|
* @param quadrature value for the sample
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
//Update current sample with values
|
||||||
|
mReceivedSample.setValues(inphase, quadrature);
|
||||||
|
|
||||||
|
//Mix current sample with costas loop to remove any rotation that is present from a mis-tuned carrier frequency
|
||||||
|
mReceivedSample.multiply(mPLL.incrementAndGetCurrentVector());
|
||||||
|
|
||||||
|
//Store the sample in the interpolating buffer
|
||||||
|
mInterpolatingSampleBuffer.receive(mReceivedSample);
|
||||||
|
|
||||||
|
//Calculate the symbol once we've stored enough samples
|
||||||
|
if(mInterpolatingSampleBuffer.hasSymbol())
|
||||||
|
{
|
||||||
|
calculateSymbol();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a symbol from the interpolating buffer
|
||||||
|
*/
|
||||||
|
protected void calculateSymbol()
|
||||||
|
{
|
||||||
|
//Get preceding sample and an interpolated current sample from the interpolating buffer
|
||||||
|
mPrecedingSample = mInterpolatingSampleBuffer.getPrecedingSample();
|
||||||
|
mCurrentSample = mInterpolatingSampleBuffer.getCurrentSample();
|
||||||
|
|
||||||
|
//Calculate preceding, current and following symbols as the delta rotation compared to the previous samples
|
||||||
|
mPrecedingSymbol.setInphase(Complex.multiplyInphase(mPrecedingSample.inphase(), mPrecedingSample.quadrature(),
|
||||||
|
mPreviousPrecedingSample.inphase(), -mPreviousPrecedingSample.quadrature()));
|
||||||
|
mPrecedingSymbol.setQuadrature(Complex.multiplyQuadrature(mPrecedingSample.inphase(), mPrecedingSample.quadrature(),
|
||||||
|
mPreviousPrecedingSample.inphase(), -mPreviousPrecedingSample.quadrature()));
|
||||||
|
|
||||||
|
mCurrentSymbol.setInphase(Complex.multiplyInphase(mCurrentSample.inphase(), mCurrentSample.quadrature(),
|
||||||
|
mPreviousCurrentSample.inphase(), -mPreviousCurrentSample.quadrature()));
|
||||||
|
mCurrentSymbol.setQuadrature(Complex.multiplyQuadrature(mCurrentSample.inphase(), mCurrentSample.quadrature(),
|
||||||
|
mPreviousCurrentSample.inphase(), -mPreviousCurrentSample.quadrature()));
|
||||||
|
|
||||||
|
//Set gain to unity before we calculate the error value
|
||||||
|
mPrecedingSymbol.normalize();
|
||||||
|
mCurrentSymbol.normalize();
|
||||||
|
|
||||||
|
mSymbolEvaluator.setSymbol(mPrecedingSymbol, mCurrentSymbol);
|
||||||
|
|
||||||
|
mInterpolatingSampleBuffer.resetAndAdjust(mSymbolEvaluator.getTimingError());
|
||||||
|
|
||||||
|
//Store current samples/symbols to use for the next period
|
||||||
|
mPreviousPrecedingSample.setValues(mPrecedingSample);
|
||||||
|
mPreviousCurrentSample.setValues(mCurrentSample);
|
||||||
|
|
||||||
|
//Calculate the phase error of the current symbol relative to the expected constellation and provide
|
||||||
|
//feedback to the PLL
|
||||||
|
mPLL.adjust(clip(mSymbolEvaluator.getPhaseError(), 0.5f));
|
||||||
|
|
||||||
|
if(mDibitListener != null)
|
||||||
|
{
|
||||||
|
mDibitListener.receive(mSymbolEvaluator.getSymbolDecision());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive dibit symbol decisions from this demodulator
|
||||||
|
*/
|
||||||
|
public void setDibitListener(Listener<Dibit> listener)
|
||||||
|
{
|
||||||
|
mDibitListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constrains value to the range of ( -maximum <> maximum )
|
||||||
|
*/
|
||||||
|
public static float clip(float value, float maximum)
|
||||||
|
{
|
||||||
|
if(value > maximum)
|
||||||
|
{
|
||||||
|
return maximum;
|
||||||
|
}
|
||||||
|
else if(value < -maximum)
|
||||||
|
{
|
||||||
|
return -maximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||||
|
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public class DQPSKDemodulatorInstrumented extends DQPSKDemodulator
|
||||||
|
{
|
||||||
|
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
|
||||||
|
private Listener<Double> mSamplesPerSymbolListener;
|
||||||
|
private Listener<Complex> mSymbolListener;
|
||||||
|
private Listener<Double> mPLLErrorListener;
|
||||||
|
private Listener<Double> mPLLFrequencyListener;
|
||||||
|
private double mSampleRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder for Differential Quaternary Phase Shift Keying (DQPSK). This decoder uses both a Costas Loop (PLL) and
|
||||||
|
* a custom DQPSK symbol timing error detector to automatically align to the incoming carrier frequency and to
|
||||||
|
* adjust for any changes in symbol timing.
|
||||||
|
*
|
||||||
|
* This detector is optimized for constant amplitude DQPSK symbols like C4FM.
|
||||||
|
*
|
||||||
|
* @param phaseLockedLoop for tracking carrier frequency error
|
||||||
|
* @param interpolatingSampleBuffer
|
||||||
|
* @param sampleRate of the incoming complex sample stream
|
||||||
|
*/
|
||||||
|
public DQPSKDemodulatorInstrumented(IPhaseLockedLoop phaseLockedLoop,
|
||||||
|
InterpolatingSampleBufferInstrumented interpolatingSampleBuffer,
|
||||||
|
double sampleRate)
|
||||||
|
{
|
||||||
|
super(phaseLockedLoop, interpolatingSampleBuffer);
|
||||||
|
mSampleRate = sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the parent class symbol calculation to capture eye diagram data
|
||||||
|
*/
|
||||||
|
protected void calculateSymbol()
|
||||||
|
{
|
||||||
|
//Eye diagram listener
|
||||||
|
if(mSymbolDecisionDataListener != null)
|
||||||
|
{
|
||||||
|
mSymbolDecisionDataListener.receive(
|
||||||
|
((InterpolatingSampleBufferInstrumented)mInterpolatingSampleBuffer).getSymbolDecisionData());
|
||||||
|
}
|
||||||
|
|
||||||
|
super.calculateSymbol();
|
||||||
|
|
||||||
|
if(mSamplesPerSymbolListener != null)
|
||||||
|
{
|
||||||
|
mSamplesPerSymbolListener.receive((double)mInterpolatingSampleBuffer.getSamplingPoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send to an external constellation symbol listener when registered
|
||||||
|
if(mSymbolListener != null)
|
||||||
|
{
|
||||||
|
mSymbolListener.receive(mCurrentSymbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mPLLErrorListener != null)
|
||||||
|
{
|
||||||
|
mPLLErrorListener.receive((double)mSymbolEvaluator.getPhaseError());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mPLLFrequencyListener != null)
|
||||||
|
{
|
||||||
|
double loopFrequency = ((CostasLoop)mPLL).getLoopFrequency();
|
||||||
|
|
||||||
|
loopFrequency *= mSampleRate / (2.0 * Math.PI);
|
||||||
|
|
||||||
|
mPLLFrequencyListener.receive(loopFrequency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener to receive symbol decision data to produce an eye diagram.
|
||||||
|
*/
|
||||||
|
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
|
||||||
|
{
|
||||||
|
mSymbolDecisionDataListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive PLL error values
|
||||||
|
*/
|
||||||
|
public void setSamplesPerSymbolListener(Listener<Double> listener)
|
||||||
|
{
|
||||||
|
mSamplesPerSymbolListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive decoded QPSK symbols
|
||||||
|
*/
|
||||||
|
public void setSymbolListener(Listener<Complex> listener)
|
||||||
|
{
|
||||||
|
mSymbolListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive PLL error values
|
||||||
|
*/
|
||||||
|
public void setPLLErrorListener(Listener<Double> listener)
|
||||||
|
{
|
||||||
|
mPLLErrorListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive PLL error values
|
||||||
|
*/
|
||||||
|
public void setPLLFrequencyListener(Listener<Double> listener)
|
||||||
|
{
|
||||||
|
mPLLFrequencyListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public class DQPSKSymbolEvaluator implements IDQPSKSymbolEvaluator
|
||||||
|
{
|
||||||
|
private static final Complex ROTATE_FROM_PLUS_135 = Complex.fromAngle(-3.0 * Math.PI / 4.0);
|
||||||
|
private static final Complex ROTATE_FROM_PLUS_45 = Complex.fromAngle(-1.0 * Math.PI / 4.0);
|
||||||
|
private static final Complex ROTATE_FROM_MINUS_45 = Complex.fromAngle(1.0 * Math.PI / 4.0);
|
||||||
|
private static final Complex ROTATE_FROM_MINUS_135 = Complex.fromAngle(3.0 * Math.PI / 4.0);
|
||||||
|
|
||||||
|
private Complex mSymbolError = new Complex(0, 0);
|
||||||
|
private float mPhaseError = 0.0f;
|
||||||
|
private float mTimingError = 0.0f;
|
||||||
|
private float mTimingErrorPolarity = 1.0f;
|
||||||
|
private Dibit mSymbolDecision = Dibit.D00_PLUS_1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decision-directed symbol phase and timing error detector and symbol decision slicer.
|
||||||
|
*
|
||||||
|
* Symbol decision is based on the closest reference quadrant for the sampled symbol.
|
||||||
|
*
|
||||||
|
* Phase and timing error are calculated as the angular distance of the sampled symbol from the reference
|
||||||
|
* symbol. Timing error is further corrected to compensate for clock-wise or counter-clock-wise vector rotation
|
||||||
|
* relative to the immediately preceding symbol.
|
||||||
|
*/
|
||||||
|
public DQPSKSymbolEvaluator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the preceding and current symbols to be evaluated for phase and timing errors and to determine the
|
||||||
|
* transmitted symbol relative to the closest reference symbol. After invoking this method, you can access the
|
||||||
|
* phase and timing errors and the symbol decision via their respective accessor methods.
|
||||||
|
*
|
||||||
|
* Phase and timing error values are calculated by first determining the symbol and then calculating the phase
|
||||||
|
* and timing errors relative to the reference symbol. The timing error is corrected with the appropriate sign
|
||||||
|
* relative to the angular vector rotation so that the error value indicates the correct error direction.
|
||||||
|
*
|
||||||
|
* @param preceding differential-decoded sample/symbol used to detect rotation - does NOT need to be interpolated
|
||||||
|
* since it's only a reference
|
||||||
|
* @param current symbol to be evaluated - interpolated
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setSymbol(Complex preceding, Complex current)
|
||||||
|
{
|
||||||
|
mSymbolError.setValues(current);
|
||||||
|
|
||||||
|
if(mSymbolError.quadrature() > 0.0f)
|
||||||
|
{
|
||||||
|
if(mSymbolError.inphase() > 0.0f)
|
||||||
|
{
|
||||||
|
mSymbolDecision = Dibit.D00_PLUS_1;
|
||||||
|
mTimingErrorPolarity = (preceding.quadrature() > current.quadrature() ? 1.0f : -1.0f);
|
||||||
|
mSymbolError.multiply(ROTATE_FROM_PLUS_45);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSymbolDecision = Dibit.D01_PLUS_3;
|
||||||
|
mTimingErrorPolarity = (preceding.quadrature() < current.quadrature() ? 1.0f : -1.0f);
|
||||||
|
mSymbolError.multiply(ROTATE_FROM_PLUS_135);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(mSymbolError.inphase() > 0.0f)
|
||||||
|
{
|
||||||
|
mSymbolDecision = Dibit.D10_MINUS_1;
|
||||||
|
mTimingErrorPolarity = (preceding.quadrature() > current.quadrature() ? 1.0f : -1.0f);
|
||||||
|
mSymbolError.multiply(ROTATE_FROM_MINUS_45);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSymbolDecision = Dibit.D11_MINUS_3;
|
||||||
|
mTimingErrorPolarity = (preceding.quadrature() < current.quadrature() ? 1.0f : -1.0f);
|
||||||
|
mSymbolError.multiply(ROTATE_FROM_MINUS_135);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the
|
||||||
|
//arctan of the error angle relative to 0 radians and this provides our error value
|
||||||
|
mPhaseError = -mSymbolError.quadrature();
|
||||||
|
|
||||||
|
//Timing error is the same as phase error with the sign corrected according to the vector's angular rotation
|
||||||
|
mTimingError = mSymbolError.quadrature() * mTimingErrorPolarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase error of the symbol relative to the nearest reference symbol.
|
||||||
|
*
|
||||||
|
* @return phase error in radians of distance from the reference symbol.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public float getPhaseError()
|
||||||
|
{
|
||||||
|
return mPhaseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timing error of the symbol relative to the nearest reference symbol.
|
||||||
|
*
|
||||||
|
* @return timing error in radians of angular distance from the reference symbol recognizing that the symbol
|
||||||
|
* originates at zero radians and rotates toward the intended reference symbol, therefore the error value indicates
|
||||||
|
* if the symbol was sampled early (-) or late (+) relative to the reference symbol.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public float getTimingError()
|
||||||
|
{
|
||||||
|
return mTimingError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference symbol that is closest to the transmitted/sampled symbol.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Dibit getSymbolDecision()
|
||||||
|
{
|
||||||
|
return mSymbolDecision;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public class GardnerDetector
|
||||||
|
{
|
||||||
|
private float mErrorInphase;
|
||||||
|
private float mErrorQuadrature;
|
||||||
|
private float mError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gardner detector for recovering symbol timing error.
|
||||||
|
*/
|
||||||
|
public GardnerDetector()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the symbol timing error by evaluating the previous, middle and current symbol instants.
|
||||||
|
*
|
||||||
|
* @param previous symbol
|
||||||
|
* @param middle symbol (ie midway between the previous and current)
|
||||||
|
* @param current symbol
|
||||||
|
* @return symbol timing error
|
||||||
|
*/
|
||||||
|
public float getError(Complex previous, Complex middle, Complex current)
|
||||||
|
{
|
||||||
|
mErrorInphase = (Math.abs(previous.inphase()) - Math.abs(current.inphase())) * Math.abs(middle.inphase());
|
||||||
|
mErrorQuadrature = (Math.abs(previous.quadrature()) - Math.abs(current.quadrature())) * Math.abs(middle.quadrature());
|
||||||
|
mError = normalize(mErrorInphase + mErrorQuadrature, 1.0f);
|
||||||
|
|
||||||
|
return mError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constrains timing error to +/- the maximum value and corrects any floating point invalid numbers
|
||||||
|
*/
|
||||||
|
public static float normalize(float error, float maximum)
|
||||||
|
{
|
||||||
|
if(Float.isNaN(error))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return clip(error, maximum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constrains value to the range of ( -maximum <> maximum )
|
||||||
|
*/
|
||||||
|
public static float clip(float value, float maximum)
|
||||||
|
{
|
||||||
|
if(value > maximum)
|
||||||
|
{
|
||||||
|
return maximum;
|
||||||
|
}
|
||||||
|
else if(value < -maximum)
|
||||||
|
{
|
||||||
|
return -maximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
|
||||||
|
public interface IDQPSKSymbolEvaluator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sets the sampled symbol to be evaluated. Note: the symbol's gain value MUST be normalized to the unit circle.
|
||||||
|
*
|
||||||
|
* @param preceding symbol used to detect (counter-)clock-wise rotation
|
||||||
|
* @param symbol currently being sampled
|
||||||
|
*/
|
||||||
|
void setSymbol(Complex preceding, Complex symbol);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase error of the symbol relative to the closest reference symbol
|
||||||
|
* @return angular phase error in radians
|
||||||
|
*/
|
||||||
|
float getPhaseError();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timing error of the symbol relative to the closest reference symbol
|
||||||
|
* @return angular timing error in radians where a positive value indicates early sampling and sample timing needs
|
||||||
|
* to be increased and a negative value indicates late sampling where sample timing needs to be decreased.
|
||||||
|
*/
|
||||||
|
float getTimingError();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dibit decision indicating the reference symbol location closest to the symbol.
|
||||||
|
* @return symbol decision
|
||||||
|
*/
|
||||||
|
Dibit getSymbolDecision();
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public interface IQPSKSymbolDecoder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Decodes the symbol and converts it into a Dibit
|
||||||
|
* @param symbol to decode
|
||||||
|
* @return decoded dibit
|
||||||
|
*/
|
||||||
|
Dibit decode(Complex symbol);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public interface ISymbolPhaseErrorCalculator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Calculates the phase error of the symbol relative to an alignment for the constellation
|
||||||
|
* @param symbol to calculate error
|
||||||
|
* @return error value
|
||||||
|
*/
|
||||||
|
float getPhaseError(Complex symbol);
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.filter.interpolator.RealInterpolator;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public class InterpolatingSampleBuffer
|
||||||
|
{
|
||||||
|
private static final float MAXIMUM_DEVIATION_SAMPLES_PER_SYMBOL = 0.02f; // +/- 2% deviation
|
||||||
|
|
||||||
|
private Complex mPrecedingSample = new Complex(0,0);
|
||||||
|
private Complex mCurrentSample = new Complex(0,0);
|
||||||
|
|
||||||
|
private float[] mDelayLineInphase;
|
||||||
|
private float[] mDelayLineQuadrature;
|
||||||
|
private int mDelayLinePointer = 0;
|
||||||
|
private int mTwiceSamplesPerSymbol;
|
||||||
|
|
||||||
|
private float mSamplingPoint;
|
||||||
|
private float mSampleCounterGain = 1.0f;
|
||||||
|
private float mDetectedSamplesPerSymbol;
|
||||||
|
private float mDetectedSamplesPerSymbolGain = 0.1f * mSampleCounterGain * mSampleCounterGain;
|
||||||
|
private float mMaximumSamplesPerSymbol;
|
||||||
|
private float mMinimumSamplesPerSymbol;
|
||||||
|
|
||||||
|
private RealInterpolator mInterpolator = new RealInterpolator(1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer to store complex sample data and produce interpolated samples.
|
||||||
|
* @param samplesPerSymbol
|
||||||
|
*/
|
||||||
|
public InterpolatingSampleBuffer(float samplesPerSymbol)
|
||||||
|
{
|
||||||
|
mSamplingPoint = samplesPerSymbol;
|
||||||
|
mDetectedSamplesPerSymbol = samplesPerSymbol;
|
||||||
|
mMaximumSamplesPerSymbol = samplesPerSymbol * (1.0f + MAXIMUM_DEVIATION_SAMPLES_PER_SYMBOL);
|
||||||
|
mMinimumSamplesPerSymbol = samplesPerSymbol * (1.0f - MAXIMUM_DEVIATION_SAMPLES_PER_SYMBOL);
|
||||||
|
mTwiceSamplesPerSymbol = (int)Math.floor(2.0 * samplesPerSymbol);
|
||||||
|
mDelayLineInphase = new float[2 * mTwiceSamplesPerSymbol];
|
||||||
|
mDelayLineQuadrature = new float[2 * mTwiceSamplesPerSymbol];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the sample in the buffer and updates pointers.
|
||||||
|
* @param sample
|
||||||
|
*/
|
||||||
|
public void receive(Complex sample)
|
||||||
|
{
|
||||||
|
mSamplingPoint--;
|
||||||
|
|
||||||
|
//Fill up the delay line to use with the interpolator
|
||||||
|
mDelayLineInphase[mDelayLinePointer] = sample.inphase();
|
||||||
|
mDelayLineInphase[mDelayLinePointer + mTwiceSamplesPerSymbol] = sample.inphase();
|
||||||
|
mDelayLineQuadrature[mDelayLinePointer] = sample.quadrature();
|
||||||
|
mDelayLineQuadrature[mDelayLinePointer + mTwiceSamplesPerSymbol] = sample.quadrature();
|
||||||
|
|
||||||
|
//Increment pointer and keep pointer in bounds
|
||||||
|
mDelayLinePointer++;
|
||||||
|
mDelayLinePointer = mDelayLinePointer % mTwiceSamplesPerSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the internal sample counter with additional samples to add to the symbol sample counter.
|
||||||
|
* @param samplesToAdd to the sample counter
|
||||||
|
*/
|
||||||
|
public void increaseSampleCounter(float samplesToAdd)
|
||||||
|
{
|
||||||
|
mSamplingPoint += samplesToAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts samples per symbol and symbol timing counters and increments the sample counter to collect another
|
||||||
|
* symbol.
|
||||||
|
*
|
||||||
|
* @param symbolTimingError from a symbol timing error detector
|
||||||
|
*/
|
||||||
|
public void resetAndAdjust(float symbolTimingError)
|
||||||
|
{
|
||||||
|
//Adjust detected samples per symbol based on timing error
|
||||||
|
mDetectedSamplesPerSymbol = mDetectedSamplesPerSymbol + (symbolTimingError * mDetectedSamplesPerSymbolGain);
|
||||||
|
|
||||||
|
//Constrain detected samples per symbol to min/max values
|
||||||
|
if(mDetectedSamplesPerSymbol > mMaximumSamplesPerSymbol)
|
||||||
|
{
|
||||||
|
mDetectedSamplesPerSymbol = mMaximumSamplesPerSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mDetectedSamplesPerSymbol < mMinimumSamplesPerSymbol)
|
||||||
|
{
|
||||||
|
mDetectedSamplesPerSymbol = mMinimumSamplesPerSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add another symbol's worth of samples to the counter and adjust timing based on gardner error
|
||||||
|
increaseSampleCounter((mDetectedSamplesPerSymbol + (symbolTimingError * mSampleCounterGain)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current value of the detected samples per symbol
|
||||||
|
*/
|
||||||
|
public float getSamplingPoint()
|
||||||
|
{
|
||||||
|
return mSamplingPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this buffer has accumulated a enough samples to represent a full symbol
|
||||||
|
*/
|
||||||
|
public boolean hasSymbol()
|
||||||
|
{
|
||||||
|
return mSamplingPoint < 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Un-interpolated sample that precedes the current interpolated sampling point. The current sampling point falls
|
||||||
|
* somewhere between sample index 3 and index 4, therefore the preceding sample is located at index 3.
|
||||||
|
*/
|
||||||
|
public Complex getPrecedingSample()
|
||||||
|
{
|
||||||
|
mPrecedingSample.setValues(mDelayLineInphase[mDelayLinePointer + 3], mDelayLineQuadrature[mDelayLinePointer + 3]);
|
||||||
|
return mPrecedingSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolated middle sample for the symbol. Note: this method should only be invoked after testing for a
|
||||||
|
* complete symbol with the hasSymbol() method.
|
||||||
|
*/
|
||||||
|
public Complex getCurrentSample()
|
||||||
|
{
|
||||||
|
/* Calculate interpolated current sample */
|
||||||
|
mCurrentSample.setValues(getInphase(mSamplingPoint), getQuadrature(mSamplingPoint));
|
||||||
|
return mCurrentSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the interpolated inphase value for the specified offset
|
||||||
|
* @param interpolation into the buffer to calculate the interpolated sample
|
||||||
|
* @return inphase value of the interpolated sample
|
||||||
|
*/
|
||||||
|
public float getInphase(float interpolation)
|
||||||
|
{
|
||||||
|
return mInterpolator.filter(mDelayLineInphase, mDelayLinePointer + 1, interpolation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the interpolated quadrature value for the specified offset
|
||||||
|
* @param interpolation into the buffer to calculate the interpolated sample
|
||||||
|
* @return quadrature value of the interpolated sample
|
||||||
|
*/
|
||||||
|
public float getQuadrature(float interpolation)
|
||||||
|
{
|
||||||
|
return mInterpolator.filter(mDelayLineQuadrature, mDelayLinePointer + 1, interpolation);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public class InterpolatingSampleBufferInstrumented extends InterpolatingSampleBuffer
|
||||||
|
{
|
||||||
|
private SymbolDecisionData mSymbolDecisionData;
|
||||||
|
|
||||||
|
public InterpolatingSampleBufferInstrumented(float samplesPerSymbol)
|
||||||
|
{
|
||||||
|
super(samplesPerSymbol);
|
||||||
|
|
||||||
|
mSymbolDecisionData = new SymbolDecisionData((int)samplesPerSymbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive(Complex sample)
|
||||||
|
{
|
||||||
|
super.receive(sample);
|
||||||
|
mSymbolDecisionData.receive(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contents of the interpolating buffer and the current buffer index and symbol decision offset. This data can
|
||||||
|
* be used to support an external eye-diagram chart.
|
||||||
|
* @return symbol decision data.
|
||||||
|
*/
|
||||||
|
public SymbolDecisionData getSymbolDecisionData()
|
||||||
|
{
|
||||||
|
mSymbolDecisionData.setSamplingPoint(getSamplingPoint());
|
||||||
|
return mSymbolDecisionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public class LSMConstellationDecoder implements IQPSKSymbolDecoder
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Dibit decode(Complex symbol)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,18 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
package io.github.dsheirer.dsp.psk;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
@@ -5,54 +20,54 @@ import io.github.dsheirer.sample.Broadcaster;
|
|||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.Complex;
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
public class QPSKStarSlicer implements Listener<Complex>
|
public class QPSKStarSlicer implements IQPSKSymbolDecoder, Listener<Complex>
|
||||||
{
|
{
|
||||||
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slices a ComplexSample representing a phase shifted symbol according to
|
* Slices a Complex sample representing a phase shifted symbol according to the following constellation pattern:
|
||||||
* the following constellation pattern:
|
*
|
||||||
*
|
* 00 +3 | 01 +1
|
||||||
* 00 | 01
|
* ------|------
|
||||||
* ---|---
|
* 10 -1 | 11 -1
|
||||||
* 10 | 11
|
*/
|
||||||
*/
|
public QPSKStarSlicer()
|
||||||
public QPSKStarSlicer()
|
{
|
||||||
{
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
mBroadcaster.dispose();
|
|
||||||
mBroadcaster = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addListener( Listener<Dibit> listener )
|
public void dispose()
|
||||||
{
|
{
|
||||||
mBroadcaster.addListener( listener );
|
mBroadcaster.dispose();
|
||||||
}
|
mBroadcaster = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeListener( Listener<Dibit> listener )
|
public void addListener(Listener<Dibit> listener)
|
||||||
{
|
{
|
||||||
mBroadcaster.removeListener( listener );
|
mBroadcaster.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeListener(Listener<Dibit> listener)
|
||||||
|
{
|
||||||
|
mBroadcaster.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(Complex sample)
|
||||||
|
{
|
||||||
|
mBroadcaster.broadcast(decode(sample));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dibit decode(Complex sample)
|
||||||
|
{
|
||||||
|
if(sample.inphase() > 0)
|
||||||
|
{
|
||||||
|
return sample.quadrature() > 0 ? Dibit.D00_PLUS_1 : Dibit.D10_MINUS_1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return sample.quadrature() > 0 ? Dibit.D01_PLUS_3 : Dibit.D11_MINUS_3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive( Complex sample )
|
|
||||||
{
|
|
||||||
mBroadcaster.broadcast( decide( sample ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dibit decide( Complex sample )
|
|
||||||
{
|
|
||||||
if( sample.inphase() > 0 )
|
|
||||||
{
|
|
||||||
return sample.quadrature() > 0 ? Dibit.D00_PLUS_1 : Dibit.D10_MINUS_1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return sample.quadrature() > 0 ? Dibit.D01_PLUS_3 : Dibit.D11_MINUS_3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
package io.github.dsheirer.dsp.psk;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
@@ -5,24 +20,7 @@ import io.github.dsheirer.sample.Broadcaster;
|
|||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.Complex;
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
/*******************************************************************************
|
public class QPSKSymbolDecoder implements Listener<Complex>, IQPSKSymbolDecoder
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2014,2015 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/>
|
|
||||||
******************************************************************************/
|
|
||||||
public class QPSKPolarSlicer implements Listener<Complex>
|
|
||||||
{
|
{
|
||||||
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
||||||
|
|
||||||
@@ -37,7 +35,7 @@ public class QPSKPolarSlicer implements Listener<Complex>
|
|||||||
* / \
|
* / \
|
||||||
* / 11 \
|
* / 11 \
|
||||||
*/
|
*/
|
||||||
public QPSKPolarSlicer()
|
public QPSKSymbolDecoder()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,10 +58,10 @@ public class QPSKPolarSlicer implements Listener<Complex>
|
|||||||
@Override
|
@Override
|
||||||
public void receive( Complex complex )
|
public void receive( Complex complex )
|
||||||
{
|
{
|
||||||
mBroadcaster.broadcast( decide( complex ) );
|
mBroadcaster.broadcast( decode( complex ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dibit decide( Complex complex )
|
public Dibit decode(Complex complex )
|
||||||
{
|
{
|
||||||
if( Math.abs( complex.inphase() ) > Math.abs( complex.quadrature() ) )
|
if( Math.abs( complex.inphase() ) > Math.abs( complex.quadrature() ) )
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the phase error for a QPSK polar-orientation constellation
|
||||||
|
*/
|
||||||
|
public class QPSKSymbolPhaseErrorCalculator implements ISymbolPhaseErrorCalculator
|
||||||
|
{
|
||||||
|
private float mPhaseError;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getPhaseError(Complex symbol)
|
||||||
|
{
|
||||||
|
mPhaseError = 0.0f;
|
||||||
|
|
||||||
|
if(Math.abs(symbol.inphase()) > Math.abs(symbol.quadrature()))
|
||||||
|
{
|
||||||
|
if(symbol.inphase() > 0)
|
||||||
|
{
|
||||||
|
mPhaseError = -symbol.quadrature();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mPhaseError = symbol.quadrature();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(symbol.quadrature() > 0)
|
||||||
|
{
|
||||||
|
mPhaseError = symbol.inphase();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mPhaseError = -symbol.inphase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPhaseError;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
import io.github.dsheirer.buffer.ComplexCircularBuffer;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
public class SymbolDecisionData
|
||||||
|
{
|
||||||
|
//45 degrees rotation to orient the symbol to a polar axis to make error calculation easy/efficient
|
||||||
|
public static final Complex DIFFERENTIAL_OFFSET = Complex.fromAngle(Math.PI / 4.0d);
|
||||||
|
|
||||||
|
private ComplexCircularBuffer mBuffer;
|
||||||
|
private float mSamplingPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Circular buffer for capturing two symbols worth of sample data for use in instrumentation.
|
||||||
|
* @param samplesPerSymbol to size the buffer
|
||||||
|
*/
|
||||||
|
public SymbolDecisionData(int samplesPerSymbol)
|
||||||
|
{
|
||||||
|
mBuffer = new ComplexCircularBuffer(2 * samplesPerSymbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the complex sample in the buffer
|
||||||
|
*/
|
||||||
|
public void receive(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
mBuffer.put(new Complex(inphase, quadrature));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the complex sample in the buffer
|
||||||
|
*/
|
||||||
|
public void receive(Complex complex)
|
||||||
|
{
|
||||||
|
mBuffer.put(complex.copy());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the fractional sampling point for the current symbol to use in interpolating the current symbol.
|
||||||
|
*/
|
||||||
|
public void setSamplingPoint(float samplingPoint)
|
||||||
|
{
|
||||||
|
mSamplingPoint = samplingPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fractional sampling point
|
||||||
|
*/
|
||||||
|
public float getSamplingPoint()
|
||||||
|
{
|
||||||
|
return mSamplingPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of demodulated samples representing the current symbol where each current sample is demodulated against
|
||||||
|
* the previous symbol's sample to produce the differential demodulated sample. Samples are in time order.
|
||||||
|
*
|
||||||
|
* Note: differential decoding is on an integral samples-per-symbol basis and does not use fractional interpolation.
|
||||||
|
*/
|
||||||
|
public Complex[] getDemodulated()
|
||||||
|
{
|
||||||
|
Complex[] samples = mBuffer.getAll();
|
||||||
|
|
||||||
|
int length = samples.length / 2;
|
||||||
|
|
||||||
|
Complex[] demodulated = new Complex[length];
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length / 2; x++)
|
||||||
|
{
|
||||||
|
Complex copy = samples[length + x].copy();
|
||||||
|
copy.multiply(samples[x].conjugate());
|
||||||
|
demodulated[x] = copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk;
|
||||||
|
|
||||||
|
public enum SymbolInversion
|
||||||
|
{
|
||||||
|
DEGREES_90_PLUS(Math.PI / 2.0),
|
||||||
|
DEGREES_90_MINUS(Math.PI / -2.0),
|
||||||
|
DEGREES_180(Math.PI);
|
||||||
|
|
||||||
|
private double mAngleRadians;
|
||||||
|
|
||||||
|
SymbolInversion(double angleRadians)
|
||||||
|
{
|
||||||
|
mAngleRadians = angleRadians;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getError()
|
||||||
|
{
|
||||||
|
return mAngleRadians;
|
||||||
|
}
|
||||||
|
}
|
||||||
182
src/main/java/io/github/dsheirer/dsp/psk/pll/CostasLoop.java
Normal file
182
src/main/java/io/github/dsheirer/dsp/psk/pll/CostasLoop.java
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk.pll;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Costas Loop - phase locked loop designed to automatically synchronize to the incoming carrier frequency in order to
|
||||||
|
* zeroize any frequency offset inherent in the signal due to either mis-tuning or carrier frequency drift.
|
||||||
|
*
|
||||||
|
* Costas loop structure was modeled from KA1RBI's OP25/cqpsk.py
|
||||||
|
*
|
||||||
|
* Loop bandwidth and alpha/beta gain formulas derived from:
|
||||||
|
* http://www.trondeau.com/blog/2011/8/13/control-loop-gain-values.html
|
||||||
|
*/
|
||||||
|
public class CostasLoop implements IPhaseLockedLoop
|
||||||
|
{
|
||||||
|
public static final double TWO_PI = 2.0 * Math.PI;
|
||||||
|
|
||||||
|
private Complex mCurrentVector = new Complex(0, 0);
|
||||||
|
private double mLoopPhase = 0.0;
|
||||||
|
private double mLoopFrequency = 0.0;
|
||||||
|
private double mMaximumLoopFrequency;
|
||||||
|
private double mDamping = Math.sqrt(2.0) / 2.0;
|
||||||
|
private double mAlphaGain;
|
||||||
|
private double mBetaGain;
|
||||||
|
private Tracking mTracking = Tracking.NORMAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Costas Loop for tracking and correcting frequency error in a received carrier signal.
|
||||||
|
*
|
||||||
|
* @param sampleRate of the incoming samples
|
||||||
|
* @param symbolRate of the digital signal
|
||||||
|
*/
|
||||||
|
public CostasLoop(double sampleRate, double symbolRate)
|
||||||
|
{
|
||||||
|
mMaximumLoopFrequency = TWO_PI * (symbolRate / 2.0) / sampleRate;
|
||||||
|
updateLoopBandwidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corrects the current phase tracker when an inverted output is detected. The inversion will take the form of
|
||||||
|
* +/- 90 degrees or +/- 180 degrees, although the latter correction is equal regardless of the sign.
|
||||||
|
*
|
||||||
|
* If the supplied correction value places the loop frequency outside of the max frequency, then the frequency will
|
||||||
|
* be corrected 360 degrees in the opposite direction to maintain within the max frequency bounds.
|
||||||
|
*
|
||||||
|
* @param correction as measured in radians
|
||||||
|
*/
|
||||||
|
public void correctInversion(double correction)
|
||||||
|
{
|
||||||
|
mLoopFrequency += correction;
|
||||||
|
|
||||||
|
while(mLoopFrequency > mMaximumLoopFrequency)
|
||||||
|
{
|
||||||
|
mLoopFrequency -= 2.0 * mMaximumLoopFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(mLoopFrequency < -mMaximumLoopFrequency)
|
||||||
|
{
|
||||||
|
mLoopFrequency += 2.0 * mMaximumLoopFrequency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the loop bandwidth and alpha/beta gains according to the current loop synchronization state.
|
||||||
|
*/
|
||||||
|
private void updateLoopBandwidth()
|
||||||
|
{
|
||||||
|
double bandwidth = TWO_PI / mTracking.getLoopBandwidth();
|
||||||
|
|
||||||
|
mAlphaGain = (4.0 * mDamping * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
|
||||||
|
mBetaGain = (4.0 * bandwidth * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tracking state for this costas loop. The tracking state affects the aggressiveness of the alpha/beta
|
||||||
|
* gain values in synchronizing with the signal carrier.
|
||||||
|
*
|
||||||
|
* @param tracking
|
||||||
|
*/
|
||||||
|
public void setTracking(Tracking tracking)
|
||||||
|
{
|
||||||
|
mTracking = tracking;
|
||||||
|
updateLoopBandwidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the phase of the loop for each sample received at the sample rate.
|
||||||
|
*/
|
||||||
|
public void increment()
|
||||||
|
{
|
||||||
|
mLoopPhase += mLoopFrequency;
|
||||||
|
|
||||||
|
/* Normalize phase between +/- 2 * PI */
|
||||||
|
if(mLoopPhase > TWO_PI)
|
||||||
|
{
|
||||||
|
mLoopPhase -= TWO_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mLoopPhase < -TWO_PI)
|
||||||
|
{
|
||||||
|
mLoopPhase += TWO_PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current vector of the loop. Note: this value is updated for the current angle in radians each time this method
|
||||||
|
* is invoked.
|
||||||
|
*/
|
||||||
|
public Complex getCurrentVector()
|
||||||
|
{
|
||||||
|
mCurrentVector.setAngle(mLoopPhase);
|
||||||
|
return mCurrentVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Complex incrementAndGetCurrentVector()
|
||||||
|
{
|
||||||
|
increment();
|
||||||
|
return getCurrentVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLoopFrequency()
|
||||||
|
{
|
||||||
|
return mLoopFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the costas loop frequency and phase to adjust for the phase
|
||||||
|
* error value
|
||||||
|
*
|
||||||
|
* @param phaseError - (-)= late and (+)= early
|
||||||
|
*/
|
||||||
|
public void adjust(double phaseError)
|
||||||
|
{
|
||||||
|
mLoopFrequency += (mBetaGain * phaseError);
|
||||||
|
mLoopPhase += mLoopFrequency + (mAlphaGain * phaseError);
|
||||||
|
|
||||||
|
/* Normalize phase between +/- 2 * PI */
|
||||||
|
if(mLoopPhase > TWO_PI)
|
||||||
|
{
|
||||||
|
mLoopPhase -= TWO_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mLoopPhase < -TWO_PI)
|
||||||
|
{
|
||||||
|
mLoopPhase += TWO_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Limit frequency to +/- maximum loop frequency */
|
||||||
|
if(mLoopFrequency > mMaximumLoopFrequency)
|
||||||
|
{
|
||||||
|
mLoopFrequency = mMaximumLoopFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mLoopFrequency < -mMaximumLoopFrequency)
|
||||||
|
{
|
||||||
|
mLoopFrequency = -mMaximumLoopFrequency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
mLoopPhase = 0.0;
|
||||||
|
mLoopFrequency = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.dsp.psk.pll;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase-Locked Loop (PLL) interface
|
||||||
|
*/
|
||||||
|
public interface IPhaseLockedLoop
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Increments the PLL by one sample period
|
||||||
|
*/
|
||||||
|
void increment();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies an error value to the PLL.
|
||||||
|
*
|
||||||
|
* @param error value in the range of -PI <> +PI
|
||||||
|
*/
|
||||||
|
void adjust(double error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the current phase of the loop to account for +/-90 or 180 degree inverted phase locks when detected
|
||||||
|
* @param correction to apply to the phase of the loop
|
||||||
|
*/
|
||||||
|
void correctInversion(double correction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current vector of the PLL to use in de-spinning the incoming samples.
|
||||||
|
* @return complex vector
|
||||||
|
*/
|
||||||
|
Complex getCurrentVector();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the phase rotation and gets the current vector of the PLL to use in de-spinning the incoming samples.
|
||||||
|
* @return complex vector
|
||||||
|
*/
|
||||||
|
Complex incrementAndGetCurrentVector();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset tracking to 0
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
}
|
||||||
42
src/main/java/io/github/dsheirer/dsp/psk/pll/Tracking.java
Normal file
42
src/main/java/io/github/dsheirer/dsp/psk/pll/Tracking.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.dsp.psk.pll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase Locked Loop (PLL) tracking bandwidth - controls how fast/slow the PLL tracks the carrier frequency
|
||||||
|
*/
|
||||||
|
public enum Tracking
|
||||||
|
{
|
||||||
|
FASTEST(50.0),
|
||||||
|
FAST(100.0),
|
||||||
|
NORMAL(200.0),
|
||||||
|
SLOW(400.0);
|
||||||
|
|
||||||
|
private double mLoopBandwidth;
|
||||||
|
|
||||||
|
Tracking(double loopBandwidth)
|
||||||
|
{
|
||||||
|
mLoopBandwidth = loopBandwidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop bandwidth setting
|
||||||
|
*/
|
||||||
|
public double getLoopBandwidth()
|
||||||
|
{
|
||||||
|
return mLoopBandwidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ import io.github.dsheirer.audio.AudioManager;
|
|||||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastStatusPanel;
|
import io.github.dsheirer.audio.broadcast.BroadcastStatusPanel;
|
||||||
import io.github.dsheirer.controller.ControllerPanel;
|
import io.github.dsheirer.controller.ControllerPanel;
|
||||||
|
import io.github.dsheirer.controller.channel.Channel;
|
||||||
|
import io.github.dsheirer.controller.channel.ChannelAutoStartFrame;
|
||||||
import io.github.dsheirer.controller.channel.ChannelModel;
|
import io.github.dsheirer.controller.channel.ChannelModel;
|
||||||
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||||
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
||||||
@@ -51,8 +53,19 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.*;
|
import javax.swing.JCheckBoxMenuItem;
|
||||||
import java.awt.*;
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JMenu;
|
||||||
|
import javax.swing.JMenuBar;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JSeparator;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import java.awt.AWTException;
|
||||||
|
import java.awt.Desktop;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.Robot;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
@@ -61,6 +74,7 @@ import java.io.IOException;
|
|||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class SDRTrunk implements Listener<TunerEvent>
|
public class SDRTrunk implements Listener<TunerEvent>
|
||||||
{
|
{
|
||||||
@@ -72,6 +86,8 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
private BroadcastStatusPanel mBroadcastStatusPanel;
|
private BroadcastStatusPanel mBroadcastStatusPanel;
|
||||||
private BroadcastModel mBroadcastModel;
|
private BroadcastModel mBroadcastModel;
|
||||||
private ControllerPanel mControllerPanel;
|
private ControllerPanel mControllerPanel;
|
||||||
|
private ChannelModel mChannelModel;
|
||||||
|
private ChannelProcessingManager mChannelProcessingManager;
|
||||||
private SettingsManager mSettingsManager;
|
private SettingsManager mSettingsManager;
|
||||||
private SpectralDisplayPanel mSpectralPanel;
|
private SpectralDisplayPanel mSpectralPanel;
|
||||||
private JFrame mMainGui = new JFrame();
|
private JFrame mMainGui = new JFrame();
|
||||||
@@ -117,7 +133,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
|
|
||||||
AliasModel aliasModel = new AliasModel();
|
AliasModel aliasModel = new AliasModel();
|
||||||
|
|
||||||
ChannelModel channelModel = new ChannelModel();
|
mChannelModel = new ChannelModel();
|
||||||
|
|
||||||
ChannelMapModel channelMapModel = new ChannelMapModel();
|
ChannelMapModel channelMapModel = new ChannelMapModel();
|
||||||
|
|
||||||
@@ -127,43 +143,42 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
|
|
||||||
SourceManager sourceManager = new SourceManager(tunerModel, mSettingsManager);
|
SourceManager sourceManager = new SourceManager(tunerModel, mSettingsManager);
|
||||||
|
|
||||||
ChannelProcessingManager channelProcessingManager = new ChannelProcessingManager(
|
mChannelProcessingManager = new ChannelProcessingManager(mChannelModel, channelMapModel, aliasModel,
|
||||||
channelModel, channelMapModel, aliasModel, eventLogManager, recorderManager, sourceManager);
|
eventLogManager, recorderManager, sourceManager);
|
||||||
channelProcessingManager.addAudioPacketListener(recorderManager);
|
mChannelProcessingManager.addAudioPacketListener(recorderManager);
|
||||||
|
|
||||||
channelModel.addListener(channelProcessingManager);
|
mChannelModel.addListener(mChannelProcessingManager);
|
||||||
|
|
||||||
ChannelSelectionManager channelSelectionManager =
|
ChannelSelectionManager channelSelectionManager =
|
||||||
new ChannelSelectionManager(channelModel);
|
new ChannelSelectionManager(mChannelModel);
|
||||||
channelModel.addListener(channelSelectionManager);
|
mChannelModel.addListener(channelSelectionManager);
|
||||||
|
|
||||||
AliasActionManager aliasActionManager = new AliasActionManager();
|
AliasActionManager aliasActionManager = new AliasActionManager();
|
||||||
channelProcessingManager.addMessageListener(aliasActionManager);
|
mChannelProcessingManager.addMessageListener(aliasActionManager);
|
||||||
|
|
||||||
AudioManager audioManager = new AudioManager(sourceManager.getMixerManager());
|
AudioManager audioManager = new AudioManager(sourceManager.getMixerManager());
|
||||||
channelProcessingManager.addAudioPacketListener(audioManager);
|
mChannelProcessingManager.addAudioPacketListener(audioManager);
|
||||||
|
|
||||||
mBroadcastModel = new BroadcastModel(mIconManager);
|
mBroadcastModel = new BroadcastModel(mIconManager);
|
||||||
|
|
||||||
channelProcessingManager.addAudioPacketListener(mBroadcastModel);
|
mChannelProcessingManager.addAudioPacketListener(mBroadcastModel);
|
||||||
|
|
||||||
MapService mapService = new MapService(mIconManager);
|
MapService mapService = new MapService(mIconManager);
|
||||||
channelProcessingManager.addMessageListener(mapService);
|
mChannelProcessingManager.addMessageListener(mapService);
|
||||||
|
|
||||||
mControllerPanel = new ControllerPanel(audioManager, aliasModel, mBroadcastModel,
|
mControllerPanel = new ControllerPanel(audioManager, aliasModel, mBroadcastModel,
|
||||||
channelModel, channelMapModel, channelProcessingManager, mIconManager,
|
mChannelModel, channelMapModel, mChannelProcessingManager, mIconManager,
|
||||||
mapService, mSettingsManager, sourceManager, tunerModel);
|
mapService, mSettingsManager, sourceManager, tunerModel);
|
||||||
|
|
||||||
mSpectralPanel = new SpectralDisplayPanel(channelModel,
|
mSpectralPanel = new SpectralDisplayPanel(mChannelModel,
|
||||||
channelProcessingManager, mSettingsManager);
|
mChannelProcessingManager, mSettingsManager);
|
||||||
|
|
||||||
TunerSpectralDisplayManager tunerSpectralDisplayManager =
|
TunerSpectralDisplayManager tunerSpectralDisplayManager = new TunerSpectralDisplayManager(mSpectralPanel,
|
||||||
new TunerSpectralDisplayManager(mSpectralPanel,
|
mChannelModel, mChannelProcessingManager, mSettingsManager);
|
||||||
channelModel, channelProcessingManager, mSettingsManager);
|
|
||||||
tunerModel.addListener(tunerSpectralDisplayManager);
|
tunerModel.addListener(tunerSpectralDisplayManager);
|
||||||
tunerModel.addListener(this);
|
tunerModel.addListener(this);
|
||||||
|
|
||||||
PlaylistManager playlistManager = new PlaylistManager(aliasModel, mBroadcastModel, channelModel,
|
PlaylistManager playlistManager = new PlaylistManager(aliasModel, mBroadcastModel, mChannelModel,
|
||||||
channelMapModel);
|
channelMapModel);
|
||||||
|
|
||||||
playlistManager.init();
|
playlistManager.init();
|
||||||
@@ -183,6 +198,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
mMainGui.setVisible(true);
|
mMainGui.setVisible(true);
|
||||||
|
autoStartChannels();
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
@@ -192,6 +208,21 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog that lists the channels that have been designated for auto-start, sorted by auto-start order and
|
||||||
|
* allows the user to start now, cancel, or allow the timer to expire and then start the channels. The dialog will
|
||||||
|
* only show if there are one ore more channels designated for auto-start.
|
||||||
|
*/
|
||||||
|
private void autoStartChannels()
|
||||||
|
{
|
||||||
|
List<Channel> channels = mChannelModel.getAutoStartChannels();
|
||||||
|
|
||||||
|
if(channels.size() > 0)
|
||||||
|
{
|
||||||
|
ChannelAutoStartFrame autoStartFrame = new ChannelAutoStartFrame(mChannelProcessingManager, channels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch the application.
|
* Launch the application.
|
||||||
*/
|
*/
|
||||||
@@ -212,7 +243,8 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
*/
|
*/
|
||||||
mTitle = SystemProperties.getInstance().getApplicationName();
|
mTitle = SystemProperties.getInstance().getApplicationName();
|
||||||
mMainGui.setTitle(mTitle);
|
mMainGui.setTitle(mTitle);
|
||||||
mMainGui.setBounds(100, 100, 1280, 800);
|
mMainGui.setSize(new Dimension(1280,800));
|
||||||
|
mMainGui.setLocationRelativeTo(null);
|
||||||
mMainGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
mMainGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
|
||||||
//Set preferred sizes to influence the split
|
//Set preferred sizes to influence the split
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
@JacksonXmlRootElement(localName = "icon")
|
@JacksonXmlRootElement(localName = "icon")
|
||||||
public class Icon implements Comparable<Icon>
|
public class Icon implements Comparable<Icon>
|
||||||
@@ -80,7 +81,24 @@ public class Icon implements Comparable<Icon>
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mImageIcon = new ImageIcon(mPath);
|
if(mPath.startsWith("/"))
|
||||||
|
{
|
||||||
|
mImageIcon = new ImageIcon(mPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
URL imageURL = Icon.class.getResource(mPath);
|
||||||
|
|
||||||
|
if(imageURL == null && !mPath.startsWith("/"))
|
||||||
|
{
|
||||||
|
imageURL = (Icon.class.getResource("/" + mPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(imageURL != null)
|
||||||
|
{
|
||||||
|
mImageIcon = new ImageIcon(imageURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ import io.github.dsheirer.util.ThreadPool;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.event.TableModelEvent;
|
import javax.swing.event.TableModelEvent;
|
||||||
import javax.swing.event.TableModelListener;
|
import javax.swing.event.TableModelListener;
|
||||||
import java.awt.*;
|
import java.awt.Component;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.Image;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@@ -127,7 +129,7 @@ public class IconManager
|
|||||||
/**
|
/**
|
||||||
* All icons in a sorted array
|
* All icons in a sorted array
|
||||||
*/
|
*/
|
||||||
public io.github.dsheirer.icon.Icon[] getIcons()
|
public Icon[] getIcons()
|
||||||
{
|
{
|
||||||
return getModel().getIconsAsArray();
|
return getModel().getIconsAsArray();
|
||||||
}
|
}
|
||||||
@@ -154,10 +156,14 @@ public class IconManager
|
|||||||
return mResizedIcons.get(scaledIconName);
|
return mResizedIcons.get(scaledIconName);
|
||||||
}
|
}
|
||||||
|
|
||||||
io.github.dsheirer.icon.Icon icon = getModel().getIcon(name);
|
Icon icon = getModel().getIcon(name);
|
||||||
|
|
||||||
ImageIcon scaledIcon = getScaledIcon(icon.getIcon(), height);
|
ImageIcon scaledIcon = getScaledIcon(icon.getIcon(), height);
|
||||||
mResizedIcons.put(scaledIconName, scaledIcon);
|
|
||||||
|
if(scaledIcon != null)
|
||||||
|
{
|
||||||
|
mResizedIcons.put(scaledIconName, scaledIcon);
|
||||||
|
}
|
||||||
|
|
||||||
return scaledIcon;
|
return scaledIcon;
|
||||||
}
|
}
|
||||||
@@ -171,14 +177,37 @@ public class IconManager
|
|||||||
*/
|
*/
|
||||||
public static ImageIcon getScaledIcon(ImageIcon original, int height)
|
public static ImageIcon getScaledIcon(ImageIcon original, int height)
|
||||||
{
|
{
|
||||||
double scale = (double) original.getIconHeight() / (double) height;
|
if(original != null)
|
||||||
|
{
|
||||||
|
double scale = (double) original.getIconHeight() / (double) height;
|
||||||
|
|
||||||
int scaledWidth = (int) ((double) original.getIconWidth() / scale);
|
int scaledWidth = (int) ((double) original.getIconWidth() / scale);
|
||||||
|
|
||||||
Image scaledImage = original.getImage().getScaledInstance(scaledWidth,
|
Image scaledImage = original.getImage().getScaledInstance(scaledWidth,
|
||||||
height, java.awt.Image.SCALE_SMOOTH);
|
height, java.awt.Image.SCALE_SMOOTH);
|
||||||
|
|
||||||
return new ImageIcon(scaledImage);
|
return new ImageIcon(scaledImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an icon and scales it to the specified height
|
||||||
|
* @param path
|
||||||
|
* @param height
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ImageIcon getScaledIcon(String path, int height)
|
||||||
|
{
|
||||||
|
if(path != null)
|
||||||
|
{
|
||||||
|
Icon icon = new Icon("", path);
|
||||||
|
|
||||||
|
return getScaledIcon(icon.getIcon(), height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -359,26 +388,26 @@ public class IconManager
|
|||||||
{
|
{
|
||||||
IconSet iconSet = new IconSet();
|
IconSet iconSet = new IconSet();
|
||||||
|
|
||||||
io.github.dsheirer.icon.Icon defaultIcon = new io.github.dsheirer.icon.Icon(IconTableModel.DEFAULT_ICON, "images/no_icon.png");
|
Icon defaultIcon = new Icon(IconTableModel.DEFAULT_ICON, "images/no_icon.png");
|
||||||
iconSet.add(defaultIcon);
|
iconSet.add(defaultIcon);
|
||||||
iconSet.setDefaultIcon(defaultIcon.getName());
|
iconSet.setDefaultIcon(defaultIcon.getName());
|
||||||
|
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Ambulance", "images/ambulance.png"));
|
iconSet.add(new Icon("Ambulance", "images/ambulance.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Block Truck", "images/concrete_block_truck.png"));
|
iconSet.add(new Icon("Block Truck", "images/concrete_block_truck.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("CWID", "images/cwid.png"));
|
iconSet.add(new Icon("CWID", "images/cwid.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Dispatcher", "images/dispatcher.png"));
|
iconSet.add(new Icon("Dispatcher", "images/dispatcher.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Dump Truck", "images/dump_truck_red.png"));
|
iconSet.add(new Icon("Dump Truck", "images/dump_truck_red.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Fire Truck", "images/fire_truck.png"));
|
iconSet.add(new Icon("Fire Truck", "images/fire_truck.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Garbage Truck", "images/garbage_truck.png"));
|
iconSet.add(new Icon("Garbage Truck", "images/garbage_truck.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Loader", "images/loader.png"));
|
iconSet.add(new Icon("Loader", "images/loader.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Police", "images/police.png"));
|
iconSet.add(new Icon("Police", "images/police.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Propane Truck", "images/propane_truck.png"));
|
iconSet.add(new Icon("Propane Truck", "images/propane_truck.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Rescue Truck", "images/rescue_truck.png"));
|
iconSet.add(new Icon("Rescue Truck", "images/rescue_truck.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("School Bus", "images/school_bus.png"));
|
iconSet.add(new Icon("School Bus", "images/school_bus.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Taxi", "images/taxi.png"));
|
iconSet.add(new Icon("Taxi", "images/taxi.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Train", "images/train.png"));
|
iconSet.add(new Icon("Train", "images/train.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Transport Bus", "images/opt_bus.png"));
|
iconSet.add(new Icon("Transport Bus", "images/opt_bus.png"));
|
||||||
iconSet.add(new io.github.dsheirer.icon.Icon("Van", "images/van.png"));
|
iconSet.add(new Icon("Van", "images/van.png"));
|
||||||
|
|
||||||
return iconSet;
|
return iconSet;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,16 @@ package io.github.dsheirer.icon;
|
|||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JFileChooser;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextField;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
import java.awt.*;
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Frame;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -119,7 +125,7 @@ public class IconSelector extends JDialog implements DocumentListener
|
|||||||
String name = mNameText.getText();
|
String name = mNameText.getText();
|
||||||
String path = mFileLabel.getText();
|
String path = mFileLabel.getText();
|
||||||
|
|
||||||
io.github.dsheirer.icon.Icon icon = new io.github.dsheirer.icon.Icon(name, path);
|
Icon icon = new Icon(name, path);
|
||||||
mIconTableModel.add(icon);
|
mIconTableModel.add(icon);
|
||||||
|
|
||||||
IconSelector.this.dispose();
|
IconSelector.this.dispose();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.icon;
|
package io.github.dsheirer.icon;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -32,10 +32,10 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
public static final int COLUMN_IMAGE_ICON = 0;
|
public static final int COLUMN_IMAGE_ICON = 0;
|
||||||
public static final int COLUMN_ICON_NAME = 1;
|
public static final int COLUMN_ICON_NAME = 1;
|
||||||
|
|
||||||
private io.github.dsheirer.icon.Icon mDefaultIcon;
|
private Icon mDefaultIcon;
|
||||||
|
|
||||||
private Map<String,io.github.dsheirer.icon.Icon> mIconMap = new HashMap<>();
|
private Map<String,Icon> mIconMap = new HashMap<>();
|
||||||
private List<io.github.dsheirer.icon.Icon> mIcons = new ArrayList<>();
|
private List<Icon> mIcons = new ArrayList<>();
|
||||||
|
|
||||||
public IconTableModel()
|
public IconTableModel()
|
||||||
{
|
{
|
||||||
@@ -49,7 +49,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
|
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
|
|
||||||
for(io.github.dsheirer.icon.Icon icon: iconSet.getIcons())
|
for(Icon icon: iconSet.getIcons())
|
||||||
{
|
{
|
||||||
add(icon);
|
add(icon);
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
/**
|
/**
|
||||||
* Adds the icon to the model
|
* Adds the icon to the model
|
||||||
*/
|
*/
|
||||||
public void add(io.github.dsheirer.icon.Icon icon)
|
public void add(Icon icon)
|
||||||
{
|
{
|
||||||
if(icon != null)
|
if(icon != null)
|
||||||
{
|
{
|
||||||
@@ -75,7 +75,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
/**
|
/**
|
||||||
* Removes the icon from the model
|
* Removes the icon from the model
|
||||||
*/
|
*/
|
||||||
public void remove(io.github.dsheirer.icon.Icon icon)
|
public void remove(Icon icon)
|
||||||
{
|
{
|
||||||
if(icon != null && mIcons.contains(icon))
|
if(icon != null && mIcons.contains(icon))
|
||||||
{
|
{
|
||||||
@@ -89,7 +89,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public io.github.dsheirer.icon.Icon get(int row)
|
public Icon get(int row)
|
||||||
{
|
{
|
||||||
if(row < mIcons.size())
|
if(row < mIcons.size())
|
||||||
{
|
{
|
||||||
@@ -116,7 +116,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
{
|
{
|
||||||
if(rowIndex < mIcons.size() && columnIndex < getColumnCount())
|
if(rowIndex < mIcons.size() && columnIndex < getColumnCount())
|
||||||
{
|
{
|
||||||
io.github.dsheirer.icon.Icon icon = get(rowIndex);
|
Icon icon = get(rowIndex);
|
||||||
|
|
||||||
switch(columnIndex)
|
switch(columnIndex)
|
||||||
{
|
{
|
||||||
@@ -162,19 +162,19 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
/**
|
/**
|
||||||
* Icons in an array - suitable for use in a JComboBox
|
* Icons in an array - suitable for use in a JComboBox
|
||||||
*/
|
*/
|
||||||
public io.github.dsheirer.icon.Icon[] getIconsAsArray()
|
public Icon[] getIconsAsArray()
|
||||||
{
|
{
|
||||||
List<io.github.dsheirer.icon.Icon> icons = new ArrayList<>(mIconMap.values());
|
List<Icon> icons = new ArrayList<>(mIconMap.values());
|
||||||
|
|
||||||
Collections.sort(icons);
|
Collections.sort(icons);
|
||||||
|
|
||||||
return icons.toArray(new io.github.dsheirer.icon.Icon[icons.size()]);
|
return icons.toArray(new Icon[icons.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current default icon
|
* Returns the current default icon
|
||||||
*/
|
*/
|
||||||
public io.github.dsheirer.icon.Icon getDefaultIcon()
|
public Icon getDefaultIcon()
|
||||||
{
|
{
|
||||||
return mDefaultIcon;
|
return mDefaultIcon;
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
/**
|
/**
|
||||||
* Indicates if the icon argument is the current default icon
|
* Indicates if the icon argument is the current default icon
|
||||||
*/
|
*/
|
||||||
public boolean isDefaultIcon(io.github.dsheirer.icon.Icon icon)
|
public boolean isDefaultIcon(Icon icon)
|
||||||
{
|
{
|
||||||
return icon == getDefaultIcon();
|
return icon == getDefaultIcon();
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
return name != null && !name.isEmpty() && mIconMap.containsKey(name);
|
return name != null && !name.isEmpty() && mIconMap.containsKey(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultIcon(io.github.dsheirer.icon.Icon icon)
|
public void setDefaultIcon(Icon icon)
|
||||||
{
|
{
|
||||||
if(icon != null)
|
if(icon != null)
|
||||||
{
|
{
|
||||||
@@ -223,7 +223,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
name = DEFAULT_ICON;
|
name = DEFAULT_ICON;
|
||||||
}
|
}
|
||||||
|
|
||||||
io.github.dsheirer.icon.Icon defaultIcon = mIconMap.get(name);
|
Icon defaultIcon = mIconMap.get(name);
|
||||||
|
|
||||||
if(defaultIcon != null)
|
if(defaultIcon != null)
|
||||||
{
|
{
|
||||||
@@ -231,7 +231,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
defaultIcon = new io.github.dsheirer.icon.Icon(DEFAULT_ICON, "images/no_icon.png");
|
defaultIcon = new Icon(DEFAULT_ICON, "images/no_icon.png");
|
||||||
add(defaultIcon);
|
add(defaultIcon);
|
||||||
setDefaultIcon(defaultIcon);
|
setDefaultIcon(defaultIcon);
|
||||||
}
|
}
|
||||||
@@ -240,14 +240,14 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
/**
|
/**
|
||||||
* Returns the named icon or the default icon if the named icon does not exist
|
* Returns the named icon or the default icon if the named icon does not exist
|
||||||
*/
|
*/
|
||||||
public io.github.dsheirer.icon.Icon getIcon(String name)
|
public Icon getIcon(String name)
|
||||||
{
|
{
|
||||||
if(name == null || name.isEmpty())
|
if(name == null || name.isEmpty())
|
||||||
{
|
{
|
||||||
return mDefaultIcon;
|
return mDefaultIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
io.github.dsheirer.icon.Icon icon = mIconMap.get(name);
|
Icon icon = mIconMap.get(name);
|
||||||
|
|
||||||
if(icon == null)
|
if(icon == null)
|
||||||
{
|
{
|
||||||
@@ -263,7 +263,7 @@ public class IconTableModel extends AbstractTableModel
|
|||||||
public IconSet getIconSet()
|
public IconSet getIconSet()
|
||||||
{
|
{
|
||||||
IconSet iconSet = new IconSet();
|
IconSet iconSet = new IconSet();
|
||||||
iconSet.setIcons(new ArrayList<io.github.dsheirer.icon.Icon>(mIconMap.values()));
|
iconSet.setIcons(new ArrayList<Icon>(mIconMap.values()));
|
||||||
iconSet.setDefaultIcon(getDefaultIcon().getName());
|
iconSet.setDefaultIcon(getDefaultIcon().getName());
|
||||||
return iconSet;
|
return iconSet;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ package io.github.dsheirer.instrument.gui;
|
|||||||
|
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||||
|
import io.github.dsheirer.controller.channel.Channel;
|
||||||
import io.github.dsheirer.controller.channel.ChannelModel;
|
import io.github.dsheirer.controller.channel.ChannelModel;
|
||||||
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
|
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
|
||||||
import io.github.dsheirer.module.decode.DecodeConfigurationEditor;
|
import io.github.dsheirer.module.decode.DecodeConfigurationEditor;
|
||||||
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
import io.github.dsheirer.playlist.PlaylistManager;
|
import io.github.dsheirer.playlist.PlaylistManager;
|
||||||
import io.github.dsheirer.source.IControllableFileSource;
|
import io.github.dsheirer.source.IControllableFileSource;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JButton;
|
||||||
import java.awt.*;
|
import javax.swing.JDesktopPane;
|
||||||
|
import javax.swing.JInternalFrame;
|
||||||
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
@@ -18,14 +22,14 @@ public class DecoderSelectionFrame extends JInternalFrame
|
|||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Channel mChannel = new Channel();
|
||||||
private DecodeConfigurationEditor mDecodeEditor = new DecodeConfigurationEditor(null);
|
private DecodeConfigurationEditor mDecodeEditor = new DecodeConfigurationEditor(null);
|
||||||
private PlaylistManager mPlaylistManager;
|
private PlaylistManager mPlaylistManager;
|
||||||
|
|
||||||
private IControllableFileSource mSource;
|
private IControllableFileSource mSource;
|
||||||
private JDesktopPane mDesktop;
|
private JDesktopPane mDesktop;
|
||||||
|
|
||||||
public DecoderSelectionFrame(JDesktopPane desktop,
|
public DecoderSelectionFrame(JDesktopPane desktop, IControllableFileSource source)
|
||||||
IControllableFileSource source)
|
|
||||||
{
|
{
|
||||||
AliasModel aliasModel = new AliasModel();
|
AliasModel aliasModel = new AliasModel();
|
||||||
BroadcastModel broadcastModel = new BroadcastModel(null);
|
BroadcastModel broadcastModel = new BroadcastModel(null);
|
||||||
@@ -37,6 +41,7 @@ public class DecoderSelectionFrame extends JInternalFrame
|
|||||||
mDesktop = desktop;
|
mDesktop = desktop;
|
||||||
mSource = source;
|
mSource = source;
|
||||||
|
|
||||||
|
mDecodeEditor.setItem(mChannel);
|
||||||
initGUI();
|
initGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,17 +77,23 @@ public class DecoderSelectionFrame extends JInternalFrame
|
|||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent arg0)
|
public void actionPerformed(ActionEvent arg0)
|
||||||
{
|
{
|
||||||
// DecodeConfiguration config = mDecodeEditor.getDecodeConfig();
|
mDecodeEditor.save();
|
||||||
//
|
|
||||||
// if( config != null )
|
Channel channel = mDecodeEditor.getItem();
|
||||||
// {
|
|
||||||
// DecoderViewFrame decoderFrame = new DecoderViewFrame(
|
if(channel != null)
|
||||||
// mPlaylistManager, null, mSource );
|
{
|
||||||
//
|
DecodeConfiguration config = channel.getDecodeConfiguration();
|
||||||
// decoderFrame.setVisible( true );
|
|
||||||
//
|
if( config != null )
|
||||||
// mDesktop.add( decoderFrame );
|
{
|
||||||
// }
|
DecoderViewFrame decoderFrame = new DecoderViewFrame(mPlaylistManager, channel, mSource );
|
||||||
|
|
||||||
|
decoderFrame.setVisible( true );
|
||||||
|
|
||||||
|
mDesktop.add( decoderFrame );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPaneFactory;
|
||||||
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Stage mStage;
|
||||||
|
private MenuBar mMenuBar;
|
||||||
|
private RecentFilesMenu mRecentFilesMenu;
|
||||||
|
private ViewerDesktop mViewerDesktop;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) throws Exception
|
||||||
|
{
|
||||||
|
mStage = primaryStage;
|
||||||
|
setTitle(null);
|
||||||
|
|
||||||
|
BorderPane borderPane = new BorderPane();
|
||||||
|
borderPane.setTop(getMenuBar());
|
||||||
|
borderPane.setCenter(getViewerDesktop());
|
||||||
|
|
||||||
|
Scene scene = new Scene(borderPane, 1500, 900);
|
||||||
|
|
||||||
|
mStage.setScene(scene);
|
||||||
|
mStage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTitle(DecoderType decoderType)
|
||||||
|
{
|
||||||
|
if(decoderType != null)
|
||||||
|
{
|
||||||
|
mStage.setTitle("Demodulator Viewer FX - " + decoderType.getDisplayString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mStage.setTitle("Demodulator Viewer FX");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
File file = fileChooser.showOpenDialog(mStage);
|
||||||
|
getViewerDesktop().load(file);
|
||||||
|
getRecentFilesMenu().add(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
MenuItem decoderMenuItem = new MenuItem(decoderType.getShortDisplayString());
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPane;
|
||||||
|
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 javax.sound.sampled.UnsupportedAudioFileException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class PlaybackController extends HBox implements IFrameLocationListener, Listener<ComplexBuffer>
|
||||||
|
{
|
||||||
|
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<ActionEvent> mPlayEventHandler;
|
||||||
|
private DecoderPane mSampleRateListener;
|
||||||
|
|
||||||
|
private IControllableFileSource mControllableFileSource;
|
||||||
|
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
|
public PlaybackController()
|
||||||
|
{
|
||||||
|
super(10);
|
||||||
|
|
||||||
|
getChildren().addAll(getControlsBox(), getFileLabel());
|
||||||
|
|
||||||
|
setControlsEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSampleRateListener(DecoderPane decoderPane)
|
||||||
|
{
|
||||||
|
mSampleRateListener = decoderPane;
|
||||||
|
|
||||||
|
if(mSampleRateListener != null && mControllableFileSource != null)
|
||||||
|
{
|
||||||
|
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(File file)
|
||||||
|
{
|
||||||
|
if(file != null && file.isFile())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mControllableFileSource = new ComplexWaveSource(file);
|
||||||
|
mControllableFileSource.setListener(this);
|
||||||
|
((ComplexWaveSource)mControllableFileSource).setListener((Listener<ComplexBuffer>)this);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileLabel().setText(file.getName());
|
||||||
|
setControlsEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if(mControllableFileSource != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mControllableFileSource.close();
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error closing file", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setControlsEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds listener to receive complex buffers from this playback
|
||||||
|
*/
|
||||||
|
public void addListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the listener from receiving complex buffers from this playback
|
||||||
|
*/
|
||||||
|
public void removeListener(Listener<ComplexBuffer> 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.setUserData(1);
|
||||||
|
mPlay1Button.setOnAction(getPlaybackEventHandler());
|
||||||
|
mPlay1Button.setGraphic(new FontIcon(FontAwesome.PLAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPlay1Button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button getPlay10Button()
|
||||||
|
{
|
||||||
|
if(mPlay10Button == null)
|
||||||
|
{
|
||||||
|
mPlay10Button = new Button("10");
|
||||||
|
mPlay10Button.setUserData(10);
|
||||||
|
mPlay10Button.setOnAction(getPlaybackEventHandler());
|
||||||
|
mPlay10Button.setGraphic(new FontIcon(FontAwesome.FAST_FORWARD));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPlay10Button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button getPlay100Button()
|
||||||
|
{
|
||||||
|
if(mPlay100Button == null)
|
||||||
|
{
|
||||||
|
mPlay100Button = new Button("100");
|
||||||
|
mPlay100Button.setUserData(100);
|
||||||
|
mPlay100Button.setOnAction(getPlaybackEventHandler());
|
||||||
|
mPlay100Button.setGraphic(new FontIcon(FontAwesome.FAST_FORWARD));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPlay100Button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button getPlay1000Button()
|
||||||
|
{
|
||||||
|
if(mPlay1000Button == null)
|
||||||
|
{
|
||||||
|
mPlay1000Button = new Button("1000");
|
||||||
|
mPlay1000Button.setUserData(1000);
|
||||||
|
mPlay1000Button.setOnAction(getPlaybackEventHandler());
|
||||||
|
mPlay1000Button.setGraphic(new FontIcon(FontAwesome.FAST_FORWARD));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPlay1000Button;
|
||||||
|
}
|
||||||
|
|
||||||
|
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?");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.broadcast(complexBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package io.github.dsheirer.instrument.gui.viewer;
|
||||||
|
|
||||||
|
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 Preferences mPreferences = Preferences.userNodeForPackage(RecentFilesMenu.class);
|
||||||
|
private IFileSelectionListener mFileSelectionListener;
|
||||||
|
private String mIdentifier;
|
||||||
|
private int mSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recently used files menu. Uses java Preferences to store recently accessed files and provides access to those
|
||||||
|
* files via child menu items to this menu.
|
||||||
|
*
|
||||||
|
* @param label to use for this menu.
|
||||||
|
* @param uniqueIdentifier to use in addition to the class path of this class to uniquely identify recently accessed
|
||||||
|
* files in a java Preferences data store.
|
||||||
|
* @param size of the maximum number of recent files to present to the user
|
||||||
|
*/
|
||||||
|
public RecentFilesMenu(String label, String uniqueIdentifier, int size)
|
||||||
|
{
|
||||||
|
super(label);
|
||||||
|
mIdentifier = uniqueIdentifier;
|
||||||
|
mSize = size;
|
||||||
|
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive file selection events when the user selects a recently used file.
|
||||||
|
* @param listener to receive file selection events
|
||||||
|
*/
|
||||||
|
public void setFileSelectionListener(IFileSelectionListener listener)
|
||||||
|
{
|
||||||
|
mFileSelectionListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the file to the recently accessed files list. If this file is already included in the recently files list,
|
||||||
|
* it will be promoted to the first menu item and all other recently used files will be demoted.
|
||||||
|
*
|
||||||
|
* @param file to add to the recently used files list.
|
||||||
|
*/
|
||||||
|
public void add(File file)
|
||||||
|
{
|
||||||
|
if(file != null)
|
||||||
|
{
|
||||||
|
String filePath = file.getAbsolutePath();
|
||||||
|
|
||||||
|
int index = mSize - 1;
|
||||||
|
|
||||||
|
//Check to see if this file is already in the recent files list
|
||||||
|
for(int x = mSize - 1; x >= 0; x--)
|
||||||
|
{
|
||||||
|
String existingItem = mPreferences.get(getKey(x), null);
|
||||||
|
|
||||||
|
if(existingItem != null && existingItem.equals(filePath))
|
||||||
|
{
|
||||||
|
index = x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Shift all recent files down to make room for the newly added file at the top of the list
|
||||||
|
while(index > 0)
|
||||||
|
{
|
||||||
|
String newerItem = mPreferences.get(getKey(index - 1), null);
|
||||||
|
|
||||||
|
if(newerItem != null)
|
||||||
|
{
|
||||||
|
mPreferences.put(getKey(index), newerItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
|
||||||
|
mPreferences.put(getKey(0), file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a unique key value for each numbered recently used file item in the preferences.
|
||||||
|
*
|
||||||
|
* @param index of recent file item.
|
||||||
|
* @return unique key to use with the preferences instance.
|
||||||
|
*/
|
||||||
|
private String getKey(int index)
|
||||||
|
{
|
||||||
|
return mIdentifier + ".recent.file." + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears and (re)loads all child recent file menu items.
|
||||||
|
*/
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
getItems().clear();
|
||||||
|
|
||||||
|
for(int x = 0; x < mSize; x++)
|
||||||
|
{
|
||||||
|
String path = mPreferences.get(getKey(x), null);
|
||||||
|
|
||||||
|
if(path != null)
|
||||||
|
{
|
||||||
|
File file = new File(path);
|
||||||
|
|
||||||
|
if(file.isFile())
|
||||||
|
{
|
||||||
|
MenuItem menuItem = new MenuItem(file.getName());
|
||||||
|
menuItem.setUserData(file);
|
||||||
|
menuItem.setOnAction(this);
|
||||||
|
getItems().add(menuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getItems().size() > 0)
|
||||||
|
{
|
||||||
|
setDisable(false);
|
||||||
|
getItems().add(new SeparatorMenuItem());
|
||||||
|
|
||||||
|
MenuItem clearMenuItem = new MenuItem("Clear History");
|
||||||
|
clearMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(ActionEvent event)
|
||||||
|
{
|
||||||
|
clearHistory();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getItems().add(clearMenuItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setDisable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the recently used files history and removes all items from the preferences instance
|
||||||
|
*/
|
||||||
|
private void clearHistory()
|
||||||
|
{
|
||||||
|
for(int x = 0; x < mSize; x++)
|
||||||
|
{
|
||||||
|
mPreferences.remove(getKey(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for all recent file child menu items. Extracts the selected file stored as the user data object
|
||||||
|
* in each of the menu items and notifies the listener that the recent file has been selected.
|
||||||
|
*
|
||||||
|
* @param event from a recent file menu item child of this menu.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handle(ActionEvent event)
|
||||||
|
{
|
||||||
|
if(mFileSelectionListener != null)
|
||||||
|
{
|
||||||
|
if(event.getSource() instanceof MenuItem)
|
||||||
|
{
|
||||||
|
Object fileObject = ((MenuItem)event.getSource()).getUserData();
|
||||||
|
|
||||||
|
if(fileObject instanceof File)
|
||||||
|
{
|
||||||
|
mFileSelectionListener.fileSelected((File)fileObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface for file selection events.
|
||||||
|
*/
|
||||||
|
public interface IFileSelectionListener
|
||||||
|
{
|
||||||
|
void fileSelected(File file);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPane;
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPaneFactory;
|
||||||
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class ViewerDesktop extends BorderPane
|
||||||
|
{
|
||||||
|
private PlaybackController mPlaybackController;
|
||||||
|
private DecoderPane mDecoderPane = DecoderPaneFactory.getDefaultPane();
|
||||||
|
|
||||||
|
public ViewerDesktop()
|
||||||
|
{
|
||||||
|
setBottom(getPlaybackController());
|
||||||
|
setCenter(getDecoderPane());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(File file)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
getPlaybackController().load(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDecoder(DecoderType decoderType)
|
||||||
|
{
|
||||||
|
getPlaybackController().removeListener(getDecoderPane());
|
||||||
|
getChildren().remove(getDecoderPane());
|
||||||
|
|
||||||
|
mDecoderPane = DecoderPaneFactory.getDecoderPane(decoderType);
|
||||||
|
setCenter(getDecoderPane());
|
||||||
|
getPlaybackController().addListener(getDecoderPane());
|
||||||
|
|
||||||
|
getPlaybackController().setSampleRateListener(getDecoderPane());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder pane
|
||||||
|
*/
|
||||||
|
public DecoderPane 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.chart;
|
||||||
|
|
||||||
|
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 org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ComplexPhaseLineChart extends LineChart implements Listener<ComplexBuffer>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(ComplexPhaseLineChart.class);
|
||||||
|
|
||||||
|
private static final Complex ANGLE_OFFSET_45_DEGREES = Complex.fromAngle(Math.PI / 4.0);
|
||||||
|
private ComplexCircularBuffer mComplexCircularBuffer;
|
||||||
|
private ComplexGain mComplexGain = new ComplexGain(600.0f);
|
||||||
|
private ObservableList<Data<Integer,Float>> mPhaseValues = FXCollections.observableArrayList();
|
||||||
|
private IntegerProperty mLengthProperty = new SimpleIntegerProperty(40);
|
||||||
|
|
||||||
|
public ComplexPhaseLineChart(int length)
|
||||||
|
{
|
||||||
|
super(new NumberAxis("Time", 1, length - 10, 2),
|
||||||
|
new NumberAxis("Phase", -Math.PI, Math.PI, Math.PI / 2.0));
|
||||||
|
|
||||||
|
Series<Integer,Float> phaseSeries = new Series<>("Phase", mPhaseValues);
|
||||||
|
ObservableList<Series<Integer,Float>> observableList = FXCollections.observableArrayList(phaseSeries);
|
||||||
|
|
||||||
|
setData(observableList);
|
||||||
|
init(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int length)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
length().setValue(length);
|
||||||
|
|
||||||
|
for(int x = 10; x <= length; x++)
|
||||||
|
{
|
||||||
|
mPhaseValues.add(new Data(x - 10, 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]);
|
||||||
|
Complex copy = sample.copy();
|
||||||
|
mComplexGain.apply(copy);
|
||||||
|
mComplexCircularBuffer.put(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateChart()
|
||||||
|
{
|
||||||
|
Complex[] samples = mComplexCircularBuffer.getAll();
|
||||||
|
|
||||||
|
for(int x = 10; x < samples.length; x++)
|
||||||
|
{
|
||||||
|
Complex previous = samples[x - 10].copy();
|
||||||
|
Complex sample = samples[x].copy();
|
||||||
|
|
||||||
|
if(previous != null && sample != null)
|
||||||
|
{
|
||||||
|
// previous.normalize();
|
||||||
|
// sample.normalize();
|
||||||
|
previous.add(ANGLE_OFFSET_45_DEGREES);
|
||||||
|
sample.multiply(previous.conjugate());
|
||||||
|
// sample.normalize();
|
||||||
|
mPhaseValues.get(x - 9).setYValue(sample.angle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.chart;
|
||||||
|
|
||||||
|
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<ComplexBuffer>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(ComplexSampleLineChart.class);
|
||||||
|
|
||||||
|
private ComplexCircularBuffer mComplexCircularBuffer;
|
||||||
|
private ComplexCircularBuffer mDemodulationCircularBuffer;
|
||||||
|
private ComplexGain mComplexGain = new ComplexGain(500.0f);
|
||||||
|
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(int length, int samplesPerSymbol)
|
||||||
|
{
|
||||||
|
super(new NumberAxis("Differential-Demodulated Samples", 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(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
float[] samples = complexBuffer.getSamples();
|
||||||
|
|
||||||
|
Complex sample = new Complex(0,0);
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
|
{
|
||||||
|
sample.setValues(samples[x], samples[x + 1]);
|
||||||
|
// mComplexGain.apply(sample);
|
||||||
|
sample.normalize();
|
||||||
|
|
||||||
|
Complex previous = mDemodulationCircularBuffer.get(sample.copy());
|
||||||
|
sample.multiply(previous.conjugate());
|
||||||
|
mComplexCircularBuffer.put(sample.copy());
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.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.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,Double>> mData = FXCollections.observableArrayList();
|
||||||
|
private int mSeriesCount;
|
||||||
|
private int mSeriesPointer;
|
||||||
|
private int mSeriesLength;
|
||||||
|
|
||||||
|
public EyeDiagramChart(int seriesCount)
|
||||||
|
{
|
||||||
|
super(new NumberAxis("Symbol Timing (5 = center)", 1.0, 10.0, 1.0),
|
||||||
|
new NumberAxis("Value", -1.25, 1.25, 0.25));
|
||||||
|
|
||||||
|
mSeriesCount = seriesCount * 2;
|
||||||
|
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,Double>> getSeries(int series, int length)
|
||||||
|
{
|
||||||
|
ObservableList<Data<Double,Double>> seriesData = mData.get(series).getData();
|
||||||
|
|
||||||
|
while(seriesData.size() > length)
|
||||||
|
{
|
||||||
|
seriesData.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(seriesData.size() < length)
|
||||||
|
{
|
||||||
|
seriesData.add(new Data<Double,Double>(0.0,0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return seriesData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkChartLength(int length)
|
||||||
|
{
|
||||||
|
if(mSeriesLength != length)
|
||||||
|
{
|
||||||
|
((NumberAxis)getXAxis()).setUpperBound(length);
|
||||||
|
mSeriesLength = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(SymbolDecisionData symbolDecisionData)
|
||||||
|
{
|
||||||
|
Complex[] demodulated = symbolDecisionData.getDemodulated();
|
||||||
|
|
||||||
|
int length = demodulated.length;
|
||||||
|
|
||||||
|
checkChartLength(length);
|
||||||
|
|
||||||
|
|
||||||
|
ObservableList<Data<Double,Double>> iSeries = getSeries(mSeriesPointer++, length + 1);
|
||||||
|
|
||||||
|
if(mSeriesPointer >= mSeriesCount)
|
||||||
|
{
|
||||||
|
mSeriesPointer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObservableList<Data<Double,Double>> qSeries = getSeries(mSeriesPointer++, length + 1);
|
||||||
|
|
||||||
|
if(mSeriesPointer >= mSeriesCount)
|
||||||
|
{
|
||||||
|
mSeriesPointer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int x = 0; x < length; x++)
|
||||||
|
{
|
||||||
|
demodulated[x].normalize();
|
||||||
|
Data<Double,Double> iDataPoint = iSeries.get(x);
|
||||||
|
iDataPoint.setXValue((double)x + 1 - symbolDecisionData.getSamplingPoint());
|
||||||
|
double iValue = demodulated[x].inphase();
|
||||||
|
iDataPoint.setYValue(iValue);
|
||||||
|
|
||||||
|
Data<Double,Double> qDataPoint = qSeries.get(x);
|
||||||
|
qDataPoint.setXValue((double)x + 1 - symbolDecisionData.getSamplingPoint());
|
||||||
|
double qValue = demodulated[x].quadrature();
|
||||||
|
qDataPoint.setYValue(qValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package io.github.dsheirer.instrument.gui.viewer.chart;
|
||||||
|
|
||||||
|
import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
||||||
|
import io.github.dsheirer.dsp.fm.FMDemodulator;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
|
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 FMDemodulatedLineChart extends LineChart implements Listener<ComplexBuffer>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatedLineChart.class);
|
||||||
|
|
||||||
|
private DoubleCircularBuffer mDoubleCircularBuffer;
|
||||||
|
private int mSamplesPerSymbol;
|
||||||
|
private int mTwiceSamplesPerSymbol;
|
||||||
|
private int mSeriesCount;
|
||||||
|
private ObservableList<Series<Integer,Double>> mData = FXCollections.observableArrayList();
|
||||||
|
private Complex mPreviousSample = new Complex(0,0);
|
||||||
|
private int mSeriesPointer;
|
||||||
|
private int mSamplePointer;
|
||||||
|
|
||||||
|
public FMDemodulatedLineChart(int samplesPerSymbol, int seriesCount)
|
||||||
|
{
|
||||||
|
super(new NumberAxis("Time", 1, samplesPerSymbol * 2, 2),
|
||||||
|
new NumberAxis("Demodulated", -1.0, 1.0, 0.25));
|
||||||
|
|
||||||
|
mSamplesPerSymbol = samplesPerSymbol;
|
||||||
|
mTwiceSamplesPerSymbol = mSamplesPerSymbol * 2;
|
||||||
|
mSeriesCount = seriesCount;
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
int seriesLength = 2 * mSamplesPerSymbol;
|
||||||
|
mDoubleCircularBuffer = new DoubleCircularBuffer(seriesLength * mSeriesCount);
|
||||||
|
|
||||||
|
for(int x = 0; x < mSeriesCount; x++)
|
||||||
|
{
|
||||||
|
ObservableList<Data<Integer,Double>> series = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
for(int y = 0; y < seriesLength; y++)
|
||||||
|
{
|
||||||
|
series.add(new Data<Integer,Double>(y + 1, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
mData.add(new Series<>(series));
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(mData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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]);
|
||||||
|
double demodulated = FMDemodulator.demodulate(mPreviousSample, sample);
|
||||||
|
demodulated *= 2.0;
|
||||||
|
|
||||||
|
if(demodulated > 1.0)
|
||||||
|
{
|
||||||
|
demodulated = 1.0;
|
||||||
|
}
|
||||||
|
else if(demodulated < -1.0)
|
||||||
|
{
|
||||||
|
demodulated = -1.0;
|
||||||
|
}
|
||||||
|
mDoubleCircularBuffer.put(demodulated);
|
||||||
|
mPreviousSample = sample;
|
||||||
|
updateChart(demodulated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateChart(double value)
|
||||||
|
{
|
||||||
|
Series<Integer,Double> series = mData.get(mSeriesPointer);
|
||||||
|
Data data = series.getData().get(mSamplePointer++);
|
||||||
|
data.setYValue(value);
|
||||||
|
|
||||||
|
if(mSamplePointer >= mTwiceSamplesPerSymbol)
|
||||||
|
{
|
||||||
|
mSamplePointer = 0;
|
||||||
|
mSeriesPointer++;
|
||||||
|
|
||||||
|
if(mSeriesPointer >= mSeriesCount)
|
||||||
|
{
|
||||||
|
mSeriesPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.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.complex.ComplexBuffer;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
public class DecoderPane extends VBox implements Listener<ComplexBuffer>
|
||||||
|
{
|
||||||
|
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||||
|
private DecoderType mDecoderType;
|
||||||
|
|
||||||
|
public DecoderPane(DecoderType decoderType)
|
||||||
|
{
|
||||||
|
mDecoderType = decoderType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package private - used by factory class to create an empty pane
|
||||||
|
*/
|
||||||
|
DecoderPane()
|
||||||
|
{
|
||||||
|
getChildren().add(new Text("Please select a decoder from the menu"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.broadcast(complexBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSampleRate(double sampleRate)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.decoder;
|
||||||
|
|
||||||
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
public class DecoderPaneFactory
|
||||||
|
{
|
||||||
|
private static final EnumSet<DecoderType> SUPPORTED_DECODER_TYPES = EnumSet.of(DecoderType.P25_PHASE1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a decoder pane for the decoder type
|
||||||
|
*/
|
||||||
|
public static DecoderPane getDecoderPane(DecoderType decoderType)
|
||||||
|
{
|
||||||
|
switch(decoderType)
|
||||||
|
{
|
||||||
|
case P25_PHASE1:
|
||||||
|
return new P25Phase1Pane();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultPane();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty decoder pane
|
||||||
|
*/
|
||||||
|
public static DecoderPane getDefaultPane()
|
||||||
|
{
|
||||||
|
return new DecoderPane();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the decoder type is supported by this factory
|
||||||
|
*/
|
||||||
|
public static boolean isSupported(DecoderType decoderType)
|
||||||
|
{
|
||||||
|
return SUPPORTED_DECODER_TYPES.contains(decoderType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.instrument.gui.viewer.decoder;
|
||||||
|
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.chart.ComplexSampleLineChart;
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.chart.DoubleLineChart;
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.chart.EyeDiagramChart;
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.chart.PhaseLineChart;
|
||||||
|
import io.github.dsheirer.instrument.gui.viewer.chart.SymbolChart;
|
||||||
|
import io.github.dsheirer.message.Message;
|
||||||
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
|
import io.github.dsheirer.module.decode.p25.P25_C4FMDecoderInstrumented;
|
||||||
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class P25Phase1Pane extends DecoderPane
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1Pane.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<ComplexBuffer> mFilteredBufferBroadcaster = new Broadcaster<>();
|
||||||
|
private P25_C4FMDecoderInstrumented mDecoder = new P25_C4FMDecoderInstrumented(null);
|
||||||
|
|
||||||
|
public P25Phase1Pane()
|
||||||
|
{
|
||||||
|
super(DecoderType.P25_PHASE1);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
addListener(getDecoder());
|
||||||
|
|
||||||
|
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
|
||||||
|
getDecoder().setSymbolListener(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<Message>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void receive(Message message)
|
||||||
|
{
|
||||||
|
mLog.debug(message.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 P25_C4FMDecoderInstrumented 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(100, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSampleLineChart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EyeDiagramChart getEyeDiagramChart()
|
||||||
|
{
|
||||||
|
if(mEyeDiagramChart == null)
|
||||||
|
{
|
||||||
|
mEyeDiagramChart = new EyeDiagramChart(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,14 +19,20 @@ package io.github.dsheirer.map;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.icon.Icon;
|
||||||
import io.github.dsheirer.settings.Setting;
|
import io.github.dsheirer.settings.Setting;
|
||||||
import io.github.dsheirer.settings.SettingType;
|
import io.github.dsheirer.settings.SettingType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.ImageIcon;
|
||||||
import java.awt.*;
|
import java.awt.Image;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
public class MapIcon extends Setting implements Comparable<MapIcon>
|
public class MapIcon extends Setting implements Comparable<MapIcon>
|
||||||
{
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(MapIcon.class);
|
||||||
|
|
||||||
private static final int sMAX_IMAGE_DIMENSION = 48;
|
private static final int sMAX_IMAGE_DIMENSION = 48;
|
||||||
private String mPath;
|
private String mPath;
|
||||||
private ImageIcon mImageIcon;
|
private ImageIcon mImageIcon;
|
||||||
@@ -100,42 +106,58 @@ public class MapIcon extends Setting implements Comparable<MapIcon>
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public ImageIcon getImageIcon()
|
public ImageIcon getImageIcon()
|
||||||
{
|
{
|
||||||
if(mImageIcon == null)
|
if(mImageIcon == null && mPath != null)
|
||||||
{
|
{
|
||||||
mImageIcon = new ImageIcon(mPath);
|
try
|
||||||
|
|
||||||
/**
|
|
||||||
* If the image is too big, scale it down to max pixel size squared
|
|
||||||
*/
|
|
||||||
if(mImageIcon.getIconWidth() > sMAX_IMAGE_DIMENSION ||
|
|
||||||
mImageIcon.getIconHeight() > sMAX_IMAGE_DIMENSION)
|
|
||||||
{
|
{
|
||||||
/**
|
URL imageURL = Icon.class.getResource(mPath);
|
||||||
* getScaled instance will correct any negative value to the
|
|
||||||
* correct value, maintaining original aspect ratio. So, we
|
|
||||||
* only scale the larger value, and allow the image class to
|
|
||||||
* determine the correct value for the other measurement
|
|
||||||
*/
|
|
||||||
int height = -1;
|
|
||||||
int width = -1;
|
|
||||||
|
|
||||||
/**
|
if(imageURL == null && !mPath.startsWith("/"))
|
||||||
* Use the larger width or height value to determine the
|
|
||||||
* scaling factor
|
|
||||||
*/
|
|
||||||
if(mImageIcon.getIconHeight() > mImageIcon.getIconWidth())
|
|
||||||
{
|
{
|
||||||
height = sMAX_IMAGE_DIMENSION;
|
imageURL = (Icon.class.getResource("/" + mPath));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
width = sMAX_IMAGE_DIMENSION;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mImageIcon = new ImageIcon(mImageIcon.getImage()
|
if(imageURL != null)
|
||||||
.getScaledInstance(width, height, Image.SCALE_SMOOTH));
|
{
|
||||||
|
mImageIcon = new ImageIcon(imageURL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the image is too big, scale it down to max pixel size squared
|
||||||
|
*/
|
||||||
|
if(mImageIcon.getIconWidth() > sMAX_IMAGE_DIMENSION ||
|
||||||
|
mImageIcon.getIconHeight() > sMAX_IMAGE_DIMENSION)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* getScaled instance will correct any negative value to the
|
||||||
|
* correct value, maintaining original aspect ratio. So, we
|
||||||
|
* only scale the larger value, and allow the image class to
|
||||||
|
* determine the correct value for the other measurement
|
||||||
|
*/
|
||||||
|
int height = -1;
|
||||||
|
int width = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the larger width or height value to determine the
|
||||||
|
* scaling factor
|
||||||
|
*/
|
||||||
|
if(mImageIcon.getIconHeight() > mImageIcon.getIconWidth())
|
||||||
|
{
|
||||||
|
height = sMAX_IMAGE_DIMENSION;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
width = sMAX_IMAGE_DIMENSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
mImageIcon = new ImageIcon(mImageIcon.getImage()
|
||||||
|
.getScaledInstance(width, height, Image.SCALE_SMOOTH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error loading Icon [" + mPath + "]", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mImageIcon;
|
return mImageIcon;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -22,39 +22,39 @@ import io.github.dsheirer.message.Message;
|
|||||||
import io.github.dsheirer.module.Module;
|
import io.github.dsheirer.module.Module;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
|
||||||
public abstract class Decoder extends Module
|
public abstract class Decoder extends Module implements IMessageProvider
|
||||||
implements IMessageProvider, Listener<Message>
|
|
||||||
{
|
{
|
||||||
/* This has to be a broadcaster in order for references to persist */
|
/* This has to be a broadcaster in order for references to persist */
|
||||||
protected Listener<Message> mMessageListener;
|
private Listener<Message> mMessageDistributor = new MessageDistributor();
|
||||||
|
protected Listener<Message> mMessageListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoder - parent class for all decoders, demodulators and components.
|
* Decoder - parent class for all decoders, demodulators and components.
|
||||||
*/
|
*/
|
||||||
public Decoder()
|
public Decoder()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
mMessageListener = null;
|
mMessageListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the decoder type (ie protocol)
|
* Identifies the decoder type (ie protocol)
|
||||||
*/
|
*/
|
||||||
public abstract DecoderType getDecoderType();
|
public abstract DecoderType getDecoderType();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a listener for receiving decoded messages from this decoder
|
* Adds a listener for receiving decoded messages from this decoder
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setMessageListener( Listener<Message> listener )
|
public void setMessageListener(Listener<Message> listener)
|
||||||
{
|
{
|
||||||
mMessageListener = listener;
|
mMessageListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the listener from receiving decoded messages from all attached
|
* Removes the listener from receiving decoded messages from all attached
|
||||||
@@ -63,23 +63,29 @@ public abstract class Decoder extends Module
|
|||||||
@Override
|
@Override
|
||||||
public void removeMessageListener()
|
public void removeMessageListener()
|
||||||
{
|
{
|
||||||
mMessageListener = null;
|
mMessageListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcasts the message to the registered message listener
|
* Message listener for receiving the output from this decoder
|
||||||
*/
|
*/
|
||||||
public void broadcast( Message message )
|
protected Listener<Message> getMessageListener()
|
||||||
{
|
{
|
||||||
if( mMessageListener != null )
|
return mMessageDistributor;
|
||||||
{
|
|
||||||
mMessageListener.receive( message );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void receive( Message message )
|
* Distributes/forwards messages from sub-class decoder implementations to the registered message listener.
|
||||||
{
|
*/
|
||||||
broadcast( message );
|
public class MessageDistributor implements Listener<Message>
|
||||||
}
|
{
|
||||||
|
@Override
|
||||||
|
public void receive(Message message)
|
||||||
|
{
|
||||||
|
if(mMessageListener != null)
|
||||||
|
{
|
||||||
|
mMessageListener.receive(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,9 +246,7 @@ public class DecoderFactory
|
|||||||
switch(modulation)
|
switch(modulation)
|
||||||
{
|
{
|
||||||
case C4FM:
|
case C4FM:
|
||||||
modules.add(new FMDemodulatorModule(P25_C4FM_IQ_FILTER));
|
modules.add(new P25_C4FMDecoder(aliasList));
|
||||||
modules.add(new DemodulatedAudioFilterModule(P25_C4FM_DEMOD_FILTER, 1.0f));
|
|
||||||
modules.add(new P25_C4FMDecoder(aliasList, decodeConfig.getAFCMaximumCorrection()));
|
|
||||||
modules.add(new P25DecoderState(aliasList, channelType, P25Decoder.Modulation.C4FM,
|
modules.add(new P25DecoderState(aliasList, channelType, P25Decoder.Modulation.C4FM,
|
||||||
p25Config.getIgnoreDataCalls()));
|
p25Config.getIgnoreDataCalls()));
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public class Fleetsync2Decoder extends Decoder implements IFilteredRealBufferLis
|
|||||||
mMessageProcessor = new Fleetsync2MessageProcessor( aliasList );
|
mMessageProcessor = new Fleetsync2MessageProcessor( aliasList );
|
||||||
mMessageFramer.addMessageListener( mMessageProcessor );
|
mMessageFramer.addMessageListener( mMessageProcessor );
|
||||||
|
|
||||||
mMessageProcessor.setMessageListener( this );
|
mMessageProcessor.setMessageListener( getMessageListener() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public class LJ1200Decoder extends Decoder implements IFilteredRealBufferListene
|
|||||||
mMessageProcessor = new LJ1200MessageProcessor( aliasList );
|
mMessageProcessor = new LJ1200MessageProcessor( aliasList );
|
||||||
mTowerMessageFramer.addMessageListener( mMessageProcessor );
|
mTowerMessageFramer.addMessageListener( mMessageProcessor );
|
||||||
mTransponderMessageFramer.addMessageListener( mMessageProcessor );
|
mTransponderMessageFramer.addMessageListener( mMessageProcessor );
|
||||||
mMessageProcessor.setMessageListener( this );
|
mMessageProcessor.setMessageListener( getMessageListener() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class LTRNetDecoder extends Decoder
|
|||||||
|
|
||||||
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
||||||
|
|
||||||
mLTRMessageProcessor.setMessageListener( this );
|
mLTRMessageProcessor.setMessageListener( getMessageListener() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class LTRStandardDecoder extends Decoder implements IUnFilteredRealBuffer
|
|||||||
|
|
||||||
mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
|
mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
|
||||||
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
||||||
mLTRMessageProcessor.setMessageListener( this );
|
mLTRMessageProcessor.setMessageListener( getMessageListener() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public class MDCDecoder extends Decoder implements IFilteredRealBufferListener,
|
|||||||
/* Message Processor */
|
/* Message Processor */
|
||||||
mMessageProcessor = new MDCMessageProcessor( aliasList );
|
mMessageProcessor = new MDCMessageProcessor( aliasList );
|
||||||
mMessageFramer.addMessageListener( mMessageProcessor );
|
mMessageFramer.addMessageListener( mMessageProcessor );
|
||||||
mMessageProcessor.addMessageListener( this );
|
mMessageProcessor.addMessageListener(getMessageListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ public class MPT1327Decoder extends Decoder implements IFilteredRealBufferListen
|
|||||||
|
|
||||||
/* Fully decoded and framed messages processor */
|
/* Fully decoded and framed messages processor */
|
||||||
mMessageProcessor = new MPT1327MessageProcessor(aliasList);
|
mMessageProcessor = new MPT1327MessageProcessor(aliasList);
|
||||||
mMessageProcessor.setMessageListener(this);
|
mMessageProcessor.setMessageListener(getMessageListener());
|
||||||
mControlMessageFramer.addMessageListener(mMessageProcessor);
|
mControlMessageFramer.addMessageListener(mMessageProcessor);
|
||||||
mTrafficMessageFramer.addMessageListener(mMessageProcessor);
|
mTrafficMessageFramer.addMessageListener(mMessageProcessor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,70 +1,91 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.module.decode.p25;
|
package io.github.dsheirer.module.decode.p25;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
import io.github.dsheirer.instrument.Instrumentable;
|
|
||||||
import io.github.dsheirer.module.decode.Decoder;
|
import io.github.dsheirer.module.decode.Decoder;
|
||||||
import io.github.dsheirer.module.decode.DecoderType;
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
|
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
|
||||||
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
|
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
|
||||||
|
|
||||||
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
|
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider
|
||||||
{
|
{
|
||||||
private P25MessageProcessor mMessageProcessor;
|
private P25MessageProcessor mMessageProcessor;
|
||||||
|
private AliasList mAliasList;
|
||||||
public P25Decoder( AliasList aliasList )
|
|
||||||
{
|
|
||||||
mMessageProcessor = new P25MessageProcessor( aliasList );
|
|
||||||
mMessageProcessor.setMessageListener( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
super.dispose();
|
|
||||||
|
|
||||||
mMessageProcessor.dispose();
|
|
||||||
mMessageProcessor = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public P25Decoder(AliasList aliasList)
|
||||||
public DecoderType getDecoderType()
|
|
||||||
{
|
{
|
||||||
return DecoderType.P25_PHASE1;
|
mAliasList = aliasList;
|
||||||
|
mMessageProcessor = new P25MessageProcessor(mAliasList);
|
||||||
|
mMessageProcessor.setMessageListener(getMessageListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected P25MessageProcessor getMessageProcessor()
|
protected AliasList getAliasList()
|
||||||
{
|
{
|
||||||
return mMessageProcessor;
|
return mAliasList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Modulation getModulation();
|
public void dispose()
|
||||||
|
{
|
||||||
public enum Modulation
|
super.dispose();
|
||||||
{
|
|
||||||
CQPSK( "Simulcast (LSM)", "LSM" ),
|
mMessageProcessor.dispose();
|
||||||
C4FM( "Normal (C4FM)", "C4FM" );
|
mMessageProcessor = null;
|
||||||
|
}
|
||||||
private String mLabel;
|
|
||||||
private String mShortLabel;
|
@Override
|
||||||
|
public DecoderType getDecoderType()
|
||||||
private Modulation( String label, String shortLabel )
|
{
|
||||||
{
|
return DecoderType.P25_PHASE1;
|
||||||
mLabel = label;
|
}
|
||||||
mShortLabel = shortLabel;
|
|
||||||
}
|
protected P25MessageProcessor getMessageProcessor()
|
||||||
|
{
|
||||||
public String getLabel()
|
return mMessageProcessor;
|
||||||
{
|
}
|
||||||
return mLabel;
|
|
||||||
}
|
public abstract Modulation getModulation();
|
||||||
|
|
||||||
public String getShortLabel()
|
public enum Modulation
|
||||||
{
|
{
|
||||||
return mShortLabel;
|
CQPSK("Simulcast (LSM)", "LSM"),
|
||||||
}
|
C4FM("Normal (C4FM)", "C4FM");
|
||||||
|
|
||||||
public String toString()
|
private String mLabel;
|
||||||
{
|
private String mShortLabel;
|
||||||
return getLabel();
|
|
||||||
}
|
private Modulation(String label, String shortLabel)
|
||||||
}
|
{
|
||||||
|
mLabel = label;
|
||||||
|
mShortLabel = shortLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShortLabel()
|
||||||
|
{
|
||||||
|
return mShortLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,244 +1,204 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* sdrtrunk
|
* sdr-trunk
|
||||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||||
* it under the terms of the GNU General Public License as published by
|
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* later version.
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
* 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
|
* You should have received a copy of the GNU General Public License along with this program.
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package io.github.dsheirer.module.decode.p25;
|
package io.github.dsheirer.module.decode.p25;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
import io.github.dsheirer.instrument.tap.Tap;
|
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||||
import io.github.dsheirer.instrument.tap.TapGroup;
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.instrument.tap.stream.DibitTap;
|
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||||
import io.github.dsheirer.instrument.tap.stream.FloatTap;
|
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter;
|
||||||
|
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||||
|
import io.github.dsheirer.dsp.psk.DQPSKDemodulator;
|
||||||
|
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
|
||||||
|
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
import io.github.dsheirer.sample.real.RealBuffer;
|
import io.github.dsheirer.sample.complex.IComplexBufferListener;
|
||||||
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
|
public class P25_C4FMDecoder extends P25Decoder implements IComplexBufferListener, Listener<ComplexBuffer>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
|
private final static Logger mLog = LoggerFactory.getLogger(P25_C4FMDecoder.class);
|
||||||
|
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
|
||||||
/* Instrumentation Taps */
|
protected static final double SYMBOL_RATE = 4800;
|
||||||
private static final String INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT = "Tap Point: Symbol Filter Output";
|
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||||
private static final String INSTRUMENT_C4FM_SLICER_OUTPUT = "Tap Point: C4FM Slicer Output";
|
protected DQPSKDemodulator mQPSKDemodulator;
|
||||||
|
protected CostasLoop mCostasLoop;
|
||||||
|
protected P25MessageFramer mMessageFramer;
|
||||||
|
protected double mSampleRate;
|
||||||
|
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
|
||||||
|
private ComplexFIRFilter mBasebandFilter;
|
||||||
|
|
||||||
private List<TapGroup> mAvailableTaps;
|
public P25_C4FMDecoder(AliasList aliasList)
|
||||||
private C4FMSymbolFilter mSymbolFilter;
|
|
||||||
private C4FMSlicer mC4FMSlicer;
|
|
||||||
private P25MessageFramer mMessageFramer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* P25 Phase 1 C4FM Decoder processes real buffers of un-filtered,
|
|
||||||
* demodulated audio and produces decoded P25 Phase 1 Messages.
|
|
||||||
*
|
|
||||||
* Provides Frequency Control to steer external tuner source and incorporates
|
|
||||||
* internal gain control.
|
|
||||||
*
|
|
||||||
* Note: use the P25AudioModule to convert the decoded messages into audio.
|
|
||||||
*
|
|
||||||
* @param aliasList - optional (can be null) list of alias values for network
|
|
||||||
* infrastructure and subscriber identities that will be included in each
|
|
||||||
* decoded message
|
|
||||||
*/
|
|
||||||
public P25_C4FMDecoder(AliasList aliasList, int frequencyCorrectionMaximum )
|
|
||||||
{
|
|
||||||
super( aliasList );
|
|
||||||
|
|
||||||
/* Shape gain and frequency offsets to optimize sample stream */
|
|
||||||
mSymbolFilter = new C4FMSymbolFilter( frequencyCorrectionMaximum );
|
|
||||||
|
|
||||||
/* Convert samples to symbols */
|
|
||||||
mC4FMSlicer = new C4FMSlicer();
|
|
||||||
mSymbolFilter.setListener( mC4FMSlicer );
|
|
||||||
|
|
||||||
/* Sync pattern detection and message construction */
|
|
||||||
mMessageFramer = new P25MessageFramer( aliasList );
|
|
||||||
mC4FMSlicer.addListener( mMessageFramer );
|
|
||||||
|
|
||||||
/* Process and broadcast messages */
|
|
||||||
mMessageFramer.setListener( getMessageProcessor() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
super.dispose();
|
|
||||||
|
|
||||||
mSymbolFilter.dispose();
|
|
||||||
mSymbolFilter = null;
|
|
||||||
|
|
||||||
mMessageFramer.dispose();
|
|
||||||
mMessageFramer = null;
|
|
||||||
|
|
||||||
mC4FMSlicer.dispose();
|
|
||||||
mC4FMSlicer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Modulation getModulation()
|
|
||||||
{
|
|
||||||
return Modulation.C4FM;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Listener<RealBuffer> getFilteredRealBufferListener()
|
|
||||||
{
|
|
||||||
return mSymbolFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumentation taps for monitoring internal processing
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<TapGroup> getTapGroups()
|
|
||||||
{
|
{
|
||||||
if( mAvailableTaps == null )
|
super(aliasList);
|
||||||
{
|
setSampleRate(48000.0);
|
||||||
mAvailableTaps = new ArrayList<>();
|
|
||||||
|
|
||||||
TapGroup group = new TapGroup( "P25 C4FM Decoder" );
|
|
||||||
|
|
||||||
group.add( new FloatTap( INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT, 0, 0.1f ) );
|
|
||||||
group.add( new DibitTap( INSTRUMENT_C4FM_SLICER_OUTPUT, 0, 0.1f ) );
|
|
||||||
|
|
||||||
mAvailableTaps.add( group );
|
|
||||||
|
|
||||||
if( mSymbolFilter != null )
|
|
||||||
{
|
|
||||||
mAvailableTaps.addAll( mSymbolFilter.getTapGroups() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mAvailableTaps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setSampleRate(double sampleRate)
|
||||||
* Adds an instrumentation tap to monitor internal processing.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void registerTap( Tap tap )
|
|
||||||
{
|
{
|
||||||
if( mSymbolFilter != null )
|
if(sampleRate <= SYMBOL_RATE * 2)
|
||||||
{
|
{
|
||||||
mSymbolFilter.registerTap( tap );
|
throw new IllegalArgumentException("Sample rate must be at least twice the symbol rate [4800]");
|
||||||
}
|
}
|
||||||
|
|
||||||
switch( tap.getName() )
|
mSampleRate = sampleRate;
|
||||||
{
|
mBasebandFilter = new ComplexFIRFilter(getBasebandFilter(), 1.0f);
|
||||||
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
|
|
||||||
FloatTap symbolTap = (FloatTap)tap;
|
mCostasLoop = new CostasLoop(mSampleRate, SYMBOL_RATE);
|
||||||
mSymbolFilter.setListener( symbolTap );
|
|
||||||
symbolTap.setListener( mC4FMSlicer );
|
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer((float)(sampleRate / SYMBOL_RATE));
|
||||||
break;
|
mQPSKDemodulator = new DQPSKDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||||
case INSTRUMENT_C4FM_SLICER_OUTPUT:
|
|
||||||
DibitTap slicerTap = (DibitTap)tap;
|
//Message framer can trigger a symbol-inversion correction to the PLL when detected
|
||||||
|
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
|
||||||
if( mC4FMSlicer != null )
|
mMessageFramer.setListener(getMessageProcessor());
|
||||||
{
|
mQPSKDemodulator.setDibitListener(mMessageFramer);
|
||||||
mC4FMSlicer.addListener( slicerTap );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Removes the instrumentation tap.
|
public void receive(ComplexBuffer complexBuffer)
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void unregisterTap( Tap tap )
|
|
||||||
{
|
{
|
||||||
if( mSymbolFilter != null )
|
ComplexBuffer basebandFiltered = filter(complexBuffer);
|
||||||
{
|
|
||||||
mSymbolFilter.unregisterTap( tap );
|
ComplexBuffer gainApplied = mAGC.filter(basebandFiltered);
|
||||||
}
|
|
||||||
|
mQPSKDemodulator.receive(gainApplied);
|
||||||
switch( tap.getName() )
|
|
||||||
{
|
|
||||||
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
|
|
||||||
mSymbolFilter.setListener( mC4FMSlicer );
|
|
||||||
break;
|
|
||||||
case INSTRUMENT_C4FM_SLICER_OUTPUT:
|
|
||||||
DibitTap slicerTap = (DibitTap)tap;
|
|
||||||
|
|
||||||
if( mC4FMSlicer != null )
|
|
||||||
{
|
|
||||||
mC4FMSlicer.removeListener( slicerTap );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
|
||||||
tap.getName() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected ComplexBuffer filter(ComplexBuffer complexBuffer)
|
||||||
public void reset()
|
{
|
||||||
{
|
return mBasebandFilter.filter(complexBuffer);
|
||||||
// TODO Auto-generated method stub
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private double getSampleRate()
|
||||||
public void start( ScheduledExecutorService executor )
|
{
|
||||||
{
|
return mSampleRate;
|
||||||
// TODO Auto-generated method stub
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private float[] getBasebandFilter()
|
||||||
public void stop()
|
{
|
||||||
{
|
float[] filter = mBasebandFilters.get(getSampleRate());
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if(filter == null)
|
||||||
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
|
{
|
||||||
{
|
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||||
if( mSymbolFilter != null )
|
.sampleRate((int)getSampleRate())
|
||||||
{
|
.passBandCutoff(5100)
|
||||||
mSymbolFilter.setFrequencyChangeListener( listener );
|
.passBandAmplitude(1.0)
|
||||||
}
|
.passBandRipple(0.01)
|
||||||
}
|
.stopBandAmplitude(0.0)
|
||||||
|
.stopBandStart(6500)
|
||||||
|
.stopBandRipple(0.01)
|
||||||
|
.build();
|
||||||
|
|
||||||
@Override
|
try
|
||||||
public void removeFrequencyChangeListener()
|
{
|
||||||
{
|
filter = FilterFactory.getTaps(specification);
|
||||||
if( mSymbolFilter != null )
|
|
||||||
{
|
|
||||||
mSymbolFilter.setFrequencyChangeListener( null );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
}
|
||||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
catch(FilterDesignException fde)
|
||||||
{
|
{
|
||||||
if( mSymbolFilter != null )
|
mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate());
|
||||||
{
|
}
|
||||||
return mSymbolFilter.getFrequencyChangeListener();
|
if(filter != null)
|
||||||
}
|
{
|
||||||
|
mBasebandFilters.put(getSampleRate(), filter);
|
||||||
return null;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Couldn't design a C4FM symbol filter for sample rate: " + mSampleRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
super.dispose();
|
||||||
|
|
||||||
|
mBasebandFilter.dispose();
|
||||||
|
mBasebandFilter = null;
|
||||||
|
|
||||||
|
mMessageFramer.dispose();
|
||||||
|
mMessageFramer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
return new Listener<FrequencyChangeEvent>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void receive(FrequencyChangeEvent frequencyChangeEvent)
|
||||||
|
{
|
||||||
|
switch(frequencyChangeEvent.getEvent())
|
||||||
|
{
|
||||||
|
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||||
|
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||||
|
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||||
|
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||||
|
mCostasLoop.reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||||
|
{
|
||||||
|
return P25_C4FMDecoder.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Modulation getModulation()
|
||||||
|
{
|
||||||
|
return Modulation.C4FM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
mCostasLoop.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(ScheduledExecutorService executor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.module.decode.p25;
|
||||||
|
|
||||||
|
import io.github.dsheirer.alias.AliasList;
|
||||||
|
import io.github.dsheirer.dsp.psk.DQPSKDemodulatorInstrumented;
|
||||||
|
import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
|
||||||
|
import io.github.dsheirer.dsp.psk.SymbolDecisionData;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.sample.complex.Complex;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
|
|
||||||
|
public class P25_C4FMDecoderInstrumented extends P25_C4FMDecoder
|
||||||
|
{
|
||||||
|
private Listener<Double> mPLLPhaseErrorListener;
|
||||||
|
private Listener<Double> mPLLFrequencyListener;
|
||||||
|
private Listener<Double> mSamplesPerSymbolListener;
|
||||||
|
private Listener<Complex> mSymbolListener;
|
||||||
|
private Listener<ComplexBuffer> mFilteredSymbolListener;
|
||||||
|
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
|
||||||
|
|
||||||
|
public P25_C4FMDecoderInstrumented(AliasList aliasList)
|
||||||
|
{
|
||||||
|
super(aliasList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the filter method so that we can capture the filtered samples for instrumentation
|
||||||
|
*/
|
||||||
|
protected ComplexBuffer filter(ComplexBuffer complexBuffer)
|
||||||
|
{
|
||||||
|
ComplexBuffer filtered = super.filter(complexBuffer);
|
||||||
|
|
||||||
|
if(mFilteredSymbolListener != null)
|
||||||
|
{
|
||||||
|
mFilteredSymbolListener.receive(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides this method so we can correctly configure for instrumented operations
|
||||||
|
*/
|
||||||
|
public void setSampleRate(double sampleRate)
|
||||||
|
{
|
||||||
|
super.setSampleRate(sampleRate);
|
||||||
|
InterpolatingSampleBufferInstrumented instrumentedBuffer = new InterpolatingSampleBufferInstrumented((float)(sampleRate / SYMBOL_RATE));
|
||||||
|
mInterpolatingSampleBuffer = instrumentedBuffer;
|
||||||
|
|
||||||
|
DQPSKDemodulatorInstrumented instrumented = new DQPSKDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, mSampleRate);
|
||||||
|
mQPSKDemodulator = instrumented;
|
||||||
|
|
||||||
|
instrumented.setSymbolListener(mSymbolListener);
|
||||||
|
instrumented.setPLLErrorListener(mPLLPhaseErrorListener);
|
||||||
|
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
|
||||||
|
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
|
||||||
|
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
|
||||||
|
instrumented.setDibitListener(mMessageFramer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSymbolListener(Listener<Complex> listener)
|
||||||
|
{
|
||||||
|
mSymbolListener = listener;
|
||||||
|
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSymbolListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPLLPhaseErrorListener(Listener<Double> listener)
|
||||||
|
{
|
||||||
|
mPLLPhaseErrorListener = listener;
|
||||||
|
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPLLFrequencyListener(Listener<Double> listener)
|
||||||
|
{
|
||||||
|
mPLLFrequencyListener = listener;
|
||||||
|
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilteredBufferListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mFilteredSymbolListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
|
||||||
|
{
|
||||||
|
mSymbolDecisionDataListener = listener;
|
||||||
|
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSamplesPerSymbolListener(Listener<Double> listener)
|
||||||
|
{
|
||||||
|
mSamplesPerSymbolListener = listener;
|
||||||
|
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -23,13 +23,8 @@ import io.github.dsheirer.dsp.filter.Window.WindowType;
|
|||||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||||
import io.github.dsheirer.dsp.psk.LSMDemodulator;
|
import io.github.dsheirer.dsp.psk.LSMDemodulator;
|
||||||
import io.github.dsheirer.dsp.psk.QPSKPolarSlicer;
|
import io.github.dsheirer.dsp.psk.QPSKSymbolDecoder;
|
||||||
import io.github.dsheirer.instrument.tap.Tap;
|
|
||||||
import io.github.dsheirer.instrument.tap.TapGroup;
|
import io.github.dsheirer.instrument.tap.TapGroup;
|
||||||
import io.github.dsheirer.instrument.tap.stream.ComplexSampleTap;
|
|
||||||
import io.github.dsheirer.instrument.tap.stream.ComplexTap;
|
|
||||||
import io.github.dsheirer.instrument.tap.stream.DibitTap;
|
|
||||||
import io.github.dsheirer.instrument.tap.stream.QPSKTap;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||||
import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter;
|
import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter;
|
||||||
@@ -38,73 +33,72 @@ import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( P25_LSMDecoder.class );
|
private final static Logger mLog = LoggerFactory.getLogger(P25_LSMDecoder.class);
|
||||||
|
|
||||||
/* Instrumentation Taps */
|
/* Instrumentation Taps */
|
||||||
private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output";
|
private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output";
|
||||||
private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output";
|
private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output";
|
||||||
private static final String INSTRUMENT_LSM_DEMODULATOR_OUTPUT = "Tap Point: LSM Demodulator Output";
|
private static final String INSTRUMENT_LSM_DEMODULATOR_OUTPUT = "Tap Point: LSM Demodulator Output";
|
||||||
private static final String INSTRUMENT_QPSK_SLICER_OUTPUT = "Tap Point: QPSK Slicer Output";
|
private static final String INSTRUMENT_QPSK_SLICER_OUTPUT = "Tap Point: QPSK Slicer Output";
|
||||||
|
|
||||||
private List<TapGroup> mAvailableTaps;
|
private List<TapGroup> mAvailableTaps;
|
||||||
|
|
||||||
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
|
||||||
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
|
||||||
private ComplexFeedForwardGainControl mAGC =
|
|
||||||
new ComplexFeedForwardGainControl( 32 );
|
|
||||||
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
|
||||||
private QPSKPolarSlicer mQPSKSlicer = new QPSKPolarSlicer();
|
|
||||||
private P25MessageFramer mMessageFramer;
|
|
||||||
|
|
||||||
public P25_LSMDecoder( AliasList aliasList )
|
|
||||||
{
|
|
||||||
super( aliasList );
|
|
||||||
|
|
||||||
mBasebandFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass(
|
|
||||||
48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0f );
|
|
||||||
|
|
||||||
mBasebandFilter.setListener( mStreamConverter );
|
|
||||||
|
|
||||||
mStreamConverter.setListener( mAGC );
|
|
||||||
|
|
||||||
mAGC.setListener( mLSMDemodulator );
|
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
||||||
|
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
||||||
mLSMDemodulator.setSymbolListener( mQPSKSlicer );
|
private ComplexFeedForwardGainControl mAGC =
|
||||||
|
new ComplexFeedForwardGainControl(32);
|
||||||
mMessageFramer = new P25MessageFramer( aliasList, mLSMDemodulator );
|
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
||||||
mQPSKSlicer.addListener( mMessageFramer );
|
private QPSKSymbolDecoder mQPSKSlicer = new QPSKSymbolDecoder();
|
||||||
|
private P25MessageFramer mMessageFramer;
|
||||||
mMessageFramer.setListener( getMessageProcessor() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
super.dispose();
|
|
||||||
|
|
||||||
mBasebandFilter.dispose();
|
|
||||||
mBasebandFilter = null;
|
|
||||||
|
|
||||||
mStreamConverter.dispose();
|
|
||||||
mStreamConverter = null;
|
|
||||||
|
|
||||||
mAGC.dispose();
|
|
||||||
mAGC = null;
|
|
||||||
|
|
||||||
mLSMDemodulator.dispose();
|
public P25_LSMDecoder(AliasList aliasList)
|
||||||
mLSMDemodulator = null;
|
{
|
||||||
|
super(aliasList);
|
||||||
mQPSKSlicer.dispose();
|
|
||||||
mQPSKSlicer = null;
|
mBasebandFilter = new ComplexFIRFilter_CB_CB(FilterFactory.getLowPass(
|
||||||
|
48000, 7250, 8000, 60, WindowType.HANNING, true), 1.0f);
|
||||||
mMessageFramer.dispose();
|
|
||||||
mMessageFramer = null;
|
mBasebandFilter.setListener(mStreamConverter);
|
||||||
}
|
|
||||||
|
mStreamConverter.setListener(mAGC);
|
||||||
|
|
||||||
|
mAGC.setListener(mLSMDemodulator);
|
||||||
|
|
||||||
|
mLSMDemodulator.setSymbolListener(mQPSKSlicer);
|
||||||
|
|
||||||
|
mMessageFramer = new P25MessageFramer(aliasList, mLSMDemodulator.getCostasLoop());
|
||||||
|
mQPSKSlicer.addListener(mMessageFramer);
|
||||||
|
|
||||||
|
mMessageFramer.setListener(getMessageProcessor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
super.dispose();
|
||||||
|
|
||||||
|
mBasebandFilter.dispose();
|
||||||
|
mBasebandFilter = null;
|
||||||
|
|
||||||
|
mStreamConverter.dispose();
|
||||||
|
mStreamConverter = null;
|
||||||
|
|
||||||
|
mAGC.dispose();
|
||||||
|
mAGC = null;
|
||||||
|
|
||||||
|
mLSMDemodulator.dispose();
|
||||||
|
mLSMDemodulator = null;
|
||||||
|
|
||||||
|
mQPSKSlicer.dispose();
|
||||||
|
mQPSKSlicer = null;
|
||||||
|
|
||||||
|
mMessageFramer.dispose();
|
||||||
|
mMessageFramer = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
@@ -130,130 +124,34 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||||
{
|
|
||||||
return mBasebandFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Modulation getModulation()
|
|
||||||
{
|
|
||||||
return Modulation.CQPSK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a list of instrumentation taps for monitoring internal processing
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<TapGroup> getTapGroups()
|
|
||||||
{
|
{
|
||||||
if( mAvailableTaps == null )
|
return mBasebandFilter;
|
||||||
{
|
|
||||||
mAvailableTaps = new ArrayList<>();
|
|
||||||
|
|
||||||
TapGroup group = new TapGroup( "P25 LSM Decoder" );
|
|
||||||
|
|
||||||
group.add( new ComplexTap( INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f ) );
|
|
||||||
group.add( new ComplexTap( INSTRUMENT_AGC_OUTPUT, 0, 1.0f ) );
|
|
||||||
group.add( new QPSKTap( INSTRUMENT_LSM_DEMODULATOR_OUTPUT, 0, 1.0f ) );
|
|
||||||
group.add( new DibitTap( INSTRUMENT_QPSK_SLICER_OUTPUT, 0, 0.1f ) );
|
|
||||||
|
|
||||||
mAvailableTaps.add( group );
|
|
||||||
|
|
||||||
if( mLSMDemodulator != null )
|
|
||||||
{
|
|
||||||
mAvailableTaps.addAll( mLSMDemodulator.getTapGroups() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mAvailableTaps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Modulation getModulation()
|
||||||
* Adds the instrumentation tap
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void registerTap( Tap tap )
|
|
||||||
{
|
{
|
||||||
if( mLSMDemodulator != null )
|
return Modulation.CQPSK;
|
||||||
{
|
|
||||||
mLSMDemodulator.registerTap( tap );
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( tap.getName() )
|
|
||||||
{
|
|
||||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
|
||||||
ComplexSampleTap baseband = (ComplexSampleTap)tap;
|
|
||||||
mStreamConverter.setListener( baseband );
|
|
||||||
baseband.setListener( mAGC );
|
|
||||||
break;
|
|
||||||
case INSTRUMENT_AGC_OUTPUT:
|
|
||||||
ComplexSampleTap agcSymbol = (ComplexSampleTap)tap;
|
|
||||||
mAGC.setListener( agcSymbol );
|
|
||||||
agcSymbol.setListener( mLSMDemodulator );
|
|
||||||
break;
|
|
||||||
case INSTRUMENT_LSM_DEMODULATOR_OUTPUT:
|
|
||||||
QPSKTap qpsk = (QPSKTap)tap;
|
|
||||||
mLSMDemodulator.setSymbolListener( qpsk );
|
|
||||||
qpsk.setListener( mQPSKSlicer );
|
|
||||||
break;
|
|
||||||
case INSTRUMENT_QPSK_SLICER_OUTPUT:
|
|
||||||
mQPSKSlicer.addListener( (DibitTap)tap );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
|
||||||
tap.getName() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Removes the instrumentation tap
|
public void reset()
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void unregisterTap( Tap tap )
|
|
||||||
{
|
{
|
||||||
if( mLSMDemodulator != null )
|
// TODO Auto-generated method stub
|
||||||
{
|
|
||||||
mLSMDemodulator.unregisterTap( tap );
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( tap.getName() )
|
|
||||||
{
|
|
||||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
|
||||||
mStreamConverter.setListener( mAGC );
|
|
||||||
break;
|
|
||||||
case INSTRUMENT_AGC_OUTPUT:
|
|
||||||
mAGC.setListener( mLSMDemodulator );
|
|
||||||
break;
|
|
||||||
case INSTRUMENT_LSM_DEMODULATOR_OUTPUT:
|
|
||||||
mLSMDemodulator.setSymbolListener( mQPSKSlicer );
|
|
||||||
break;
|
|
||||||
case INSTRUMENT_QPSK_SLICER_OUTPUT:
|
|
||||||
mQPSKSlicer.removeListener( (DibitTap)tap );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
|
||||||
tap.getName() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset()
|
public void start(ScheduledExecutorService executor)
|
||||||
{
|
{
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
}
|
||||||
public void start( ScheduledExecutorService executor )
|
|
||||||
{
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class SyncBroadcast extends TSBKMessage
|
|||||||
|
|
||||||
if( isMicroslotsLockedToMinuteRollover() )
|
if( isMicroslotsLockedToMinuteRollover() )
|
||||||
{
|
{
|
||||||
sb.append( " MICROSLOT-MINUTE ROLLOVER:LOCKED" );
|
sb.append( " MICROSLOT-MINUTE ROLLOVER:SLOW" );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class PassportDecoder extends Decoder
|
|||||||
|
|
||||||
mPassportMessageProcessor = new PassportMessageProcessor( aliasList );
|
mPassportMessageProcessor = new PassportMessageProcessor( aliasList );
|
||||||
mPassportMessageFramer.addMessageListener( mPassportMessageProcessor );
|
mPassportMessageFramer.addMessageListener( mPassportMessageProcessor );
|
||||||
mPassportMessageProcessor.setMessageListener( this );
|
mPassportMessageProcessor.setMessageListener( getMessageListener() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -104,8 +104,8 @@ public class Tait1200Decoder extends Decoder implements IFilteredRealBufferListe
|
|||||||
mMessageFramerGPS.addMessageListener( mMessageAProcessor );
|
mMessageFramerGPS.addMessageListener( mMessageAProcessor );
|
||||||
mMessageFramerANI.addMessageListener( mMessageBProcessor );
|
mMessageFramerANI.addMessageListener( mMessageBProcessor );
|
||||||
|
|
||||||
mMessageAProcessor.setMessageListener( this );
|
mMessageAProcessor.setMessageListener(getMessageListener());
|
||||||
mMessageBProcessor.setMessageListener( this );
|
mMessageBProcessor.setMessageListener(getMessageListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class);
|
private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class);
|
||||||
|
|
||||||
public static final int AUDIO_SAMPLE_RATE = 8000;
|
public static final int AUDIO_SAMPLE_RATE = 8000;
|
||||||
|
public static final int BASEBAND_SAMPLE_RATE = 48000;
|
||||||
public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
|
public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
|
||||||
|
|
||||||
private Map<String,AudioPacketWaveRecorder> mRecorders = new HashMap<>();
|
private Map<String,AudioPacketWaveRecorder> mRecorders = new HashMap<>();
|
||||||
@@ -92,6 +93,7 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary ingest point for audio packets from all decoding channels
|
* Primary ingest point for audio packets from all decoding channels
|
||||||
|
*
|
||||||
* @param audioPacket to process
|
* @param audioPacket to process
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -114,7 +116,7 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
|
|
||||||
while(!audioPackets.isEmpty())
|
while(!audioPackets.isEmpty())
|
||||||
{
|
{
|
||||||
for(AudioPacket audioPacket: audioPackets)
|
for(AudioPacket audioPacket : audioPackets)
|
||||||
{
|
{
|
||||||
String identifier = audioPacket.getMetadata().getUniqueIdentifier();
|
String identifier = audioPacket.getMetadata().getUniqueIdentifier();
|
||||||
|
|
||||||
@@ -229,7 +231,7 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
sb.append(SystemProperties.getInstance().getApplicationFolder("recordings"));
|
sb.append(SystemProperties.getInstance().getApplicationFolder("recordings"));
|
||||||
sb.append(File.separator).append(channelName).append("_baseband");
|
sb.append(File.separator).append(channelName).append("_baseband");
|
||||||
|
|
||||||
return new ComplexBufferWaveRecorder(AUDIO_SAMPLE_RATE, sb.toString());
|
return new ComplexBufferWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public class AudioPacketWaveRecorder extends Module implements Listener<AudioPac
|
|||||||
sb.append(mFilePrefix);
|
sb.append(mFilePrefix);
|
||||||
sb.append("_");
|
sb.append("_");
|
||||||
sb.append(TimeStamp.getLongTimeStamp("_"));
|
sb.append(TimeStamp.getLongTimeStamp("_"));
|
||||||
sb.append(".wav");
|
sb.append(".tmp");
|
||||||
|
|
||||||
mFile = Paths.get(sb.toString());
|
mFile = Paths.get(sb.toString());
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class WaveWriter implements AutoCloseable
|
|||||||
|
|
||||||
public static final String DATA_CHUNK_ID = "data";
|
public static final String DATA_CHUNK_ID = "data";
|
||||||
|
|
||||||
private static final Pattern FILENAME_PATTERN = Pattern.compile("(.*_)(\\d+)(\\.wav)");
|
private static final Pattern FILENAME_PATTERN = Pattern.compile("(.*_)(\\d+)(\\.tmp)");
|
||||||
public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE;
|
public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE;
|
||||||
|
|
||||||
private AudioFormat mAudioFormat;
|
private AudioFormat mAudioFormat;
|
||||||
@@ -112,7 +112,7 @@ public class WaveWriter implements AutoCloseable
|
|||||||
|
|
||||||
while(Files.exists(mFile))
|
while(Files.exists(mFile))
|
||||||
{
|
{
|
||||||
mFile = Paths.get(mFile.toFile().getAbsolutePath().replace(".wav", "_" + version + ".wav"));
|
mFile = Paths.get(mFile.toFile().getAbsolutePath().replace(".tmp", "_" + version + ".tmp"));
|
||||||
version++;
|
version++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +134,35 @@ public class WaveWriter implements AutoCloseable
|
|||||||
mFileChannel.force(true);
|
mFileChannel.force(true);
|
||||||
mFileChannel.close();
|
mFileChannel.close();
|
||||||
mFileChannel = null;
|
mFileChannel = null;
|
||||||
|
|
||||||
|
rename();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renames the file from *.tmp to *.wav after file has been closed.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void rename() throws IOException
|
||||||
|
{
|
||||||
|
if(mFile != null && Files.exists(mFile))
|
||||||
|
{
|
||||||
|
String renamed = mFile.getFileName().toString();
|
||||||
|
renamed = renamed.replace(".tmp", ".wav");
|
||||||
|
Path renamedPath = mFile.resolveSibling(renamed);
|
||||||
|
|
||||||
|
int suffix = 1;
|
||||||
|
|
||||||
|
while(Files.exists(renamedPath))
|
||||||
|
{
|
||||||
|
String renamedWithSuffix = mFile.getFileName().toString();
|
||||||
|
renamedWithSuffix = renamedWithSuffix.replace(".tmp", "_" + suffix + ".wav");
|
||||||
|
Path renamedWithSuffixPath = mFile.resolveSibling(renamedWithSuffix);
|
||||||
|
renamedPath = mFile.resolveSibling(renamedWithSuffixPath);
|
||||||
|
suffix++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.move(mFile, renamedPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -379,7 +408,7 @@ public class WaveWriter implements AutoCloseable
|
|||||||
|
|
||||||
if(mFileRolloverCounter == 2)
|
if(mFileRolloverCounter == 2)
|
||||||
{
|
{
|
||||||
filename = filename.replace(".wav", "_2.wav");
|
filename = filename.replace(".tmp", "_2.tmp");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,14 +21,13 @@ public class ConversionUtils
|
|||||||
*/
|
*/
|
||||||
public static float[] convertFromSigned16BitSamples( ByteBuffer buffer )
|
public static float[] convertFromSigned16BitSamples( ByteBuffer buffer )
|
||||||
{
|
{
|
||||||
ShortBuffer byteBuffer =
|
ShortBuffer byteBuffer = buffer.order( ByteOrder.LITTLE_ENDIAN ).asShortBuffer();
|
||||||
buffer.order( ByteOrder.LITTLE_ENDIAN ).asShortBuffer();
|
|
||||||
|
|
||||||
float[] samples = new float[ buffer.limit() / 2 ];
|
float[] samples = new float[ buffer.limit() / 2 ];
|
||||||
|
|
||||||
for( int x = 0; x < samples.length; x++ )
|
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;
|
return samples;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
* Copyright (C) 2014 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -27,407 +27,424 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
public class Complex implements Serializable
|
public class Complex implements Serializable
|
||||||
{
|
{
|
||||||
private final static Logger mLog =
|
private final static Logger mLog = LoggerFactory.getLogger(Complex.class);
|
||||||
LoggerFactory.getLogger( Complex.class );
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private float mLeft;
|
private float mLeft;
|
||||||
private float mRight;
|
private float mRight;
|
||||||
|
|
||||||
public Complex( float left, float right )
|
|
||||||
{
|
|
||||||
mLeft = left;
|
|
||||||
mRight = right;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Complex()
|
|
||||||
{
|
|
||||||
this( 0.0f, 0.0f );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInphase( float inphase )
|
|
||||||
{
|
|
||||||
mLeft = inphase;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuadrature( float quadrature )
|
|
||||||
{
|
|
||||||
mRight = quadrature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValues( float inphase, float quadrature )
|
|
||||||
{
|
|
||||||
mLeft = inphase;
|
|
||||||
mRight = quadrature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Complex copy()
|
public Complex(float left, float right)
|
||||||
{
|
{
|
||||||
return new Complex( mLeft, mRight );
|
mLeft = left;
|
||||||
}
|
mRight = right;
|
||||||
|
}
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.append( "I:" );
|
|
||||||
sb.append( mLeft );
|
|
||||||
sb.append( " Q:" );
|
|
||||||
sb.append( mRight );
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new sample representing the conjugate of this one
|
|
||||||
*/
|
|
||||||
public Complex conjugate()
|
|
||||||
{
|
|
||||||
return new Complex( mLeft, -mRight );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public Complex()
|
||||||
* Multiplies this sample by the scalor value
|
{
|
||||||
*/
|
this(0.0f, 0.0f);
|
||||||
public void multiply( float scalor )
|
}
|
||||||
{
|
|
||||||
mLeft *= scalor;
|
|
||||||
mRight *= scalor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Complex multiply( Complex sample, float scalor )
|
|
||||||
{
|
|
||||||
return new Complex( sample.left() * scalor,
|
|
||||||
sample.right() * scalor );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the inphase component of multiplying two complex numbers, A and B.
|
|
||||||
*/
|
|
||||||
public static float multiplyInphase( float inphaseA, float quadratureA, float inphaseB, float quadratureB )
|
|
||||||
{
|
|
||||||
return ( inphaseA * inphaseB ) - ( quadratureA * quadratureB );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the quadrature component of multiplying two complex numbers, A and B.
|
|
||||||
*/
|
|
||||||
public static float multiplyQuadrature( float inphaseA, float quadratureA, float inphaseB, float quadratureB )
|
|
||||||
{
|
|
||||||
return ( quadratureA * inphaseB ) + ( inphaseA * quadratureB );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Multiplies this sample by the multiplier sample
|
|
||||||
*/
|
|
||||||
public void multiply( Complex multiplier )
|
|
||||||
{
|
|
||||||
float inphase = multiplyInphase( inphase(), quadrature(),
|
|
||||||
multiplier.inphase(), multiplier.quadrature() );
|
|
||||||
float quadrature = multiplyQuadrature( inphase(), quadrature(),
|
|
||||||
multiplier.inphase(), multiplier.quadrature() );
|
|
||||||
|
|
||||||
mLeft = inphase;
|
|
||||||
mRight = quadrature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Multiplies both samples returning a new sample with the results
|
|
||||||
*/
|
|
||||||
public static Complex multiply( Complex sample1, Complex sample2 )
|
|
||||||
{
|
|
||||||
float inphase = multiplyInphase( sample1.inphase(), sample1.quadrature(),
|
|
||||||
sample2.inphase(), sample2.quadrature() );
|
|
||||||
float quadrature = multiplyQuadrature( sample1.inphase(), sample1.quadrature(),
|
|
||||||
sample2.inphase(), sample2.quadrature() );
|
|
||||||
|
|
||||||
return new Complex( inphase, quadrature );
|
public void setInphase(float inphase)
|
||||||
}
|
{
|
||||||
|
mLeft = inphase;
|
||||||
|
}
|
||||||
|
|
||||||
public static Complex multiply( Complex sample,
|
public void setQuadrature(float quadrature)
|
||||||
Float inphase, Float quadrature )
|
{
|
||||||
{
|
mRight = quadrature;
|
||||||
float i = multiplyInphase( sample.inphase(), sample.quadrature(),
|
}
|
||||||
inphase, quadrature );
|
|
||||||
float q = multiplyQuadrature( sample.inphase(), sample.quadrature(),
|
|
||||||
inphase, quadrature );
|
|
||||||
|
|
||||||
return new Complex( i, q );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Complex multiply( Float inphase, Float quadrature, Complex sample )
|
|
||||||
{
|
|
||||||
float i = multiplyInphase( inphase, quadrature, sample.inphase(), sample.quadrature() );
|
|
||||||
float q = multiplyQuadrature( inphase, quadrature, sample.inphase(), sample.quadrature() );
|
|
||||||
|
|
||||||
return new Complex( i, q );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Complex multiply( float inphaseA, float quadratureA, float inphaseB, float quadratureB )
|
public void setValues(float inphase, float quadrature)
|
||||||
{
|
{
|
||||||
float i = multiplyInphase( inphaseA, quadratureA, inphaseB, quadratureB );
|
mLeft = inphase;
|
||||||
float q = multiplyQuadrature( inphaseA, quadratureA, inphaseB, quadratureB );
|
mRight = quadrature;
|
||||||
|
}
|
||||||
return new Complex( i, q );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the adder sample value to this sample
|
|
||||||
*/
|
|
||||||
public void add( Complex adder )
|
|
||||||
{
|
|
||||||
mLeft += adder.left();
|
|
||||||
mRight += adder.right();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void setValues(Complex sampleToCopy)
|
||||||
* Adds the two complex samples returning a new complex sample with the result
|
{
|
||||||
*/
|
mLeft = sampleToCopy.left();
|
||||||
public static Complex add( Complex first, Complex second )
|
mRight = sampleToCopy.right();
|
||||||
{
|
}
|
||||||
return new Complex( first.left() + second.left(),
|
|
||||||
first.right() + second.right() );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public Complex copy()
|
||||||
* Adds the two complex samples returning a new complex sample with the result
|
{
|
||||||
*/
|
return new Complex(mLeft, mRight);
|
||||||
public static Complex subtract( Complex first, Complex second )
|
}
|
||||||
{
|
|
||||||
return new Complex( first.left() - second.left(),
|
|
||||||
first.right() - second.right() );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public String toString()
|
||||||
* Magnitude of this sample
|
{
|
||||||
*/
|
StringBuilder sb = new StringBuilder();
|
||||||
public float magnitude()
|
|
||||||
{
|
|
||||||
return (float)Math.sqrt( magnitudeSquared() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float magnitude( float inphase, float quadrature )
|
|
||||||
{
|
|
||||||
return (float)Math.sqrt( ( inphase * inphase ) + ( quadrature * quadrature ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
sb.append("I:");
|
||||||
* Magnitude squared of this sample
|
sb.append(mLeft);
|
||||||
*/
|
sb.append(" Q:");
|
||||||
public float magnitudeSquared()
|
sb.append(mRight);
|
||||||
{
|
|
||||||
return norm();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return sb.toString();
|
||||||
* Norm of this sample = ( i * i ) + ( q * q )
|
}
|
||||||
*/
|
|
||||||
public float norm()
|
|
||||||
{
|
|
||||||
return (float)( ( inphase() * inphase() ) +
|
|
||||||
( quadrature() * quadrature() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the vector length to 1 (unit circle)
|
|
||||||
*/
|
|
||||||
public void normalize()
|
|
||||||
{
|
|
||||||
float magnitude = magnitude();
|
|
||||||
|
|
||||||
if( magnitude != 0 )
|
|
||||||
{
|
|
||||||
multiply( (float)( 1.0f / magnitude() ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the vector length to 1 (unit circle),
|
* Returns a new sample representing the conjugate of this one
|
||||||
* avoiding square root multiplication
|
*/
|
||||||
*/
|
public Complex conjugate()
|
||||||
public void fastNormalize()
|
{
|
||||||
{
|
return new Complex(mLeft, -mRight);
|
||||||
multiply( (float)( 1.95f - magnitudeSquared() ) );
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public float left()
|
|
||||||
{
|
|
||||||
return mLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float right()
|
|
||||||
{
|
|
||||||
return mRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float inphase()
|
/**
|
||||||
{
|
* Multiplies this sample by the scalor value
|
||||||
return mLeft;
|
*/
|
||||||
}
|
public void multiply(float scalor)
|
||||||
|
{
|
||||||
/**
|
mLeft *= scalor;
|
||||||
* Absolute value of in-phase component
|
mRight *= scalor;
|
||||||
*/
|
}
|
||||||
public float inPhaseAbsolute()
|
|
||||||
{
|
|
||||||
return Math.abs( mLeft );
|
|
||||||
}
|
|
||||||
|
|
||||||
public float quadrature()
|
public static Complex multiply(Complex sample, float scalor)
|
||||||
{
|
{
|
||||||
return mRight;
|
return new Complex(sample.left() * scalor,
|
||||||
}
|
sample.right() * scalor);
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Absolute value of quadrature component
|
|
||||||
*/
|
|
||||||
public float quadratureAbsolute()
|
|
||||||
{
|
|
||||||
return Math.abs( mRight );
|
|
||||||
}
|
|
||||||
|
|
||||||
public float x()
|
/**
|
||||||
{
|
* Calculates the inphase component of multiplying two complex numbers, A and B.
|
||||||
return mLeft;
|
*/
|
||||||
}
|
public static float multiplyInphase(float inphaseA, float quadratureA, float inphaseB, float quadratureB)
|
||||||
|
{
|
||||||
public float y()
|
return (inphaseA * inphaseB) - (quadratureA * quadratureB);
|
||||||
{
|
}
|
||||||
return mRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float real()
|
|
||||||
{
|
|
||||||
return mLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Absolute value of real component
|
* Calculates the quadrature component of multiplying two complex numbers, A and B.
|
||||||
*/
|
*/
|
||||||
public float realAbsolute()
|
public static float multiplyQuadrature(float inphaseA, float quadratureA, float inphaseB, float quadratureB)
|
||||||
{
|
{
|
||||||
return Math.abs( mLeft );
|
return (quadratureA * inphaseB) + (inphaseA * quadratureB);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float imaginary()
|
/**
|
||||||
{
|
* Multiplies this sample by the multiplier sample
|
||||||
return mRight;
|
*/
|
||||||
}
|
public void multiply(Complex multiplier)
|
||||||
|
{
|
||||||
|
float inphase = multiplyInphase(inphase(), quadrature(),
|
||||||
|
multiplier.inphase(), multiplier.quadrature());
|
||||||
|
float quadrature = multiplyQuadrature(inphase(), quadrature(),
|
||||||
|
multiplier.inphase(), multiplier.quadrature());
|
||||||
|
|
||||||
/**
|
mLeft = inphase;
|
||||||
* Absolute value of imaginary component
|
mRight = quadrature;
|
||||||
*/
|
}
|
||||||
public float imaginaryAbsolute()
|
|
||||||
{
|
|
||||||
return Math.abs( mRight );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the greater absolute value between left and right values
|
* Multiplies both samples returning a new sample with the results
|
||||||
*/
|
*/
|
||||||
public float maximumAbsolute()
|
public static Complex multiply(Complex sample1, Complex sample2)
|
||||||
{
|
{
|
||||||
if( Math.abs( mLeft ) > Math.abs( mRight ) )
|
float inphase = multiplyInphase(sample1.inphase(), sample1.quadrature(),
|
||||||
{
|
sample2.inphase(), sample2.quadrature());
|
||||||
return Math.abs( mLeft );
|
float quadrature = multiplyQuadrature(sample1.inphase(), sample1.quadrature(),
|
||||||
}
|
sample2.inphase(), sample2.quadrature());
|
||||||
else
|
|
||||||
{
|
|
||||||
return Math.abs( mRight );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return new Complex(inphase, quadrature);
|
||||||
* Creates a new complex sample representing the angle with unit circle
|
}
|
||||||
* magnitude
|
|
||||||
* @param angle in radians
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static Complex fromAngle( float angle )
|
|
||||||
{
|
|
||||||
return new Complex( (float)Math.cos( angle ),
|
|
||||||
(float)Math.sin( angle ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public static Complex multiply(Complex sample,
|
||||||
* Angle of this sample in radians
|
Float inphase, Float quadrature)
|
||||||
*/
|
{
|
||||||
public float angle()
|
float i = multiplyInphase(sample.inphase(), sample.quadrature(),
|
||||||
{
|
inphase, quadrature);
|
||||||
return (float)Math.atan2( y(), x() );
|
float q = multiplyQuadrature(sample.inphase(), sample.quadrature(),
|
||||||
}
|
inphase, quadrature);
|
||||||
|
|
||||||
/**
|
return new Complex(i, q);
|
||||||
* Constrains the i and q quantities to +/- value.
|
}
|
||||||
*
|
|
||||||
* @param value - maximum absolute value
|
|
||||||
*/
|
|
||||||
public void clip( float value )
|
|
||||||
{
|
|
||||||
if( mLeft > value )
|
|
||||||
{
|
|
||||||
mLeft = value;
|
|
||||||
}
|
|
||||||
else if( mLeft < -value )
|
|
||||||
{
|
|
||||||
mLeft = -value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mRight > value )
|
|
||||||
{
|
|
||||||
mRight = value;
|
|
||||||
}
|
|
||||||
else if( mRight < -value )
|
|
||||||
{
|
|
||||||
mRight = -value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public static Complex multiply(Float inphase, Float quadrature, Complex sample)
|
||||||
* Angle of this sample in degrees
|
{
|
||||||
*/
|
float i = multiplyInphase(inphase, quadrature, sample.inphase(), sample.quadrature());
|
||||||
public float polarAngle()
|
float q = multiplyQuadrature(inphase, quadrature, sample.inphase(), sample.quadrature());
|
||||||
{
|
|
||||||
return (float)Math.toDegrees( angle() );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return new Complex(i, q);
|
||||||
* Provides an approximate magnitude value for this sample.
|
}
|
||||||
*/
|
|
||||||
public float envelope()
|
|
||||||
{
|
|
||||||
return envelope( mLeft, mRight );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float envelope( float inphase, float quadrature )
|
|
||||||
{
|
|
||||||
float inphaseAbsolute = Math.abs( inphase );
|
|
||||||
float quadratureAbsolute = Math.abs( quadrature );
|
|
||||||
|
|
||||||
if( inphaseAbsolute > quadratureAbsolute )
|
|
||||||
{
|
|
||||||
return inphaseAbsolute + ( 0.4f * quadratureAbsolute );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return quadratureAbsolute + ( 0.4f * inphaseAbsolute );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main( String[] args )
|
|
||||||
{
|
|
||||||
double angle = Math.PI; //180 degrees
|
|
||||||
|
|
||||||
Complex s1 = new Complex( (float)Math.sin( angle ), (float)Math.cos( angle ) );
|
|
||||||
|
|
||||||
Complex tap = new Complex( 1.0f, -0.1f );
|
public static Complex multiply(float inphaseA, float quadratureA, float inphaseB, float quadratureB)
|
||||||
|
{
|
||||||
|
float i = multiplyInphase(inphaseA, quadratureA, inphaseB, quadratureB);
|
||||||
|
float q = multiplyQuadrature(inphaseA, quadratureA, inphaseB, quadratureB);
|
||||||
|
|
||||||
Complex convolved = Complex.multiply( s1, tap );
|
return new Complex(i, q);
|
||||||
|
}
|
||||||
mLog.debug( "s: " + s1.toString() +
|
|
||||||
" t: " + tap.toString() +
|
/**
|
||||||
" convolved: " + convolved.toString() );
|
* Adds the adder sample value to this sample
|
||||||
|
*/
|
||||||
}
|
public void add(Complex adder)
|
||||||
|
{
|
||||||
|
mLeft += adder.left();
|
||||||
|
mRight += adder.right();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the two complex samples returning a new complex sample with the result
|
||||||
|
*/
|
||||||
|
public static Complex add(Complex first, Complex second)
|
||||||
|
{
|
||||||
|
return new Complex(first.left() + second.left(),
|
||||||
|
first.right() + second.right());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the two complex samples returning a new complex sample with the result
|
||||||
|
*/
|
||||||
|
public static Complex subtract(Complex first, Complex second)
|
||||||
|
{
|
||||||
|
return new Complex(first.left() - second.left(),
|
||||||
|
first.right() - second.right());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magnitude of this sample
|
||||||
|
*/
|
||||||
|
public float magnitude()
|
||||||
|
{
|
||||||
|
return (float)Math.sqrt(magnitudeSquared());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float magnitude(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
return (float)Math.sqrt((inphase * inphase) + (quadrature * quadrature));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magnitude squared of this sample
|
||||||
|
*/
|
||||||
|
public float magnitudeSquared()
|
||||||
|
{
|
||||||
|
return norm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Norm of this sample = ( i * i ) + ( q * q )
|
||||||
|
*/
|
||||||
|
public float norm()
|
||||||
|
{
|
||||||
|
return (float)((inphase() * inphase()) +
|
||||||
|
(quadrature() * quadrature()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the vector length to 1 (unit circle)
|
||||||
|
*/
|
||||||
|
public void normalize()
|
||||||
|
{
|
||||||
|
float magnitude = magnitude();
|
||||||
|
|
||||||
|
if(magnitude != 0)
|
||||||
|
{
|
||||||
|
multiply((float)(1.0f / magnitude()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the vector length to 1 (unit circle),
|
||||||
|
* avoiding square root multiplication
|
||||||
|
*/
|
||||||
|
public void fastNormalize()
|
||||||
|
{
|
||||||
|
multiply((float)(1.95f - magnitudeSquared()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public float left()
|
||||||
|
{
|
||||||
|
return mLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float right()
|
||||||
|
{
|
||||||
|
return mRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float inphase()
|
||||||
|
{
|
||||||
|
return mLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute value of in-phase component
|
||||||
|
*/
|
||||||
|
public float inPhaseAbsolute()
|
||||||
|
{
|
||||||
|
return Math.abs(mLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float quadrature()
|
||||||
|
{
|
||||||
|
return mRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute value of quadrature component
|
||||||
|
*/
|
||||||
|
public float quadratureAbsolute()
|
||||||
|
{
|
||||||
|
return Math.abs(mRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float x()
|
||||||
|
{
|
||||||
|
return mLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float y()
|
||||||
|
{
|
||||||
|
return mRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float real()
|
||||||
|
{
|
||||||
|
return mLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute value of real component
|
||||||
|
*/
|
||||||
|
public float realAbsolute()
|
||||||
|
{
|
||||||
|
return Math.abs(mLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float imaginary()
|
||||||
|
{
|
||||||
|
return mRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute value of imaginary component
|
||||||
|
*/
|
||||||
|
public float imaginaryAbsolute()
|
||||||
|
{
|
||||||
|
return Math.abs(mRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the greater absolute value between left and right values
|
||||||
|
*/
|
||||||
|
public float maximumAbsolute()
|
||||||
|
{
|
||||||
|
if(Math.abs(mLeft) > Math.abs(mRight))
|
||||||
|
{
|
||||||
|
return Math.abs(mLeft);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Math.abs(mRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new complex sample representing the angle with unit circle
|
||||||
|
* magnitude
|
||||||
|
*
|
||||||
|
* @param angle in radians
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Complex fromAngle(double angle)
|
||||||
|
{
|
||||||
|
return new Complex((float)Math.cos(angle), (float)Math.sin(angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets this complex instance with the specified angle in radians.
|
||||||
|
* @param angle in radians
|
||||||
|
*/
|
||||||
|
public void setAngle(double angle)
|
||||||
|
{
|
||||||
|
setInphase((float)Math.cos(angle));
|
||||||
|
setQuadrature((float)Math.sin(angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Angle of this sample in radians
|
||||||
|
*/
|
||||||
|
public float angle()
|
||||||
|
{
|
||||||
|
return (float)Math.atan2(y(), x());
|
||||||
|
}
|
||||||
|
|
||||||
|
public float angleDegrees()
|
||||||
|
{
|
||||||
|
return (float)Math.toDegrees(angle());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constrains the i and q quantities to +/- value.
|
||||||
|
*
|
||||||
|
* @param value - maximum absolute value
|
||||||
|
*/
|
||||||
|
public void clip(float value)
|
||||||
|
{
|
||||||
|
if(mLeft > value)
|
||||||
|
{
|
||||||
|
mLeft = value;
|
||||||
|
}
|
||||||
|
else if(mLeft < -value)
|
||||||
|
{
|
||||||
|
mLeft = -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mRight > value)
|
||||||
|
{
|
||||||
|
mRight = value;
|
||||||
|
}
|
||||||
|
else if(mRight < -value)
|
||||||
|
{
|
||||||
|
mRight = -value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Angle of this sample in degrees
|
||||||
|
*/
|
||||||
|
public float polarAngle()
|
||||||
|
{
|
||||||
|
return (float)Math.toDegrees(angle());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an approximate magnitude value for this sample.
|
||||||
|
*/
|
||||||
|
public float envelope()
|
||||||
|
{
|
||||||
|
return envelope(mLeft, mRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float envelope(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
float inphaseAbsolute = Math.abs(inphase);
|
||||||
|
float quadratureAbsolute = Math.abs(quadrature);
|
||||||
|
|
||||||
|
if(inphaseAbsolute > quadratureAbsolute)
|
||||||
|
{
|
||||||
|
return inphaseAbsolute + (0.4f * quadratureAbsolute);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return quadratureAbsolute + (0.4f * inphaseAbsolute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
Complex ROTATE_FROM_MINUS_45 = Complex.fromAngle(1.0 * Math.PI / 4.0);
|
||||||
|
Complex ROTATE_FROM_PLUS_135 = Complex.fromAngle(-3.0 * Math.PI / 4.0);
|
||||||
|
mLog.debug("REF: " + ROTATE_FROM_MINUS_45.toString());
|
||||||
|
|
||||||
|
Complex symbol = new Complex(0.7665009f, -0.6422432f);
|
||||||
|
mLog.debug("SYM: " + symbol.toString());
|
||||||
|
|
||||||
|
symbol.multiply(ROTATE_FROM_MINUS_45);
|
||||||
|
mLog.debug("DER: " + symbol.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.source;
|
package io.github.dsheirer.source;
|
||||||
|
|
||||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||||
@@ -10,22 +25,24 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public interface IControllableFileSource
|
public interface IControllableFileSource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Opens the file source
|
* Opens the file source
|
||||||
*/
|
*/
|
||||||
public void open() throws IOException, UnsupportedAudioFileException;
|
public void open() throws IOException, UnsupportedAudioFileException;
|
||||||
|
|
||||||
public void close() throws IOException;
|
public void close() throws IOException;
|
||||||
|
|
||||||
public File getFile();
|
public File getFile();
|
||||||
|
|
||||||
public void next( int frames ) throws IOException;
|
public void next(int frames) throws IOException;
|
||||||
|
|
||||||
public void next( int frames, boolean broadcast ) throws IOException;
|
public void next(int frames, boolean broadcast) throws IOException;
|
||||||
|
|
||||||
public long getFrameCount() throws IOException;
|
public long getFrameCount() throws IOException;
|
||||||
|
|
||||||
public void setListener( IFrameLocationListener listener );
|
public int getSampleRate();
|
||||||
|
|
||||||
public void removeListener( IFrameLocationListener listener );
|
public void setListener(IFrameLocationListener listener);
|
||||||
|
|
||||||
|
public void removeListener(IFrameLocationListener listener);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user