Compare commits

...

29 Commits

Author SHA1 Message Date
Denny Sheirer
3fe070987a Resolves issue with clunky process for adding recording tuners. (#605) 2019-11-23 12:55:12 -05:00
Denny Sheirer
00d5610213 Resolves issue with the multi-frequency tuner source and P25/MPT1327 (#603)
Resolves issue with the multi-frequency tuner source and P25/MPT1327 (#603) rotating channel frequency configurations failing to maintain lock
on the channel.

Converted the ChannelRotationMonitor from using identifier updates to
now using DecoderStateEvents so that the ChannelState can supply a
steady stream of channel state events to the monitor.
2019-11-23 10:19:23 -05:00
Denny Sheirer
d3c1a605df Resolves issue with application version display in title bar (#601) 2019-11-22 09:47:09 -05:00
Denny Sheirer
85df151a96 Updates map tile fetcher to use TLSv1.2 when connecting to (#599)
openstreetmap.  Java13 uses TLSv1.3 by default and that's not supported
by the tile server.
2019-11-22 09:21:28 -05:00
Denny Sheirer
8915797aac Updates gradle build to use gradle 6 and Java 13.0.1 (#596) 2019-11-19 19:41:01 -05:00
Denny
03a5d71a95 Updates recording tuner to accept center frequency commands. 2019-11-18 19:42:01 -05:00
Denny Sheirer
4158d4ac10 Resolves #570 map tile data not downloading.
Also resolves issue with the default map loading icon pointing to an old image reference.
2019-11-17 11:41:41 -05:00
Denny Sheirer
f934676bd0 #296 APCO25 Phase II Decoder Implementation (#589)
P25 Phase 2 Decoder implementation.
2019-11-17 06:56:52 -05:00
Denny
d5b802e794 Updates next version to 0.4.0 beta 1. 2019-02-17 14:51:57 -05:00
Denny Sheirer
139227e46d Resolves #535 (#540)
Updates Icecast HTTP (2.4) streaming client to perpetually attempt to
reconnect to the server, even if there are (temp) errors.
2019-02-17 06:12:11 -05:00
Denny Sheirer
2394d51a51 Resolves #537 (#539)
Updates application title bar with currently displayed tuner.
2019-02-17 05:49:26 -05:00
Denny Sheirer
d9fa2bea5b Resolves #533. Updates broadcastify stream editor to use new (#538)
radio reference client library and adds support for storing user
credentials in user preferences.

Removes legacy apache axis SOAP and WSDL libraries calls to radio
reference web services -- not required by the new library.
2019-02-17 05:34:31 -05:00
Denny Sheirer
0cb360eeed Resovles #531 updates gradle build script to remove extra jfx runtimes (#532) 2019-02-02 15:17:11 +00:00
Denny Sheirer
ea472f873f Resolves #517 NPE thrown by TalkgroupRangeEditor (#530) 2019-02-02 14:24:23 +00:00
Denny Sheirer
8cbdbd527e Resolves #525 NPE in P25 activity summary report. (#529) 2019-02-02 13:40:57 +00:00
Denny Sheirer
ff0d893bd9 Updates build script to use latest beryx runtime zip plugin and adds (#528)
validation to check for each of the platform-specific JDKs before
executing the runtimeZip task.
2019-02-02 13:37:09 +00:00
Denny
7b924d0486 Removes debug logging 2019-01-27 20:21:46 -05:00
Denny
712eb07f4b Updates build to 0.4.0 alpha 8 2019-01-27 19:59:04 -05:00
Denny Sheirer
d5c3ecc7df Resolves #519 (#520)
- Clears stale patch groups
- Reworks the audio playback panel label update code to properly update
  talkgroups and aliases in concert with the audio playback.
- Removes code that was truncating playback aliases at 32 characters.
2019-01-28 00:58:01 +00:00
Denny Sheirer
c4bf089667 502 icecast http connect error (#518)
* Resolves #502

Resolves issue with connecting to icecast http streaming server.

The issue stems from Icecast HTTP (2.4+) servers not sending a full
HTTP OK response (missing header tokens) to a connection request.

Prior to the upgrade to apache mina 2.0.19, we received a specific
exception and handled it and set the status to connected.

After the upgrade, apache mina is now sending an array index out of
bounds exception when it decodes the incomplete http response.

So, we now catch that specific exception, inspect the http response
hex bytes and set the status to connected.
2019-01-27 14:00:57 +00:00
Denny Sheirer
378b03e06e 515 Patch Group Manager Cross Linking of Talk and Patch Groups (#516)
* Stores minimal main application window location and size/layout settings
to allow application to be restored to the same size/location on
restart.

* Removes talkgroup swapping from the patch group manager which may have
been causing talkgroup <> patch group cross-linking on certain systems.
2019-01-27 12:25:08 +00:00
Denny Sheirer
34235382fb 513 persist application layout across restarts (#514)
* Stores minimal main application window location and size/layout settings
to allow application to be restored to the same size/location on
restart.

* Adds column width monitor to all tables in application that stores the
column width settings in the user preferences so that on restarting
the application the table column widths are restored to the previous
widths.
2019-01-27 12:05:20 +00:00
Denny Sheirer
c05aff0768 Updates alias controller so that when adding an alias to a filtered (#512)
column view it doesn't throw an error.
2019-01-26 13:03:59 +00:00
Denny Sheirer
f0c3e0fd1a Adds logging/warning when alias list contains talkgroups or talkgroup (#511)
range collisions or overlaps.
2019-01-26 11:38:42 +00:00
Denny Sheirer
d601cbda0e Resolves issue with missing case statements in talkgroup format classes. (#509) 2019-01-26 11:11:41 +00:00
Denny Sheirer
2653dc6b38 Removes debug logging. (#508) 2019-01-26 11:02:43 +00:00
Denny Sheirer
48cbbfc656 Updates beryx gradle plugin to resolve issue where launcher script (#503)
did not like '&' ampersand in the execution path.
2019-01-23 09:21:16 +00:00
Denny
d6892b9203 Updates build version to 0.4.0 alpha 7 2019-01-21 04:23:42 -05:00
Denny Sheirer
59f0844fcc 499 Audio Playback Talkgroup Display (#500)
* Resolves issue with non-aliased talkgroup values not displaying in
audio playback (left/right/mono) windows.

* Adds buildSdr build target back into gradle script.
2019-01-21 09:22:31 +00:00
600 changed files with 37612 additions and 6948 deletions

2
.gitignore vendored
View File

@@ -17,7 +17,7 @@ out/
*.class
*.log
# Package Files #
*.jar
#*.jar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

2
.idea/compiler.xml generated
View File

@@ -4,6 +4,8 @@
<bytecodeTargetLevel>
<module name="sdr-trunk_main" target="11" />
<module name="sdr-trunk_test" target="11" />
<module name="sdrtrunk-sdr-trunk_main" target="11" />
<module name="sdrtrunk-sdr-trunk_test" target="11" />
</bytecodeTargetLevel>
</component>
</project>

View File

@@ -1,12 +1,10 @@
<component name="libraryTable">
<library name="imports">
<CLASSES>
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.3.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/java-lame-3.98.4.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/radioreference-14.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/wsdl4j-1.5.1.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-math3-3.6.1.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libresample4j-0.1.3.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/jmbe-api-1.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

4
.idea/modules.xml generated
View File

@@ -2,9 +2,11 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdrtrunk-sdr-trunk_main.iml" filepath="$PROJECT_DIR$/.idea/modules/sdrtrunk-sdr-trunk_main.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdrtrunk-sdr-trunk_test.iml" filepath="$PROJECT_DIR$/.idea/modules/sdrtrunk-sdr-trunk_test.iml" group="sdr-trunk" />
</modules>
</component>
</project>

View File

@@ -1,37 +1,30 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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/>
* *****************************************************************************
*/
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
}
}
plugins {
id 'application'
id 'java'
id 'idea'
id 'org.openjfx.javafxplugin' version '0.0.6'
id 'org.beryx.runtime' version '1.1.1'
id 'org.beryx.runtime' version '1.8.0'
}
repositories {
@@ -39,7 +32,7 @@ repositories {
mavenCentral()
}
version = '0.4.0-alpha.6'
version = '0.4.0-beta.1'
sourceCompatibility = '11'
sourceSets {
@@ -54,6 +47,7 @@ dependencies {
compile 'ch.qos.logback:logback-core:1.2.3'
compile 'ch.qos.logback:logback-classic:1.2.3'
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
compile 'com.fazecast:jSerialComm:2.5.0'
compile 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
compile 'com.github.jiconfont:jiconfont-javafx:1.0.0'
compile 'com.github.jiconfont:jiconfont-swing:1.0.1'
@@ -61,10 +55,10 @@ dependencies {
compile 'com.google.guava:guava:27.0.1-jre'
compile 'com.jidesoft:jide-oss:3.6.18'
compile 'com.miglayout:miglayout-swing:5.2'
compile 'eu.hansolo:charts:1.0.5'
compile 'io.github.dsheirer:radio-reference-api:15.0.1'
compile 'javax.usb:usb-api:1.0.2'
compile 'net.coderazzi:tablefilter-swing:5.4.0'
compile 'org.apache.axis:axis:1.4'
compile 'org.apache.axis:axis-jaxrpc:1.4'
compile 'org.apache.commons:commons-lang3:3.8.1'
compile 'org.apache.commons:commons-io:1.3.2'
compile 'org.apache.mina:mina-core:2.0.19'
@@ -72,15 +66,15 @@ dependencies {
compile "org.openjfx:javafx-base:11:win"
compile "org.openjfx:javafx-base:11:linux"
compile "org.openjfx:javafx-base:11:mac"
compile "org.openjfx:javafx-controls:11:win"
compile "org.openjfx:javafx-controls:11:linux"
compile "org.openjfx:javafx-controls:11:mac"
compile "org.openjfx:javafx-graphics:11:win"
compile "org.openjfx:javafx-graphics:11:linux"
compile "org.openjfx:javafx-graphics:11:mac"
compile "org.openjfx:javafx-swing:11:win"
compile "org.openjfx:javafx-swing:11:linux"
compile "org.openjfx:javafx-swing:11:mac"
compile "org.openjfx:javafx-controls:11.0.2:win"
compile "org.openjfx:javafx-controls:11.0.2:linux"
compile "org.openjfx:javafx-controls:11.0.2:mac"
compile "org.openjfx:javafx-graphics:11.0.2:win"
compile "org.openjfx:javafx-graphics:11.0.2:linux"
compile "org.openjfx:javafx-graphics:11.0.2:mac"
compile "org.openjfx:javafx-swing:11.0.2:win"
compile "org.openjfx:javafx-swing:11.0.2:linux"
compile "org.openjfx:javafx-swing:11.0.2:mac"
compile 'org.slf4j:slf4j-api:1.7.25'
compile 'org.usb4java:libusb4java:1.3.0'
compile 'org.usb4java:usb4java:1.3.0'
@@ -103,11 +97,131 @@ javafx {
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
}
jar {
manifest {
attributes (
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Class-Path' : 'jmbe-1.0.0.jar',
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
)
}
}
/**
* Java Development Kit (JDK) locations. These paths must point to the 'bin' directory
* within a JDK for each of the specified architectures. These JDKs are used by the
* runtime and runtimeZip tasks to produce platform-specific builds
*/
def jdkLinux_x64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-13.0.1'
def jdkLinux_x86 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-13.0.1'
def jdkOsx_x64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-13.0.1.jdk'
def jdkWindows_x64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-13.0.1'
def jdkWindows_x86 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-13.0.1'
//def jdkLinux_aarch64 = '/media/denny/WD250GB/java/bellsoft/linux-aarch64/jdk-11.0.2'
//def jdkLinux_arm32 = '/media/denny/WD250GB/java/bellsoft/linux-arm32/jdk-11.0.2'
//def jfxLinux_arm32 = '/media/denny/WD250GB/java/bellsoft/linux-arm32/armv6hf-sdk/lib'
runtime {
targetPlatform('linux-x64', '/media/denny/WD250GB/java/linux/jdk-11.0.1')
targetPlatform('osx-x64', '/media/denny/WD250GB/java/osx/jdk-11.0.1.jdk/Contents/Home')
targetPlatform('windows-x64', '/media/denny/WD250GB/java/windows/jdk-11.0.1')
targetPlatform('linux-x64', jdkLinux_x64)
targetPlatform('linux-x86', jdkLinux_x86)
targetPlatform('osx-x64', jdkOsx_x64)
targetPlatform('windows-x64', jdkWindows_x64)
targetPlatform('windows-x86', jdkWindows_x86)
// targetPlatform('linux-aarch64', jdkLinux_aarch64)
// targetPlatform('linux-arm32', jdkLinux_arm32)
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop']
imageZip = file("$buildDir/image/sdr-trunk-" + version + ".zip")
}
/**
* Check for existence of JDK folders for each supported platform before creating runtime images
*/
tasks.runtime.doFirst {
def linux_x64 = new File(jdkLinux_x64)
if(!linux_x64.exists())
{
println("Linux x64 JDK was not found at " + jdkLinux_x64)
throw new GradleException("Cannot find Java Development Kit (JDK) for linux-x64 architecture. " +
"Please update the build.gradle script to provide the correct path")
}
def linux_x86 = new File(jdkLinux_x86)
if(!linux_x86.exists())
{
println("Linux x86 JDK was not found at " + jdkLinux_x86)
throw new GradleException("Cannot find Java Development Kit (JDK) for linux-x86 32-bit architecture. " +
"Please update the build.gradle script to provide the correct path")
}
def osx_x64 = new File(jdkOsx_x64)
if(!osx_x64.exists())
{
println("OSX x64 JDK was not found at " + jdkOsx_x64);
throw new GradleException("Cannot find Java Development Kit (JDK) for osx-x64 architecture. " +
"Please update the build.gradle script to provide the correct path")
}
def windows_x64 = new File(jdkWindows_x64)
if(!windows_x64.exists())
{
println("Windows x64 JDK was not found at " + jdkWindows_x64)
throw new GradleException("Cannot find Java Development Kit (JDK) for windows-x64 architecture. " +
"Please update the build.gradle script to provide the correct path")
}
def windows_x86 = new File(jdkWindows_x86)
if(!windows_x86.exists())
{
println("Windows x86 32-bit JDK was not found at " + jdkWindows_x86)
throw new GradleException("Cannot find Java Development Kit (JDK) for windows-x64 architecture. " +
"Please update the build.gradle script to provide the correct path")
}
// def linux_aarch64 = new File(jdkLinux_aarch64)
//
// if(!linux_aarch64.exists())
// {
// println("Linux aarch64 JDK was not found at " + jdkLinux_aarch64)
// throw new GradleException("Cannot find Java Development Kit (JDK) for linux-aarch64 architecture. " +
// "Please update the build.gradle script to provide the correct path")
// }
//
// def linux_arm32 = new File(jdkLinux_arm32)
//
// if(!linux_arm32.exists())
// {
// println("Linux arm32 JDK was not found at " + jdkLinux_arm32)
// throw new GradleException("Cannot find Java Development Kit (JDK) for linux-arm32 architecture. " +
// "Please update the build.gradle script to provide the correct path")
// }
}
/**
* Since we have to list each of the platform-specific JavaFX libraries as dependencies,
* remove them from each build image
*/
tasks.runtime.doLast {
delete(fileTree('build/image/sdr-trunk-linux-x64/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-linux-x86/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-osx-x64/lib').include { it.name ==~ /javafx.*-(win|linux)\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x64/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
delete(fileTree('build/image/sdr-trunk-windows-x86/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
// delete(fileTree('build/image/sdr-trunk-linux-aarch64/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
// delete(fileTree('build/image/sdr-trunk-linux-arm32/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
// copy {
// from(jfxLinux_arm32).exclude("src.zip")
// into 'build/image/sdr-trunk-linux-arm32/lib'
// }
}

View File

@@ -1,25 +1,27 @@
#
# *******************************************************************************
# sdrtrunk
# Copyright (C) 2014-2019 Dennis Sheirer
# /*
# * ******************************************************************************
# * Copyright (C) 2014-2019 Dennis Sheirer
# *
# * This program is free software: you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation, either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program. If not, see <http://www.gnu.org/licenses/>
# * *****************************************************************************
# */
#
# 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/>
# ******************************************************************************
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Binary file not shown.

Binary file not shown.

View File

@@ -11,9 +11,9 @@
<entry name="!?*.java" />
<entry name="!?*.groovy" />
</wildcardResourcePatterns>
<bytecodeTargetLevel target="1.8">
<module name="sdr-trunk_main" target="1.8" />
<module name="sdr-trunk_test" target="1.8" />
<bytecodeTargetLevel target="11">
<module name="sdr-trunk_main" target="11" />
<module name="sdr-trunk_test" target="11" />
</bytecodeTargetLevel>
</component>
<component name="CopyrightManager" default="sdrtrunk">
@@ -36,6 +36,7 @@
<option name="distributionType" value="WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$USER_HOME$/gradle/gradle-2.12" />
<option name="gradleJvm" value="11" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@@ -206,12 +207,12 @@
</component>
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/sdr-trunk.iml" filepath="$PROJECT_DIR$/sdr-trunk.iml" />
<module fileurl="file://$PROJECT_DIR$/sdr-trunk.iml" filepath="$PROJECT_DIR$/sdr-trunk.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/sdr-trunk_main.iml" filepath="$PROJECT_DIR$/sdr-trunk_main.iml" group="sdr-trunk" />
<module fileurl="file://$PROJECT_DIR$/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/sdr-trunk_test.iml" group="sdr-trunk" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="SvnBranchConfigurationManager">
@@ -255,48 +256,48 @@
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.9.0/a0ad4e203304ccab7e01266fa814115850edb8a9/jackson-annotations-2.9.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.core:jackson-core:2.9.3">
<library name="Gradle: com.fasterxml.jackson.core:jackson-core:2.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.3/ea9b6fc7bc3ccba9777b0827091f9aa1f8580371/jackson-core-2.9.3.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/f5a654e4675769c716e5b387830d19b501ca191/jackson-core-2.9.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.3/a1c84964d4a1ef15eeb91d0c2b62460e7d7c4829/jackson-core-2.9.3-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/acc65b3b6e07784dc1760fa4e460238eb2a9073a/jackson-core-2.9.8-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.3/99006a307afedb30000fc93b462e98f0d822fa11/jackson-core-2.9.3-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/ecaea301e166a0b48f11615864246de739b6619b/jackson-core-2.9.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.core:jackson-databind:2.9.3">
<library name="Gradle: com.fasterxml.jackson.core:jackson-databind:2.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.3/193b96ef555b2f2573b576887ba9a93e4bf48e8c/jackson-databind-2.9.3.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/11283f21cc480aa86c4df7a0a3243ec508372ed2/jackson-databind-2.9.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.3/486b5cb643a0c98342b6f533d9fc5aa75ab736df/jackson-databind-2.9.3-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/a1dea243882227fc27c350a5b25ca76ef4ecb7d7/jackson-databind-2.9.8-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.3/eab1ff15884e40c0adafe219a98c46513eb1d0e6/jackson-databind-2.9.3-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/f66792d499a6fea6c7a743558f940e0ebf775ce3/jackson-databind-2.9.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.3">
<library name="Gradle: com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.3/c7d7d9f5316e0098e4d652f2d013aa9f9d69e3b1/jackson-dataformat-xml-2.9.3.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/6a068ddeeafe736da35fba3a7f64462081b562c9/jackson-dataformat-xml-2.9.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.3/9f09f5e7b1ff00b02953b0dc64e6edcc03d8dbc2/jackson-dataformat-xml-2.9.3-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/ba3a439d041b333550de55e34c370d9221011911/jackson-dataformat-xml-2.9.8-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.3/585e357aeb2ce0428740e88efd1443889e5834f4/jackson-dataformat-xml-2.9.3-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/2d48d64536472ff6138cab6ba731b29247fdb0fb/jackson-dataformat-xml-2.9.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.3">
<library name="Gradle: com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.3/ffe9afb0a9ee84fc79a9fd451b00643da1fa2466/jackson-module-jaxb-annotations-2.9.3.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/da08815ba1c7f7b435e7df02b7bf98327bad2fd4/jackson-module-jaxb-annotations-2.9.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.3/21ec5b47ad8833bbe1ce1f99192bcb6a4d0d3846/jackson-module-jaxb-annotations-2.9.3-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/575e1a2a77b830cd3c369931b71b653114314a43/jackson-module-jaxb-annotations-2.9.8-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.3/660f2e6f5bc3dcd507e21ec656218d92095e4e01/jackson-module-jaxb-annotations-2.9.3-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/b68061d95e61cc7c4a574f449cd9db7bad66d75b/jackson-module-jaxb-annotations-2.9.8-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fasterxml.woodstox:woodstox-core:5.0.3">
@@ -310,6 +311,61 @@
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.woodstox/woodstox-core/5.0.3/f38482e8b060d9e5354c10103f4c4db53c58db7d/woodstox-core-5.0.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.fazecast:jSerialComm:2.5.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/df146deefea578aa38de43a0658f06ac297acb6f/jSerialComm-2.5.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/1aa3e4d710396f7bc56661b23a6d125bf5d3749/jSerialComm-2.5.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/b9e6222384a17cb10474b7f25768edf4b742ec16/jSerialComm-2.5.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.jiconfont:jiconfont-font_awesome:4.7.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/121d019023f631af04a1357e637dd05a2cc91f7b/jiconfont-font_awesome-4.7.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/278c25d163a2e2eec5b2e93669b73936365cea69/jiconfont-font_awesome-4.7.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/8c79f5ccd872310c6dfd4f89f7fe3f748fba94a4/jiconfont-font_awesome-4.7.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.jiconfont:jiconfont-javafx:1.0.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/233530f110ab0c3ff845aa52799c43645a11f1c8/jiconfont-javafx-1.0.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/8cf9282bff0c0cf56cab9ba18f74943019f9a979/jiconfont-javafx-1.0.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/687e090ac5e1b4d20a242995a80631043d4ee78d/jiconfont-javafx-1.0.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.jiconfont:jiconfont-swing:1.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/14621054d72340b4a664cce4710ba974a3882a19/jiconfont-swing-1.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/55df548424fa5159247b05f02eb712719ce75d5/jiconfont-swing-1.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/cae76b75041118062159ffa0a7e2109cc7f88fad/jiconfont-swing-1.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.jiconfont:jiconfont:1.0.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/e17d93321975b6735ebc283f6832fa0350614d3a/jiconfont-1.0.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/8dc32616a97fe4b54380821a6aab9d8930262262/jiconfont-1.0.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/e79316fba9458423ce944fba6da101262549464/jiconfont-1.0.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.github.wendykierp:JTransforms:3.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.wendykierp/JTransforms/3.1/f1f6d3c9c9bc04f848e38bdb77dea01e8764a01f/JTransforms-3.1.jar!/" />
@@ -343,26 +399,26 @@
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.2.0/a8cd7823aa1dcd2fd6677c0c5988fdde9d1fb0a3/error_prone_annotations-2.2.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.google.guava:failureaccess:1.0">
<library name="Gradle: com.google.guava:failureaccess:1.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0/50bacb0bdbbeeda25142aff57e2214c402e1193c/failureaccess-1.0.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1dcf1de382a0bf95a3d8b0849546c88bac1292c9/failureaccess-1.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0/d4f5c095ed310f738d56b26bad666f342c1fd73d/failureaccess-1.0-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/a9aadb2d577ff54ba3115ee7357cbe65bd2a470f/failureaccess-1.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0/1e90402881010c96ea5e4b7e6f56acb283e78db3/failureaccess-1.0-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1d064e61aad6c51cc77f9b59dc2cccc78e792f5a/failureaccess-1.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.google.guava:guava:27.0-jre">
<library name="Gradle: com.google.guava:guava:27.0.1-jre">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0-jre/c6ad87d2575af8ac8ec38e28e75aefa882cc3a1f/guava-27.0-jre.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/bd41a290787b5301e63929676d792c507bbc00ae/guava-27.0.1-jre.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0-jre/607bfb3210bdc9c60315b033710e35428bc400bb/guava-27.0-jre-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/7515204e130bb73fea9f6df11e58559e6be2dcf0/guava-27.0.1-jre-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0-jre/d6484e2ee11ad928ccf61cf3e4ce9cedc2eead7e/guava-27.0-jre-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/cb5c1119df8d41a428013289b193eba3ccaf5f60/guava-27.0.1-jre-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava">
@@ -394,26 +450,26 @@
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jidesoft/jide-oss/3.6.18/b9cadcf869c0bdd032756228315252516856ad56/jide-oss-3.6.18-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.miglayout:miglayout-core:5.0">
<library name="Gradle: com.miglayout:miglayout-core:5.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.0/956daa1576bccb24e727ab75540b078171c7c432/miglayout-core-5.0.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/c1234205bde8fa72a420ecc5f63b9f269edccbfb/miglayout-core-5.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.0/c346dd69f6de7ae6ab2e93d42fa5def41c99aa1c/miglayout-core-5.0-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/43595a2a4d2da858dfe1163f0d0ce769b7fbb11/miglayout-core-5.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.0/c457b92ed51255fdb5dfab4e89c7aec06674cd7c/miglayout-core-5.0-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/4b3f0bfa2312173be31f87caa3cc58b617eea1df/miglayout-core-5.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: com.miglayout:miglayout-swing:5.0">
<library name="Gradle: com.miglayout:miglayout-swing:5.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.0/7b4c4d92002459e68e8f115b9ae8a774b2660490/miglayout-swing-5.0.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/53e2fc5586a120797e3af0ff4cf9adb3b403ab14/miglayout-swing-5.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.0/7f87b7818189138a1e9691b861d06f20f121820d/miglayout-swing-5.0-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/9ca06755a629747815fa91b3cd480bfb40fbb0e1/miglayout-swing-5.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.0/d3e06bcc8aabd8cdd1f40989e12ced6413fae68c/miglayout-swing-5.0-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/df798d1660b3106a0304efa9e04dfd1a1a2e6025/miglayout-swing-5.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: commons-io:commons-io:1.3.2">
@@ -427,6 +483,28 @@
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/1.3.2/b41a5f39fa90007a6c247175933555ba5f166d70/commons-io-1.3.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: eu.hansolo:charts:1.0.5">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/2bb9d233ac3e5f678920ff04effd57c0ba21d860/charts-1.0.5.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/d65ed2f94c0d5579cf87eea01c5d14fa8196d235/charts-1.0.5-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/aa26011a2c525ddf86cb92ed23558b18c0174fa4/charts-1.0.5-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: io.github.dsheirer:radio-reference-api:15.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/8af16506c26025517f0bc971ba35485e96130c9a/radio-reference-api-15.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/219528429b3ed73f285a8c2c4ded40d8eae61fc0/radio-reference-api-15.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/b699a9821030b54fbb7f472f97fcc634afb114fb/radio-reference-api-15.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: javax.usb:usb-api:1.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.usb/usb-api/1.0.2/f0ae0d322a99811cf79d508d37f3dc2d82753070/usb-api-1.0.2.jar!/" />
@@ -449,31 +527,15 @@
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/a6c32b40bf3d76eca54e3c601e5d1470c86fcdfa/junit-4.12-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: net.coderazzi:tablefilter-swing:5.3.1">
<library name="Gradle: net.coderazzi:tablefilter-swing:5.4.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.3.1/998bed231fba3ca6ab2f182aa576e5cb1df3339c/tablefilter-swing-5.3.1.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/ba7415b409712bf1988b2f03eda7730deba26c10/tablefilter-swing-5.4.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.3.1/90c361ce16cd898f1629906fa1df6d74a678704c/tablefilter-swing-5.3.1-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/3bc5c263fc43180f4137a41bd0848851830761ff/tablefilter-swing-5.4.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.3.1/9e46adc554cfdf19029afa46ee81044ea5602b44/tablefilter-swing-5.3.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.axis:axis-jaxrpc:1.4">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.axis/axis-jaxrpc/1.4/b393f1f0c0d95b68c86d0b1ab2e687bb71f3c075/axis-jaxrpc-1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
<library name="Gradle: org.apache.axis:axis:1.4">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.axis/axis/1.4/94a9ce681a42d0352b3ad22659f67835e560d107/axis-1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.axis/axis/1.4/442ab1fe3e66b6d5c916ae3f203fbfa618515f1c/axis-1.4-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/298121afccdf36281e234aaf478f97ddbe8aecf3/tablefilter-swing-5.4.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.commons:commons-lang3:3.8.1">
@@ -498,26 +560,26 @@
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.5/1d3f412d28c8bff0e8f048240b08bcd21cfa432e/commons-math3-3.5-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.mina:mina-core:2.0.16">
<library name="Gradle: org.apache.mina:mina-core:2.0.19">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.16/f720f17643eaa7b0fec07c1d7f6272972c02bba4/mina-core-2.0.16.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/b6ba5d8c6d1dd38a1151c590018e4e5edd6c4cfa/mina-core-2.0.19.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.16/9b8b385cba38847719659ce6fde466828c4ab623/mina-core-2.0.16-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/b50fe4a0802588c70fa9f23ae6dcefe54a688bca/mina-core-2.0.19-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.16/660fb813ca1c8d8a936f894324091400a5ac128a/mina-core-2.0.16-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/a070a03134f7c4f5c9ad79b094423d48f3e9551b/mina-core-2.0.19-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.apache.mina:mina-http:2.0.16">
<library name="Gradle: org.apache.mina:mina-http:2.0.19">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.16/b2081b5255a3998a044c34b12797b915e1640122/mina-http-2.0.16.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/b42328dbaeed685376821fe704f70ad36f4de24/mina-http-2.0.19.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.16/38119b808477795cc161a2aaf3e5bb16920a9aae/mina-http-2.0.16-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/3d6ce6d25fc14d858486f50a96213872ca7ae467/mina-http-2.0.19-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.16/fadd15439d830c99ccd6e020a2e125f0a4224059/mina-http-2.0.16-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/ce16dff76ec41be9f9c21df8bb8c271a3bb709cb/mina-http-2.0.19-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.assertj:assertj-core:3.8.0">
@@ -575,37 +637,158 @@
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b/hamcrest-core-1.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.kordamp.ikonli:ikonli-core:2.1.1">
<library name="Gradle: org.openjfx:javafx-base:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-core/2.1.1/99b0142ea24785d14d51e329dd8050a9a55b8904/ikonli-core-2.1.1.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/7fb2e4a8528ec9e434a9ac9ee98b39af79e6dcb8/javafx-base-11.0.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-core/2.1.1/2931b2864da41ce42f1a5973b4239d25e02d0f37/ikonli-core-2.1.1-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-core/2.1.1/ce342290407ae3c6ce7b1249978c9ddf45b92974/ikonli-core-2.1.1-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.kordamp.ikonli:ikonli-fontawesome-pack:2.1.1">
<library name="Gradle: org.openjfx:javafx-base:linux:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-fontawesome-pack/2.1.1/5c48f3fbb11a21b6fe460650569085aa0b7e4ae1/ikonli-fontawesome-pack-2.1.1.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/8db178adb1085d455e5ef643a48c34959c8771c1/javafx-base-11.0.2-linux.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-fontawesome-pack/2.1.1/e8c0c8694953bdbcca312b8cc0e6dbc5c2e7ec8b/ikonli-fontawesome-pack-2.1.1-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-fontawesome-pack/2.1.1/19de8abf192dcc83c0d0b7b507fa1fc72ce6bbb2/ikonli-fontawesome-pack-2.1.1-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.kordamp.ikonli:ikonli-javafx:2.1.1">
<library name="Gradle: org.openjfx:javafx-base:mac:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-javafx/2.1.1/bf3e8f21146016f3fcee9ab48ab2228b7db4bc49/ikonli-javafx-2.1.1.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/b774922e5da6a56afee221d1a288d179a1342aa9/javafx-base-11.0.2-mac.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-javafx/2.1.1/7c45822547d7a8a53d80ebb8e4a5602db10f9e45/ikonli-javafx-2.1.1-javadoc.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-javafx/2.1.1/a9c7c4aaad34e274df03701b84e064d9169ddd1d/ikonli-javafx-2.1.1-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-base:win:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/1852e57b8cf9a9b6488c33605bccd5d06ff210e1/javafx-base-11.0.2-win.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-controls:linux:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/5f6929050a744aab39ebfc1e8e5dd03bcd2ad47b/javafx-controls-11.0.2-linux.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-controls:mac:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/996371d2868035845572cdb19179dca2bfd1139f/javafx-controls-11.0.2-mac.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-controls:win:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/6c763707769c18adce406904c771c2ad1fcc370b/javafx-controls-11.0.2-win.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-graphics:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/e522eb4ea422eceeee207b1c266ba3db19b2343a/javafx-graphics-11.0.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-graphics:linux:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/ef4c34f0ca77cd99100b76c2ccf1dce383572bb1/javafx-graphics-11.0.2-linux.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-graphics:mac:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/c1d99ae5bc48e7173f4514a941d27d8f8c972c71/javafx-graphics-11.0.2-mac.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-graphics:win:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/20459ea2cf714942bcbeb78a7f70ba3531dc1a44/javafx-graphics-11.0.2-win.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-swing:linux:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/cb57db3e5bd1e2b62b17c8a504858bfc26fa54b1/javafx-swing-11.0.2-linux.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-swing:mac:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/47efd914c1ebdeda8789a779c8341266340c743/javafx-swing-11.0.2-mac.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.openjfx:javafx-swing:win:11.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/9b5728e61148142fae8c47f6295c84d7a8d6c89/javafx-swing-11.0.2-win.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.slf4j:slf4j-api:1.7.25">

View File

@@ -1,34 +1,67 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.alias;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import net.coderazzi.filters.gui.AutoChoices;
import net.coderazzi.filters.gui.TableFilterHeader;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
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.ListSelectionListener;
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.ActionListener;
import java.util.ArrayList;
import java.util.List;
public class AliasController extends JPanel
implements ActionListener, ListSelectionListener
public class AliasController extends JPanel implements ActionListener, ListSelectionListener
{
private static final long serialVersionUID = 1L;
private static final String TABLE_PREFERENCE_KEY = "alias.controller";
private AliasModel mAliasModel;
private JTable mAliasTable;
private TableFilterHeader mTableFilterHeader;
private AliasEditor mAliasEditor;
private MultipleAliasEditor mMultipleAliasEditor;
private JideSplitPane mSplitPane;
private UserPreferences mUserPreferences;
private static final String NEW_ALIAS = "New";
private static final String COPY_ALIAS = "Copy";
@@ -39,15 +72,16 @@ public class AliasController extends JPanel
private JButton mDeleteButton = new JButton(DELETE_ALIAS);
private IconCellRenderer mIconCellRenderer;
private JTableColumnWidthMonitor mColumnWidthMonitor;
public AliasController(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager)
public AliasController(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager,
UserPreferences userPreferences)
{
mAliasModel = aliasModel;
mAliasEditor = new AliasEditor(mAliasModel, broadcastModel, iconManager);
mMultipleAliasEditor = new MultipleAliasEditor(mAliasModel, broadcastModel, iconManager);
mIconCellRenderer = new IconCellRenderer(iconManager);
mUserPreferences = userPreferences;
init();
@@ -56,9 +90,7 @@ public class AliasController extends JPanel
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0",
"[grow,fill]",
"[grow,fill]"));
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
//System Configuration View and Editor
mAliasTable = new JTable(mAliasModel);
@@ -66,11 +98,11 @@ public class AliasController extends JPanel
mAliasTable.getSelectionModel().addListSelectionListener(this);
mAliasTable.setAutoCreateRowSorter(true);
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_COLOR)
.setCellRenderer(new ColorCellRenderer());
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_COLOR).setCellRenderer(new ColorCellRenderer());
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_ICON)
.setCellRenderer(mIconCellRenderer);
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_ICON).setCellRenderer(mIconCellRenderer);
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mAliasTable, TABLE_PREFERENCE_KEY);
mTableFilterHeader = new TableFilterHeader(mAliasTable, AutoChoices.ENABLED);
mTableFilterHeader.setFilterOnUpdates(true);
@@ -79,8 +111,8 @@ public class AliasController extends JPanel
JPanel buttonsPanel = new JPanel();
buttonsPanel.setLayout(
new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
buttonsPanel.setLayout(new MigLayout("insets 0 0 0 0",
"[grow,fill][grow,fill][grow,fill]", "[]"));
mNewButton.addActionListener(this);
mNewButton.setToolTipText("Adds a new alias");
@@ -98,8 +130,8 @@ public class AliasController extends JPanel
JPanel listAndButtonsPanel = new JPanel();
listAndButtonsPanel.setLayout(
new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
listAndButtonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]",
"[grow,fill][]"));
listAndButtonsPanel.add(tableScroller, "wrap");
listAndButtonsPanel.add(buttonsPanel);
@@ -212,18 +244,11 @@ public class AliasController extends JPanel
*/
private void addAlias(Alias alias)
{
//HACK: when inserting a row to the model, the JTable gets
//notified and attempts to tell the coderazzi table filter
//adaptive choices filter to refresh before the table filter is
//notified of the row additions, causing an index out of bounds
//exception. We turn off adaptive choices temporarily, add the
//channel, and turn on adaptive choices again.
mTableFilterHeader.setAdaptiveChoices(false);
//Remove any column headers so that the newly added alias shows correctly.
mTableFilterHeader.resetFilter();
int index = mAliasModel.addAlias(alias);
mTableFilterHeader.setAdaptiveChoices(true);
if(index >= 0)
{
int translatedIndex = mAliasTable.convertRowIndexToView(index);

View File

@@ -505,11 +505,32 @@ public class AliasList implements Listener<AliasEvent>
public void add(Talkgroup talkgroup, Alias alias)
{
//Detect talkgroup collisions
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
{
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
mLog.warn("Alias [" + alias.getName() + "] talkgroup [" + talkgroup.getValue() +
"] has the same talkgroup value as alias [" + existing.getName() +
"] - alias [" + alias.getName() + "] will be used for alias list [" + getName() + "]");
}
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
}
public void add(TalkgroupRange talkgroupRange, Alias alias)
{
//Log warning if the new talkgroup range overlaps with any existing ranges
for(Map.Entry<TalkgroupRange,Alias> entry: mTalkgroupRangeAliasMap.entrySet())
{
if(talkgroupRange.overlaps(entry.getKey()))
{
mLog.warn("Alias [" + alias.getName() + "] with talkgroup range [" + talkgroupRange.toString() +
"] overlaps with alias [" + entry.getValue().getName() +
"] with talkgroup range [" + entry.getKey().toString() + "] for alias list [" + getName() + "]");
}
}
mTalkgroupRangeAliasMap.put(talkgroupRange, alias);
}

View File

@@ -193,6 +193,11 @@ public class TalkgroupEditor extends DocumentListenerEditor<AliasID>
{
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
if(protocol == null)
{
protocol = Protocol.UNKNOWN;
}
int value = -1;
try

View File

@@ -83,6 +83,14 @@ public enum TalkgroupFormat
return mMaximumValue;
}
/**
* Indicates if the value is valid for the protocol
*/
public boolean isValid(int value)
{
return getMinimumValidValue() <= value && value <= getMaximumValidValue();
}
/**
* Short description of the valid value range
*/
@@ -99,9 +107,14 @@ public enum TalkgroupFormat
return mValidRangeHelpText;
}
public static TalkgroupFormat get(Protocol prococol)
public static TalkgroupFormat get(Protocol protocol)
{
switch(prococol)
if(protocol == null)
{
return UNKNOWN;
}
switch(protocol)
{
case APCO25:
return APCO25;

View File

@@ -140,6 +140,21 @@ public class TalkgroupRange extends AliasID
return getMinTalkgroup() <= talkgroupValue && talkgroupValue <= getMaxTalkgroup();
}
/**
* Indicates if this talkgroup range overlaps the talkgroup range argument.
* @param talkgroupRange to check for overlap
* @return true if the ranges overlap
*/
public boolean overlaps(TalkgroupRange talkgroupRange)
{
return contains(talkgroupRange.getMinTalkgroup()) ||
contains(talkgroupRange.getMaxTalkgroup()) ||
(getMinTalkgroup() < talkgroupRange.getMinTalkgroup() &&
talkgroupRange.getMaxTalkgroup() < getMaxTalkgroup()) ||
(talkgroupRange.getMinTalkgroup() < getMinTalkgroup() &&
getMaxTalkgroup() < talkgroupRange.getMaxTalkgroup());
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public AliasIDType getType()

View File

@@ -174,6 +174,8 @@ public class TalkgroupRangeEditor extends DocumentListenerEditor<AliasID>
TalkgroupRange talkgroupRange = getTalkgroupRange();
updateEditor(talkgroupRange.getProtocol());
if(talkgroupRange != null)
{
mComboProtocol.getModel().setSelectedItem(talkgroupRange.getProtocol());
@@ -202,6 +204,12 @@ public class TalkgroupRangeEditor extends DocumentListenerEditor<AliasID>
if(talkgroupRange != null)
{
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
if(protocol == null)
{
protocol = Protocol.UNKNOWN;
}
talkgroupRange.setProtocol(protocol);
int minValue = -1;

View File

@@ -1,25 +1,28 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.audio;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
@@ -136,7 +139,7 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
@Override
public Listener<SquelchState> getSquelchStateListener()
public Listener<SquelchStateEvent> getSquelchStateListener()
{
return mSquelchStateListener;
}
@@ -169,7 +172,7 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
}
@Override
public Listener getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
//Redirect received reusable buffers to the receive(buffer) method
return this;
@@ -178,12 +181,12 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
/**
* Wrapper for squelch state listener
*/
public class SquelchStateListener implements Listener<SquelchState>
public class SquelchStateListener implements Listener<SquelchStateEvent>
{
@Override
public void receive(SquelchState state)
public void receive(SquelchStateEvent event)
{
if(state == SquelchState.SQUELCH && hasAudioPacketListener())
if(event.getSquelchState() == SquelchState.SQUELCH && hasAudioPacketListener())
{
ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer();
endAudioPacket.resetAttributes();
@@ -192,7 +195,7 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
getAudioPacketListener().receive(endAudioPacket);
}
mSquelchState = state;
mSquelchState = event.getSquelchState();
}
}
}

View File

@@ -1,21 +1,24 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.audio.broadcast;
import io.github.dsheirer.alias.AliasModel;
@@ -40,6 +43,7 @@ import io.github.dsheirer.audio.convert.MP3SilenceGenerator;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.AudioRecorder;
import io.github.dsheirer.record.mp3.MP3Recorder;
import org.slf4j.Logger;
@@ -147,7 +151,8 @@ public class BroadcastFactory
* @param broadcastModel model for broadcastAudio configurations
* @return an editor for the specified broadcastAudio configuration
*/
public static Editor<BroadcastConfiguration> getEditor(BroadcastConfiguration configuration,
public static Editor<BroadcastConfiguration> getEditor(UserPreferences userPreferences,
BroadcastConfiguration configuration,
BroadcastModel broadcastModel,
AliasModel aliasModel,
IconManager iconManager)
@@ -157,7 +162,7 @@ public class BroadcastFactory
switch(configuration.getBroadcastServerType())
{
case BROADCASTIFY:
editor = new BroadcastifyConfigurationEditor(broadcastModel, aliasModel, iconManager);
editor = new BroadcastifyConfigurationEditor(userPreferences, broadcastModel, aliasModel, iconManager);
break;
case ICECAST_TCP:
editor = new IcecastTCPConfigurationEditor(broadcastModel, aliasModel, iconManager);

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* sdrtrunk
* 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.audio.broadcast;
@@ -586,12 +588,20 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Reusa
@Override
public Class<?> getColumnClass(int columnIndex)
{
if(columnIndex == COLUMN_SERVER_ICON)
switch(columnIndex)
{
return ImageIcon.class;
case COLUMN_BROADCASTER_STATUS:
return BroadcastState.class;
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
case COLUMN_BROADCASTER_QUEUE_SIZE:
case COLUMN_BROADCASTER_STREAMED_COUNT:
return Integer.class;
case COLUMN_SERVER_ICON:
return ImageIcon.class;
case COLUMN_STREAM_NAME:
default:
return String.class;
}
return String.class;
}
/**

View File

@@ -1,21 +1,24 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.audio.broadcast;
import com.jidesoft.swing.JideSplitPane;
@@ -23,6 +26,7 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,6 +55,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
private BroadcastModel mBroadcastModel;
private AliasModel mAliasModel;
private IconManager mIconManager;
private UserPreferences mUserPreferences;
private BroadcastStatusPanel mBroadcastStatusPanel;
private JideSplitPane mSplitPane;
@@ -60,12 +65,14 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
private JButton mCopyButton = new JButton(COPY_BROADCAST_CONFIGURATION);
private JButton mDeleteButton = new JButton(DELETE_BROADCAST_CONFIGURATION);
public BroadcastPanel(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
public BroadcastPanel(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager,
UserPreferences userPreferences)
{
mBroadcastModel = broadcastModel;
mAliasModel = aliasModel;
mIconManager = iconManager;
mEditor = mEmptyEditor;
mUserPreferences = userPreferences;
init();
}
@@ -74,7 +81,8 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
{
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel);
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel, mUserPreferences,
"broadcast.panel.broadcast.status.panel");
mBroadcastStatusPanel.getTable().setAutoCreateRowSorter(true);
mBroadcastStatusPanel.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -210,8 +218,8 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
if (selectedConfiguration != null)
{
mEditor = BroadcastFactory.getEditor(selectedConfiguration, mBroadcastModel, mAliasModel,
mIconManager);
mEditor = BroadcastFactory.getEditor(mUserPreferences, selectedConfiguration, mBroadcastModel,
mAliasModel, mIconManager);
}
else
{

View File

@@ -1,6 +1,7 @@
/*******************************************************************************
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,10 +15,12 @@
*
* 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.audio.broadcast;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import net.miginfocom.swing.MigLayout;
import javax.swing.JLabel;
@@ -32,12 +35,17 @@ import java.awt.Component;
public class BroadcastStatusPanel extends JPanel
{
private JTable mTable;
private JTableColumnWidthMonitor mColumnWidthMonitor;
private JScrollPane mScrollPane;
private BroadcastModel mBroadcastModel;
private UserPreferences mUserPreferences;
private String mPreferenceKey;
public BroadcastStatusPanel(BroadcastModel broadcastModel)
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
{
mBroadcastModel = broadcastModel;
mUserPreferences = userPreferences;
mPreferenceKey = preferenceKey;
init();
}
@@ -57,6 +65,7 @@ public class BroadcastStatusPanel extends JPanel
renderer.setHorizontalAlignment(SwingConstants.CENTER);
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
mScrollPane = new JScrollPane(mTable);

View File

@@ -1,24 +1,26 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.audio.broadcast.broadcastify;
import com.radioreference.api.soap2.UserFeedBroadcast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastConfigurationEditor;
@@ -26,6 +28,8 @@ import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
@@ -64,10 +68,13 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
private JCheckBox mEnabled;
private JButton mSaveButton;
private JButton mResetButton;
private UserPreferences mUserPreferences;
public BroadcastifyConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
public BroadcastifyConfigurationEditor(UserPreferences userPreferences, BroadcastModel broadcastModel,
AliasModel aliasModel, IconManager iconManager)
{
super(broadcastModel, aliasModel, iconManager);
mUserPreferences = userPreferences;
init();
}
@@ -91,7 +98,7 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
@Override
public void actionPerformed(ActionEvent e)
{
final UserFeedSelectionDialog dialog = new UserFeedSelectionDialog(BroadcastifyConfigurationEditor.this);
final UserFeedSelectionDialog dialog = new UserFeedSelectionDialog(mUserPreferences,BroadcastifyConfigurationEditor.this);
EventQueue.invokeLater(new Runnable()
{
@@ -391,7 +398,7 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
@Override
public void receive(UserFeedBroadcast userFeedBroadcast)
{
mName.setText(userFeedBroadcast.getDescr());
mName.setText(userFeedBroadcast.getDescription());
mServer.setText(userFeedBroadcast.getHostname());
mPort.setText(userFeedBroadcast.getPort());
mMountPoint.setText(userFeedBroadcast.getMount());

View File

@@ -1,78 +1,155 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.audio.broadcast.broadcastify;
import com.radioreference.api.soap2.UserFeedBroadcast;
import io.github.dsheirer.radioreference.RadioReferenceService;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.RadioReferenceException;
import io.github.dsheirer.rrapi.RadioReferenceService;
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import io.github.dsheirer.rrapi.type.UserInfo;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.xml.rpc.ServiceException;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.util.List;
public class UserFeedSelectionDialog extends JDialog implements DocumentListener
{
private final static Logger mLog = LoggerFactory.getLogger( UserFeedSelectionDialog.class );
public static final String SDRTRUNK_APP_KEY = "88969092";
private Listener<UserFeedBroadcast> mListener;
private UserPreferences mUserPreferences;
private JTextField mUserNameText;
private JTextField mPasswordText;
private JCheckBox mSaveCredentialsCheckBox;
private JButton mClearStoredCredentialsButton;
private JButton mLookupButton;
private JList mUserFeedBroadcastList;
private JButton mOKButton;
public UserFeedSelectionDialog(Listener<UserFeedBroadcast> listener)
public UserFeedSelectionDialog(UserPreferences userPreferences, Listener<UserFeedBroadcast> listener)
{
mUserPreferences = userPreferences;
mListener = listener;
//Register to receive radio reference preferences updates
MyEventBus.getEventBus().register(this);
init();
}
private void init()
{
JPanel panel = new JPanel();
panel.setLayout(new MigLayout("","[align right,grow,fill][align left,grow,fill][]","[][][][][grow][]"));
panel.setLayout(new MigLayout("","[align right,grow,fill][align left,grow,fill][]","[][][][][][grow][]"));
panel.add(new JLabel("Please enter your credentials"), "align left,span");
panel.add(new JLabel("User Name:"));
mUserNameText = new JTextField();
String storedUserName = mUserPreferences.getRadioReferencePreference().getUserName();
if(storedUserName != null)
{
mUserNameText.setText(storedUserName);
}
mUserNameText.getDocument().addDocumentListener(this);
panel.add(mUserNameText, "wrap");
panel.add(new JLabel("Password:"));
mPasswordText = new JTextField();
String storedPassword = mUserPreferences.getRadioReferencePreference().getPassword();
if(storedPassword != null)
{
mPasswordText.setText(storedPassword);
}
mPasswordText.getDocument().addDocumentListener(this);
panel.add(mPasswordText, "wrap");
mSaveCredentialsCheckBox = new JCheckBox("Store Credentials");
mSaveCredentialsCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
mSaveCredentialsCheckBox.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
//If user unchecks box and there are stored credentials, prompt to clear them
if(!mSaveCredentialsCheckBox.isSelected() && mUserPreferences.getRadioReferencePreference().hasStoredCredentials())
{
int choice = JOptionPane.showOptionDialog(UserFeedSelectionDialog.this,
"Would you like to remove previously stored credentials from this computer?",
"Clear Stored Credentials?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
null, null, 0);
if(choice == JOptionPane.YES_OPTION)
{
mUserPreferences.getRadioReferencePreference().removeStoreCredentials();
}
}
}
});
panel.add(mSaveCredentialsCheckBox);
mClearStoredCredentialsButton = new JButton("Remove");
mClearStoredCredentialsButton.setToolTipText("Clear stored credentials");
mClearStoredCredentialsButton.setEnabled(mUserPreferences.getRadioReferencePreference().hasStoredCredentials());
mClearStoredCredentialsButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mUserPreferences.getRadioReferencePreference().removeStoreCredentials();
}
});
panel.add(mClearStoredCredentialsButton, "wrap");
mLookupButton = new JButton("Get Feeds");
mLookupButton.setEnabled(false);
mLookupButton.setEnabled(storedPassword != null && storedPassword != null);
mLookupButton.addActionListener(new ActionListener()
{
@Override
@@ -87,45 +164,25 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
{
try
{
RadioReferenceService service =
new RadioReferenceService(mUserNameText.getText(), mPasswordText.getText());
AuthorizationInformation authorizationInformation = new AuthorizationInformation(SDRTRUNK_APP_KEY,
mUserNameText.getText(), mPasswordText.getText());
UserFeedBroadcast[] feeds = service.getUserFeedBroadcasts();
mLog.debug("User:" + authorizationInformation.getUserName() + " Pass:" + authorizationInformation.getPassword() + " Key:" + authorizationInformation.getApplicationKey());
mUserFeedBroadcastList.setListData(feeds);
RadioReferenceService service = new RadioReferenceService(authorizationInformation);
UserInfo userInfo = service.getUserInfo();
mLog.info("RR User:" + userInfo.getUserName() + " Expiration Date:" + userInfo.getExpirationDate());
List<UserFeedBroadcast> feeds = service.getUserFeeds();
mUserFeedBroadcastList.setListData(feeds.toArray(new UserFeedBroadcast[feeds.size()]));
}
catch(ServiceException se)
catch(RadioReferenceException rre)
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
"Unable to create radio reference web service client. See log file.",
"Error", JOptionPane.ERROR_MESSAGE);
mLog.error("Error creating the radio reference web service client", se);
}
catch(RemoteException re)
{
Throwable cause = re.getCause();
if(cause == null)
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
re.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
else if(cause instanceof UnknownHostException)
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
"Broadcastify server is unavailable", "No Server",
JOptionPane.ERROR_MESSAGE);
}
else
{
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
"An unknown error has occurred. Please see log file.", "" +
"Unknown Error", JOptionPane.ERROR_MESSAGE);
mLog.error("Error retrieving user broadcast feed configurations from radio reference " +
"service: ", re);
}
mLog.error("Error creating the radio reference web service client", rre);
}
mLookupButton.setEnabled(true);
@@ -160,6 +217,7 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
{
if(mListener != null)
{
storeCredentials();
mListener.receive((UserFeedBroadcast)mUserFeedBroadcastList.getSelectedValue());
}
@@ -182,11 +240,37 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
setTitle("Broadcastify Feed Lookup");
setLayout(new BorderLayout());
add(panel, BorderLayout.CENTER);
setSize(300,250);
setSize(400,450);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.APPLICATION_MODAL);
}
/**
* Stores login credentials in the user preferences
*/
private void storeCredentials()
{
boolean store = mSaveCredentialsCheckBox.isSelected();
mUserPreferences.getRadioReferencePreference().setStoreCredentials(store);
if(store)
{
String userName = mUserNameText.getText();
if(userName != null)
{
mUserPreferences.getRadioReferencePreference().setUserName(userName);
}
String password = mPasswordText.getText();
if(password != null)
{
mUserPreferences.getRadioReferencePreference().setPassword(password);
}
}
}
private void updateButtons()
{
String username = mUserNameText.getText();
@@ -240,8 +324,20 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
setForeground(list.getForeground());
}
setText(value.getDescr());
setText(value.getDescription());
return this; }
return this;
}
}
@Subscribe
public void preferenceUpdated(PreferenceType preferenceType)
{
if(preferenceType == PreferenceType.RADIO_REFERENCE)
{
//Update the store-credentials checkbox only, so we don't change the username or password currently displayed
mSaveCredentialsCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
mClearStoredCredentialsButton.setEnabled(mUserPreferences.getRadioReferencePreference().hasStoredCredentials());
}
}
}

View File

@@ -1,21 +1,24 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
@@ -30,11 +33,9 @@ import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderException;
import org.apache.mina.http.HttpClientCodec;
import org.apache.mina.http.HttpException;
import org.apache.mina.http.HttpRequestImpl;
import org.apache.mina.http.api.DefaultHttpResponse;
import org.apache.mina.http.api.HttpMethod;
import org.apache.mina.http.api.HttpStatus;
import org.apache.mina.http.api.HttpVersion;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
@@ -51,6 +52,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
private static final String HTTP_1_0_OK_HEX_DUMP = "48 54 54 50 2F 31 2E 30 20 32 30 30 20 4F 4B";
private NioSocketConnector mSocketConnector;
private IoSession mStreamingSession = null;
@@ -113,7 +115,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
mSocketConnector.setConnectTimeoutCheckInterval(10000);
// mSocketConnector.getFilterChain().addLast("logger",
// new LoggingFilter(IcecastTCPAudioBroadcaster.class));
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
@@ -146,12 +148,12 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
}
else if(throwableCause != null)
{
setBroadcastState(BroadcastState.ERROR);
setBroadcastState(BroadcastState.DISCONNECTED);
mLog.debug("Failed to connect", rie);
}
else
{
setBroadcastState(BroadcastState.ERROR);
setBroadcastState(BroadcastState.DISCONNECTED);
mLog.debug("Failed to connect - no exception is available");
}
@@ -266,25 +268,30 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
if(throwable instanceof ProtocolDecoderException)
{
Throwable cause = ((ProtocolDecoderException) throwable).getCause();
Throwable cause = throwable.getCause();
if(cause instanceof HttpException &&
((HttpException) cause).getStatusCode() == HttpStatus.CLIENT_ERROR_LENGTH_REQUIRED.code())
/**
* Warning: Hack. Icecast 2.4.2+ doesn't like to send HTTP headers along with their HTTP/1.0 OK
* message which causes Apache Mina to think that it hasn't received the full HTTP response. So, we
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
* OK response and then play like everything is well and good.
*/
if(cause instanceof ArrayIndexOutOfBoundsException &&
((ProtocolDecoderException)throwable).getHexdump().startsWith(HTTP_1_0_OK_HEX_DUMP))
{
//Ignore - Icecast 2.4 sometimes doesn't send any headers with their HTTP responses. Mina expects a
//content-length for HTTP responses.
setBroadcastState(BroadcastState.CONNECTED);
}
else
{
mLog.error("HTTP protocol decoder error", throwable);
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
}
else
{
mLog.error("Broadcast error", throwable);
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}

View File

@@ -0,0 +1,59 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.codec.mbe;
import io.github.dsheirer.preference.UserPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AmbeAudioModule extends JmbeAudioModule
{
private static final Logger mLog = LoggerFactory.getLogger(AmbeAudioModule.class);
private static final String AMBE_CODEC = "AMBE 3600 x 2450";
private static boolean sLibraryStatusLogged = false;
public AmbeAudioModule(UserPreferences userPreferences)
{
super(userPreferences);
if(!sLibraryStatusLogged)
{
if(getAudioCodec() != null)
{
mLog.info("AMBE CODEC successfully loaded - P25-2/DMR/NXDN audio will be available");
}
else
{
mLog.warn("AMBE CODEC not loaded - P25-2/DMR/NXDN audio will NOT be available");
}
sLibraryStatusLogged = true;
}
}
@Override
protected String getCodecName()
{
return AMBE_CODEC;
}
}

View File

@@ -0,0 +1,42 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.codec.mbe;
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
/**
* P25 Encryption Parameters
*/
public interface IEncryptionSyncParameters
{
/**
* Encryption key identifier
*/
EncryptionKeyIdentifier getEncryptionKey();
/**
* Message Indicator
* @return key generator fill values
*/
String getMessageIndicator();
}

View File

@@ -0,0 +1,59 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.codec.mbe;
import io.github.dsheirer.preference.UserPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ImbeAudioModule extends JmbeAudioModule
{
private static final Logger mLog = LoggerFactory.getLogger(ImbeAudioModule.class);
private static final String IMBE_CODEC = "IMBE";
private static boolean sLibraryStatusLogged = false;
public ImbeAudioModule(UserPreferences userPreferences)
{
super(userPreferences);
if(!sLibraryStatusLogged)
{
if(getAudioCodec() != null)
{
mLog.info("JMBE audio conversion library IMBE CODEC successfully loaded - P25-1 audio will be available");
}
else
{
mLog.warn("JMBE audio conversion library, IMBE CODEC not loaded - P25-1 audio will NOT be available");
}
sLibraryStatusLogged = true;
}
}
@Override
protected String getCodecName()
{
return IMBE_CODEC;
}
}

View File

@@ -0,0 +1,248 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.codec.mbe;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.audio.AbstractAudioModule;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageListener;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
import jmbe.iface.IAudioCodec;
import jmbe.iface.IAudioCodecLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener
{
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
private static List<String> mLibraryLoadStatusLogged = new ArrayList<>();
private IAudioCodec mAudioCodec;
private UserPreferences mUserPreferences;
private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("JmbeAudioModule");
public JmbeAudioModule(UserPreferences userPreferences)
{
mUserPreferences = userPreferences;
MyEventBus.getEventBus().register(this);
loadConverter();
}
protected IAudioCodec getAudioCodec()
{
return mAudioCodec;
}
/**
* Indicates that the JMBE audio library has been loaded and a suitable audio codec is usable (ie non-null)
*/
protected boolean hasAudioCodec()
{
return getAudioCodec() != null;
}
protected ReusableAudioPacketQueue getAudioPacketQueue()
{
return mAudioPacketQueue;
}
@Override
public Listener<IMessage> getMessageListener()
{
return this;
}
/**
* Receives notifications that the JMBE library preference has been updated via the Guava event bus
*
* @param preferenceType that was updated
*/
@Subscribe
public void preferenceUpdated(PreferenceType preferenceType)
{
if(preferenceType == PreferenceType.JMBE_LIBRARY)
{
mLibraryLoadStatusLogged.clear();
loadConverter();
}
}
/**
* Name of the CODEC to use from the JMBE library
*/
protected abstract String getCodecName();
/**
* Loads audio frame processing chain. Constructs an imbe targetdataline
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
*/
protected void loadConverter()
{
IAudioCodec audioConverter = null;
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
if(path != null)
{
try
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.info("Loading JMBE library from [" + path.toString() + "]");
}
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
this.getClass().getClassLoader());
Class classToLoad = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
Object instance = classToLoad.getDeclaredConstructor().newInstance();
if(instance instanceof IAudioCodecLibrary)
{
IAudioCodecLibrary library = (IAudioCodecLibrary)instance;
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
{
audioConverter = library.getAudioConverter(getCodecName());
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.info("JMBE audio conversion library loaded: " + library.getVersion());
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
else
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.warn("JMBE library version 1.0.0 or higher is required - found: " + library.getVersion());
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
}
else
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.info("JMBE audio conversion library NOT FOUND");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
}
catch(IllegalArgumentException iae)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
{
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
}
}
catch(NoSuchMethodException nsme)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(MalformedURLException mue)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(ClassNotFoundException e1)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - class not found");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(InvocationTargetException ite)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - invocation target exception", ite);
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(InstantiationException e1)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - instantiation exception", e1);
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(IllegalAccessException e1)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - security restrictions");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
}
else
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.warn("JMBE audio library path is NOT SET in your User Preferences.");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
if(audioConverter != null)
{
mAudioCodec = audioConverter;
}
else
{
mAudioCodec = null;
}
}
@Override
public void dispose()
{
mAudioCodec = null;
}
}

View File

@@ -0,0 +1,291 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.codec.mbe;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonRootName;
import io.github.dsheirer.identifier.encryption.EncryptionKey;
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
import java.util.ArrayList;
import java.util.List;
/**
* MBE Call Sequence containing one or more voice frames with optional encryption parameters and optional from and to
* radio identifiers.
*/
@JsonRootName("mbe_call")
@JsonPropertyOrder({"protocol", "call_type", "from", "to", "encrypted", "frames"})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MBECallSequence
{
private String mProtocol;
private String mFromIdentifier;
private String mToIdentifier;
private String mCallType;
private String mSystem;
private String mSite;
private boolean mEncrypted;
private List<VoiceFrame> mVoiceFrames = new ArrayList<>();
private IEncryptionSyncParameters mTemporaryEncryptionSyncParameters;
/**
* Constructs a call sequence
*/
public MBECallSequence(String protocol)
{
mProtocol = protocol;
}
public MBECallSequence()
{
//no-arg constructor for faster jackson deserialization
}
/**
* Protocol/format for the voice frames
*/
@JsonProperty("protocol")
public String getProtocol()
{
return mProtocol;
}
public void setProtocol(String protocol)
{
mProtocol = protocol;
}
/**
* Indicates if this sequences contains any audio frames
*/
@JsonIgnore
public boolean hasAudio()
{
return mVoiceFrames.size() > 4;
}
/**
* Indicates if this call sequence is encrypted
*/
@JsonProperty("encrypted")
public boolean isEncrypted()
{
return mEncrypted;
}
/**
* Sets the encrypted status of this call sequence
*
* @param encrypted status
*/
public void setEncrypted(boolean encrypted)
{
mEncrypted = encrypted;
}
/**
* Sets encryption sync parameters to use with the next sequence of voice frames. This is usually obtained from the
* HDU (phase1) or the PTT (phase2)
*
* @param encryptionSyncParameters to apply to the next set of voice frames
*/
public void setEncryptionSyncParameters(IEncryptionSyncParameters encryptionSyncParameters)
{
mTemporaryEncryptionSyncParameters = encryptionSyncParameters;
}
/**
* Sets the from radio identifier
*
* @param from id
*/
public void setFromIdentifier(String from)
{
if(from != null && !from.isEmpty() && !from.contentEquals("0"))
{
mFromIdentifier = from;
}
}
/**
* Radio identifier that originated the call
*
* @return from id
*/
@JsonProperty("from")
public String getFromIdentifier()
{
return mFromIdentifier;
}
/**
* Sets the to radio identifier
*
* @param to id
*/
public void setToIdentifier(String to)
{
if(to != null && !to.isEmpty() && !to.contentEquals("0"))
{
mToIdentifier = to;
}
}
/**
* Radio identifier that received the call
*
* @return to id
*/
@JsonProperty("to")
public String getToIdentifier()
{
return mToIdentifier;
}
/**
* Call type
*
* @param type from GROUP, INDIVIDUAL, or TELEPHONE INTERCONNECT
*/
public void setCallType(String type)
{
mCallType = type;
}
/**
* Sets the call type
*
* @return call type
*/
@JsonProperty("call_type")
public String getCallType()
{
return mCallType;
}
/**
* System name defined by the user
* @return
*/
@JsonProperty("system")
public String getSystem()
{
return mSystem;
}
/**
* Sets the system name
* @param system
*/
public void setSystem(String system)
{
mSystem = system;
}
/**
* Site name defined by the user
* @return
*/
@JsonProperty("site")
public String getSite()
{
return mSite;
}
/**
* Sets the site name
* @param site
*/
public void setSite(String site)
{
mSite = site;
}
/**
* Ordered list of voice frames for the call sequence
*
* @return list of unencrypted or encrypted audio frames
*/
@JsonProperty("frames")
public List<VoiceFrame> getVoiceFrames()
{
return mVoiceFrames;
}
public void setVoiceFrames(List<VoiceFrame> voiceFrames)
{
mVoiceFrames = voiceFrames;
}
/**
* Adds an unencrypted audio frame to this call sequence
*
* @param timestamp of the audio frame
* @param frame of hexadecimal values representing the transmitted audio frame and ecc bits
*/
public void addVoiceFrame(long timestamp, String frame)
{
if(mTemporaryEncryptionSyncParameters != null)
{
addEncryptedVoiceFrame(timestamp, frame, mTemporaryEncryptionSyncParameters);
mTemporaryEncryptionSyncParameters = null;
}
else
{
mVoiceFrames.add(new VoiceFrame(timestamp, frame));
}
}
/**
* Adds an encrypted audio frame to this call sequence
*
* @param timestamp of the audio frame
* @param frame of hexadecimal values representing the transmitted audio frame and ecc bits
* @param parameters identifying the encryption used for the frame
*/
public void addEncryptedVoiceFrame(long timestamp, String frame, IEncryptionSyncParameters parameters)
{
EncryptionKey encryption = parameters.getEncryptionKey().getValue();
addEncryptedVoiceFrame(timestamp, frame, encryption.getAlgorithm(), encryption.getKey(), parameters.getMessageIndicator());
setEncrypted(true);
}
/**
* Adds an encrypted audio frame to this call sequence
*
* @param timestamp of the audio frame
* @param frame of hexadecimal values representing the transmitted audio frame and ecc bits
* @param algorithm identifier for encryption
* @param keyid identifying which encryption key was used by the radio which may have multiple keys
* @param messageIndicator to seed the key generator
*/
public void addEncryptedVoiceFrame(long timestamp, String frame, int algorithm, int keyid, String messageIndicator)
{
mVoiceFrames.add(new VoiceFrame(timestamp, frame, algorithm, keyid, messageIndicator));
}
}

View File

@@ -0,0 +1,122 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.codec.mbe;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.dsheirer.audio.convert.thumbdv.ThumbDv;
import io.github.dsheirer.audio.convert.thumbdv.message.response.AmbeResponse;
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
import io.github.dsheirer.record.wave.AudioPacketWaveRecorder;
import io.github.dsheirer.record.wave.WaveMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Utility for converting MBE call sequences (*.mbe) to PCM wave audio format
*/
public class MBECallSequenceConverter
{
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
public static void convert(Path input, Path output) throws IOException
{
InputStream inputStream = Files.newInputStream(input);
ObjectMapper mapper = new ObjectMapper();
MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
convert(sequence, output);
}
public static void convert(MBECallSequence callSequence, Path outputPath)
{
if(callSequence == null || callSequence.isEncrypted())
{
throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
}
if(callSequence != null && !callSequence.isEncrypted())
{
ThumbDv.AudioProtocol protocol = ThumbDv.AudioProtocol.P25_PHASE2;
AudioPacketWaveRecorder recorder = new AudioPacketWaveRecorder(outputPath);
recorder.start();
long delayMillis = 0;
try(ThumbDv thumbDv = new ThumbDv(protocol, recorder))
{
thumbDv.start();
for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
{
mLog.debug("Frame [" + voiceFrame.getFrame() + "] + Hex [" + AmbeResponse.toHex(voiceFrame.getFrameBytes()) + "]");
thumbDv.decode(voiceFrame.getFrameBytes());
delayMillis += 30;
}
if(delayMillis > 0)
{
delayMillis += 1000;
try
{
Thread.sleep(delayMillis);
}
catch(InterruptedException ie)
{
}
}
}
catch(IOException ioe)
{
mLog.error("Error", ioe);
}
recorder.stop(Paths.get(outputPath.toString().replace(".tmp", ".wav")), new WaveMetadata());
}
}
public static void main(String[] args)
{
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_3_TS0_65084_6591007.mbe";
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_2_TS1_65035.mbe";
//
// Path input = Paths.get(mbe);
// Path output = Paths.get(mbe.replace(".mbe", ".tmp"));
//
// mLog.info("Converting: " + mbe);
//
// try
// {
// MBECallSequenceConverter.convert(input, output);
// }
// catch(IOException ioe)
// {
// mLog.error("Error", ioe);
// }
}
}

View File

@@ -0,0 +1,79 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.codec.mbe;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Reader for MBE call sequence recordings
*/
public class MBECallSequenceReader
{
public static List<String> getAudioFrames(Path path) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
Object obj = mapper.readValue(path.toFile(), MBECallSequence.class);
if(obj instanceof MBECallSequence)
{
MBECallSequence sequence = (MBECallSequence)obj;
List<String> audioFrames = new ArrayList<>();
for(VoiceFrame voiceFrame: sequence.getVoiceFrames())
{
audioFrames.add(voiceFrame.getFrame());
}
return audioFrames;
}
return Collections.emptyList();
}
public static void main(String[] args)
{
Path path = Path.of("/home/denny/SDRTrunk/recordings/20190706063149_154250000_7_TS1_65084_6591001.mbe");
try
{
List<String> frames = MBECallSequenceReader.getAudioFrames(path);
Files.writeString(Path.of("/home/denny/SDRTrunk/recordings/mbe_frames.txt"), Joiner.on("\",\n\"").join(frames));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,163 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.codec.mbe;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageListener;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.preference.TimestampFormat;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
/**
* Records MBE audio frame call sequences and metadata to a JSON format recording file
*/
public abstract class MBECallSequenceRecorder extends Module implements IMessageListener, Listener<IMessage>
{
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceRecorder.class);
protected static final String CALL_TYPE_GROUP = "GROUP";
protected static final String CALL_TYPE_INDIVIDUAL = "INDIVIDUAL";
protected static final String CALL_TYPE_TELEPHONE_INTERCONNECT = "TELEPHONE INTERCONNECT";
protected UserPreferences mUserPreferences;
protected long mChannelFrequency;
protected String mSystem;
protected String mSite;
private int mCallNumber = 1;
/**
* Constructs an instance
* @param userPreferences to obtain recording directory
* @param channelFrequency for the channel to record
*/
public MBECallSequenceRecorder(UserPreferences userPreferences, long channelFrequency, String system, String site)
{
mUserPreferences = userPreferences;
mChannelFrequency = channelFrequency;
mSystem = system;
mSite = site;
}
@Override
public void reset()
{
}
@Override
public void start()
{
}
@Override
public void dispose()
{
}
/**
* Writes an MBE call sequence recording to the recording directory
* @param sequence to write
*/
protected void writeCallSequence(MBECallSequence sequence)
{
writeCallSequence(sequence, null);
}
/**
* Writes an MBE call sequence recording to the recording directory.
*
* @param optionalChannelTag to include in the filename
* @param sequence containing voice frames
*/
protected void writeCallSequence(MBECallSequence sequence, String optionalChannelTag)
{
if(sequence != null && sequence.hasAudio())
{
sequence.setSystem(mSystem);
sequence.setSite(mSite);
StringBuilder sb = new StringBuilder();
sb.append(TimestampFormat.TIMESTAMP_COMPACT.getFormatter().format(new Date(System.currentTimeMillis())));
sb.append("_").append(mChannelFrequency);
sb.append("_").append(mCallNumber++);
if(mCallNumber < 1)
{
mCallNumber = 1;
}
if(optionalChannelTag != null)
{
sb.append("_").append(optionalChannelTag);
}
if(sequence.getToIdentifier() != null)
{
sb.append("_").append(sequence.getToIdentifier().replace(":", ""));
}
if(sequence.getFromIdentifier() != null)
{
sb.append("_").append(sequence.getFromIdentifier().replace(":", ""));
}
if(sequence.isEncrypted())
{
sb.append("_encrypted");
}
sb.append(".mbe");
Path recordingDirectory = mUserPreferences.getDirectoryPreference().getDirectoryRecording();
Path filePath = recordingDirectory.resolve(sb.toString());
try
{
OutputStream outputStream = Files.newOutputStream(filePath);
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(outputStream, sequence);
outputStream.close();
}
catch(IOException ioe)
{
mLog.error("Couldn't write MBE call sequence to path [" + filePath.toString() + "]", ioe);
}
}
}
/**
* Implementation of IMessageListener interface
*/
@Override
public Listener<IMessage> getMessageListener()
{
return this;
}
}

View File

@@ -0,0 +1,492 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv;
import com.fazecast.jSerialComm.SerialPort;
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessageFactory;
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
import io.github.dsheirer.audio.convert.thumbdv.message.request.AmbeRequest;
import io.github.dsheirer.audio.convert.thumbdv.message.request.DecodeSpeechRequest;
import io.github.dsheirer.audio.convert.thumbdv.message.request.EncodeSpeechRequest;
import io.github.dsheirer.audio.convert.thumbdv.message.request.ResetRequest;
import io.github.dsheirer.audio.convert.thumbdv.message.request.SetVocoderParametersRequest;
import io.github.dsheirer.audio.convert.thumbdv.message.request.SetVocoderRequest;
import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Northwest Digital Radio (NWDR) ThumbDv dongle.
*
* Note: linux users must allow access to the serial port:
*
* sudo usermod -a -G uucp username
* sudo usermod -a -G dialout username
* sudo usermod -a -G lock username
* sudo usermod -a -G tty username
*/
public class ThumbDv implements AutoCloseable
{
private final static Logger mLog = LoggerFactory.getLogger(ThumbDv.class);
private static final String PORT_DESCRIPTION = "USB-to-Serial Port (ftdi_sio)";
private static final String PORT_DESCRIPTION_FRAGMENT = "USB Serial Port";
private static final int BAUD_RATE = 460800;
private static final byte PACKET_START = (byte) 0x61;
public enum AudioProtocol
{
DMR,
DSTAR,
NXDN,
P25_PHASE2,
}
private SerialPort mSerialPort;
private ScheduledFuture mSerialPortReaderHandle;
private ScheduledFuture mAudioDecodeProcessorHandle;
private LinkedTransferQueue<DecodeSpeechRequest> mDecodeSpeechRequests = new LinkedTransferQueue<>();
private AudioProtocol mAudioProtocol;
private Listener<ReusableAudioPacket> mAudioPacketListener;
private ReusableAudioPacketQueue mReusableAudioPacketQueue = new ReusableAudioPacketQueue("ThumbDv");
private boolean mStarted;
public ThumbDv(AudioProtocol audioProtocol, Listener<ReusableAudioPacket> listener)
{
mAudioProtocol = audioProtocol;
mAudioPacketListener = listener;
}
/**
* Enqueues the audio code frame for decoding. Decoded PCM speech packet will be sent to the registered
* audio packet listener.
*
* @param codecFrame
*/
public void decode(byte[] codecFrame)
{
if(!mStarted || mSerialPort == null)
{
throw new IllegalStateException("Must invoke start() method and thumbdv serial device must be available");
}
mDecodeSpeechRequests.offer(new DecodeSpeechRequest(codecFrame));
}
public void close() throws IOException
{
if(mSerialPortReaderHandle != null)
{
mSerialPortReaderHandle.cancel(true);
mSerialPortReaderHandle = null;
}
if(mSerialPort != null)
{
mSerialPort.closePort();
}
}
public void start() throws IOException
{
if(mSerialPort == null)
{
SerialPort[] ports = SerialPort.getCommPorts();
for(SerialPort port : ports)
{
mLog.debug("Serial Port Name:" + port.getDescriptivePortName());
if(port.getDescriptivePortName().contentEquals(PORT_DESCRIPTION) ||
port.getDescriptivePortName().contains(PORT_DESCRIPTION_FRAGMENT))
{
mSerialPort = port;
mSerialPort.setBaudRate(BAUD_RATE);
mSerialPort.openPort(0, 5000, 10000);
if(mSerialPort.isOpen())
{
mLog.info("Resetting ThumbDv Device");
send(new ResetRequest());
mLog.info("Creating Serial Port Reader");
final Runnable r = new SerialPortReader(mSerialPort.getInputStream());
mLog.info("Starting Serial Port Reader");
mSerialPortReaderHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(r, 0,
5, TimeUnit.MILLISECONDS);
mStarted = true;
mLog.info("Startup complete - awaiting reset response");
}
else
{
mLog.warn("Could not open serial port: " + mSerialPort.getSystemPortName());
throw new IOException("Could not open serial port:" + mSerialPort.getSystemPortName());
}
break;
}
}
}
if(mSerialPort == null)
{
throw new IOException("ThumbDV serial port not found");
}
return;
}
public void send(byte[] message) throws IOException
{
if(mSerialPort == null)
{
throw new IOException("ThumbDv must be started before use");
}
int bytesWritten = mSerialPort.writeBytes(message, message.length);
if(bytesWritten < 0)
{
throw new IOException("Unable to write message:" + Arrays.toString(message));
}
else if(bytesWritten != message.length)
{
throw new IOException("Unable to write message:" + Arrays.toString(message) + " Bytes Written:" + bytesWritten);
}
}
/**
* Sends the AMBE request message
*
* @param request message
* @throws IOException if there is an error
*/
public void send(AmbeRequest request) throws IOException
{
send(request.getData());
}
private void receive(byte[] bytes)
{
AmbeMessage message = AmbeMessageFactory.getMessage(bytes);
if(message instanceof DecodeSpeechResponse && mAudioPacketListener != null)
{
float[] samples = ((DecodeSpeechResponse)message).getSamples();
ReusableAudioPacket audioPacket = mReusableAudioPacketQueue.getBuffer(samples.length);
audioPacket.loadAudioFrom(samples);
mAudioPacketListener.receive(audioPacket);
}
else if(message instanceof ReadyResponse && mAudioDecodeProcessorHandle == null)
{
if(mAudioDecodeProcessorHandle == null)
{
try
{
switch(mAudioProtocol)
{
case DSTAR:
send(new SetVocoderRequest(VocoderRate.RATE_33));
send(new SetVocoderParametersRequest(0x0130, 0x0763, 0x4000, 0x0000, 0x0000, 0x0048));
break;
case DMR:
case NXDN:
case P25_PHASE2:
send(new SetVocoderRequest(VocoderRate.RATE_33));
send(new SetVocoderParametersRequest(0x0431, 0x0754, 0x2400, 0x0000, 0x0000, 0x6F48));
break;
default:
throw new IllegalStateException("Unrecognized audio protocol:" + mAudioProtocol);
}
}
catch(IOException ioe)
{
mLog.error("Error setting audio protocol vocoder parameters");
}
mLog.info("ThumbDv Reset Complete");
}
else
{
mLog.info("ThumbDv Reset Detected");
}
}
else if(message instanceof SetVocoderParameterResponse)
{
if(((SetVocoderParameterResponse)message).isSuccessful())
{
mLog.info("Audio vocoder parameters configured for " + mAudioProtocol);
//Start the audio frame decode processor
mAudioDecodeProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioDecodeProcessor(), 0,
10, TimeUnit.MILLISECONDS);
}
}
else if(message != null)
{
mLog.debug("RECEIVED:" + message.toString());
}
}
/**
* Logs the current settings of the serial port. Note: you must invoke start() before this method so that
* the serial port can be discovered and opened.
*/
public void logSerialPort()
{
if(mSerialPort != null)
{
StringBuilder sb = new StringBuilder();
sb.append("\nPort:\t\t\t").append(mSerialPort.getSystemPortName()).append("\n");
sb.append("Name:\t\t\t").append(mSerialPort.getDescriptivePortName()).append("\n");
sb.append("Baud Rate:\t\t").append(mSerialPort.getBaudRate()).append("\n");
sb.append("Data Bits:\t\t").append(mSerialPort.getNumDataBits()).append("\n");
sb.append("Parity Bits:\t").append(mSerialPort.getParity()).append("\n");
sb.append("Stop Bits:\t\t").append(mSerialPort.getNumStopBits()).append("\n");
sb.append("Flow Control:\t").append(mSerialPort.getFlowControlSettings()).append("\n");
sb.append("Is Open:\t\t").append(mSerialPort.isOpen()).append("\n");
mLog.info(sb.toString());
}
else
{
mLog.info("No serial port found");
}
}
/**
* Processes the audio frame decode queue
*/
public class AudioDecodeProcessor implements Runnable
{
@Override
public void run()
{
try
{
DecodeSpeechRequest request = mDecodeSpeechRequests.poll();
if(request != null)
{
try
{
send(request);
}
catch(IOException ioe)
{
mLog.error("Error decoding audio frame", ioe);
}
// try
// {
// //Force a 20ms sleep after submitting an audio frame to avoid overflowing the thumbdv
// Thread.sleep(20);
// }
// catch(InterruptedException ie)
// {
// //Do nothing
// }
}
}
catch(Throwable t)
{
mLog.error("Error", t);
}
}
}
public class SerialPortReader implements Runnable
{
private InputStream mInputStream;
public SerialPortReader(InputStream inputStream)
{
mInputStream = inputStream;
}
@Override
public void run()
{
try
{
if(mInputStream != null)
{
try
{
while(mInputStream.available() > 0)
{
byte[] buffer = new byte[400];
int bytesRead = mInputStream.readNBytes(buffer, 0, 1);
if(bytesRead == 1 && buffer[0] == PACKET_START)
{
while(mInputStream.available() < 2)
{
try
{
Thread.sleep(1);
}
catch(InterruptedException ie)
{
}
}
bytesRead = mInputStream.readNBytes(buffer, 1, 2);
if(bytesRead == 2)
{
int length = (0xFF & buffer[1]) << 8;
length += (0xFF & buffer[2]);
if(0 < length && length < 400)
{
length++; //Add a byte for the type byte
while(mInputStream.available() < length)
{
try
{
Thread.sleep(1);
}
catch(InterruptedException ie)
{
}
}
bytesRead = mInputStream.readNBytes(buffer, 3, length);
if(bytesRead == length)
{
receive(Arrays.copyOf(buffer, length + 3));
}
else
{
mLog.debug("Expected [" + length + "] but received [" + bytesRead + "] bytes - " + Arrays.toString(buffer));
}
}
else
{
mLog.error("Received packet with unexpected length: " + length);
//Don't process the buffer ... let the reader read and inspect 1 byte at a time
//to regain sync on the packet start byte
}
}
else
{
mLog.debug("Expected [2] but received [" + bytesRead + "] -- this shouldn't happen");
}
}
else
{
mLog.debug("Unrecognized byte: " + Arrays.toString(buffer));
}
}
}
catch(IOException ioe)
{
mLog.error("Error while reading ThumbDv serial port", ioe);
}
}
}
catch(Throwable t)
{
mLog.error("Error", t);
}
}
}
public static void main(String[] args)
{
mLog.debug("Starting");
String silence = "BEDDEA821EFD660C08";
String[] frames = {"0E46122323067C60F8", "0E469433C1067CF1BC", "0E46122B23067C60F8", "0E67162BE08874E2B4",
"0E46163BE1067CF1BC", "0E46122B23067C60F8", "0A06163BE00A5C303E", "0E46122B23067C60F8", "0E46163BE1847CE1FC",
"0E46122B23067C60F8"};
List<byte[]> frameData = new ArrayList<>();
for(String frame : frames)
{
byte[] bytes = new byte[frame.length() / 2];
for(int x = 0; x < frame.length(); x += 2)
{
String hex = frame.substring(x, x + 2);
bytes[x / 2] = (byte) (0xFF & Integer.parseInt(hex, 16));
}
frameData.add(bytes);
}
mLog.debug("Starting thumb dv thread(s)");
final Listener<ReusableAudioPacket> listener = reusableAudioPacket -> {
mLog.info("Got an audio packet!");
reusableAudioPacket.decrementUserCount();
};
try(ThumbDv thumbDv = new ThumbDv(AudioProtocol.P25_PHASE2, listener))
{
thumbDv.start();
Thread.sleep(6000);
for(int x = 0; x < 20; x++)
{
thumbDv.send(new EncodeSpeechRequest(new short[160]));
Thread.sleep(20);
}
// for(byte[] frame : frameData)
// {
// thumbDv.decode(frame);
// }
while(true);
}
catch(IOException ioe)
{
mLog.error("Error", ioe);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,44 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message;
/**
* AMBE-3000R Message
*/
public abstract class AmbeMessage
{
/**
* Converts the byte array to a hexadecimal string array
*/
public static String toHex(byte[] data)
{
StringBuilder sb = new StringBuilder();
for(byte b: data)
{
sb.append(String.format("%02X", b));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,95 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message;
import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.EncodeSpeechResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.GetConfigResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.InitializeCodecResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.ProductIdResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetChannelFormatResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetChannelResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetPacketModeResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetSpeechFormatResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.UnknownResponse;
import io.github.dsheirer.audio.convert.thumbdv.message.response.VersionResponse;
public class AmbeMessageFactory
{
private static final byte CONTROL_PACKET = (byte)0x00;
private static final byte CHANNEL_PACKET = (byte)0x01;
private static final byte SPEECH_PACKET = (byte)0x02;
private static final int INDEX_PACKET_TYPE = 3;
private static final int INDEX_CONTROL_PACKET_TYPE = 4;
public static AmbeMessage getMessage(byte[] data)
{
if(data != null && data.length >= 5)
{
if(data[INDEX_PACKET_TYPE] == CONTROL_PACKET)
{
PacketField packetField = PacketField.fromValue(data[INDEX_CONTROL_PACKET_TYPE]);
switch(packetField)
{
case PKT_CHANNEL_0:
return new SetChannelResponse(data);
case PKT_CHANNEL_FORMAT:
return new SetChannelFormatResponse(data);
case PKT_CODEC_STOP:
return new SetPacketModeResponse(data);
case PKT_GET_CONFIG:
return new GetConfigResponse(data);
case PKT_INIT:
return new InitializeCodecResponse(data);
case PKT_PRODUCT_ID:
return new ProductIdResponse(data);
case PKT_RATE_PARAMETER:
return new SetVocoderParameterResponse(data);
case PKT_RATE_TABLE:
return new SetVocoderResponse(data);
case PKT_READY:
return new ReadyResponse(data);
case PKT_SPEECH_FORMAT:
return new SetSpeechFormatResponse(data);
case PKT_VERSION_STRING:
return new VersionResponse(data);
}
}
else if(data[INDEX_PACKET_TYPE] == CHANNEL_PACKET)
{
return new EncodeSpeechResponse(data);
}
else if(data[INDEX_PACKET_TYPE] == SPEECH_PACKET)
{
return new DecodeSpeechResponse(data);
}
}
return new UnknownResponse(data);
}
}

View File

@@ -0,0 +1,47 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message;
/**
* AMBE-3000 Initialization Options
*/
public enum InitializeOption
{
ENCODER((byte)0x01),
DECODER((byte)0x02),
ENCODER_AND_DECODER((byte)0x03),
ECHO_CANCELLER((byte)0x04),
ENCODER_DECODER_ECHO_CANCELLER((byte)0x07);
private byte mCode;
InitializeOption(byte code)
{
mCode = code;
}
public byte getCode()
{
return mCode;
}
}

View File

@@ -0,0 +1,163 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message;
/**
* AMBE-3000R Control Packet Field enumeration
*/
public enum PacketField
{
PKT_ENCODER_CMODE((byte)0x05),
PKT_DECODER_CMODE((byte)0x06),
PKT_RATE_TABLE((byte)0x09),
PKT_RATE_PARAMETER((byte)0x0A),
PKT_INIT((byte)0x0B),
PKT_LOW_POWER((byte)0x10),
PKT_CHANNEL_FORMAT((byte)0x15),
PKT_SPEECH_FORMAT((byte)0x16),
PKT_CODEC_START((byte)0x2A),
PKT_CODEC_STOP((byte)0x2B),
PKT_PRODUCT_ID((byte)0x30),
PKT_VERSION_STRING((byte)0x31),
PKT_COMPAND((byte)0x32),
PKT_RESET((byte)0x33),
PKT_RESET_TO_SOFTWARE_CONFIG((byte)0x34),
PKT_HALT((byte)0x35),
PKT_GET_CONFIG((byte)0x36),
PKT_READ_CONFIG((byte)0x37),
PKT_CODEC_CFG((byte)0x38),
PKT_READY((byte)0x39),
PKT_PARITY_MODE((byte)0x3F),
PKT_CHANNEL_0((byte)0x40),
PKT_WRITE_I2C((byte)0x44),
PKT_CLEAR_CODEC_RESET((byte)0x46),
PKT_SET_CODEC_RESET((byte)0x47),
PKT_DISCARD_CODEC_SAMPLES((byte)0x48),
PKT_DELAY_NUMBER_MICRO_SECONDS((byte)0x49),
PKT_DELAY_NUMBER_NANO_SECONDS((byte)0x4A),
PKT_GAIN((byte)0x4B),
PKT_RTS_THRESHOLD((byte)0x4E),
//Speech packet values
SPEECH_DATA((byte)0x00),
CHANNEL_DATA_HARD_SYMBOL((byte)0x01),
CMODE((byte)0x02),
SAMPLE_COUNT((byte)0x03),
TONE((byte)0x08),
CHANNEL_DATA_SOFT_SYMBOL((byte)0x17),
VOCODER((byte)0x40),
//Packet Types
PACKET_TYPE_CONTROL((byte)0x00),
PACKET_TYPE_ENCODE_SPEECH((byte)0x01),
PACKET_TYPE_DECODE_SPEECH((byte)0x02),
UNKNOWN((byte)0x00);
private byte mCode;
PacketField(byte code)
{
mCode = code;
}
/**
* Packet type byte code value
*/
public byte getCode()
{
return mCode;
}
/**
* Lookup a packet type from the code byte value
*/
public static PacketField fromValue(byte value)
{
switch(value)
{
case (byte)0x05:
return PKT_ENCODER_CMODE;
case (byte)0x06:
return PKT_DECODER_CMODE;
case (byte)0x09:
return PKT_RATE_TABLE;
case (byte)0x0A:
return PKT_RATE_PARAMETER;
case (byte)0x0B:
return PKT_INIT;
case (byte)0x10:
return PKT_LOW_POWER;
case (byte)0x15:
return PKT_CHANNEL_FORMAT;
case (byte)0x16:
return PKT_SPEECH_FORMAT;
case (byte)0x2A:
return PKT_CODEC_START;
case (byte)0x2B:
return PKT_CODEC_STOP;
case (byte)0x30:
return PKT_PRODUCT_ID;
case (byte)0x31:
return PKT_VERSION_STRING;
case (byte)0x32:
return PKT_COMPAND;
case (byte)0x33:
return PKT_RESET;
case (byte)0x34:
return PKT_RESET_TO_SOFTWARE_CONFIG;
case (byte)0x35:
return PKT_HALT;
case (byte)0x36:
return PKT_GET_CONFIG;
case (byte)0x37:
return PKT_READ_CONFIG;
case (byte)0x38:
return PKT_CODEC_CFG;
case (byte)0x39:
return PKT_READY;
case (byte)0x3F:
return PKT_PARITY_MODE;
case (byte)0x40:
return PKT_CHANNEL_0;
case (byte)0x44:
return PKT_WRITE_I2C;
case (byte)0x46:
return PKT_CLEAR_CODEC_RESET;
case (byte)0x47:
return PKT_SET_CODEC_RESET;
case (byte)0x48:
return PKT_DISCARD_CODEC_SAMPLES;
case (byte)0x49:
return PKT_DELAY_NUMBER_MICRO_SECONDS;
case (byte)0x4A:
return PKT_DELAY_NUMBER_NANO_SECONDS;
case (byte)0x4B:
return PKT_GAIN;
case (byte)0x4E:
return PKT_RTS_THRESHOLD;
default:
return UNKNOWN;
}
}
}

View File

@@ -0,0 +1,129 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message;
public enum VocoderRate
{
//AMBE-1000 Standard Rates
RATE_0((byte)0x00, 2400,0),
RATE_1((byte)0x01, 3600,0),
RATE_2((byte)0x02, 3600,1200),
RATE_3((byte)0x03, 4800,0),
RATE_4((byte)0x04, 9600,0),
RATE_5((byte)0x05, 2350,50),
RATE_6((byte)0x06, 4850,4750),
RATE_7((byte)0x07, 4550,250),
RATE_8((byte)0x08, 3100,1700),
RATE_9((byte)0x09, 4400,2800),
RATE_10((byte)0x0A, 4150,2250),
RATE_11((byte)0x0B, 3350,250),
RATE_12((byte)0x0C, 7750,250),
RATE_13((byte)0x0D, 4650,3350),
RATE_14((byte)0x0E, 3750,250),
RATE_15((byte)0x0F, 4000,0),
//AMBE-2000 Standard Rates
RATE_16((byte)0x10, 3600,0),
RATE_17((byte)0x11, 4000,0),
RATE_18((byte)0x12, 4800,0),
RATE_19((byte)0x13, 6400,0),
RATE_20((byte)0x14, 8000,0),
RATE_21((byte)0x15, 9600,0),
RATE_22((byte)0x16, 2400,1600),
RATE_23((byte)0x17, 3600,1200),
RATE_24((byte)0x18, 4000,800),
RATE_25((byte)0x19, 2400,2400),
RATE_26((byte)0x1A, 4000,2400),
RATE_27((byte)0x1B, 4400,2800), //Is this compatible with APCO25 Phase 1?
RATE_28((byte)0x1C, 4000,4000),
RATE_29((byte)0x1D, 2400,7200),
RATE_30((byte)0x1E, 3600,6000),
RATE_31((byte)0x1F, 2000,0),
RATE_32((byte)0x20, 3600,2800),
//AMBE-3000 Standard Rates (AMBE+2)
RATE_33((byte)0x21, 2450,1150), //APCO25 HR (Phase 2), DMR, NXDN, DSTAR
RATE_34((byte)0x22, 2450,0),
RATE_35((byte)0x23, 2250,1150),
RATE_36((byte)0x24, 2250,0),
RATE_37((byte)0x25, 2400,0),
RATE_38((byte)0x26, 3000,0),
RATE_39((byte)0x27, 3600,0),
RATE_40((byte)0x28, 4000,0),
RATE_41((byte)0x29, 4400,0),
RATE_42((byte)0x2A, 4800,0),
RATE_43((byte)0x2B, 6400,0),
RATE_44((byte)0x2C, 7200,0),
RATE_45((byte)0x2D, 8000,0),
RATE_46((byte)0x2E, 9600,0),
RATE_47((byte)0x2F, 2450,250),
RATE_48((byte)0x30, 3350,250),
RATE_49((byte)0x31, 3750,250),
RATE_50((byte)0x32, 4550,250),
RATE_51((byte)0x33, 2450,1950),
RATE_52((byte)0x34, 2450,2350),
RATE_53((byte)0x35, 2450,3550),
RATE_54((byte)0x36, 2450,4750),
RATE_55((byte)0x37, 2600,1400),
RATE_56((byte)0x38, 3600,1200),
RATE_57((byte)0x39, 4000,800),
RATE_58((byte)0x3A, 4000,2400),
RATE_59((byte)0x3B, 4400,2800), //Is this compatible with APCO25 Phase 1?
RATE_60((byte)0x3C, 4000,4000),
RATE_61((byte)0x3D, 3600,6000);
private byte mCode;
private int mSpeechRate;
private int mFecRate;
VocoderRate(byte code, int speechRate, int fecRate)
{
mCode = code;
mSpeechRate = speechRate;
mFecRate = fecRate;
}
/**
* Byte code value for the vocoder rate
*/
public byte getCode()
{
return mCode;
}
/**
* Speech rate in bits per second (bps)
*/
public int getSpeechRate()
{
return mSpeechRate;
}
/**
* Forward Error Correction rate in bits per second (bps)
*/
public int getFecRate()
{
return mFecRate;
}
}

View File

@@ -0,0 +1,67 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* AMBE-3000R Request Packet
*/
public abstract class AmbeRequest extends AmbeMessage
{
private static final byte PACKET_START_BYTE = (byte)0x61;
protected static final int PACKET_TYPE_INDEX = 3;
protected static final int PAYLOAD_START_INDEX = 4;
public abstract PacketField getType();
public abstract byte[] getData();
/**
* Creates a byte array of the specified length plus 4 packet header bytes with packet start, length and control
* bytes filled in.
*
* @param length of the message (not including the 4 byte packet header)
* @return byte array with packet header pre-filled.
*/
protected byte[] createMessage(int length, PacketField type)
{
byte[] data = new byte[length + 4];
data[0] = PACKET_START_BYTE;
data[1] = (byte)((length >> 8 & 0xFF));
data[2] = (byte)(length & 0xFF);
if(type == PacketField.PACKET_TYPE_ENCODE_SPEECH || type == PacketField.PACKET_TYPE_DECODE_SPEECH)
{
data[PACKET_TYPE_INDEX] = type.getCode();
}
else
{
data[PACKET_TYPE_INDEX] = PacketField.PACKET_TYPE_CONTROL.getCode();
data[PAYLOAD_START_INDEX] = type.getCode();
}
return data;
}
}

View File

@@ -0,0 +1,108 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
/**
* Decode speech request, used to request decode of an encoded audio frame.
*/
public class DecodeSpeechRequest extends AmbeRequest
{
private static final int CHANNEL_DATA_IDENTIFIER_INDEX = 4;
private static final byte SAMPLE_COUNT = (byte)(0xFF & 160); //8 kHz Audio sample count for 20ms frame
private byte[] mAudioFrame;
private VocoderRate mVocoderRate;
/**
* Constructs an audio frame decode request using the specified vocoder rate.
* @param audioFrame of encoded audio samples
* @param vocoderRate to use when decoding
*/
public DecodeSpeechRequest(byte[] audioFrame, VocoderRate vocoderRate)
{
mAudioFrame = audioFrame;
mVocoderRate = vocoderRate;
}
/**
* Constructs an audio frame decode request using the current vocoder rate.
* @param audioFrame of encoded audio samples
*/
public DecodeSpeechRequest(byte[] audioFrame)
{
this(audioFrame, null);
}
@Override
public PacketField getType()
{
return PacketField.PACKET_TYPE_ENCODE_SPEECH;
}
private boolean hasVocoderRate()
{
return mVocoderRate != null;
}
@Override
public byte[] getData()
{
if(hasVocoderRate())
{
int length = mAudioFrame.length + 9;
byte[] data = createMessage(length, getType());
int offset = CHANNEL_DATA_IDENTIFIER_INDEX;
data[offset++] = PacketField.VOCODER.getCode();
data[offset++] = mVocoderRate.getCode();
data[offset++] = PacketField.CHANNEL_DATA_HARD_SYMBOL.getCode();
data[offset++] = (byte)(0xFF & (mAudioFrame.length * 8));
System.arraycopy(mAudioFrame, 0, data, offset, mAudioFrame.length);
offset += mAudioFrame.length;
data[offset++] = PacketField.SAMPLE_COUNT.getCode();
data[offset++] = (byte)0xA0;
data[offset++] = (byte)0x02;
data[offset++] = (byte)0x00;
data[offset] = (byte)0x00;
return data;
}
else
{
int length = mAudioFrame.length + 2;
byte[] data = createMessage(length, getType());
int offset = CHANNEL_DATA_IDENTIFIER_INDEX;
data[offset++] = PacketField.CHANNEL_DATA_HARD_SYMBOL.getCode();
data[offset++] = (byte)(0xFF & (mAudioFrame.length * 8));
System.arraycopy(mAudioFrame, 0, data, offset, mAudioFrame.length);
return data;
}
}
}

View File

@@ -0,0 +1,88 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
public class EncodeSpeechRequest extends AmbeRequest
{
private static final int CHANNEL_IDENTIFIER_INDEX = 4;
private static final int SPEECH_DATA_IDENTIFIER_INDEX = 5;
private static final int SAMPLE_COUNT_INDEX = 6;
private static final int SPEECH_DATA_START_INDEX = 7;
private short[] mSamples;
public EncodeSpeechRequest(short[] samples)
{
mSamples = samples;
}
public EncodeSpeechRequest(float[] samples)
{
mSamples = new short[samples.length];
for(int x = 0; x < samples.length; x++)
{
if(samples[x] > 1.0f)
{
mSamples[x] = Short.MAX_VALUE;
}
else if(samples[x] < -1.0f)
{
mSamples[x] = Short.MIN_VALUE;
}
else
{
mSamples[x] = (short)(samples[x] * Short.MAX_VALUE);
}
}
}
@Override
public PacketField getType()
{
return PacketField.PACKET_TYPE_DECODE_SPEECH;
}
@Override
public byte[] getData()
{
int length = (mSamples.length * 2) + 3;
byte[] data = createMessage(length, getType());
data[CHANNEL_IDENTIFIER_INDEX] = PacketField.PKT_CHANNEL_0.getCode();
data[SPEECH_DATA_IDENTIFIER_INDEX] = (byte)0x00;
data[SAMPLE_COUNT_INDEX] = (byte)(0xFF & mSamples.length);
int pointer = SPEECH_DATA_START_INDEX;
for(short sample: mSamples)
{
data[pointer++] = (byte)((sample >> 8 & 0xFF));
data[pointer++] = (byte)(sample & 0xFF);
}
return data;
}
}

View File

@@ -0,0 +1,43 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Get configuration request packet
*/
public class GetConfigRequest extends AmbeRequest
{
@Override
public PacketField getType()
{
return PacketField.PKT_GET_CONFIG;
}
@Override
public byte[] getData()
{
return createMessage(1, getType());
}
}

View File

@@ -0,0 +1,54 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.InitializeOption;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Initialize CODEC request packet
*/
public class InitializeCodecRequest extends AmbeRequest
{
private static final int INITIALIZE_OPTION_INDEX = 5;
private InitializeOption mInitializeOption;
public InitializeCodecRequest(InitializeOption option)
{
mInitializeOption = option;
}
@Override
public PacketField getType()
{
return PacketField.PKT_INIT;
}
@Override
public byte[] getData()
{
byte[] data = createMessage(2, getType());
data[INITIALIZE_OPTION_INDEX] = mInitializeOption.getCode();
return data;
}
}

View File

@@ -0,0 +1,43 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Product ID request packet
*/
public class ProductIdRequest extends AmbeRequest
{
@Override
public PacketField getType()
{
return PacketField.PKT_PRODUCT_ID;
}
@Override
public byte[] getData()
{
return createMessage(1, getType());
}
}

View File

@@ -0,0 +1,43 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Reset request packet
*/
public class ResetRequest extends AmbeRequest
{
@Override
public PacketField getType()
{
return PacketField.PKT_RESET;
}
@Override
public byte[] getData()
{
return createMessage(1, getType());
}
}

View File

@@ -0,0 +1,48 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set channel format request packet
*/
public class SetChannelFormatRequest extends AmbeRequest
{
private static final int FORMAT_INDEX = 5;
private static final byte FORMAT = (byte)0x00;
@Override
public PacketField getType()
{
return PacketField.PKT_CHANNEL_FORMAT;
}
@Override
public byte[] getData()
{
byte[] data = createMessage(2, getType());
data[FORMAT_INDEX] = FORMAT;
return data;
}
}

View File

@@ -0,0 +1,43 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set channel request packet
*/
public class SetChannelRequest extends AmbeRequest
{
@Override
public PacketField getType()
{
return PacketField.PKT_CHANNEL_0;
}
@Override
public byte[] getData()
{
return createMessage(1, getType());
}
}

View File

@@ -0,0 +1,43 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Packet mode (versus CODEC mode) request packet
*/
public class SetPacketModeRequest extends AmbeRequest
{
@Override
public PacketField getType()
{
return PacketField.PKT_CODEC_STOP;
}
@Override
public byte[] getData()
{
return createMessage(1, getType());
}
}

View File

@@ -0,0 +1,48 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set speech format request packet
*/
public class SetSpeechFormatRequest extends AmbeRequest
{
private static final int FORMAT_INDEX = 5;
private static final byte FORMAT = (byte)0x00;
@Override
public PacketField getType()
{
return PacketField.PKT_SPEECH_FORMAT;
}
@Override
public byte[] getData()
{
byte[] data = createMessage(2, getType());
data[FORMAT_INDEX] = FORMAT;
return data;
}
}

View File

@@ -0,0 +1,74 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set Vocoder parameters request packet
*/
public class SetVocoderParametersRequest extends AmbeRequest
{
private int mWord0;
private int mWord1;
private int mWord2;
private int mWord3;
private int mWord4;
private int mWord5;
public SetVocoderParametersRequest(int word0, int word1, int word2, int word3, int word4, int word5)
{
mWord0 = word0;
mWord1 = word1;
mWord2 = word2;
mWord3 = word3;
mWord4 = word4;
mWord5 = word5;
}
@Override
public PacketField getType()
{
return PacketField.PKT_RATE_PARAMETER;
}
@Override
public byte[] getData()
{
byte[] data = createMessage(14, getType());
data[PAYLOAD_START_INDEX + 1] = (byte)(mWord0 >> 8 & 0xFF);
data[PAYLOAD_START_INDEX + 2] = (byte)(mWord0 & 0xFF);
data[PAYLOAD_START_INDEX + 3] = (byte)(mWord1 >> 8 & 0xFF);
data[PAYLOAD_START_INDEX + 4] = (byte)(mWord1 & 0xFF);
data[PAYLOAD_START_INDEX + 5] = (byte)(mWord2 >> 8 & 0xFF);
data[PAYLOAD_START_INDEX + 6] = (byte)(mWord2 & 0xFF);
data[PAYLOAD_START_INDEX + 7] = (byte)(mWord3 >> 8 & 0xFF);
data[PAYLOAD_START_INDEX + 8] = (byte)(mWord3 & 0xFF);
data[PAYLOAD_START_INDEX + 9] = (byte)(mWord4 >> 8 & 0xFF);
data[PAYLOAD_START_INDEX + 10] = (byte)(mWord4 & 0xFF);
data[PAYLOAD_START_INDEX + 11] = (byte)(mWord5 >> 8 & 0xFF);
data[PAYLOAD_START_INDEX + 12] = (byte)(mWord5 & 0xFF);
return data;
}
}

View File

@@ -0,0 +1,53 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
/**
* Set Vocoder Rate request packet
*/
public class SetVocoderRequest extends AmbeRequest
{
private VocoderRate mVocoderRate;
public SetVocoderRequest(VocoderRate rate)
{
mVocoderRate = rate;
}
@Override
public PacketField getType()
{
return PacketField.PKT_RATE_TABLE;
}
@Override
public byte[] getData()
{
byte[] data = createMessage(2, getType());
data[PAYLOAD_START_INDEX + 1] = mVocoderRate.getCode();
return data;
}
}

View File

@@ -0,0 +1,43 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.request;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Version string request packet
*/
public class VersionRequest extends AmbeRequest
{
@Override
public PacketField getType()
{
return PacketField.PKT_VERSION_STRING;
}
@Override
public byte[] getData()
{
return createMessage(1, getType());
}
}

View File

@@ -0,0 +1,63 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
import java.util.Arrays;
/**
* AMBE-3000R Response Packet
*/
public abstract class AmbeResponse extends AmbeMessage
{
protected static final int PAYLOAD_START_INDEX = 5;
private byte[] mMessage;
protected AmbeResponse(byte[] message)
{
mMessage = message;
}
/**
* Control packet type
*/
public abstract PacketField getType();
/**
* Received message bytes
*/
protected byte[] getMessage()
{
return mMessage;
}
/**
* Payload of the packet (does not include the packet header)
*/
protected byte[] getPayload()
{
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX, getMessage().length);
}
}

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
import io.github.dsheirer.sample.ConversionUtils;
import java.util.Arrays;
/**
* Decode speech response
*/
public class DecodeSpeechResponse extends AmbeResponse
{
public DecodeSpeechResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PACKET_TYPE_DECODE_SPEECH;
}
/**
* Payload of the packet (does not include the packet header)
*/
protected byte[] getPayload()
{
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX + 1, getMessage().length);
}
public float[] getSamples()
{
return ConversionUtils.convertFromSigned16BitSamples(getPayload());
}
@Override
public String toString()
{
return "DECODED SPEECH: SAMPLE COUNT:" + getSamples().length + " " + toHex(getPayload());
}
}

View File

@@ -0,0 +1,59 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
import java.util.Arrays;
/**
* Encode speech response
*/
public class EncodeSpeechResponse extends AmbeResponse
{
public EncodeSpeechResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PACKET_TYPE_ENCODE_SPEECH;
}
/**
* Payload of the packet (does not include the packet header)
*/
protected byte[] getPayload()
{
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX + 1, getMessage().length);
}
@Override
public String toString()
{
return "ENCODED SPEECH: " + toHex(getPayload()) + " " + Arrays.toString(getMessage());
}
}

View File

@@ -0,0 +1,49 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Get configuration response
*/
public class GetConfigResponse extends AmbeResponse
{
public GetConfigResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_GET_CONFIG;
}
@Override
public String toString()
{
return "CONFIGURATION: " + toHex(getPayload());
}
}

View File

@@ -0,0 +1,58 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Initialize CODEC response
*/
public class InitializeCodecResponse extends AmbeResponse
{
public InitializeCodecResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_INIT;
}
/**
* Success / fail
*/
public boolean isSuccessful()
{
byte[] payload = getPayload();
return payload != null && payload.length ==1 && payload[0] == 0;
}
@Override
public String toString()
{
return "INITIALIZE CODEC " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
}
}

View File

@@ -0,0 +1,56 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Product ID Response
*/
public class ProductIdResponse extends AmbeResponse
{
public ProductIdResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_PRODUCT_ID;
}
/**
* Product ID string
*/
public String getProductId()
{
return new String(getPayload()).trim();
}
@Override
public String toString()
{
return "PRODUCT ID:" + getProductId();
}
}

View File

@@ -0,0 +1,50 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
import java.util.Arrays;
/**
* Ready response
*/
public class ReadyResponse extends AmbeResponse
{
public ReadyResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_READY;
}
@Override
public String toString()
{
return "READY" + Arrays.toString(getMessage());
}
}

View File

@@ -0,0 +1,57 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Reset response
*/
public class ResetResponse extends AmbeResponse
{
public ResetResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_RESET;
}
/**
* Success indicator
*/
public boolean isSuccessful()
{
byte[] payload = getPayload();
return payload != null && payload.length ==1 && payload[0] == 0;
}
@Override
public String toString()
{
return "RESET " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
}
}

View File

@@ -0,0 +1,58 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set channel format response
*/
public class SetChannelFormatResponse extends AmbeResponse
{
public SetChannelFormatResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_CHANNEL_FORMAT;
}
/**
* Success or fail
*/
public boolean isSuccessful()
{
byte[] payload = getPayload();
return payload != null && payload.length ==1 && payload[0] == 0;
}
@Override
public String toString()
{
return "SET CHANNEL FORMAT " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
}
}

View File

@@ -0,0 +1,57 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set Vocoder rate response
*/
public class SetChannelResponse extends AmbeResponse
{
public SetChannelResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_CHANNEL_0;
}
/**
* Success indicator
*/
public boolean isSuccessful()
{
byte[] payload = getPayload();
return payload != null && payload.length ==1 && payload[0] == 0;
}
@Override
public String toString()
{
return "SET CHANNEL 0 " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
}
}

View File

@@ -0,0 +1,58 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set packet mode response
*/
public class SetPacketModeResponse extends AmbeResponse
{
public SetPacketModeResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_CODEC_STOP;
}
/**
* Success or fail
*/
public boolean isSuccessful()
{
byte[] payload = getPayload();
return payload != null && payload.length ==1 && payload[0] == 0;
}
@Override
public String toString()
{
return "SET PACKET MODE " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
}
}

View File

@@ -0,0 +1,58 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set speech format response
*/
public class SetSpeechFormatResponse extends AmbeResponse
{
public SetSpeechFormatResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_SPEECH_FORMAT;
}
/**
* Success or fail
*/
public boolean isSuccessful()
{
byte[] payload = getPayload();
return payload != null && payload.length ==1 && payload[0] == 0;
}
@Override
public String toString()
{
return "SET SPEECH FORMAT " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
}
}

View File

@@ -0,0 +1,65 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set Vocoder parameter response
*/
public class SetVocoderParameterResponse extends AmbeResponse
{
public SetVocoderParameterResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_RATE_PARAMETER;
}
/**
* Success indicator
*/
public boolean isSuccessful()
{
byte[] payload = getPayload();
return payload != null && payload.length ==3 && payload[0] == 0;
}
@Override
public String toString()
{
if(isSuccessful())
{
return "SET VOCODER RATE SUCCESSFUL";
}
else
{
return "SET VOCODER RATE **FAILED** - RESPONSE:" + toHex(getPayload());
}
}
}

View File

@@ -0,0 +1,58 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Set Vocoder rate response
*/
public class SetVocoderResponse extends AmbeResponse
{
public SetVocoderResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_RATE_TABLE;
}
/**
* Success indicator
*/
public boolean isSuccessful()
{
byte[] payload = getPayload();
return payload != null && payload.length ==1 && payload[0] == 0;
}
@Override
public String toString()
{
return "SET VOCODER RATE " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
}
}

View File

@@ -0,0 +1,50 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
import java.util.Arrays;
/**
* Unknown Response Message
*/
public class UnknownResponse extends AmbeResponse
{
public UnknownResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.UNKNOWN;
}
@Override
public String toString()
{
return "UNKNOWN MESSAGE:" + Arrays.toString(getMessage());
}
}

View File

@@ -0,0 +1,56 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.convert.thumbdv.message.response;
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
/**
* Version Response
*/
public class VersionResponse extends AmbeResponse
{
public VersionResponse(byte[] message)
{
super(message);
}
@Override
public PacketField getType()
{
return PacketField.PKT_VERSION_STRING;
}
/**
* Version string
*/
public String getVersion()
{
return new String(getPayload()).trim();
}
@Override
public String toString()
{
return "VERSION:" + getVersion();
}
}

View File

@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -83,8 +83,6 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
private JLabel mIdentifierLabel = new JLabel("-----");
private Identifier mIdentifier;
private List<Alias> mAliases = Collections.EMPTY_LIST;
private boolean mConfigured = false;
private AliasModel mAliasModel;
public AudioChannelPanel(IconManager iconManager, UserPreferences userPreferences, SettingsManager settingsManager,
@@ -191,31 +189,70 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
}
/**
* Resets the from and to labels. Note: this does not happen on the swing
* event thread. Only invoke from the swing thread.
* Resets the from and to labels.
*/
private void resetLabels()
{
boolean updated = mIdentifier != null;
mIdentifier = null;
mAliases = Collections.EMPTY_LIST;
updateLabels();
mConfigured = false;
if(updated)
{
updateLabels();
}
}
/**
* Truncates the string to the specified length using an ellipses at the end
* @param value to optionally truncate
* @param length of the final formatted string
* @return formatted string or null
*/
private static String truncate(String value, int length)
private void updateIdentifiers(IdentifierCollection identifierCollection)
{
if(value != null && value.length() > length)
if(identifierCollection == null || identifierCollection.isEmpty())
{
return value.substring(0, length - 3) + "...";
resetLabels();
return;
}
return value;
List<Identifier> toIds = identifierCollection.getIdentifiers(IdentifierClass.USER, Role.TO);
if(toIds.isEmpty())
{
resetLabels();
return;
}
boolean updated = false;
if(toIds.size() == 1)
{
Identifier currentIdentifier = mIdentifier;
if(currentIdentifier == null || currentIdentifier != toIds.get(0))
{
mIdentifier = toIds.get(0);
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
if(aliasList != null)
{
mAliases = aliasList.getAliases(mIdentifier);
}
updated = true;
}
}
else
{
mIdentifier = toIds.get(0);
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
if(aliasList != null)
{
mAliases = aliasList.getAliases(mIdentifier);
}
updated = true;
}
if(updated)
{
updateLabels();
}
}
/**
@@ -238,13 +275,19 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
identifier = Joiner.on(", ").skipNulls().join(mAliases);
}
if(identifier == null && mIdentifier != null)
{
identifier = mTalkgroupFormatPreference.format(mIdentifier);
}
if(identifier == null)
{
identifier = "-----";
}
final ImageIcon icon = iconName != null ? mIconManager.getIcon(iconName, 18) : null;
final String identifierText = truncate(identifier, 33);
final String identifierText = identifier;
EventQueue.invokeLater(new Runnable()
{
@@ -253,7 +296,6 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
{
mIdentifierLabel.setText(identifierText);
mIconLabel.setIcon(icon);
mConfigured = true;
}
});
@@ -267,23 +309,10 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
@Override
public void receive(final IdentifierCollection identifierCollection)
{
if(identifierCollection != null && (identifierCollection.isUpdated() || !mConfigured))
{
List<Identifier> toIds = identifierCollection.getIdentifiers(IdentifierClass.USER, Role.TO);
mIdentifier = !toIds.isEmpty() ? toIds.get(0) : null;
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
if(aliasList != null)
{
mAliases = aliasList.getAliases(mIdentifier);
}
updateLabels();
}
updateIdentifiers(identifierCollection);
}
}
@Override
public void settingChanged(Setting setting)
{

View File

@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -39,7 +39,8 @@ public class AudioChannelsPanel extends JPanel
public AudioChannelsPanel(IconManager iconManager, UserPreferences userPreferences, SettingsManager settingsManager,
IAudioController controller, AliasModel aliasModel)
{
setLayout(new MigLayout("insets 0 0 0 0", "[][sg abc,grow,fill][][sg abc,grow,fill]", "[grow,fill]"));
setLayout(new MigLayout("insets 0 0 0 0",
"[][sizegroup abc,grow,fill][][sizegroup abc,grow,fill]", "[grow,fill]"));
setBackground(Color.BLACK);

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* sdrtrunk
* 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.audio.playback;
@@ -54,6 +56,11 @@ public abstract class AudioOutput implements Listener<ReusableAudioPacket>, Line
private LinkedTransferQueue<ReusableAudioPacket> mBuffer = new LinkedTransferQueue<>();
private int mBufferStartThreshold;
private int mBufferStopThreshold;
private static IdentifierCollection EMPTY_IDENTIFIER_COLLECTION = new IdentifierCollection(0);
static
{
EMPTY_IDENTIFIER_COLLECTION.setUpdated(true);
}
private Listener<IdentifierCollection> mIdentifierCollectionListener;
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
@@ -380,12 +387,11 @@ public abstract class AudioOutput implements Listener<ReusableAudioPacket>, Line
*/
private void checkStop()
{
if(mCanProcessAudio &&
mOutput.isRunning() &&
mOutput.available() >= mBufferStopThreshold)
if(mCanProcessAudio && mOutput.isRunning() && mOutput.available() >= mBufferStopThreshold)
{
mOutput.drain();
mOutput.stop();
broadcast(EMPTY_IDENTIFIER_COLLECTION);
}
}
}

View File

@@ -1,8 +1,30 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.squelch;
import io.github.dsheirer.sample.Listener;
public interface ISquelchStateListener
{
public Listener<SquelchState> getSquelchStateListener();
Listener<SquelchStateEvent> getSquelchStateListener();
}

View File

@@ -1,9 +1,32 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.squelch;
import io.github.dsheirer.sample.Listener;
public interface ISquelchStateProvider
{
public void setSquelchStateListener( Listener<SquelchState> listener );
public void removeSquelchStateListener();
void setSquelchStateListener(Listener<SquelchStateEvent> listener);
void removeSquelchStateListener();
}

View File

@@ -0,0 +1,72 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.squelch;
/**
* Squelch state change event
*/
public class SquelchStateEvent
{
private SquelchState mSquelchState;
private int mTimeslot;
public SquelchStateEvent(SquelchState squelchState, int timeslot)
{
mSquelchState = squelchState;
mTimeslot = timeslot;
}
public SquelchState getSquelchState()
{
return mSquelchState;
}
/**
* Timeslot associated with the squelch state
*/
public int getTimeslot()
{
return mTimeslot;
}
/**
* Creates a squelch state event with a default timeslot of 0
* @param squelchState for the event
* @return event
*/
public static SquelchStateEvent create(SquelchState squelchState)
{
return new SquelchStateEvent(squelchState, 0);
}
/**
* Creates a squelch state event for the specified timeslot
* @param squelchState for the event
* @param timeslot that the event applies to
* @return event
*/
public static SquelchStateEvent create(SquelchState squelchState, int timeslot)
{
return new SquelchStateEvent(squelchState, timeslot);
}
}

View File

@@ -1,20 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
/*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.bits;
import io.github.dsheirer.edac.CRC;
@@ -285,19 +289,28 @@ public class BinaryMessage extends BitSet
public String toHexString()
{
int pointer = 0;
StringBuilder sb = new StringBuilder();
while(pointer < size())
for(int x = 0; x < size(); x += 4)
{
sb.append(getHex(pointer, pointer + 3, 1));
pointer += 4;
sb.append(getNibbleAsHex(x));
}
return sb.toString();
}
/**
* Overrides the parent method which returns a BitSet so that we can return a BinaryMessage
* @param from
* @param to
* @return
*/
@Override
public BinaryMessage get(int from, int to)
{
return new BinaryMessage(super.get(from, to), (to - from));
}
/**
* Returns this bitset as an array of integer ones and zeros
*/
@@ -545,22 +558,23 @@ public class BinaryMessage extends BitSet
}
/**
* Returns the byte value contained between index and index + 7 bit positions
* Returns the byte value contained between index and index + 7 bit positions. If the length of this message is
* shorter than index + 7, then the least significant bits are set to zero in the returned value.
*
* @param index specifying the start of the byte value
* @param startIndex specifying the start of the byte value
* @return byte value contained at index <> index + 7 bit positions
*/
public byte getByte(int index)
public byte getByte(int startIndex)
{
Validate.isTrue((index + 7) <= size());
int value = 0;
for(int x = 0; x < 8; x++)
{
value = value << 1;
value <<= 1;
if(get(index + x))
int index = startIndex + x;
if(index <= size() && get(index))
{
value++;
}
@@ -569,6 +583,50 @@ public class BinaryMessage extends BitSet
return (byte)value;
}
/**
* Converts the message to a byte array.
*
* Note: BitSet.toByteArray() outputs the bytes in big-endian (inverted) order. This method retains the correct
* endianness where bit 0 is the most significant bit of the first byte.
*/
public byte[] getBytes()
{
byte[] bytes = new byte[(int)Math.ceil((double)size() / 8.0)];
for(int x = 0; x < bytes.length; x++)
{
bytes[x] = getByte(x * 8);
}
return bytes;
}
/**
* Returns the 4-bit nibble value contained between index and index + 3 bit positions. If the length of this
* message is shorter than index + 3, then the least significant bits are set to zero in the returned value.
*
* @param startIndex specifying the start of the byte value
* @return nibble value contained at index <> index + 3 bit positions
*/
public int getNibble(int startIndex)
{
int value = 0;
for(int x = 0; x < 4; x++)
{
value <<= 1;
int index = startIndex + x;
if(index <= size() && get(index))
{
value++;
}
}
return value;
}
/**
* Sets the byte value at index position through index + 7 position.
*
@@ -627,6 +685,69 @@ public class BinaryMessage extends BitSet
return value;
}
/**
* Returns the long value represented by the bit array
*
* @param bits - an array of bit positions that will be treated as if they
* were contiguous bits, with index 0 being the MSB and index
* length - 1 being the LSB
* @param offset to apply to each of the bits indices
* @return - integer value of the bit array
*/
public long getLong(int[] bits, int offset)
{
if(bits.length > 64)
{
throw new IllegalArgumentException("Overflow - must be 64 bits "
+ "or less to fit into a primitive long value");
}
long value = 0;
for(int index : bits)
{
value = Long.rotateLeft(value, 1);
if(get(index + offset))
{
value++;
}
}
return value;
}
/**
* Returns the bit values between start and end (inclusive) bit indices. If the overall length of the bit sequence
* is not a multiple of 8 bits, the value is zero padded with least significant bits to make it a multiple of 8.
* @param start index
* @param end index
* @return hexadecimal representation of the bit sequence
*/
public String getHex(int start, int end)
{
StringBuilder sb = new StringBuilder();
for(int x = start; x <= end; x += 4)
{
sb.append(getNibbleAsHex(x));
}
return sb.toString();
}
/**
* Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less
* than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in
* the least significant bits to make up the 8 bit value.
* @param startIndex of the byte.
* @return hexadecimal representation of the byte value
*/
public String getNibbleAsHex(int startIndex)
{
int value = getNibble(startIndex);
return Integer.toHexString(value).toUpperCase();
}
/**
* Converts up to 63 bits from the bit array into an integer and then
@@ -658,29 +779,6 @@ public class BinaryMessage extends BitSet
}
}
public String getHex(int msb, int lsb, int digitDisplayCount)
{
int length = lsb - msb;
if(length <= 32)
{
int value = getInt(msb, lsb);
return String.format("%0" + digitDisplayCount + "X", value);
}
else if(length <= 64)
{
long value = getLong(msb, lsb);
return String.format("%0" + digitDisplayCount + "X", value);
}
else
{
throw new IllegalArgumentException("BitSetBuffer.getHex() "
+ "maximum array length is 64 bits");
}
}
/**
* Returns the int value represented by the bit range. This method will
* parse the bits in big endian or little endian format. The start value
@@ -877,6 +975,23 @@ public class BinaryMessage extends BitSet
return checksumIndexes;
}
/**
* Creates a binary message loaded with the byte array
* @param bytes to load
* @return loaded binary message
*/
public static BinaryMessage from(byte[] bytes)
{
BinaryMessage message = new BinaryMessage(bytes.length * 8);
for(int x = 0; x < bytes.length; x++)
{
message.setByte(x * 8, bytes[x]);
}
return message;
}
/**
* Creates a bitsetbuffer loaded from a string of zeros and ones
*
@@ -998,4 +1113,20 @@ public class BinaryMessage extends BitSet
this.xor(mask);
}
public static void main(String[] args)
{
BinaryMessage message = new BinaryMessage(10);
message.set(1);
message.set(4);
message.set(5);
message.set(8);
message.set(9);
System.out.println("Hex: " + message.toHexString() + " Size:" + message.size());
for(int x = 0; x < 10; x++)
{
System.out.println(x + ": " + message.getHex(x, x + 10));
}
}
}

View File

@@ -1,21 +1,24 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.bits;
import java.util.BitSet;
@@ -48,6 +51,12 @@ public class CorrectedBinaryMessage extends BinaryMessage
super(data);
}
public CorrectedBinaryMessage(BinaryMessage message)
{
this(message.size());
this.xor(message);
}
@Override
public int getCorrectedBitCount()
{
@@ -72,4 +81,16 @@ public class CorrectedBinaryMessage extends BinaryMessage
mCorrectedBitCount += additionalCount;
}
/**
* Returns a new binary message containing the bits from (inclusive) to end (exclusive).
*
* @param start bit
* @param end bit
* @return message
*/
public CorrectedBinaryMessage getSubMessage(int start, int end)
{
BitSet subset = this.get(start, end);
return new CorrectedBinaryMessage(subset, end - start);
}
}

View File

@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,7 +19,7 @@
*/
package io.github.dsheirer.channel;
import io.github.dsheirer.module.decode.p25.message.IFrequencyBand;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.protocol.Protocol;
/**

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.channel.metadata;
@@ -61,14 +63,31 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
private List<Alias> mFromIdentifierAliases;
private Identifier mToIdentifier;
private List<Alias> mToIdentifierAliases;
private Integer mTimeslot;
private IChannelMetadataUpdateListener mIChannelMetadataUpdateListener;
private AliasModel mAliasModel;
private AliasList mAliasList;
public ChannelMetadata(AliasModel aliasModel)
public ChannelMetadata(AliasModel aliasModel, Integer timeslot)
{
mAliasModel = aliasModel;
mTimeslot = timeslot;
}
public ChannelMetadata(AliasModel aliasModel)
{
this(aliasModel, null);
}
public Integer getTimeslot()
{
return mTimeslot;
}
public boolean hasTimeslot()
{
return mTimeslot != null;
}
@Override
@@ -242,6 +261,7 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
@Override
public void receive(IdentifierUpdateNotification update)
{
// mLog.debug("Received update: " + update + " class:" + update.getIdentifier().getClass());
Identifier identifier = update.getIdentifier();
switch(identifier.getIdentifierClass())

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.channel.metadata;
@@ -24,6 +26,7 @@ import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
import io.github.dsheirer.preference.PreferenceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,6 +34,7 @@ import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -77,7 +81,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
}
public void add(ChannelMetadata channelMetadata, Channel channel)
public void add(Collection<ChannelMetadata> channelMetadatas, Channel channel)
{
//Execute on the swing thread to avoid threading issues
EventQueue.invokeLater(new Runnable()
@@ -85,11 +89,14 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
@Override
public void run()
{
mChannelMetadata.add(channelMetadata);
mMetadataChannelMap.put(channelMetadata, channel);
int index = mChannelMetadata.indexOf(channelMetadata);
fireTableRowsInserted(index, index);
channelMetadata.setUpdateEventListener(ChannelMetadataModel.this);
for(ChannelMetadata channelMetadata: channelMetadatas)
{
mChannelMetadata.add(channelMetadata);
mMetadataChannelMap.put(channelMetadata, channel);
int index = mChannelMetadata.indexOf(channelMetadata);
fireTableRowsInserted(index, index);
channelMetadata.setUpdateEventListener(ChannelMetadataModel.this);
}
}
});
}
@@ -161,6 +168,8 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
case COLUMN_USER_FROM_ALIAS:
case COLUMN_USER_TO_ALIAS:
return Alias.class;
case COLUMN_DECODER_LOGICAL_CHANNEL_NAME:
return String.class;
default:
return Identifier.class;
}
@@ -180,7 +189,30 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
case COLUMN_DECODER_TYPE:
return channelMetadata.getDecoderTypeConfigurationIdentifier();
case COLUMN_DECODER_LOGICAL_CHANNEL_NAME:
return channelMetadata.getDecoderLogicalChannelNameIdentifier();
DecoderLogicalChannelNameIdentifier id = channelMetadata.getDecoderLogicalChannelNameIdentifier();
if(channelMetadata.hasTimeslot())
{
if(id == null)
{
return "TS:" + channelMetadata.getTimeslot();
}
else
{
return id.getValue() + " TS:" + channelMetadata.getTimeslot();
}
}
else
{
if(id == null)
{
return null;
}
else
{
return id.getValue();
}
}
case COLUMN_CONFIGURATION_FREQUENCY:
return channelMetadata.getFrequencyConfigurationIdentifier();
case COLUMN_CONFIGURATION_CHANNEL:

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.channel.metadata;
@@ -33,6 +35,7 @@ import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
@@ -58,6 +61,7 @@ import java.util.Map;
public class ChannelMetadataPanel extends JPanel implements ListSelectionListener
{
private static final String TABLE_PREFERENCE_KEY = "channel.metadata.panel";
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private IconManager mIconManager;
@@ -66,6 +70,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
private Broadcaster<ProcessingChain> mSelectedProcessingChainBroadcaster = new Broadcaster<>();
private Map<State,Color> mBackgroundColors = new HashMap<>();
private Map<State,Color> mForegroundColors = new HashMap<>();
private JTableColumnWidthMonitor mTableColumnMonitor;
/**
* Table view for currently decoding channel metadata
@@ -108,6 +113,9 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_CONFIGURATION_FREQUENCY)
.setCellRenderer(new FrequencyCellRenderer());
//Add a table column width monitor to store/restore column widths
mTableColumnMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
JScrollPane scrollPane = new JScrollPane(mTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
@@ -122,7 +130,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
private void setColors()
{
mBackgroundColors.put(State.ACTIVE, Color.CYAN);
mForegroundColors.put(State.ACTIVE, Color.YELLOW);
mForegroundColors.put(State.ACTIVE, Color.BLUE);
mBackgroundColors.put(State.CALL, Color.BLUE);
mForegroundColors.put(State.CALL, Color.YELLOW);
mBackgroundColors.put(State.CONTROL, Color.ORANGE);

View File

@@ -1,6 +1,7 @@
/*******************************************************************************
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,8 +15,8 @@
*
* 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.channel.metadata;
import com.jidesoft.swing.JideSplitPane;
@@ -33,10 +34,9 @@ import net.miginfocom.swing.MigLayout;
import javax.swing.JPanel;
import java.awt.Color;
public class ChannelMetadataViewer extends JPanel
public class NowPlayingPanel extends JPanel
{
private ChannelMetadataPanel mChannelMetadataPanel;
private ChannelDetailPanel mChannelDetailPanel;
private DecodeEventPanel mDecodeEventPanel;
private MessageActivityPanel mMessageActivityPanel;
@@ -45,12 +45,12 @@ public class ChannelMetadataViewer extends JPanel
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
* messages, events, and spectral view.
*/
public ChannelMetadataViewer(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager,
IconManager iconManager, AliasModel aliasModel, UserPreferences userPreferences)
public NowPlayingPanel(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager,
IconManager iconManager, AliasModel aliasModel, UserPreferences userPreferences)
{
mChannelDetailPanel = new ChannelDetailPanel(channelProcessingManager);
mDecodeEventPanel = new DecodeEventPanel(iconManager, userPreferences, aliasModel);
mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager);
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
mChannelMetadataPanel = new ChannelMetadataPanel(channelModel, channelProcessingManager, iconManager, userPreferences);
init();
@@ -64,7 +64,6 @@ public class ChannelMetadataViewer extends JPanel
tabbedPane.addTab("Details", mChannelDetailPanel);
tabbedPane.addTab("Events", mDecodeEventPanel);
tabbedPane.addTab("Messages", mMessageActivityPanel);
// tabbedPane.addTab("Spectrum", mChannelSpectrumPanel);
tabbedPane.setFont(this.getFont());
tabbedPane.setForeground(Color.BLACK);

View File

@@ -0,0 +1,187 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
import io.github.dsheirer.channel.metadata.ChannelMetadata;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.ChannelEvent;
import io.github.dsheirer.controller.channel.IChannelEventProvider;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
public abstract class AbstractChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider,
IDecoderStateEventProvider, ISourceEventProvider, IHeartbeatListener, ISquelchStateProvider,
IdentifierUpdateProvider, IOverflowListener
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractChannelState.class);
protected Listener<ChannelEvent> mChannelEventListener;
protected Listener<IDecodeEvent> mDecodeEventListener;
protected Listener<DecoderStateEvent> mDecoderStateListener;
protected Listener<SourceEvent> mExternalSourceEventListener;
protected Channel mChannel;
protected boolean mSourceOverflow = false;
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
public AbstractChannelState(Channel channel)
{
mChannel = channel;
}
/**
* Invoked each time that a heartbeat is received so that sub-class implementations can check current timers and
* adjust channel state as necessary. The heartbeat arrives on a periodic basis independent of any decoded
* messages so that channel state is not entirely dependent on a continuous decoded message stream.
*/
protected abstract void checkState();
public abstract Collection<ChannelMetadata> getChannelMetadata();
public abstract void updateChannelStateIdentifiers(IdentifierUpdateNotification notification);
/**
* Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages.
*/
@Override
public Listener<Heartbeat> getHeartbeatListener()
{
return mHeartbeatReceiver;
}
/**
* This method is invoked if the source buffer provider goes into overflow state. Since this is an external state,
* we use the mSourceOverflow variable to override the internal state reported to external listeners.
*
* @param overflow true to indicate an overflow state
*/
@Override
public void sourceOverflow(boolean overflow)
{
mSourceOverflow = overflow;
}
/**
* Indicates if this channel's sample buffer is in overflow state, meaning that the inbound sample
* stream is not being processed fast enough and samples are being thrown away until the processing can
* catch up.
*
* @return true if the channel is in overflow state.
*/
public boolean isOverflow()
{
return mSourceOverflow;
}
@Override
public void setChannelEventListener(Listener<ChannelEvent> listener)
{
mChannelEventListener = listener;
}
@Override
public void removeChannelEventListener()
{
mChannelEventListener = null;
}
@Override
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventListener = listener;
}
@Override
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventListener = null;
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
/**
* Registers the listener to receive source events from the channel state
*/
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mExternalSourceEventListener = listener;
}
/**
* De-Registers a listener from receiving source events from the channel state
*/
@Override
public void removeSourceEventListener()
{
mExternalSourceEventListener = null;
}
/**
* Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
* functions.
*
* Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to
* TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state.
*
* Provides a FADE transition state to allow for momentary decoding dropouts and to allow the user access to call
* details for a fade period upon call end.
*/
public class HeartbeatReceiver implements Listener<Heartbeat>
{
@Override
public void receive(Heartbeat heartbeat)
{
checkState();
}
}
}

View File

@@ -0,0 +1,148 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageListener;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.event.ActivitySummaryProvider;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractDecoderState extends Module implements ActivitySummaryProvider, Listener<IMessage>,
IDecodeEventProvider, IDecoderStateEventListener, IDecoderStateEventProvider, IMessageListener,
IdentifierUpdateProvider, IdentifierUpdateListener
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractDecoderState.class);
protected String DIVIDER1 = "======================================================\n";
protected String DIVIDER2 = "------------------------------------------------------\n";
/* This has to be a broadcaster in order for references to persist */
protected Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
protected Listener<DecoderStateEvent> mDecoderStateListener;
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
public abstract DecoderType getDecoderType();
/**
* Provides subclass reference to the decode event broadcaster
*/
protected Broadcaster<IDecodeEvent> getDecodeEventBroadcaster()
{
return mDecodeEventBroadcaster;
}
@Override
public Listener<IMessage> getMessageListener()
{
return this;
}
/**
* Implements the IDecoderStateEventListener interface to receive state
* reset events.
*/
public abstract void receiveDecoderStateEvent(DecoderStateEvent event);
/**
* Activity Summary - textual summary of activity observed by the channel state.
*/
public abstract String getActivitySummary();
/**
* Broadcasts a decode event to any registered listeners
*/
protected void broadcast(IDecodeEvent event)
{
mDecodeEventBroadcaster.broadcast(event);
}
/**
* Adds a call event listener
*/
@Override
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventBroadcaster.addListener(listener);
}
/**
* Removes the call event listener
*/
@Override
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventBroadcaster.removeListener(listener);
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventListener;
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast(DecoderStateEvent event)
{
if(mDecoderStateListener != null)
{
mDecoderStateListener.receive(event);
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
public void receive(DecoderStateEvent event)
{
receiveDecoderStateEvent(event);
}
}
}

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* sdrtrunk
* 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.channel.state;
@@ -48,11 +50,6 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
{
}
@Override
public void reset()
{
}
@Override
public String getActivitySummary()
{

View File

@@ -1,3 +1,25 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.controller.channel.Channel.ChannelType;
@@ -7,23 +29,28 @@ import io.github.dsheirer.controller.channel.Channel.ChannelType;
*/
public class ChangeChannelTimeoutEvent extends DecoderStateEvent
{
private ChannelType mChannelType;
private long mCallTimeout;
public ChangeChannelTimeoutEvent( Object source, ChannelType channelType, long timeout )
{
super( source, Event.CHANGE_CALL_TIMEOUT, State.IDLE );
mChannelType = channelType;
mCallTimeout = timeout;
}
public ChannelType getChannelType()
{
return mChannelType;
}
public long getCallTimeout()
{
return mCallTimeout;
}
private ChannelType mChannelType;
private long mCallTimeout;
public ChangeChannelTimeoutEvent(Object source, ChannelType channelType, long timeout, int timeslot)
{
super(source, Event.CHANGE_CALL_TIMEOUT, State.IDLE, timeslot);
mChannelType = channelType;
mCallTimeout = timeout;
}
public ChangeChannelTimeoutEvent(Object source, ChannelType channelType, long timeout)
{
this(source, channelType, timeout, 0);
}
public ChannelType getChannelType()
{
return mChannelType;
}
public long getCallTimeout()
{
return mCallTimeout;
}
}

View File

@@ -1,760 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.channel.metadata.ChannelMetadata;
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.Channel.ChannelType;
import io.github.dsheirer.controller.channel.ChannelEvent;
import io.github.dsheirer.controller.channel.IChannelEventProvider;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class ChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider,
IDecoderStateEventListener, IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider,
IHeartbeatListener, ISquelchStateProvider, IOverflowListener, IdentifierUpdateListener, IdentifierUpdateProvider
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
public static final long FADE_TIMEOUT_DELAY = 1200;
public static final long RESET_TIMEOUT_DELAY = 2000;
private State mState = State.IDLE;
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private Listener<ChannelEvent> mChannelEventListener;
private Listener<IDecodeEvent> mDecodeEventListener;
private Listener<DecoderStateEvent> mDecoderStateListener;
private Listener<SquelchState> mSquelchStateListener;
private Listener<SourceEvent> mExternalSourceEventListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
private Channel mChannel;
private SourceEventListener mInternalSourceEventListener;
private ChannelMetadata mChannelMetadata;
private IdentifierUpdateNotificationProxy mIdentifierUpdateNotificationProxy = new IdentifierUpdateNotificationProxy();
private boolean mSquelchLocked = false;
private boolean mSelected = false;
private boolean mSourceOverflow = false;
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
private long mTrafficChannelFadeTimeout = DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
private long mFadeTimeout;
private long mEndTimeout;
/**
* Channel state tracks the overall state of all processing modules and decoders configured for the channel and
* provides squelch control and decoder state reset events.
*
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
* reset to IDLE.
*
* State Descriptions:
* IDLE: Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
* signal to the user that the call is ended, while continuing to display the call details for the user
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
*/
public ChannelState(Channel channel, AliasModel aliasModel)
{
mChannel = channel;
mChannelMetadata = new ChannelMetadata(aliasModel);
mIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
createConfigurationIdentifiers(channel);
}
/**
* Creates configuration identifiers for the channel name, system, site and alias list name.
*/
private void createConfigurationIdentifiers(Channel channel)
{
mIdentifierCollection.update(DecoderTypeConfigurationIdentifier.create(channel.getDecodeConfiguration().getDecoderType()));
if(channel.hasSystem())
{
mIdentifierCollection.update(SystemConfigurationIdentifier.create(channel.getSystem()));
}
if(channel.hasSite())
{
mIdentifierCollection.update(SiteConfigurationIdentifier.create(channel.getSite()));
}
if(channel.getName() != null && !channel.getName().isEmpty())
{
mIdentifierCollection.update(ChannelNameConfigurationIdentifier.create(channel.getName()));
}
if(channel.getAliasListName() != null && !channel.getAliasListName().isEmpty())
{
mIdentifierCollection.update(AliasListConfigurationIdentifier.create(channel.getAliasListName()));
}
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequency));
}
else if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
{
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencies();
if(frequencies.size() > 0)
{
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequencies.get(0)));
}
}
}
/**
* Interface to receive channel identifier updates from this channel state and from any
* decoder states.
*/
@Override
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
{
return mChannelMetadata;
}
/**
* Updates the channel state identifier collection using the update notification. This update will be reflected
* in the internal channel state and will also be broadcast to any listeners, including the channel metadata for
* this channel state.
*/
public void updateChannelStateIdentifiers(IdentifierUpdateNotification notification)
{
mIdentifierCollection.receive(notification);
}
@Override
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationProxy.setListener(listener);
}
@Override
public void removeIdentifierUpdateListener()
{
mIdentifierUpdateNotificationProxy.removeListener();
}
/**
* Channel metadata for this channel.
*/
public ChannelMetadata getChannelMetadata()
{
return mChannelMetadata;
}
/**
* Resets this channel state and prepares it for reuse.
*/
@Override
public void reset()
{
mState = State.IDLE;
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
mSourceOverflow = false;
}
@Override
public void start()
{
mIdentifierCollection.broadcastIdentifiers();
if(mChannel.getChannelType() == ChannelType.TRAFFIC)
{
setState(State.CALL);
}
}
@Override
public void stop()
{
processTeardownState();
mSquelchLocked = false;
}
public void dispose()
{
mDecodeEventListener = null;
mDecoderStateListener = null;
mSquelchStateListener = null;
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
if(mInternalSourceEventListener == null)
{
mInternalSourceEventListener = new SourceEventListener();
}
return mInternalSourceEventListener;
}
public void setStandardChannelTimeout(long milliseconds)
{
mStandardChannelFadeTimeout = milliseconds;
if(mChannel.isStandardChannel())
{
mFadeTimeout = mStandardChannelFadeTimeout;
}
}
public void setTrafficChannelTimeout(long milliseconds)
{
mTrafficChannelFadeTimeout = milliseconds;
if(mChannel.isTrafficChannel())
{
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
}
}
public void setSelected(boolean selected)
{
mSelected = selected;
}
public boolean isSelected()
{
return mSelected;
}
public State getState()
{
return mState;
}
/**
* Updates the fade timeout threshold to the current time plus delay
*/
private void updateFadeTimeout()
{
if(mChannel.isTrafficChannel())
{
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
}
else
{
mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout;
}
}
/**
* Updates the reset timeout threshold to the current time plus delay
*/
private void updateResetTimeout()
{
if(mChannel.isTrafficChannel())
{
mEndTimeout = System.currentTimeMillis();
}
else
{
mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
}
}
/**
* Broadcasts the squelch state to the registered listener
*/
protected void broadcast(SquelchState state)
{
if(mSquelchStateListener != null && !mSquelchLocked)
{
mSquelchStateListener.receive(state);
}
}
/**
* Broadcasts the source event to a registered external source event listener
*/
protected void broadcast(SourceEvent sourceEvent)
{
if(mExternalSourceEventListener != null)
{
mExternalSourceEventListener.receive(sourceEvent);
}
}
/**
* Sets the squelch state listener
*/
@Override
public void setSquelchStateListener(Listener<SquelchState> listener)
{
mSquelchStateListener = listener;
}
/**
* Removes the squelch state listener
*/
@Override
public void removeSquelchStateListener()
{
mSquelchStateListener = null;
}
/**
* Sets the channel state to the specified state, or updates the timeout values so that the state monitor will not
* change state. Broadcasts a squelch event when the state changes and the audio squelch state should change. Also
* broadcasts changed attribute and decoder state events so that external processes can maintain sync with this
* channel state.
*/
protected void setState(State state)
{
if(state == mState)
{
if(State.CALL_STATES.contains(state))
{
updateFadeTimeout();
}
}
else if(mState.canChangeTo(state))
{
switch(state)
{
case ACTIVE:
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
mIdentifierCollection.update(ChannelStateIdentifier.ACTIVE);
break;
case CONTROL:
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
if(mChannel.isStandardChannel())
{
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
mIdentifierCollection.update(ChannelStateIdentifier.CONTROL);
}
break;
case DATA:
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
mIdentifierCollection.update(ChannelStateIdentifier.DATA);
break;
case ENCRYPTED:
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
mIdentifierCollection.update(ChannelStateIdentifier.ENCRYPTED);
break;
case CALL:
broadcast(SquelchState.UNSQUELCH);
updateFadeTimeout();
mState = state;
mIdentifierCollection.update(ChannelStateIdentifier.CALL);
break;
case FADE:
processFadeState();
mIdentifierCollection.update(ChannelStateIdentifier.FADE);
break;
case IDLE:
processIdleState();
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
break;
case TEARDOWN:
processTeardownState();
break;
case RESET:
mState = State.IDLE;
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
break;
default:
break;
}
}
}
/**
* Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages.
*/
@Override
public Listener<Heartbeat> getHeartbeatListener()
{
return mHeartbeatReceiver;
}
/**
* This method is invoked if the source buffer provider goes into overflow state. Since this is an external state,
* we use the mSourceOverflow variable to override the internal state reported to external listeners.
*
* @param overflow true to indicate an overflow state
*/
@Override
public void sourceOverflow(boolean overflow)
{
mSourceOverflow = overflow;
}
/**
* Indicates if this channel's sample buffer is in overflow state, meaning that the inbound sample
* stream is not being processed fast enough and samples are being thrown away until the processing can
* catch up.
*
* @return true if the channel is in overflow state.
*/
public boolean isOverflow()
{
return mSourceOverflow;
}
/**
* Sets the state and processes related actions
*/
private void processFadeState()
{
updateResetTimeout();
mState = State.FADE;
mIdentifierCollection.update(ChannelStateIdentifier.FADE);
broadcast(SquelchState.SQUELCH);
}
private void processIdleState()
{
broadcast(SquelchState.SQUELCH);
if(mState == State.FADE)
{
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
}
mState = State.IDLE;
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
}
private void processTeardownState()
{
broadcast(SquelchState.SQUELCH);
mState = State.TEARDOWN;
mIdentifierCollection.update(ChannelStateIdentifier.TEARDOWN);
if(mChannel.isTrafficChannel())
{
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
}
}
/**
* Broadcasts the call event to the registered listener
*/
protected void broadcast(IDecodeEvent event)
{
if(mDecodeEventListener != null)
{
mDecodeEventListener.receive(event);
}
}
/**
* Broadcasts the channel event to a registered listener
*/
private void broadcast(ChannelEvent channelEvent)
{
if(mChannelEventListener != null)
{
mChannelEventListener.receive(channelEvent);
}
}
@Override
public void setChannelEventListener(Listener<ChannelEvent> listener)
{
mChannelEventListener = listener;
}
@Override
public void removeChannelEventListener()
{
mChannelEventListener = null;
}
@Override
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventListener = listener;
}
@Override
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventListener = null;
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast(DecoderStateEvent event)
{
if(mDecoderStateListener != null)
{
mDecoderStateListener.receive(event);
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventReceiver;
}
/**
* Registers the listener to receive source events from the channel state
*/
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mExternalSourceEventListener = listener;
}
/**
* De-Registers a listener from receiving source events from the channel state
*/
@Override
public void removeSourceEventListener()
{
mExternalSourceEventListener = null;
}
/**
* Listener to receive source events.
*/
public class SourceEventListener implements Listener<SourceEvent>
{
@Override
public void receive(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
//Rebroadcast source frequency change events for the decoder(s) to process
long frequency = sourceEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
//Create a new frequency configuration identifier so that downstream consumers receive the change
//via channel metadata and audio packet updates - this is a silent add that is sent as a notification
//to all identifier collections so that they don't rebroadcast the change and cause a feedback loop
mIdentifierUpdateNotificationProxy.receive(new IdentifierUpdateNotification(
FrequencyConfigurationIdentifier.create(frequency), IdentifierUpdateNotification.Operation.SILENT_ADD));
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
//Rebroadcast frequency error measurements to external listener if we're currently
//in an active (ie sync locked) state.
if(getState().isActiveState())
{
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
mChannel.getChannelType().name()));
}
break;
}
}
}
/**
* DecoderStateEvent receiver wrapper
*/
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
{
@Override
public void receive(DecoderStateEvent event)
{
if(event.getSource() != this)
{
switch(event.getEvent())
{
case ALWAYS_UNSQUELCH:
broadcast(SquelchState.UNSQUELCH);
mSquelchLocked = true;
break;
case CHANGE_CALL_TIMEOUT:
if(event instanceof ChangeChannelTimeoutEvent)
{
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
if(timeout.getChannelType() == ChannelType.STANDARD)
{
setStandardChannelTimeout(timeout.getCallTimeout());
}
else
{
setTrafficChannelTimeout(timeout.getCallTimeout());
}
}
case CONTINUATION:
case DECODE:
case START:
if(State.CALL_STATES.contains(event.getState()))
{
setState(event.getState());
}
break;
case END:
if(mChannel.isTrafficChannel())
{
setState(State.TEARDOWN);
}
else
{
setState(State.FADE);
}
break;
case RESET:
/* Channel State does not respond to reset events */
break;
default:
break;
}
}
}
}
/**
* Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
* functions.
*
* Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to
* TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state.
*
* Provides a FADE transition state to allow for momentary decoding dropouts and to allow the user access to call
* details for a fade period upon call end.
*/
public class HeartbeatReceiver implements Listener<Heartbeat>
{
@Override
public void receive(Heartbeat heartbeat)
{
try
{
if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
{
processFadeState();
}
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
{
if(mChannel.isTrafficChannel())
{
processTeardownState();
}
else
{
processIdleState();
}
}
}
catch(Throwable e)
{
mLog.error("An error occurred while state monitor was running " +
"- state [" + getState() +
"] current [" + System.currentTimeMillis() +
"] mResetTimeout [" + mEndTimeout +
"] mFadeTimeout [" + mFadeTimeout +
"]", e);
}
}
}
/**
* Proxy between the identifier collection and the external update notification listener. This proxy enables
* access to internal components to broadcast silent identifier update notifications externally.
*/
public class IdentifierUpdateNotificationProxy implements Listener<IdentifierUpdateNotification>
{
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
if(mIdentifierUpdateNotificationListener != null)
{
mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
}
}
public void setListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationListener = listener;
}
public void removeListener()
{
mIdentifierUpdateNotificationListener = null;
}
}
}

View File

@@ -1,24 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.channel.state;
public interface ChannelStateSelectionListener
{
public void selected( ChannelState channelState );
}

View File

@@ -1,43 +1,33 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.channel.state;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageListener;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.event.ActivitySummaryProvider;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
/**
@@ -46,27 +36,22 @@ import io.github.dsheirer.sample.Listener;
*
* Provides access to a textual activity summary of events observed.
*/
public abstract class DecoderState extends Module implements ActivitySummaryProvider, Listener<IMessage>,
IDecodeEventProvider, IDecoderStateEventListener, IDecoderStateEventProvider, IMessageListener,
IdentifierUpdateProvider, IdentifierUpdateListener
public abstract class DecoderState extends AbstractDecoderState
{
// private final static Logger mLog = LoggerFactory.getLogger(DecoderState.class);
private MutableIdentifierCollection mIdentifierCollection;
protected Listener<IdentifierUpdateNotification> mConfigurationIdentifierListener;
protected IChannelDescriptor mCurrentChannel;
protected String DIVIDER1 = "======================================================\n";
protected String DIVIDER2 = "------------------------------------------------------\n";
/* This has to be a broadcaster in order for references to persist */
private Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
private Listener<DecoderStateEvent> mDecoderStateListener;
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private ConfigurationIdentifierListener mConfigurationIdentifierListener = new ConfigurationIdentifierListener();
private IChannelDescriptor mCurrentChannel;
public DecoderState(MutableIdentifierCollection mutableIdentifierCollection)
{
mIdentifierCollection = mutableIdentifierCollection;
mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType()));
}
public DecoderState()
{
mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType()));
this(new MutableIdentifierCollection());
mConfigurationIdentifierListener = new ConfigurationIdentifierListener();
}
@Override
@@ -76,16 +61,6 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
mIdentifierCollection.broadcastIdentifiers();
}
public abstract DecoderType getDecoderType();
/**
* Current collection of identifiers managed by the decoder state.
*/
public MutableIdentifierCollection getIdentifierCollection()
{
return mIdentifierCollection;
}
/**
* Registers the listener to receive identifier update notifications
*/
@@ -105,148 +80,11 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
}
/**
* Provides subclass reference to the decode event broadcaster
* Current collection of identifiers managed by the decoder state.
*/
protected Broadcaster<IDecodeEvent> getDecodeEventBroadcaster()
public MutableIdentifierCollection getIdentifierCollection()
{
return mDecodeEventBroadcaster;
}
@Override
public Listener<IMessage> getMessageListener()
{
return this;
}
/**
* Resets the current temporal state variables to end the current event and prepare for the next.
*/
protected void resetState()
{
mIdentifierCollection.remove(IdentifierClass.USER);
mCurrentChannel = null;
}
/**
* Reset the decoder state to prepare for processing a different sample
* source
*/
public abstract void reset();
/**
* Allow the decoder to perform any setup actions
*/
public abstract void init();
/**
* Implements the IDecoderStateEventListener interface to receive state
* reset events.
*/
public abstract void receiveDecoderStateEvent(DecoderStateEvent event);
/**
* Disposes any resources or pointers held by this instance to prepare for
* garbage collection
*/
public void dispose()
{
mDecodeEventBroadcaster.dispose();
mDecodeEventBroadcaster = null;
mDecoderStateListener = null;
}
/**
* Activity Summary - textual summary of activity observed by the channel state.
*/
public abstract String getActivitySummary();
/**
* Broadcasts a decode event to any registered listeners
*/
protected void broadcast(IDecodeEvent event)
{
mDecodeEventBroadcaster.broadcast(event);
}
/**
* Adds a call event listener
*/
@Override
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventBroadcaster.addListener(listener);
}
/**
* Removes the call event listener
*/
@Override
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventBroadcaster.removeListener(listener);
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventListener;
}
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
public void receive(DecoderStateEvent event)
{
receiveDecoderStateEvent(event);
}
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast(DecoderStateEvent event)
{
if(mDecoderStateListener != null)
{
mDecoderStateListener.receive(event);
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
/**
* Listens for configuration identifiers and adds them to this decoder state's identifier collection.
* @return
*/
@Override
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
{
return mConfigurationIdentifierListener;
}
/**
* Configuration identifier listener.
*/
public ConfigurationIdentifierListener getConfigurationIdentifierListener()
{
return mConfigurationIdentifierListener;
return mIdentifierCollection;
}
/**
@@ -265,6 +103,54 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
mCurrentChannel = channel;
}
/**
* Resets the current temporal state variables to end the current event and prepare for the next.
*/
protected void resetState()
{
mIdentifierCollection.remove(IdentifierClass.USER);
}
/**
* Reset the decoder state to prepare for processing a different sample
* source
*/
public void reset()
{
setCurrentChannel(null);
}
/**
* Allow the decoder to perform any setup actions
*/
public abstract void init();
/**
* Disposes any resources or pointers held by this instance to prepare for
* garbage collection
*/
public void dispose()
{
}
/**
* Listens for configuration identifiers and adds them to this decoder state's identifier collection.
* @return
*/
@Override
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
{
return mConfigurationIdentifierListener;
}
/**
* Configuration identifier listener.
*/
public Listener<IdentifierUpdateNotification> getConfigurationIdentifierListener()
{
return mConfigurationIdentifierListener;
}
/**
* Listener for configuration type identifier updates sent from the channel state. Adds configuration
* identifiers to this decoder state so that decode events will contain configuration details in the
@@ -275,28 +161,11 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
if(identifierUpdateNotification.getIdentifier().getIdentifierClass() == IdentifierClass.CONFIGURATION &&
identifierUpdateNotification.getIdentifier().getForm() != Form.DECODER_TYPE &&
identifierUpdateNotification.getIdentifier().getForm() != Form.CHANNEL_DESCRIPTOR)
{
if(identifierUpdateNotification.isAdd())
{
getIdentifierCollection().update(identifierUpdateNotification.getIdentifier());
}
else if(identifierUpdateNotification.isSilentAdd())
{
getIdentifierCollection().silentUpdate(identifierUpdateNotification.getIdentifier());
}
}
getIdentifierCollection().receive(identifierUpdateNotification);
if(identifierUpdateNotification.getOperation() == IdentifierUpdateNotification.Operation.ADD)
if(identifierUpdateNotification.getIdentifier() instanceof ChannelDescriptorConfigurationIdentifier)
{
Identifier identifier = identifierUpdateNotification.getIdentifier();
if(identifier instanceof ChannelDescriptorConfigurationIdentifier)
{
mCurrentChannel = ((ChannelDescriptorConfigurationIdentifier)identifier).getValue();
}
setCurrentChannel(((ChannelDescriptorConfigurationIdentifier)identifierUpdateNotification.getIdentifier()).getValue());
}
}
}

View File

@@ -1,67 +1,178 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
public class DecoderStateEvent
{
private Object mSource;
private Event mEvent;
private State mState;
private long mFrequency;
public DecoderStateEvent( Object source, Event event, State state )
{
mSource = source;
mEvent = event;
mState = state;
}
public DecoderStateEvent( Object source, Event event, State state, long frequency )
{
this( source, event, state );
mFrequency = frequency;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "Decoder State Event - source[" + mSource.getClass() +
"] event[" + mEvent.toString() +
"] state[" + mState.toString() +
"] frequency [" + mFrequency + "]" );
return sb.toString();
}
public Object getSource()
{
return mSource;
}
public Event getEvent()
{
return mEvent;
}
public State getState()
{
return mState;
}
public long getFrequency()
{
return mFrequency;
}
public enum Event
{
ALWAYS_UNSQUELCH,
CHANGE_CALL_TIMEOUT,
CONTINUATION,
DECODE,
END,
RESET,
SOURCE_FREQUENCY,
START;
}
private static final DecoderStateEvent ACTIVE_STATE_TIMESLOT_0 =
new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_ACTIVE_STATE, State.ACTIVE, 0);
private static final DecoderStateEvent ACTIVE_STATE_TIMESLOT_1 =
new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_ACTIVE_STATE, State.ACTIVE, 1);
private static final DecoderStateEvent INACTIVE_STATE_TIMESLOT_0 =
new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_INACTIVE_STATE, State.ACTIVE, 0);
private static final DecoderStateEvent INACTIVE_STATE_TIMESLOT_1 =
new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_INACTIVE_STATE, State.ACTIVE, 1);
private Object mSource;
private Event mEvent;
private State mState;
private int mTimeslot;
private long mFrequency;
public DecoderStateEvent(Object source, Event event, State state, int timeslot, long frequency)
{
mSource = source;
mEvent = event;
mState = state;
mTimeslot = timeslot;
mFrequency = frequency;
}
public DecoderStateEvent(Object source, Event event, State state, int timeslot)
{
this(source, event, state, timeslot, 0l);
}
public DecoderStateEvent(Object source, Event event, State state)
{
this(source, event, state, 0, 0l);
}
public DecoderStateEvent(Object source, Event event, State state, long frequency)
{
this(source, event, state, 0, frequency);
}
/**
* Creates a channel state active event for the specified timeslot
* @param timeslot that the state applies to
* @return new event
*/
public static DecoderStateEvent activeState(int timeslot)
{
if(timeslot == 0)
{
return ACTIVE_STATE_TIMESLOT_0;
}
else if(timeslot == 1)
{
return ACTIVE_STATE_TIMESLOT_1;
}
else
{
throw new IllegalArgumentException("Only timeslots 0 and 1 are currently supported");
}
}
/**
* Creates a channel state active event for the specified timeslot.
* @param state currently
* @return new event
*/
public static DecoderStateEvent activeState()
{
return activeState(0);
}
/**
* Creates a channel state inactive event for the specified timeslot
* @param timeslot that the state applies to
* @return new event
*/
public static DecoderStateEvent inactiveState(int timeslot)
{
if(timeslot == 0)
{
return INACTIVE_STATE_TIMESLOT_0;
}
else if(timeslot == 1)
{
return INACTIVE_STATE_TIMESLOT_1;
}
else
{
throw new IllegalArgumentException("Only timeslots 0 and 1 are currently supported");
}
}
/**
* Creates a channel state active event for the specified timeslot.
* @param state currently
* @return new event
*/
public static DecoderStateEvent inactiveState()
{
return inactiveState(0);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Decoder State Event - source[" + mSource.getClass() +
"] event[" + mEvent.toString() +
"] state[" + mState.toString() +
"] timeslot[" + mTimeslot +
"] frequency [" + mFrequency + "]");
return sb.toString();
}
public Object getSource()
{
return mSource;
}
public Event getEvent()
{
return mEvent;
}
public State getState()
{
return mState;
}
public long getFrequency()
{
return mFrequency;
}
public int getTimeslot()
{
return mTimeslot;
}
public enum Event
{
ALWAYS_UNSQUELCH,
CHANGE_CALL_TIMEOUT,
CONTINUATION,
DECODE,
END,
NOTIFICATION_CHANNEL_ACTIVE_STATE,
NOTIFICATION_CHANNEL_INACTIVE_STATE,
RESET,
SOURCE_FREQUENCY,
START;
}
}

View File

@@ -0,0 +1,33 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
public interface IStateMachineListener
{
/**
* Indicates that the state has changed for the specified timeslot
* @param state of the state machine currently
* @param timeslot that the state applies to
*/
void stateChanged(State state, int timeslot);
}

View File

@@ -0,0 +1,586 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.channel.metadata.ChannelMetadata;
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.Channel.ChannelType;
import io.github.dsheirer.controller.channel.ChannelEvent;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class MultiChannelState extends AbstractChannelState implements IDecoderStateEventListener, ISourceEventListener,
IdentifierUpdateListener, IStateMachineListener
{
private final static Logger mLog = LoggerFactory.getLogger(MultiChannelState.class);
public static final long FADE_TIMEOUT_DELAY = 1200;
public static final long RESET_TIMEOUT_DELAY = 2000;
private IdentifierUpdateNotificationProxy mIdentifierUpdateNotificationProxy = new IdentifierUpdateNotificationProxy();
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
private SourceEventListener mInternalSourceEventListener;
private Map<Integer,ChannelMetadata> mChannelMetadataMap = new TreeMap<>();
private Map<Integer,MutableIdentifierCollection> mIdentifierCollectionMap = new TreeMap<>();
private Map<Integer,StateMachine> mStateMachineMap = new TreeMap<>();
private Map<Integer,StateMonitoringSquelchController> mSquelchControllerMap = new TreeMap<>();
private int mTimeslotCount;
private Listener<IdentifierUpdateNotification> mIdentifierUpdateListener = new IdentifierUpdateListenerProxy();
/**
* Multi-Channel state tracks the overall state of all processing modules and decoders configured for the channel
* and provides squelch control and decoder state reset events.
*
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
* reset to IDLE.
*
* State Descriptions:
* IDLE: Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
* signal to the user that the call is ended, while continuing to display the call details for the user
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
*/
public MultiChannelState(Channel channel, AliasModel aliasModel, int timeslots)
{
super(channel);
mTimeslotCount = timeslots;
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
{
mChannelMetadataMap.put(timeslot, new ChannelMetadata(aliasModel, timeslot));
MutableIdentifierCollection mutableIdentifierCollection = new MutableIdentifierCollection(timeslot);
mIdentifierCollectionMap.put(timeslot, mutableIdentifierCollection);
//Set the proxy as a listener so that echo'd updates are broadcast externally
mutableIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
StateMachine stateMachine = new StateMachine(timeslot);
mStateMachineMap.put(timeslot, stateMachine);
stateMachine.addListener(this);
StateMonitoringSquelchController squelchController = new StateMonitoringSquelchController(timeslot);
mSquelchControllerMap.put(timeslot, squelchController);
stateMachine.addListener(squelchController);
stateMachine.setChannelType(mChannel.getChannelType());
stateMachine.setIdentifierUpdateListener(mutableIdentifierCollection);
stateMachine.setEndTimeoutBuffer(RESET_TIMEOUT_DELAY);
if(channel.getChannelType() == ChannelType.STANDARD)
{
stateMachine.setFadeTimeoutBuffer(FADE_TIMEOUT_DELAY);
}
else
{
stateMachine.setFadeTimeoutBuffer(DecodeConfiguration.DEFAULT_CALL_TIMEOUT_DELAY_SECONDS * 1000);
}
}
createConfigurationIdentifiers(channel);
}
@Override
public void stateChanged(State state, int timeslot)
{
//Broadcast current channel state so that channel rotation monitor can track
if(state.isActiveState())
{
broadcast(DecoderStateEvent.activeState(timeslot));
}
else
{
broadcast(DecoderStateEvent.inactiveState(timeslot));
}
ChannelStateIdentifier stateIdentifier = ChannelStateIdentifier.create(state);
mIdentifierCollectionMap.get(timeslot).update(stateIdentifier);
mChannelMetadataMap.get(timeslot).receive(new IdentifierUpdateNotification(stateIdentifier, IdentifierUpdateNotification.Operation.ADD, timeslot));
switch(state)
{
case IDLE:
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
break;
case RESET:
reset(timeslot);
mStateMachineMap.get(timeslot).setState(State.IDLE);
break;
case TEARDOWN:
if(mChannel.isTrafficChannel())
{
checkTeardown();
}
else
{
mStateMachineMap.get(timeslot).setState(State.RESET);
}
break;
}
}
/**
* Checks the state of each timeslot and issues a teardown request if all timeslots are inactive
*/
private void checkTeardown()
{
boolean teardown = true;
for(StateMachine stateMachine: mStateMachineMap.values())
{
if(stateMachine.getState().isActiveState())
{
teardown = false;
}
}
if(teardown)
{
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
}
}
@Override
protected void checkState()
{
for(StateMachine stateMachine: mStateMachineMap.values())
{
stateMachine.checkState();
}
}
/**
* Creates configuration identifiers for the channel name, system, site and alias list name.
*/
private void createConfigurationIdentifiers(Channel channel)
{
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
{
MutableIdentifierCollection identifierCollection = mIdentifierCollectionMap.get(timeslot);
identifierCollection.update(DecoderTypeConfigurationIdentifier.create(channel.getDecodeConfiguration().getDecoderType()));
if(channel.hasSystem())
{
identifierCollection.update(SystemConfigurationIdentifier.create(channel.getSystem()));
}
if(channel.hasSite())
{
identifierCollection.update(SiteConfigurationIdentifier.create(channel.getSite()));
}
if(channel.getName() != null && !channel.getName().isEmpty())
{
identifierCollection.update(ChannelNameConfigurationIdentifier.create(channel.getName()));
}
if(channel.getAliasListName() != null && !channel.getAliasListName().isEmpty())
{
identifierCollection.update(AliasListConfigurationIdentifier.create(channel.getAliasListName()));
}
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
identifierCollection.update(FrequencyConfigurationIdentifier.create(frequency));
}
else if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
{
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencies();
if(frequencies.size() > 0)
{
identifierCollection.update(FrequencyConfigurationIdentifier.create(frequencies.get(0)));
}
}
}
}
/**
* Interface to receive channel identifier updates from this channel state and from any
* decoder states.
*/
@Override
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
{
return mIdentifierUpdateListener;
}
/**
* Registers the external listener that will receive identifier update notifications produced by this channel state
* @param listener
*/
@Override
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationProxy.setListener(listener);
}
/**
* Unregisters the external listener from receiving identifier update notifications produced by this channel state
*/
@Override
public void removeIdentifierUpdateListener()
{
mIdentifierUpdateNotificationProxy.setListener(null);
}
/**
* Updates the channel state identifier collection using the update notification. This update will be reflected
* in the internal channel state and will also be broadcast to any listeners, including the channel metadata for
* this channel state.
*/
@Override
public void updateChannelStateIdentifiers(IdentifierUpdateNotification notification)
{
//Explicitly add or remove the identifier from the local identifier collection to allow it to be rebroadcast
//to external listeners, which includes this state's channel metadata
MutableIdentifierCollection identifierCollection = mIdentifierCollectionMap.get(notification.getTimeslot());
if(identifierCollection != null)
{
if(notification.isAdd())
{
identifierCollection.update(notification.getIdentifier());
}
else if(notification.isSilentAdd())
{
identifierCollection.silentUpdate(notification.getIdentifier());
}
else if(notification.isRemove())
{
identifierCollection.remove(notification.getIdentifier());
}
else if(notification.isSilentRemove())
{
identifierCollection.silentRemove(notification.getIdentifier());
}
}
}
/**
* Channel metadata for this channel.
*/
public Collection<ChannelMetadata> getChannelMetadata()
{
return mChannelMetadataMap.values();
}
/**
* Resets this channel state and prepares it for reuse.
*/
@Override
public void reset()
{
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
{
reset(timeslot);
}
sourceOverflow(false);
}
private void reset(int timeslot)
{
mStateMachineMap.get(timeslot).setState(State.RESET);
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE, timeslot));
MutableIdentifierCollection identifierCollection = mIdentifierCollectionMap.get(timeslot);
identifierCollection.remove(IdentifierClass.USER);
}
@Override
public void start()
{
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
{
mIdentifierCollectionMap.get(timeslot).broadcastIdentifiers();
if(mChannel.getChannelType() == ChannelType.TRAFFIC)
{
mStateMachineMap.get(timeslot).setState(State.ACTIVE);
}
}
}
@Override
public void stop()
{
for(StateMonitoringSquelchController squelchController: mSquelchControllerMap.values())
{
squelchController.setSquelchLock(false);
}
}
public void dispose()
{
mDecodeEventListener = null;
mDecoderStateListener = null;
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
if(mInternalSourceEventListener == null)
{
mInternalSourceEventListener = new SourceEventListener();
}
return mInternalSourceEventListener;
}
@Override
public void setSquelchStateListener(Listener<SquelchStateEvent> listener)
{
for(StateMonitoringSquelchController squelchController: mSquelchControllerMap.values())
{
squelchController.setSquelchStateListener(listener);
}
}
@Override
public void removeSquelchStateListener()
{
for(StateMonitoringSquelchController squelchController: mSquelchControllerMap.values())
{
squelchController.removeSquelchStateListener();
}
}
/**
* Broadcasts the source event to a registered external source event listener
*/
protected void broadcast(SourceEvent sourceEvent)
{
if(mExternalSourceEventListener != null)
{
mExternalSourceEventListener.receive(sourceEvent);
}
}
/**
* Broadcasts the call event to the registered listener
*/
protected void broadcast(IDecodeEvent event)
{
if(mDecodeEventListener != null)
{
mDecodeEventListener.receive(event);
}
}
/**
* Broadcasts the channel event to a registered listener
*/
private void broadcast(ChannelEvent channelEvent)
{
if(mChannelEventListener != null)
{
mChannelEventListener.receive(channelEvent);
}
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast(DecoderStateEvent event)
{
if(mDecoderStateListener != null)
{
mDecoderStateListener.receive(event);
}
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventReceiver;
}
/**
* Listener to receive source events.
*/
public class SourceEventListener implements Listener<SourceEvent>
{
@Override
public void receive(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
//Rebroadcast source frequency change events for the decoder(s) to process
long frequency = sourceEvent.getValue().longValue();
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
{
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY,
mStateMachineMap.get(timeslot).getState(), frequency));
//Create a new frequency configuration identifier so that downstream consumers receive the change
//via channel metadata and audio packet updates - this is a silent add that is sent as a notification
//to all identifier collections so that they don't rebroadcast the change and cause a feedback loop
mIdentifierUpdateNotificationProxy.receive(new IdentifierUpdateNotification(
FrequencyConfigurationIdentifier.create(frequency), IdentifierUpdateNotification.Operation.SILENT_ADD, timeslot));
}
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
//Rebroadcast frequency error measurements to external listener if we're currently
//in an active (ie sync locked) state.
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
{
if(mStateMachineMap.get(timeslot).getState().isActiveState())
{
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
mChannel.getChannelType().name()));
}
}
break;
}
}
}
/**
* DecoderStateEvent receiver wrapper
*/
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
{
@Override
public void receive(DecoderStateEvent event)
{
if(event.getSource() != this)
{
switch(event.getEvent())
{
case ALWAYS_UNSQUELCH:
mSquelchControllerMap.get(event.getTimeslot()).setSquelchLock(true);
break;
case CHANGE_CALL_TIMEOUT:
if(event instanceof ChangeChannelTimeoutEvent)
{
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
mStateMachineMap.get(event.getTimeslot()).setFadeTimeoutBuffer(timeout.getCallTimeout());
}
case CONTINUATION:
case DECODE:
case START:
if(State.CALL_STATES.contains(event.getState()))
{
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
}
break;
case END:
if(mChannel.isTrafficChannel())
{
mStateMachineMap.get(event.getTimeslot()).setState(State.TEARDOWN);
}
else
{
mStateMachineMap.get(event.getTimeslot()).setState(State.FADE);
}
break;
case RESET:
/* Channel State does not respond to reset events */
break;
default:
break;
}
}
}
}
/**
* Receives and passes identifier update notifications to the correct channel metadata collections
*/
public class IdentifierUpdateListenerProxy implements Listener<IdentifierUpdateNotification>
{
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
int timeslot = identifierUpdateNotification.getTimeslot();
ChannelMetadata channelMetadata = mChannelMetadataMap.get(timeslot);
if(channelMetadata != null)
{
channelMetadata.receive(identifierUpdateNotification);
}
}
}
/**
* Proxy between the internal identifier collections and the external update notification listener. This proxy
* enables access to internal components to broadcast silent identifier update notifications externally.
*/
public class IdentifierUpdateNotificationProxy implements Listener<IdentifierUpdateNotification>
{
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
if(mIdentifierUpdateNotificationListener != null)
{
mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
}
}
public void setListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationListener = listener;
}
public void removeListener()
{
mIdentifierUpdateNotificationListener = null;
}
}
}

View File

@@ -0,0 +1,470 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.channel.metadata.ChannelMetadata;
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.Channel.ChannelType;
import io.github.dsheirer.controller.channel.ChannelEvent;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Channel state tracks the overall state of all processing modules and decoders configured for the channel and
* provides squelch control and decoder state reset events.
*
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
* reset to IDLE.
*
* State Descriptions:
* IDLE: Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
* signal to the user that the call is ended, while continuing to display the call details for the user
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
*
* Identifiers and Channel Metadata
*
* The internal identifier collection maintains all of the channel configuration items and monitors source events to
* update the channel's frequency as it changes. These updates are broadcast externally to any identifier collection
* listeners. The internal identifier collection does not listen to incoming identifier update notifications in order
* to prevent a feedback loop. Channel Metadata is the only listener for externally generated updates.
*
* Channel Metadata receives all external identifier notifications and any internal notifications generated by the
* internal identifier collection via feedback.
*/
public class SingleChannelState extends AbstractChannelState implements IDecoderStateEventListener, ISourceEventListener,
IdentifierUpdateListener, IStateMachineListener
{
private final static Logger mLog = LoggerFactory.getLogger(SingleChannelState.class);
public static final long FADE_TIMEOUT_DELAY = 1200;
public static final long RESET_TIMEOUT_DELAY = 2000;
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
private IdentifierUpdateNotificationProxy mIdentifierUpdateNotificationProxy = new IdentifierUpdateNotificationProxy();
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
private SourceEventListener mInternalSourceEventListener;
private ChannelMetadata mChannelMetadata;
private StateMachine mStateMachine = new StateMachine(0);
private StateMonitoringSquelchController mSquelchController = new StateMonitoringSquelchController(0);
public SingleChannelState(Channel channel, AliasModel aliasModel)
{
super(channel);
mChannelMetadata = new ChannelMetadata(aliasModel);
mIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
createConfigurationIdentifiers(channel);
mStateMachine.addListener(this);
mStateMachine.addListener(mSquelchController);
mStateMachine.setChannelType(mChannel.getChannelType());
mStateMachine.setIdentifierUpdateListener(mIdentifierCollection);
mStateMachine.setEndTimeoutBuffer(RESET_TIMEOUT_DELAY);
if(channel.getChannelType() == ChannelType.STANDARD)
{
mStateMachine.setFadeTimeoutBuffer(FADE_TIMEOUT_DELAY);
}
else
{
mStateMachine.setFadeTimeoutBuffer(DecodeConfiguration.DEFAULT_CALL_TIMEOUT_DELAY_SECONDS * 1000);
}
}
@Override
public void stateChanged(State state, int timeslot)
{
//Broadcast current channel state so that channel rotation monitor can track
if(state.isActiveState())
{
broadcast(DecoderStateEvent.activeState(timeslot));
}
else
{
broadcast(DecoderStateEvent.inactiveState(timeslot));
}
ChannelStateIdentifier stateIdentifier = ChannelStateIdentifier.create(state);
mIdentifierCollection.update(stateIdentifier);
mChannelMetadata.receive(new IdentifierUpdateNotification(stateIdentifier, IdentifierUpdateNotification.Operation.ADD, timeslot));
switch(state)
{
case IDLE:
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
break;
case RESET:
reset();
mStateMachine.setState(State.IDLE);
break;
case TEARDOWN:
if(mChannel.isTrafficChannel())
{
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
}
else
{
mStateMachine.setState(State.RESET);
}
break;
}
}
@Override
protected void checkState()
{
mStateMachine.checkState();
}
@Override
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationProxy.setListener(listener);
}
@Override
public void removeIdentifierUpdateListener()
{
mIdentifierUpdateNotificationProxy.removeListener();
}
@Override
public void setSquelchStateListener(Listener<SquelchStateEvent> listener)
{
mSquelchController.setSquelchStateListener(listener);
}
@Override
public void removeSquelchStateListener()
{
mSquelchController.removeSquelchStateListener();
}
/**
* Creates configuration identifiers for the channel name, system, site and alias list name.
*/
private void createConfigurationIdentifiers(Channel channel)
{
mIdentifierCollection.update(DecoderTypeConfigurationIdentifier.create(channel.getDecodeConfiguration().getDecoderType()));
if(channel.hasSystem())
{
mIdentifierCollection.update(SystemConfigurationIdentifier.create(channel.getSystem()));
}
if(channel.hasSite())
{
mIdentifierCollection.update(SiteConfigurationIdentifier.create(channel.getSite()));
}
if(channel.getName() != null && !channel.getName().isEmpty())
{
mIdentifierCollection.update(ChannelNameConfigurationIdentifier.create(channel.getName()));
}
if(channel.getAliasListName() != null && !channel.getAliasListName().isEmpty())
{
mIdentifierCollection.update(AliasListConfigurationIdentifier.create(channel.getAliasListName()));
}
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequency));
}
else if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
{
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencies();
if(frequencies.size() > 0)
{
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequencies.get(0)));
}
}
}
/**
* Interface to receive channel identifier updates from this channel state and from any
* decoder states.
*/
@Override
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
{
return mChannelMetadata;
}
/**
* Updates the channel state identifier collection using the update notification. This update will be reflected
* in the internal channel state and will also be broadcast to any listeners, including the channel metadata for
* this channel state.
*/
@Override
public void updateChannelStateIdentifiers(IdentifierUpdateNotification notification)
{
mIdentifierCollection.receive(notification);
mChannelMetadata.receive(notification);
}
/**
* Channel metadata for this channel.
*/
public Collection<ChannelMetadata> getChannelMetadata()
{
return Collections.singletonList(mChannelMetadata);
}
/**
* Resets this channel state and prepares it for reuse.
*/
@Override
public void reset()
{
mStateMachine.setState(State.RESET);
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
mIdentifierCollection.remove(IdentifierClass.USER);
sourceOverflow(false);
}
@Override
public void start()
{
mIdentifierCollection.broadcastIdentifiers();
if(mChannel.getChannelType() == ChannelType.TRAFFIC)
{
mStateMachine.setState(State.ACTIVE);
}
}
@Override
public void stop()
{
mSquelchController.setSquelchLock(false);
}
public void dispose()
{
mDecodeEventListener = null;
mDecoderStateListener = null;
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
if(mInternalSourceEventListener == null)
{
mInternalSourceEventListener = new SourceEventListener();
}
return mInternalSourceEventListener;
}
/**
* Broadcasts the source event to a registered external source event listener
*/
protected void broadcast(SourceEvent sourceEvent)
{
if(mExternalSourceEventListener != null)
{
mExternalSourceEventListener.receive(sourceEvent);
}
}
/**
* Broadcasts the call event to the registered listener
*/
protected void broadcast(IDecodeEvent event)
{
if(mDecodeEventListener != null)
{
mDecodeEventListener.receive(event);
}
}
/**
* Broadcasts the channel event to a registered listener
*/
private void broadcast(ChannelEvent channelEvent)
{
if(mChannelEventListener != null)
{
mChannelEventListener.receive(channelEvent);
}
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast(DecoderStateEvent event)
{
if(mDecoderStateListener != null)
{
mDecoderStateListener.receive(event);
}
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventReceiver;
}
/**
* Listener to receive source events.
*/
public class SourceEventListener implements Listener<SourceEvent>
{
@Override
public void receive(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
//Rebroadcast source frequency change events for the decoder(s) to process
long frequency = sourceEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, mStateMachine.getState(), frequency));
//Create a new frequency configuration identifier so that downstream consumers receive the change
//via channel metadata and audio packet updates - this is a silent add that is sent as a notification
//to all identifier collections so that they don't rebroadcast the change and cause a feedback loop
mIdentifierUpdateNotificationProxy.receive(new IdentifierUpdateNotification(
FrequencyConfigurationIdentifier.create(frequency), IdentifierUpdateNotification.Operation.SILENT_ADD, 0));
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
//Rebroadcast frequency error measurements to external listener if we're currently
//in an active (ie sync locked) state.
if(mStateMachine.getState().isActiveState())
{
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
mChannel.getChannelType().name()));
}
break;
}
}
}
/**
* DecoderStateEvent receiver wrapper
*/
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
{
@Override
public void receive(DecoderStateEvent event)
{
if(event.getSource() != this)
{
switch(event.getEvent())
{
case ALWAYS_UNSQUELCH:
mSquelchController.setSquelchLock(true);
break;
case CHANGE_CALL_TIMEOUT:
if(event instanceof ChangeChannelTimeoutEvent)
{
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
mStateMachine.setFadeTimeoutBuffer(timeout.getCallTimeout());
}
case CONTINUATION:
case DECODE:
case START:
if(State.CALL_STATES.contains(event.getState()))
{
mStateMachine.setState(event.getState());
}
break;
case END:
if(mChannel.isTrafficChannel())
{
mStateMachine.setState(State.TEARDOWN);
}
else
{
mStateMachine.setState(State.FADE);
}
break;
case RESET:
/* Channel State does not respond to reset events */
break;
default:
break;
}
}
}
}
//TODO: this can probably be removed. The only reason it exists is so that we can inject frequency change
//updates directly to the external broadcaster as a silent add. Otherwise, we could simply register the external
//listener directly on the identifier collection.
/**
* Proxy between the internal identifier collection and the external update notification listener. This proxy
* enables access to internal components to broadcast silent identifier update notifications externally.
*/
public class IdentifierUpdateNotificationProxy implements Listener<IdentifierUpdateNotification>
{
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
if(mIdentifierUpdateNotificationListener != null)
{
mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
}
}
public void setListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationListener = listener;
}
public void removeListener()
{
mIdentifierUpdateNotificationListener = null;
}
}
}

View File

@@ -1,20 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.channel.state;
import java.util.EnumSet;
@@ -39,7 +43,8 @@ public enum State
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
state == TEARDOWN ||
state == RESET;
}
},
/**
@@ -55,7 +60,8 @@ public enum State
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
state == TEARDOWN ||
state == RESET;
}
},
/**
@@ -67,7 +73,8 @@ public enum State
public boolean canChangeTo(State state)
{
return state == IDLE ||
state == FADE;
state == FADE ||
state == RESET;
}
},
/**
@@ -83,6 +90,7 @@ public enum State
state == CONTROL ||
state == ENCRYPTED ||
state == FADE ||
state == RESET ||
state == TEARDOWN;
}
},
@@ -95,7 +103,8 @@ public enum State
public boolean canChangeTo(State state)
{
return state == FADE ||
state == TEARDOWN;
state == TEARDOWN ||
state == RESET;
}
},
/**

View File

@@ -0,0 +1,214 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class StateMachine
{
private final static Logger mLog = LoggerFactory.getLogger(StateMachine.class);
protected State mState = State.IDLE;
protected long mFadeTimeout;
protected long mFadeTimeoutBuffer = 0;
protected long mEndTimeout;
protected long mEndTimeoutBuffer = 0;
private int mTimeslot;
private Channel.ChannelType mChannelType = Channel.ChannelType.STANDARD;
private List<IStateMachineListener> mStateMachineListeners = new ArrayList<>();
private Listener<IdentifierUpdateNotification> mIdentifierUpdateListener;
public StateMachine(int timeslot)
{
mTimeslot = timeslot;
}
public void addListener(IStateMachineListener listener)
{
mStateMachineListeners.add(listener);
}
public void removeListener(IStateMachineListener listener)
{
mStateMachineListeners.remove(listener);
}
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateListener = listener;
}
public void setChannelType(Channel.ChannelType channelType)
{
mChannelType = channelType;
}
public void checkState()
{
if(mState.isActiveState() && mFadeTimeout <= System.currentTimeMillis())
{
setState(State.FADE);
}
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
{
setState(State.TEARDOWN);
}
}
public State getState()
{
return mState;
}
public void setState(State state)
{
if(state == mState)
{
if(State.CALL_STATES.contains(state))
{
updateFadeTimeout();
}
}
else if(mState.canChangeTo(state))
{
switch(state)
{
case ACTIVE:
mState = state;
updateFadeTimeout();
broadcast(ChannelStateIdentifier.ACTIVE);
break;
case CONTROL:
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
if(mChannelType == Channel.ChannelType.STANDARD)
{
mState = state;
updateFadeTimeout();
broadcast(ChannelStateIdentifier.CONTROL);
}
break;
case DATA:
mState = state;
updateFadeTimeout();
broadcast(ChannelStateIdentifier.DATA);
break;
case ENCRYPTED:
mState = state;
updateFadeTimeout();
broadcast(ChannelStateIdentifier.ENCRYPTED);
break;
case CALL:
mState = state;
updateFadeTimeout();
broadcast(ChannelStateIdentifier.CALL);
break;
case FADE:
mState = state;
broadcast(ChannelStateIdentifier.FADE);
break;
case IDLE:
mState = state;
broadcast(ChannelStateIdentifier.IDLE);
break;
case TEARDOWN:
mState = state;
broadcast(ChannelStateIdentifier.TEARDOWN);
break;
case RESET:
mState = State.RESET;
broadcast(ChannelStateIdentifier.RESET);
break;
default:
break;
}
//If the state successfully changed to the new state, announce it
if(mState == state)
{
for(IStateMachineListener listener: mStateMachineListeners)
{
listener.stateChanged(mState, mTimeslot);
}
}
}
}
private void broadcast(ChannelStateIdentifier channelStateIdentifier)
{
if(mIdentifierUpdateListener != null)
{
mIdentifierUpdateListener.receive(new IdentifierUpdateNotification(channelStateIdentifier,
IdentifierUpdateNotification.Operation.ADD, mTimeslot));
}
}
private void updateFadeTimeout()
{
mFadeTimeout = System.currentTimeMillis() + mFadeTimeoutBuffer;
}
public void setFadeTimeout(long timeout)
{
mFadeTimeout = timeout;
}
public long getFadeTimeout()
{
return mFadeTimeout;
}
public void setFadeTimeoutBuffer(long buffer)
{
mFadeTimeoutBuffer = buffer;
updateFadeTimeout();
}
private void updateEndTimeout()
{
mEndTimeout = System.currentTimeMillis() + mEndTimeoutBuffer;
}
public void setEndTimeoutBuffer(long buffer)
{
mEndTimeoutBuffer = buffer;
updateEndTimeout();
}
public void setEndTimeout(long endTimeout)
{
mEndTimeout = endTimeout;
}
public long getEndTimeout()
{
return mEndTimeout;
}
}

View File

@@ -0,0 +1,86 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.sample.Listener;
public class StateMonitoringSquelchController implements IStateMachineListener, ISquelchStateProvider
{
private boolean mSquelchLocked;
private SquelchState mSquelchState = SquelchState.SQUELCH;
private Listener<SquelchStateEvent> mSquelchStateListener;
private int mTimeslot;
public StateMonitoringSquelchController(int timeslot)
{
mTimeslot = timeslot;
}
public void setSquelchStateListener(Listener<SquelchStateEvent> listener)
{
mSquelchStateListener = listener;
}
public void removeSquelchStateListener()
{
mSquelchStateListener = null;
}
public void setSquelchLock(boolean locked)
{
mSquelchLocked = locked;
setSquelchState(mSquelchLocked ? SquelchState.UNSQUELCH : SquelchState.SQUELCH);
}
private void setSquelchState(SquelchState squelchState)
{
if(mSquelchState != squelchState)
{
mSquelchState = squelchState;
if(mSquelchStateListener != null)
{
mSquelchStateListener.receive(new SquelchStateEvent(squelchState, mTimeslot));
}
}
}
@Override
public void stateChanged(State state, int timeslot)
{
if(!mSquelchLocked)
{
if(state == State.CALL)
{
setSquelchState(SquelchState.UNSQUELCH);
}
else
{
setSquelchState(SquelchState.SQUELCH);
}
}
}
}

View File

@@ -0,0 +1,92 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.channel.state;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
import io.github.dsheirer.sample.Listener;
public abstract class TimeslotDecoderState extends DecoderState
{
private int mTimeslot;
public TimeslotDecoderState(int timeslot)
{
super(new MutableIdentifierCollection(timeslot));
mTimeslot = timeslot;
mConfigurationIdentifierListener = new TimeslotConfigurationIdentifierListener();
}
protected int getTimeslot()
{
return mTimeslot;
}
/**
* Listener for configuration type identifier updates sent from the channel state. Adds configuration
* identifiers to this decoder state so that decode events will contain configuration details in the
* event's identifier collection.
*/
public class TimeslotConfigurationIdentifierListener implements Listener<IdentifierUpdateNotification>
{
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
if(identifierUpdateNotification.getTimeslot() == getTimeslot())
{
if(identifierUpdateNotification.getIdentifier().getIdentifierClass() == IdentifierClass.CONFIGURATION &&
identifierUpdateNotification.getIdentifier().getForm() != Form.DECODER_TYPE &&
identifierUpdateNotification.getIdentifier().getForm() != Form.CHANNEL_DESCRIPTOR)
{
if(identifierUpdateNotification.isAdd())
{
getIdentifierCollection().update(identifierUpdateNotification.getIdentifier());
}
else if(identifierUpdateNotification.isSilentAdd())
{
getIdentifierCollection().silentUpdate(identifierUpdateNotification.getIdentifier());
}
}
if(identifierUpdateNotification.getOperation() == IdentifierUpdateNotification.Operation.ADD)
{
Identifier identifier = identifierUpdateNotification.getIdentifier();
if(identifier instanceof ChannelDescriptorConfigurationIdentifier)
{
setCurrentChannel(((ChannelDescriptorConfigurationIdentifier)identifier).getValue());
}
else if(identifier instanceof IChannelDescriptor)
{
setCurrentChannel((IChannelDescriptor)identifier);
}
}
}
}
}
}

View File

@@ -1,21 +1,24 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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;
import com.jidesoft.swing.JideTabbedPane;
@@ -25,7 +28,7 @@ import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastPanel;
import io.github.dsheirer.audio.playback.AudioPanel;
import io.github.dsheirer.audio.playback.AudioPlaybackManager;
import io.github.dsheirer.channel.metadata.ChannelMetadataViewer;
import io.github.dsheirer.channel.metadata.NowPlayingPanel;
import io.github.dsheirer.controller.channel.ChannelController;
import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
@@ -34,6 +37,7 @@ import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.map.MapPanel;
import io.github.dsheirer.map.MapService;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.RecorderManager;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceManager;
import io.github.dsheirer.source.tuner.TunerModel;
@@ -41,7 +45,6 @@ import io.github.dsheirer.source.tuner.TunerViewPanel;
import net.miginfocom.swing.MigLayout;
import javax.swing.JPanel;
import javax.swing.JTable;
import java.awt.Color;
import java.awt.Dimension;
@@ -53,27 +56,19 @@ public class ControllerPanel extends JPanel
private AudioPanel mAudioPanel;
private BroadcastPanel mBroadcastPanel;
private ChannelController mChannelController;
private ChannelMetadataViewer mChannelMetadataViewer;
private NowPlayingPanel mNowPlayingPanel;
private ChannelModel mChannelModel;
private MapPanel mMapPanel;
private TunerViewPanel mTunerManagerPanel;
private BroadcastModel mBroadcastModel;
private JideTabbedPane mTabbedPane;
protected JTable mChannelActivityTable = new JTable();
public ControllerPanel(AudioPlaybackManager audioPlaybackManager,
AliasModel aliasModel,
BroadcastModel broadcastModel,
ChannelModel channelModel,
ChannelMapModel channelMapModel,
ChannelProcessingManager channelProcessingManager,
IconManager iconManager,
MapService mapService,
SettingsManager settingsManager,
SourceManager sourceManager,
TunerModel tunerModel,
UserPreferences userPreferences)
public ControllerPanel(AudioPlaybackManager audioPlaybackManager, AliasModel aliasModel, BroadcastModel broadcastModel,
ChannelModel channelModel, ChannelMapModel channelMapModel, ChannelProcessingManager channelProcessingManager,
IconManager iconManager, MapService mapService, SettingsManager settingsManager,
SourceManager sourceManager, TunerModel tunerModel, UserPreferences userPreferences,
RecorderManager recorderManager)
{
mBroadcastModel = broadcastModel;
mChannelModel = channelModel;
@@ -81,19 +76,19 @@ public class ControllerPanel extends JPanel
mAudioPanel = new AudioPanel(iconManager, userPreferences, settingsManager, sourceManager, audioPlaybackManager,
aliasModel);
mChannelMetadataViewer = new ChannelMetadataViewer(channelModel, channelProcessingManager, iconManager,
mNowPlayingPanel = new NowPlayingPanel(channelModel, channelProcessingManager, iconManager,
aliasModel, userPreferences);
mMapPanel = new MapPanel(mapService, aliasModel, iconManager, settingsManager);
mBroadcastPanel = new BroadcastPanel(broadcastModel, aliasModel, iconManager);
mBroadcastPanel = new BroadcastPanel(broadcastModel, aliasModel, iconManager, userPreferences);
mChannelController = new ChannelController(channelModel, channelProcessingManager, channelMapModel,
sourceManager, aliasModel);
sourceManager, aliasModel, userPreferences);
mAliasController = new AliasController(aliasModel, broadcastModel, iconManager);
mAliasController = new AliasController(aliasModel, broadcastModel, iconManager, userPreferences);
mTunerManagerPanel = new TunerViewPanel(tunerModel);
mTunerManagerPanel = new TunerViewPanel(tunerModel, userPreferences, recorderManager);
init();
}
@@ -108,7 +103,7 @@ public class ControllerPanel extends JPanel
mTabbedPane = new JideTabbedPane();
mTabbedPane.setFont(this.getFont());
mTabbedPane.setForeground(Color.BLACK);
mTabbedPane.addTab("Now Playing", mChannelMetadataViewer);
mTabbedPane.addTab("Now Playing", mNowPlayingPanel);
mTabbedPane.addTab("Aliases", mAliasController);
mTabbedPane.addTab("Channels", mChannelController);
mTabbedPane.addTab("Map", mMapPanel);

View File

@@ -1,23 +1,29 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* 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 com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import io.github.dsheirer.source.SourceManager;
import net.coderazzi.filters.gui.AutoChoices;
import net.coderazzi.filters.gui.TableFilterHeader;
@@ -43,11 +49,14 @@ public class ChannelController extends JPanel implements ActionListener, ListSel
{
private static final long serialVersionUID = 1L;
private static final String TABLE_PREFERENCE_KEY = "channel.controller";
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private JTable mChannelTable;
private JTableColumnWidthMonitor mColumnWidthMonitor;
private TableFilterHeader mTableFilterHeader;
private ChannelEditor mEditor;
private UserPreferences mUserPreferences;
private static final String NEW_CHANNEL = "New";
private static final String COPY_CHANNEL = "Copy";
@@ -57,17 +66,16 @@ public class ChannelController extends JPanel implements ActionListener, ListSel
private JButton mCopyChannelButton = new JButton(COPY_CHANNEL);
private JButton mDeleteChannelButton = new JButton(DELETE_CHANNEL);
public ChannelController(ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
ChannelMapModel channelMapModel,
SourceManager sourceManager,
AliasModel aliasModel)
public ChannelController(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager,
ChannelMapModel channelMapModel, SourceManager sourceManager, AliasModel aliasModel,
UserPreferences userPreferences)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mEditor = new ChannelEditor(channelModel, channelProcessingManager, channelMapModel, sourceManager, aliasModel);
mChannelModel.addListener(mEditor);
mChannelProcessingManager.addChannelEventListener(mEditor);
mUserPreferences = userPreferences;
init();
}
@@ -82,6 +90,7 @@ public class ChannelController extends JPanel implements ActionListener, ListSel
mChannelTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mChannelTable.getSelectionModel().addListSelectionListener(this);
mChannelTable.setAutoCreateRowSorter(true);
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mChannelTable, TABLE_PREFERENCE_KEY);
mTableFilterHeader = new TableFilterHeader(mChannelTable, AutoChoices.ENABLED);
mTableFilterHeader.setFilterOnUpdates(true);

View File

@@ -1,34 +1,38 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.alias.AliasModel;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.channel.metadata.ChannelMetadata;
import io.github.dsheirer.channel.metadata.ChannelMetadataModel;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.module.ProcessingChain;
@@ -37,9 +41,8 @@ import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.module.decode.event.MessageActivityModel;
import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.RecorderFactory;
import io.github.dsheirer.record.RecorderManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.binary.BinaryRecorder;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
@@ -237,8 +240,8 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
{
channel.setProcessing(false);
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_START_REJECTED,
TUNER_UNAVAILABLE_DESCRIPTION));
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel,
ChannelEvent.Event.NOTIFICATION_PROCESSING_START_REJECTED, TUNER_UNAVAILABLE_DESCRIPTION));
return;
}
@@ -285,43 +288,10 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
processingChain.addModules(loggers);
}
/* Setup recorders */
List<RecorderType> recorders = channel.getRecordConfiguration().getRecorders();
if(!recorders.isEmpty())
{
/* Add baseband recorder */
if((recorders.contains(RecorderType.BASEBAND) && channel.getChannelType() == Channel.ChannelType.STANDARD))
{
processingChain.addModule(mRecorderManager.getBasebandRecorder(channel.toString()));
}
/* Add traffic channel baseband recorder */
if(recorders.contains(RecorderType.TRAFFIC_BASEBAND) && channel.getChannelType() == Channel.ChannelType.TRAFFIC)
{
processingChain.addModule(mRecorderManager.getBasebandRecorder(channel.toString()));
}
/* Add decoded bit stream recorder if the decoder supports bitstream output */
if(DecoderFactory.getBitstreamDecoders().contains(channel.getDecodeConfiguration().getDecoderType()))
{
if((recorders.contains(RecorderType.DEMODULATED_BIT_STREAM) &&
channel.getChannelType() == Channel.ChannelType.STANDARD))
{
processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(),
channel.toString(), channel.getDecodeConfiguration().getDecoderType().getProtocol()));
}
/* Add traffic channel decoded bit stream recorder */
if(recorders.contains(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM) &&
channel.getChannelType() == Channel.ChannelType.TRAFFIC)
{
processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(),
channel.toString(), channel.getDecodeConfiguration().getDecoderType().getProtocol()));
}
}
}
//Add recorders
processingChain.addModules(RecorderFactory.getRecorders(mRecorderManager, mUserPreferences, channel));
//Set the samples source
processingChain.setSource(source);
//Inject the channel identifier for traffic channels and preload user identifiers
@@ -330,22 +300,47 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
ChannelGrantEvent channelGrantEvent = (ChannelGrantEvent)event;
IChannelDescriptor channelDescriptor = channelGrantEvent.getChannelDescriptor();
IdentifierCollection identifierCollection = channelGrantEvent.getIdentifierCollection();
if(channelDescriptor != null)
{
ChannelDescriptorConfigurationIdentifier identifier = new ChannelDescriptorConfigurationIdentifier(channelDescriptor);
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(identifier,
IdentifierUpdateNotification.Operation.ADD);
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
}
for(int timeslot = 0; timeslot < channelDescriptor.getTimeslotCount(); timeslot++)
{
DecoderLogicalChannelNameIdentifier identifier =
DecoderLogicalChannelNameIdentifier.create(channelDescriptor.toString(), channelDescriptor.getProtocol());
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(identifier,
IdentifierUpdateNotification.Operation.ADD, timeslot);
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
IdentifierCollection identifierCollection = channelGrantEvent.getIdentifierCollection();
//Inject scramble parameters
for(Identifier scrambleParameters: identifierCollection.getIdentifiers(Form.SCRAMBLE_PARAMETERS))
{
//Broadcast scramble parameters to both timeslots
IdentifierUpdateNotification scrambleNotification = new IdentifierUpdateNotification(scrambleParameters,
IdentifierUpdateNotification.Operation.ADD, timeslot);
processingChain.getChannelState().updateChannelStateIdentifiers(scrambleNotification);
}
}
}
for(Identifier userIdentifier : identifierCollection.getIdentifiers(IdentifierClass.USER))
{
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(userIdentifier,
IdentifierUpdateNotification.Operation.ADD);
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
if(channelDescriptor.getTimeslotCount() > 1)
{
//Only broadcast an identifier update for the timeslot specified in the originating collection
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(userIdentifier,
IdentifierUpdateNotification.Operation.ADD, channelGrantEvent.getIdentifierCollection().getTimeslot());
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
}
else
{
//Only broadcast an identifier update for the timeslot specified in the originating collection
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(userIdentifier,
IdentifierUpdateNotification.Operation.ADD, 0);
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
}
}
}
processingChain.start();
@@ -372,7 +367,10 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
{
ProcessingChain processingChain = mProcessingChains.get(channel);
getChannelMetadataModel().remove(processingChain.getChannelState().getChannelMetadata());
for(ChannelMetadata channelMetadata: processingChain.getChannelState().getChannelMetadata())
{
getChannelMetadataModel().remove(channelMetadata);
}
processingChain.stop();

View File

@@ -1,18 +1,24 @@
/*******************************************************************************
* 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.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* 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.channelizer;
import io.github.dsheirer.dsp.filter.FilterFactory;
@@ -299,6 +305,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_RECORDING_FILE_LOADED:
//no-op
break;
default:

Some files were not shown because too many files have changed in this diff Show More