diff --git a/.gitignore b/.gitignore
index 6633be8cf..e4a1c09b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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*
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index cb7eff953..967a782f9 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -4,6 +4,8 @@
+
+
\ No newline at end of file
diff --git a/.idea/libraries/imports.xml b/.idea/libraries/imports.xml
index b90eef82b..2bdceef59 100644
--- a/.idea/libraries/imports.xml
+++ b/.idea/libraries/imports.xml
@@ -1,12 +1,10 @@
-
-
-
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
index f67d0d5be..5e8edec26 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,9 +2,11 @@
-
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 7c48cf89c..045362c3c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -47,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'
@@ -54,6 +55,7 @@ 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'
@@ -100,7 +102,7 @@ task buildSdr(type: Jar) {
attributes 'Implementation-Title': 'SdrTrunk project',
'Implementation-Version': version,
'Main-Class': 'io.github.dsheirer.gui.SDRTrunk',
- 'Class-Path': 'jmbe-0.3.2.jar jmbe-0.3.3.jar'
+ 'Class-Path': 'jmbe-1.0.0.jar'
}
baseName = project.name + '-all'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
diff --git a/imports/jmbe-interface-0.3.3.jar b/imports/jmbe-api-1.0.0.jar
similarity index 73%
rename from imports/jmbe-interface-0.3.3.jar
rename to imports/jmbe-api-1.0.0.jar
index 73fac2869..9e398e186 100644
Binary files a/imports/jmbe-interface-0.3.3.jar and b/imports/jmbe-api-1.0.0.jar differ
diff --git a/sdr-trunk.ipr b/sdr-trunk.ipr
index f3b4d0ecb..9ff6b03bd 100644
--- a/sdr-trunk.ipr
+++ b/sdr-trunk.ipr
@@ -11,9 +11,9 @@
-
-
-
+
+
+
@@ -36,6 +36,7 @@
+
-
+
-
+
@@ -255,48 +256,48 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -310,6 +311,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -343,26 +399,26 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -394,26 +450,26 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -427,53 +483,59 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
+
+
+
+
-
+
-
+
-
+
+
+
-
+
@@ -498,26 +560,26 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -575,37 +637,158 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/src/main/java/io/github/dsheirer/audio/AudioModule.java b/src/main/java/io/github/dsheirer/audio/AudioModule.java
index 2d034ca12..d8be85dcb 100644
--- a/src/main/java/io/github/dsheirer/audio/AudioModule.java
+++ b/src/main/java/io/github/dsheirer/audio/AudioModule.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.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 void stop()
@Override
- public Listener getSquelchStateListener()
+ public Listener getSquelchStateListener()
{
return mSquelchStateListener;
}
@@ -169,7 +172,7 @@ public void receive(ReusableFloatBuffer reusableFloatBuffer)
}
@Override
- public Listener getReusableBufferListener()
+ public Listener getReusableBufferListener()
{
//Redirect received reusable buffers to the receive(buffer) method
return this;
@@ -178,12 +181,12 @@ public Listener getReusableBufferListener()
/**
* Wrapper for squelch state listener
*/
- public class SquelchStateListener implements Listener
+ public class SquelchStateListener implements Listener
{
@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 void receive(SquelchState state)
getAudioPacketListener().receive(endAudioPacket);
}
- mSquelchState = state;
+ mSquelchState = event.getSquelchState();
}
}
}
diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/AmbeAudioModule.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/AmbeAudioModule.java
new file mode 100644
index 000000000..714cddbc2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/AmbeAudioModule.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/IEncryptionSyncParameters.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/IEncryptionSyncParameters.java
new file mode 100644
index 000000000..284f5b764
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/IEncryptionSyncParameters.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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();
+}
diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/ImbeAudioModule.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/ImbeAudioModule.java
new file mode 100644
index 000000000..8a94870e3
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/ImbeAudioModule.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/JmbeAudioModule.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/JmbeAudioModule.java
new file mode 100644
index 000000000..2a27531cc
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/JmbeAudioModule.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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, IMessageListener
+{
+ private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
+ private static final String JMBE_AUDIO_LIBRARY = "JMBE";
+ private static List 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 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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequence.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequence.java
new file mode 100644
index 000000000..15776dd5f
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequence.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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 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 getVoiceFrames()
+ {
+ return mVoiceFrames;
+ }
+
+ public void setVoiceFrames(List 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));
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceConverter.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceConverter.java
new file mode 100644
index 000000000..f879e7288
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceConverter.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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);
+// }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceReader.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceReader.java
new file mode 100644
index 000000000..be9b48643
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceReader.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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 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 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 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();
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceRecorder.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceRecorder.java
new file mode 100644
index 000000000..459a9a2f9
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceRecorder.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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
+{
+ 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 getMessageListener()
+ {
+ return this;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java
new file mode 100644
index 000000000..d791c10f1
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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 mDecodeSpeechRequests = new LinkedTransferQueue<>();
+ private AudioProtocol mAudioProtocol;
+ private Listener mAudioPacketListener;
+ private ReusableAudioPacketQueue mReusableAudioPacketQueue = new ReusableAudioPacketQueue("ThumbDv");
+ private boolean mStarted;
+
+ public ThumbDv(AudioProtocol audioProtocol, Listener 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 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 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();
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/AmbeMessage.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/AmbeMessage.java
new file mode 100644
index 000000000..2df75085b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/AmbeMessage.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/AmbeMessageFactory.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/AmbeMessageFactory.java
new file mode 100644
index 000000000..0cf9acfc3
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/AmbeMessageFactory.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/InitializeOption.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/InitializeOption.java
new file mode 100644
index 000000000..bc60ff7aa
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/InitializeOption.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/PacketField.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/PacketField.java
new file mode 100644
index 000000000..1b15f9933
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/PacketField.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/VocoderRate.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/VocoderRate.java
new file mode 100644
index 000000000..cd986e4bf
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/VocoderRate.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/AmbeRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/AmbeRequest.java
new file mode 100644
index 000000000..cab855921
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/AmbeRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/DecodeSpeechRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/DecodeSpeechRequest.java
new file mode 100644
index 000000000..0ec12fa5c
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/DecodeSpeechRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/EncodeSpeechRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/EncodeSpeechRequest.java
new file mode 100644
index 000000000..2c826014a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/EncodeSpeechRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/GetConfigRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/GetConfigRequest.java
new file mode 100644
index 000000000..a6c7227a1
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/GetConfigRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/InitializeCodecRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/InitializeCodecRequest.java
new file mode 100644
index 000000000..1737d404e
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/InitializeCodecRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/ProductIdRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/ProductIdRequest.java
new file mode 100644
index 000000000..fe7969fff
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/ProductIdRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/ResetRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/ResetRequest.java
new file mode 100644
index 000000000..4ae89073a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/ResetRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetChannelFormatRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetChannelFormatRequest.java
new file mode 100644
index 000000000..4326e615a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetChannelFormatRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetChannelRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetChannelRequest.java
new file mode 100644
index 000000000..b8ea0fef4
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetChannelRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetPacketModeRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetPacketModeRequest.java
new file mode 100644
index 000000000..ca8a52b3d
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetPacketModeRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetSpeechFormatRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetSpeechFormatRequest.java
new file mode 100644
index 000000000..8ebcbbbb0
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetSpeechFormatRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetVocoderParametersRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetVocoderParametersRequest.java
new file mode 100644
index 000000000..ba980284b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetVocoderParametersRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetVocoderRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetVocoderRequest.java
new file mode 100644
index 000000000..c39d5cd16
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/SetVocoderRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/VersionRequest.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/VersionRequest.java
new file mode 100644
index 000000000..fa2d78beb
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/request/VersionRequest.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/AmbeResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/AmbeResponse.java
new file mode 100644
index 000000000..b51836984
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/AmbeResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/DecodeSpeechResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/DecodeSpeechResponse.java
new file mode 100644
index 000000000..44b6f191e
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/DecodeSpeechResponse.java
@@ -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
+ *
+ ******************************************************************************/
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/EncodeSpeechResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/EncodeSpeechResponse.java
new file mode 100644
index 000000000..d86de460d
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/EncodeSpeechResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/GetConfigResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/GetConfigResponse.java
new file mode 100644
index 000000000..fda9f31e0
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/GetConfigResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/InitializeCodecResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/InitializeCodecResponse.java
new file mode 100644
index 000000000..e661ecf73
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/InitializeCodecResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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**");
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ProductIdResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ProductIdResponse.java
new file mode 100644
index 000000000..98f1e8768
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ProductIdResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ReadyResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ReadyResponse.java
new file mode 100644
index 000000000..e979b422c
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ReadyResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ResetResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ResetResponse.java
new file mode 100644
index 000000000..c76a688d1
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/ResetResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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**");
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetChannelFormatResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetChannelFormatResponse.java
new file mode 100644
index 000000000..e7e5ae81e
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetChannelFormatResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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**");
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetChannelResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetChannelResponse.java
new file mode 100644
index 000000000..509de3f03
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetChannelResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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**");
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetPacketModeResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetPacketModeResponse.java
new file mode 100644
index 000000000..04b04c5e2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetPacketModeResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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**");
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetSpeechFormatResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetSpeechFormatResponse.java
new file mode 100644
index 000000000..dae8b3589
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetSpeechFormatResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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**");
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetVocoderParameterResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetVocoderParameterResponse.java
new file mode 100644
index 000000000..6d5dd05ad
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetVocoderParameterResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetVocoderResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetVocoderResponse.java
new file mode 100644
index 000000000..eb629c283
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/SetVocoderResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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**");
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/UnknownResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/UnknownResponse.java
new file mode 100644
index 000000000..2a31e438a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/UnknownResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/VersionResponse.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/VersionResponse.java
new file mode 100644
index 000000000..2522b9f27
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/message/response/VersionResponse.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java b/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java
index db74594b9..f04c315a5 100644
--- a/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java
+++ b/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.audio.playback;
@@ -54,7 +56,7 @@ public abstract class AudioOutput implements Listener, Line
private LinkedTransferQueue mBuffer = new LinkedTransferQueue<>();
private int mBufferStartThreshold;
private int mBufferStopThreshold;
- private static IdentifierCollection EMPTY_IDENTIFIER_COLLECTION = new IdentifierCollection();
+ private static IdentifierCollection EMPTY_IDENTIFIER_COLLECTION = new IdentifierCollection(0);
static
{
EMPTY_IDENTIFIER_COLLECTION.setUpdated(true);
diff --git a/src/main/java/io/github/dsheirer/audio/squelch/ISquelchStateListener.java b/src/main/java/io/github/dsheirer/audio/squelch/ISquelchStateListener.java
index 0d7249a7b..1a3150e4b 100644
--- a/src/main/java/io/github/dsheirer/audio/squelch/ISquelchStateListener.java
+++ b/src/main/java/io/github/dsheirer/audio/squelch/ISquelchStateListener.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
package io.github.dsheirer.audio.squelch;
import io.github.dsheirer.sample.Listener;
public interface ISquelchStateListener
{
- public Listener getSquelchStateListener();
+ Listener getSquelchStateListener();
}
diff --git a/src/main/java/io/github/dsheirer/audio/squelch/ISquelchStateProvider.java b/src/main/java/io/github/dsheirer/audio/squelch/ISquelchStateProvider.java
index 908dad7a2..7bcff40cb 100644
--- a/src/main/java/io/github/dsheirer/audio/squelch/ISquelchStateProvider.java
+++ b/src/main/java/io/github/dsheirer/audio/squelch/ISquelchStateProvider.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
package io.github.dsheirer.audio.squelch;
import io.github.dsheirer.sample.Listener;
public interface ISquelchStateProvider
{
- public void setSquelchStateListener( Listener listener );
- public void removeSquelchStateListener();
+ void setSquelchStateListener(Listener listener);
+
+ void removeSquelchStateListener();
}
diff --git a/src/main/java/io/github/dsheirer/audio/squelch/SquelchStateEvent.java b/src/main/java/io/github/dsheirer/audio/squelch/SquelchStateEvent.java
new file mode 100644
index 000000000..e96c62843
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/squelch/SquelchStateEvent.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java
index 3a3e2cc6c..68667e7ad 100644
--- a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java
+++ b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
+ */
package io.github.dsheirer.bits;
import io.github.dsheirer.edac.CRC;
@@ -285,19 +289,28 @@ public String toString()
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 byte getByte(int[] bits, int offset)
}
/**
- * 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 byte getByte(int index)
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 long getLong(int[] bits)
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 @@ else if(bits.length <= 64)
}
}
- 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 static int[] getFieldIndexes(int start, int length, boolean bigEndian)
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 void xor(int offset, int width, int value)
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));
+ }
+ }
}
diff --git a/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java b/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java
index a0465be1f..40dfa1603 100644
--- a/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java
+++ b/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.bits;
import java.util.BitSet;
@@ -48,6 +51,12 @@ public CorrectedBinaryMessage(byte[] data)
super(data);
}
+ public CorrectedBinaryMessage(BinaryMessage message)
+ {
+ this(message.size());
+ this.xor(message);
+ }
+
@Override
public int getCorrectedBitCount()
{
@@ -72,4 +81,16 @@ public void incrementCorrectedBitCount(int additionalCount)
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);
+ }
}
diff --git a/src/main/java/io/github/dsheirer/channel/IChannelDescriptor.java b/src/main/java/io/github/dsheirer/channel/IChannelDescriptor.java
index 18a359639..05ccb6f5a 100644
--- a/src/main/java/io/github/dsheirer/channel/IChannelDescriptor.java
+++ b/src/main/java/io/github/dsheirer/channel/IChannelDescriptor.java
@@ -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;
/**
diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java
index fdd8ca4e5..5cb68e236 100644
--- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java
+++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.channel.metadata;
@@ -61,14 +63,31 @@ public class ChannelMetadata implements Listener,
private List mFromIdentifierAliases;
private Identifier mToIdentifier;
private List 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 @@ private void broadcastUpdate(ChannelMetadataField field)
@Override
public void receive(IdentifierUpdateNotification update)
{
+// mLog.debug("Received update: " + update + " class:" + update.getIdentifier().getClass());
Identifier identifier = update.getIdentifier();
switch(identifier.getIdentifierClass())
diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java
index 33a19efdd..0b44878cd 100644
--- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java
+++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.channel.metadata;
@@ -24,6 +26,7 @@
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 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 void preferenceUpdated(PreferenceType preferenceType)
}
- public void add(ChannelMetadata channelMetadata, Channel channel)
+ public void add(Collection channelMetadatas, Channel channel)
{
//Execute on the swing thread to avoid threading issues
EventQueue.invokeLater(new Runnable()
@@ -85,11 +89,14 @@ public void add(ChannelMetadata channelMetadata, Channel channel)
@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> getColumnClass(int columnIndex)
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 Object getValueAt(int rowIndex, int columnIndex)
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:
diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java
index 30da50703..13fc3f129 100644
--- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java
+++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.channel.metadata;
@@ -128,7 +130,7 @@ private void init()
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);
diff --git a/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java b/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java
new file mode 100644
index 000000000..2c93486c9
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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 mChannelEventListener;
+ protected Listener mDecodeEventListener;
+ protected Listener mDecoderStateListener;
+ protected Listener 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 getChannelMetadata();
+
+ public abstract void updateChannelStateIdentifiers(IdentifierUpdateNotification notification);
+
+ /**
+ * Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages.
+ */
+ @Override
+ public Listener 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 listener)
+ {
+ mChannelEventListener = listener;
+ }
+
+ @Override
+ public void removeChannelEventListener()
+ {
+ mChannelEventListener = null;
+ }
+
+ @Override
+ public void addDecodeEventListener(Listener listener)
+ {
+ mDecodeEventListener = listener;
+ }
+
+ @Override
+ public void removeDecodeEventListener(Listener listener)
+ {
+ mDecodeEventListener = null;
+ }
+
+ /**
+ * Adds a decoder state event listener
+ */
+ @Override
+ public void setDecoderStateListener(Listener 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 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
+ {
+ @Override
+ public void receive(Heartbeat heartbeat)
+ {
+ checkState();
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/channel/state/AbstractDecoderState.java b/src/main/java/io/github/dsheirer/channel/state/AbstractDecoderState.java
new file mode 100644
index 000000000..2ec19bedd
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/channel/state/AbstractDecoderState.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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,
+ 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 mDecodeEventBroadcaster = new Broadcaster<>();
+ protected Listener mDecoderStateListener;
+ private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
+
+ public abstract DecoderType getDecoderType();
+
+ /**
+ * Provides subclass reference to the decode event broadcaster
+ */
+ protected Broadcaster getDecodeEventBroadcaster()
+ {
+ return mDecodeEventBroadcaster;
+ }
+
+ @Override
+ public Listener 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 listener)
+ {
+ mDecodeEventBroadcaster.addListener(listener);
+ }
+
+ /**
+ * Removes the call event listener
+ */
+ @Override
+ public void removeDecodeEventListener(Listener listener)
+ {
+ mDecodeEventBroadcaster.removeListener(listener);
+ }
+
+ @Override
+ public Listener 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 listener)
+ {
+ mDecoderStateListener = listener;
+ }
+
+ /**
+ * Removes the decoder state event listener
+ */
+ @Override
+ public void removeDecoderStateListener()
+ {
+ mDecoderStateListener = null;
+ }
+
+ private class DecoderStateEventListener implements Listener
+ {
+ @Override
+ public void receive(DecoderStateEvent event)
+ {
+ receiveDecoderStateEvent(event);
+ }
+ }
+
+}
diff --git a/src/main/java/io/github/dsheirer/channel/state/AlwaysUnsquelchedDecoderState.java b/src/main/java/io/github/dsheirer/channel/state/AlwaysUnsquelchedDecoderState.java
index 4d145b907..310046b81 100644
--- a/src/main/java/io/github/dsheirer/channel/state/AlwaysUnsquelchedDecoderState.java
+++ b/src/main/java/io/github/dsheirer/channel/state/AlwaysUnsquelchedDecoderState.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.channel.state;
@@ -48,11 +50,6 @@ public void init()
{
}
- @Override
- public void reset()
- {
- }
-
@Override
public String getActivitySummary()
{
diff --git a/src/main/java/io/github/dsheirer/channel/state/ChangeChannelTimeoutEvent.java b/src/main/java/io/github/dsheirer/channel/state/ChangeChannelTimeoutEvent.java
index df7679a40..6e5bb3081 100644
--- a/src/main/java/io/github/dsheirer/channel/state/ChangeChannelTimeoutEvent.java
+++ b/src/main/java/io/github/dsheirer/channel/state/ChangeChannelTimeoutEvent.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
package io.github.dsheirer.channel.state;
import io.github.dsheirer.controller.channel.Channel.ChannelType;
@@ -7,23 +29,28 @@
*/
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;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/channel/state/ChannelState.java b/src/main/java/io/github/dsheirer/channel/state/ChannelState.java
deleted file mode 100644
index bc4097f1f..000000000
--- a/src/main/java/io/github/dsheirer/channel/state/ChannelState.java
+++ /dev/null
@@ -1,762 +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
- * *****************************************************************************
- */
-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.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.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 mChannelEventListener;
- private Listener mDecodeEventListener;
- private Listener mDecoderStateListener;
- private Listener mSquelchStateListener;
- private Listener 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 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 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 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.remove(IdentifierClass.USER);
- 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 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 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 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 listener)
- {
- mChannelEventListener = listener;
- }
-
- @Override
- public void removeChannelEventListener()
- {
- mChannelEventListener = null;
- }
-
- @Override
- public void addDecodeEventListener(Listener listener)
- {
- mDecodeEventListener = listener;
- }
-
- @Override
- public void removeDecodeEventListener(Listener 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 listener)
- {
- mDecoderStateListener = listener;
- }
-
- /**
- * Removes the decoder state event listener
- */
- @Override
- public void removeDecoderStateListener()
- {
- mDecoderStateListener = null;
- }
-
- @Override
- public Listener getDecoderStateListener()
- {
- return mDecoderStateEventReceiver;
- }
-
- /**
- * Registers the listener to receive source events from the channel state
- */
- @Override
- public void setSourceEventListener(Listener 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
- {
- @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
- {
- @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
- {
- @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
- {
- private Listener mIdentifierUpdateNotificationListener;
-
- @Override
- public void receive(IdentifierUpdateNotification identifierUpdateNotification)
- {
- if(mIdentifierUpdateNotificationListener != null)
- {
- mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
- }
- }
-
- public void setListener(Listener listener)
- {
- mIdentifierUpdateNotificationListener = listener;
- }
-
- public void removeListener()
- {
- mIdentifierUpdateNotificationListener = null;
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/channel/state/ChannelStateSelectionListener.java b/src/main/java/io/github/dsheirer/channel/state/ChannelStateSelectionListener.java
deleted file mode 100644
index cbdbcf93a..000000000
--- a/src/main/java/io/github/dsheirer/channel/state/ChannelStateSelectionListener.java
+++ /dev/null
@@ -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
- ******************************************************************************/
-package io.github.dsheirer.channel.state;
-
-
-public interface ChannelStateSelectionListener
-{
- public void selected( ChannelState channelState );
-}
diff --git a/src/main/java/io/github/dsheirer/channel/state/DecoderState.java b/src/main/java/io/github/dsheirer/channel/state/DecoderState.java
index 68267a96b..fbb24a8bc 100644
--- a/src/main/java/io/github/dsheirer/channel/state/DecoderState.java
+++ b/src/main/java/io/github/dsheirer/channel/state/DecoderState.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.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 @@
*
* Provides access to a textual activity summary of events observed.
*/
-public abstract class DecoderState extends Module implements ActivitySummaryProvider, Listener,
- 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 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 mDecodeEventBroadcaster = new Broadcaster<>();
- private Listener 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 void start()
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,18 +80,27 @@ public void removeIdentifierUpdateListener()
}
/**
- * Provides subclass reference to the decode event broadcaster
+ * Current collection of identifiers managed by the decoder state.
*/
- protected Broadcaster getDecodeEventBroadcaster()
+ public MutableIdentifierCollection getIdentifierCollection()
{
- return mDecodeEventBroadcaster;
+ return mIdentifierCollection;
}
+ /**
+ * Optional current channel descriptor
+ */
+ protected IChannelDescriptor getCurrentChannel()
+ {
+ return mCurrentChannel;
+ }
- @Override
- public Listener getMessageListener()
+ /**
+ * Sets the current channel descriptor
+ */
+ protected void setCurrentChannel(IChannelDescriptor channel)
{
- return this;
+ mCurrentChannel = channel;
}
/**
@@ -125,110 +109,28 @@ public Listener getMessageListener()
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();
+ public void reset()
+ {
+ setCurrentChannel(null);
+ }
/**
* 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 listener)
- {
- mDecodeEventBroadcaster.addListener(listener);
- }
-
- /**
- * Removes the call event listener
- */
- @Override
- public void removeDecodeEventListener(Listener listener)
- {
- mDecodeEventBroadcaster.removeListener(listener);
- }
-
- @Override
- public Listener getDecoderStateListener()
- {
- return mDecoderStateEventListener;
- }
-
- private class DecoderStateEventListener implements Listener
- {
- @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 listener)
- {
- mDecoderStateListener = listener;
- }
-
- /**
- * Removes the decoder state event listener
- */
- @Override
- public void removeDecoderStateListener()
- {
- mDecoderStateListener = null;
}
/**
@@ -244,27 +146,11 @@ public Listener getIdentifierUpdateListener()
/**
* Configuration identifier listener.
*/
- public ConfigurationIdentifierListener getConfigurationIdentifierListener()
+ public Listener getConfigurationIdentifierListener()
{
return mConfigurationIdentifierListener;
}
- /**
- * Optional current channel descriptor
- */
- protected IChannelDescriptor getCurrentChannel()
- {
- return mCurrentChannel;
- }
-
- /**
- * Sets the current channel descriptor
- */
- protected void setCurrentChannel(IChannelDescriptor channel)
- {
- mCurrentChannel = channel;
- }
-
/**
* 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 class ConfigurationIdentifierListener implements Listener
+ * * *****************************************************************************
+ *
+ *
+ */
+
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 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);
+ }
+
+ 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,
+ RESET,
+ SOURCE_FREQUENCY,
+ START;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/channel/state/IStateMachineListener.java b/src/main/java/io/github/dsheirer/channel/state/IStateMachineListener.java
new file mode 100644
index 000000000..9e766c6e8
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/channel/state/IStateMachineListener.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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);
+}
diff --git a/src/main/java/io/github/dsheirer/channel/state/MultiChannelState.java b/src/main/java/io/github/dsheirer/channel/state/MultiChannelState.java
new file mode 100644
index 000000000..3ea1f2dd4
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/channel/state/MultiChannelState.java
@@ -0,0 +1,576 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+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 mChannelMetadataMap = new TreeMap<>();
+ private Map mIdentifierCollectionMap = new TreeMap<>();
+ private Map mStateMachineMap = new TreeMap<>();
+ private Map mSquelchControllerMap = new TreeMap<>();
+ private int mTimeslotCount;
+ private Listener 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)
+ {
+ 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 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 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 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 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 getSourceEventListener()
+ {
+ if(mInternalSourceEventListener == null)
+ {
+ mInternalSourceEventListener = new SourceEventListener();
+ }
+
+ return mInternalSourceEventListener;
+ }
+
+
+ @Override
+ public void setSquelchStateListener(Listener 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 getDecoderStateListener()
+ {
+ return mDecoderStateEventReceiver;
+ }
+
+ /**
+ * Listener to receive source events.
+ */
+ public class SourceEventListener implements Listener
+ {
+ @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
+ {
+ @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
+ {
+ @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
+ {
+ private Listener mIdentifierUpdateNotificationListener;
+
+ @Override
+ public void receive(IdentifierUpdateNotification identifierUpdateNotification)
+ {
+ if(mIdentifierUpdateNotificationListener != null)
+ {
+ mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
+ }
+ }
+
+ public void setListener(Listener listener)
+ {
+ mIdentifierUpdateNotificationListener = listener;
+ }
+
+ public void removeListener()
+ {
+ mIdentifierUpdateNotificationListener = null;
+ }
+ }
+
+}
diff --git a/src/main/java/io/github/dsheirer/channel/state/SingleChannelState.java b/src/main/java/io/github/dsheirer/channel/state/SingleChannelState.java
new file mode 100644
index 000000000..cff05d757
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/channel/state/SingleChannelState.java
@@ -0,0 +1,460 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+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)
+ {
+ 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 listener)
+ {
+ mIdentifierUpdateNotificationProxy.setListener(listener);
+ }
+
+ @Override
+ public void removeIdentifierUpdateListener()
+ {
+ mIdentifierUpdateNotificationProxy.removeListener();
+ }
+
+ @Override
+ public void setSquelchStateListener(Listener 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 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 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 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 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 getDecoderStateListener()
+ {
+ return mDecoderStateEventReceiver;
+ }
+
+ /**
+ * Listener to receive source events.
+ */
+ public class SourceEventListener implements Listener
+ {
+ @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
+ {
+ @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
+ {
+ private Listener mIdentifierUpdateNotificationListener;
+
+ @Override
+ public void receive(IdentifierUpdateNotification identifierUpdateNotification)
+ {
+ if(mIdentifierUpdateNotificationListener != null)
+ {
+ mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
+ }
+ }
+
+ public void setListener(Listener listener)
+ {
+ mIdentifierUpdateNotificationListener = listener;
+ }
+
+ public void removeListener()
+ {
+ mIdentifierUpdateNotificationListener = null;
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/channel/state/State.java b/src/main/java/io/github/dsheirer/channel/state/State.java
index e88b697fb..63b356d23 100644
--- a/src/main/java/io/github/dsheirer/channel/state/State.java
+++ b/src/main/java/io/github/dsheirer/channel/state/State.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
+ */
package io.github.dsheirer.channel.state;
import java.util.EnumSet;
@@ -39,7 +43,8 @@ public boolean canChangeTo(State state)
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
- state == TEARDOWN;
+ state == TEARDOWN ||
+ state == RESET;
}
},
/**
@@ -55,7 +60,8 @@ public boolean canChangeTo(State state)
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
- state == TEARDOWN;
+ state == TEARDOWN ||
+ state == RESET;
}
},
/**
@@ -67,7 +73,8 @@ public boolean canChangeTo(State state)
public boolean canChangeTo(State state)
{
return state == IDLE ||
- state == FADE;
+ state == FADE ||
+ state == RESET;
}
},
/**
@@ -83,6 +90,7 @@ public boolean canChangeTo(State state)
state == CONTROL ||
state == ENCRYPTED ||
state == FADE ||
+ state == RESET ||
state == TEARDOWN;
}
},
@@ -95,7 +103,8 @@ public boolean canChangeTo(State state)
public boolean canChangeTo(State state)
{
return state == FADE ||
- state == TEARDOWN;
+ state == TEARDOWN ||
+ state == RESET;
}
},
/**
diff --git a/src/main/java/io/github/dsheirer/channel/state/StateMachine.java b/src/main/java/io/github/dsheirer/channel/state/StateMachine.java
new file mode 100644
index 000000000..c08bf1b2a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/channel/state/StateMachine.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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 mStateMachineListeners = new ArrayList<>();
+ private Listener 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 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;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/channel/state/StateMonitoringSquelchController.java b/src/main/java/io/github/dsheirer/channel/state/StateMonitoringSquelchController.java
new file mode 100644
index 000000000..68ae350c2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/channel/state/StateMonitoringSquelchController.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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 mSquelchStateListener;
+ private int mTimeslot;
+
+ public StateMonitoringSquelchController(int timeslot)
+ {
+ mTimeslot = timeslot;
+ }
+
+ public void setSquelchStateListener(Listener 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);
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/channel/state/TimeslotDecoderState.java b/src/main/java/io/github/dsheirer/channel/state/TimeslotDecoderState.java
new file mode 100644
index 000000000..622606bfb
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/channel/state/TimeslotDecoderState.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+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
+ {
+ @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);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/controller/ControllerPanel.java b/src/main/java/io/github/dsheirer/controller/ControllerPanel.java
index 3d8fb5f39..248073290 100644
--- a/src/main/java/io/github/dsheirer/controller/ControllerPanel.java
+++ b/src/main/java/io/github/dsheirer/controller/ControllerPanel.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.controller;
@@ -35,6 +37,7 @@
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;
@@ -64,7 +67,8 @@ public class ControllerPanel extends JPanel
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)
+ SourceManager sourceManager, TunerModel tunerModel, UserPreferences userPreferences,
+ RecorderManager recorderManager)
{
mBroadcastModel = broadcastModel;
mChannelModel = channelModel;
@@ -84,7 +88,7 @@ public ControllerPanel(AudioPlaybackManager audioPlaybackManager, AliasModel ali
mAliasController = new AliasController(aliasModel, broadcastModel, iconManager, userPreferences);
- mTunerManagerPanel = new TunerViewPanel(tunerModel, userPreferences);
+ mTunerManagerPanel = new TunerViewPanel(tunerModel, userPreferences, recorderManager);
init();
}
diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java
index 3aa568992..8e738d4d8 100644
--- a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java
+++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.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.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 @@ private void startProcessing(ChannelEvent event)
{
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 @@ private void startProcessing(ChannelEvent event)
processingChain.addModules(loggers);
}
- /* Setup recorders */
- List 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 @@ private void startProcessing(ChannelEvent event)
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);
+
+ //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);
+ }
+ }
}
- IdentifierCollection identifierCollection = channelGrantEvent.getIdentifierCollection();
-
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 @@ private void stopProcessing(Channel channel, boolean remove)
{
ProcessingChain processingChain = mProcessingChains.get(channel);
- getChannelMetadataModel().remove(processingChain.getChannelState().getChannelMetadata());
+ for(ChannelMetadata channelMetadata: processingChain.getChannelState().getChannelMetadata())
+ {
+ getChannelMetadataModel().remove(channelMetadata);
+ }
processingChain.stop();
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
index 8cabd5ace..eaaf131b9 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
package io.github.dsheirer.dsp.filter.design;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
@@ -62,13 +84,12 @@ private float[] getFilter()
// .build();
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
- .sampleRate(25000.0)
- .gridDensity(16)
- .passBandCutoff(11800)
+ .sampleRate(50000.0)
+ .passBandCutoff(6500)
.passBandAmplitude(1.0)
- .passBandRipple(0.01)
- .stopBandStart(12400)
+ .passBandRipple(0.005)
.stopBandAmplitude(0.0)
+ .stopBandStart(7200)
.stopBandRipple(0.01)
.build();
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java
index 819a91249..4543f0d6f 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java
@@ -1,23 +1,30 @@
-/*******************************************************************************
- * 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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.dsp.psk;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.Complex;
public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionDirectedDemodulator
@@ -27,6 +34,7 @@ public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionD
private Listener mComplexSymbolListener;
private Listener mPLLErrorListener;
private Listener mPLLFrequencyListener;
+ private Listener mFilteredGainAppliedComplexBufferListener;
private double mSampleRate;
/**
@@ -48,6 +56,18 @@ public DQPSKDecisionDirectedDemodulatorInstrumented(IPhaseLockedLoop phaseLocked
mSampleRate = sampleRate;
}
+ @Override
+ public void receive(ReusableComplexBuffer reusableComplexBuffer)
+ {
+ if(mFilteredGainAppliedComplexBufferListener != null)
+ {
+ reusableComplexBuffer.incrementUserCount();
+ mFilteredGainAppliedComplexBufferListener.receive(reusableComplexBuffer);
+ }
+
+ super.receive(reusableComplexBuffer);
+ }
+
/**
* Overrides the parent class symbol calculation to capture eye diagram data
*/
@@ -128,4 +148,11 @@ public void setPLLFrequencyListener(Listener listener)
mPLLFrequencyListener = listener;
}
+ /**
+ * Regsiters the listener to receive complex sample buffers that have been filtered with automatic gain control applied
+ */
+ public void setFilteredGainAppliedComplexBufferListener(Listener listener)
+ {
+ mFilteredGainAppliedComplexBufferListener = listener;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java
index 272b05342..2e7399145 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java
@@ -1,23 +1,30 @@
-/*******************************************************************************
- * 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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.dsp.psk;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.Complex;
public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator
@@ -27,6 +34,7 @@ public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator
private Listener mComplexSymbolListener;
private Listener mPLLErrorListener;
private Listener mPLLFrequencyListener;
+ private Listener mFilteredGainAppliedComplexBufferListener;
private double mSampleRate;
/**
@@ -45,6 +53,18 @@ public DQPSKGardnerDemodulatorInstrumented(IPhaseLockedLoop phaseLockedLoop,
mSampleRate = sampleRate;
}
+ @Override
+ public void receive(ReusableComplexBuffer reusableComplexBuffer)
+ {
+ if(mFilteredGainAppliedComplexBufferListener != null)
+ {
+ reusableComplexBuffer.incrementUserCount();
+ mFilteredGainAppliedComplexBufferListener.receive(reusableComplexBuffer);
+ }
+
+ super.receive(reusableComplexBuffer);
+ }
+
/**
* Overrides the parent class symbol calculation to capture eye diagram data
*/
@@ -124,4 +144,12 @@ public void setPLLFrequencyListener(Listener listener)
{
mPLLFrequencyListener = listener;
}
+
+ /**
+ * Registers the listener to receive complex sample buffers that have been filtered with automatic gain control applied
+ */
+ public void setFilteredGainAppliedComplexBufferListener(Listener listener)
+ {
+ mFilteredGainAppliedComplexBufferListener = listener;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java
index 37ea8fe95..bdf6e817b 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java
@@ -1,34 +1,43 @@
-/*******************************************************************************
- * 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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.dsp.psk;
import io.github.dsheirer.dsp.filter.interpolator.RealInterpolator;
import io.github.dsheirer.sample.complex.Complex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class InterpolatingSampleBuffer
{
+ private final static Logger mLog = LoggerFactory.getLogger(InterpolatingSampleBuffer.class);
private static final float MAXIMUM_DEVIATION_SAMPLES_PER_SYMBOL = 0.02f; // +/- 2% deviation
private Complex mPrecedingSample = new Complex(0,0);
private Complex mCurrentSample = new Complex(0,0);
private Complex mMiddleSample = new Complex(0,0);
- private float[] mDelayLineInphase;
- private float[] mDelayLineQuadrature;
- private int mDelayLinePointer = 0;
+ protected float[] mDelayLineInphase;
+ protected float[] mDelayLineQuadrature;
+ protected int mDelayLinePointer = 0;
private int mTwiceSamplesPerSymbol;
private float mSamplingPoint;
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBufferInstrumented.java b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBufferInstrumented.java
index dcdef3998..610e9ac01 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBufferInstrumented.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBufferInstrumented.java
@@ -1,36 +1,54 @@
-/*******************************************************************************
- * 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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.dsp.psk;
import io.github.dsheirer.sample.complex.Complex;
+import io.github.dsheirer.sample.complex.ComplexSampleListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class InterpolatingSampleBufferInstrumented extends InterpolatingSampleBuffer
{
+ private final static Logger mLog = LoggerFactory.getLogger(InterpolatingSampleBufferInstrumented.class);
private SymbolDecisionData mSymbolDecisionData;
+ private ComplexSampleListener mSampleListener;
+ private int mBufferLength;
public InterpolatingSampleBufferInstrumented(float samplesPerSymbol, float symbolTimingGain)
{
super(samplesPerSymbol, symbolTimingGain);
- mSymbolDecisionData = new SymbolDecisionData((int)samplesPerSymbol);
+ mBufferLength = (int)Math.ceil(samplesPerSymbol);
+ mSymbolDecisionData = new SymbolDecisionData(mBufferLength);
}
public void receive(Complex sample)
{
super.receive(sample);
mSymbolDecisionData.receive(sample);
+
+ if(mSampleListener != null)
+ {
+ mSampleListener.receive(sample.inphase(), sample.quadrature());
+ }
}
/**
@@ -40,8 +58,22 @@ public void receive(Complex sample)
*/
public SymbolDecisionData getSymbolDecisionData()
{
+ for(int x = mDelayLinePointer; x < mDelayLinePointer + mBufferLength; x++)
+ {
+ mSymbolDecisionData.receive(mDelayLineInphase[x], mDelayLineQuadrature[x]);
+ }
+
mSymbolDecisionData.setSamplingPoint(getSamplingPoint());
return mSymbolDecisionData;
}
+ /**
+ * Sets the listener to receive samples being sent to this buffer. Note: these samples have
+ * already been corrected by the PLL, so this provides an ideal tap point for PLL corrected samples.
+ * @param listener to receive samples.
+ */
+ public void setSampleListener(ComplexSampleListener listener)
+ {
+ mSampleListener = listener;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/SymbolDecisionData.java b/src/main/java/io/github/dsheirer/dsp/psk/SymbolDecisionData.java
index e06f3d22d..b2dd87f2d 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/SymbolDecisionData.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/SymbolDecisionData.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.dsp.psk;
import io.github.dsheirer.buffer.ComplexCircularBuffer;
@@ -25,14 +31,16 @@ public class SymbolDecisionData
private ComplexCircularBuffer mBuffer;
private float mSamplingPoint;
+ private float mSamplesPerSymbol;
/**
* Circular buffer for capturing two symbols worth of sample data for use in instrumentation.
* @param samplesPerSymbol to size the buffer
*/
- public SymbolDecisionData(int samplesPerSymbol)
+ public SymbolDecisionData(float samplesPerSymbol)
{
- mBuffer = new ComplexCircularBuffer(2 * samplesPerSymbol);
+ mSamplesPerSymbol = samplesPerSymbol;
+ mBuffer = new ComplexCircularBuffer(8);
}
/**
@@ -68,7 +76,20 @@ public float getSamplingPoint()
}
/**
- * Array of demodulated samples representing the current symbol where each current sample is demodulated against
+ * Raw samples
+ */
+ public Complex[] getSamples()
+ {
+ return mBuffer.getAll();
+ }
+
+ public Complex[] getSamples(int length)
+ {
+ return mBuffer.get(length);
+ }
+
+ /**
+ * Array of (FM) demodulated samples representing the current symbol where each current sample is demodulated against
* the previous symbol's sample to produce the differential demodulated sample. Samples are in time order.
*
* Note: differential decoding is on an integral samples-per-symbol basis and does not use fractional interpolation.
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/pll/AdaptivePLLGainMonitor.java b/src/main/java/io/github/dsheirer/dsp/psk/pll/AdaptivePLLGainMonitor.java
index 6b94d6777..15db89bb7 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/pll/AdaptivePLLGainMonitor.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/pll/AdaptivePLLGainMonitor.java
@@ -1,22 +1,26 @@
-/*******************************************************************************
- * 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
- *
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * *****************************************************************************
+ */
package io.github.dsheirer.dsp.psk.pll;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
-import io.github.dsheirer.module.decode.p25.P25Decoder;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,7 +34,7 @@ public class AdaptivePLLGainMonitor implements ISyncDetectListener, IFrequencyEr
private static final int MAX_SYNC_COUNT = 6;
private CostasLoop mCostasLoop;
- private P25Decoder mP25Decoder;
+ private P25P1Decoder mP25P1Decoder;
private PLLGain mPLLGain = PLLGain.LEVEL_1;
private int mSyncCount;
@@ -39,14 +43,14 @@ public class AdaptivePLLGainMonitor implements ISyncDetectListener, IFrequencyEr
* level of the costas loop accordingly.
*
* @param costasLoop to receive adaptive gain updates.
- * @param p25Decoder to receive frequency error source events
+ * @param p25P1Decoder to receive frequency error source events
*/
- public AdaptivePLLGainMonitor(CostasLoop costasLoop, P25Decoder p25Decoder)
+ public AdaptivePLLGainMonitor(CostasLoop costasLoop, P25P1Decoder p25P1Decoder)
{
mCostasLoop = costasLoop;
mCostasLoop.setPLLGain(mPLLGain);
mCostasLoop.setFrequencyErrorProcessor(this);
- mP25Decoder = p25Decoder;
+ mP25P1Decoder = p25P1Decoder;
}
/**
@@ -113,7 +117,7 @@ public void processFrequencyError(long frequencyError)
//default gain of 1, meaning that we have received at least 2 sync events
if(mPLLGain != PLLGain.LEVEL_1)
{
- mP25Decoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
+ mP25P1Decoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
}
}
}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/pll/PLLGain.java b/src/main/java/io/github/dsheirer/dsp/psk/pll/PLLGain.java
index 2e0a52a6a..f02607b5e 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/pll/PLLGain.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/pll/PLLGain.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.dsp.psk.pll;
/**
@@ -22,11 +28,18 @@ public enum PLLGain
{
//NOTE: static gain level of 200 produced the best results for releases prior to 0.3.4b2
+ LEVEL_0(100.0, 0, 0),
LEVEL_1(150.0, 0, 1),
LEVEL_2(170.0, 2, 4),
LEVEL_3(190.0, 5, 6),
LEVEL_4(200.0, 7, 8),
- LEVEL_5(200.0, 9, 10);
+ LEVEL_5(200.0, 9, 10),
+ LEVEL_6(250.0, 11, 12),
+ LEVEL_7(275.0, 12,13),
+ LEVEL_8(325.0, 14, 15),
+ LEVEL_9(350.0, 16,17),
+ LEVEL_10(375.0, 18, 19),
+ LEVEL_11(400.0, 19,20);
private double mLoopBandwidth;
private int mRangeStart;
diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/FrameSync.java b/src/main/java/io/github/dsheirer/dsp/symbol/FrameSync.java
index d4d82fb99..d1683384f 100644
--- a/src/main/java/io/github/dsheirer/dsp/symbol/FrameSync.java
+++ b/src/main/java/io/github/dsheirer/dsp/symbol/FrameSync.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
package io.github.dsheirer.dsp.symbol;
public enum FrameSync
@@ -5,11 +27,13 @@ public enum FrameSync
P25_PHASE1_ERROR_90_CCW( 0xFFEFAFAAEEAAl ),
P25_PHASE1_NORMAL( 0x5575F5FF77FFl ), // +33333 -3 +33 -33 +33 -3333 +3 -3 +3 -33333
P25_PHASE1_ERROR_90_CW( 0x001050551155l ),
- P25_PHASE1_ERROR_180( 0xAA8A0A008800l );
+ P25_PHASE1_ERROR_180( 0xAA8A0A008800l ),
+
+ P25_PHASE2_NORMAL(0x575D57F7FFl);
private long mSync;
- private FrameSync( long sync )
+ FrameSync( long sync )
{
mSync = sync;
}
diff --git a/src/main/java/io/github/dsheirer/edac/BerlekempMassey_63.java b/src/main/java/io/github/dsheirer/edac/BerlekempMassey_63.java
index 7467d6512..300147c61 100644
--- a/src/main/java/io/github/dsheirer/edac/BerlekempMassey_63.java
+++ b/src/main/java/io/github/dsheirer/edac/BerlekempMassey_63.java
@@ -1,43 +1,27 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
package io.github.dsheirer.edac;
-/*******************************************************************************
- * 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
- *
- * --------------------------------------------------------------------
- * Based on Ed Fuentetaja's DSD RS decoder, May 2014, at:
- * https://github.com/szechyjs/dsd/blob/master/ReedSolomon.hpp
- * --------------------------------------------------------------------
- * Adapted from Mr. Simon Rockliff's version at: http://www.eccpage.com/rs.c
- *
- * Simon Rockliff, University of Adelaide 21/9/89
- * 26/6/91 Slight modifications to remove a compiler dependent bug which
- * hadn't previously surfaced. A few extra comments added for clarity.
- * Appears to all work fine, ready for posting to net!
- *
- * Notice
- * --------
- * This program may be freely modified and/or given to whoever wants it.
- * A condition of such distribution is that the author's contribution be
- * acknowledged by his name being left in the comments heading the program,
- * however no responsibility is accepted for any financial or other loss which
- * may result from some unforseen errors or malfunctioning of the program
- * during use.
- * Simon Rockliff, 26th June 1991
- ******************************************************************************/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/main/java/io/github/dsheirer/edac/ReedSolomon_44_16_29.java b/src/main/java/io/github/dsheirer/edac/ReedSolomon_44_16_29.java
new file mode 100644
index 000000000..14c4eb016
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/edac/ReedSolomon_44_16_29.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.edac;
+
+public class ReedSolomon_44_16_29 extends BerlekempMassey_63
+{
+ //Hamming distance = 29, so max correctable errors = 29 / 2 = 14
+ private static final int MAXIMUM_CORRECTABLE_ERRORS = 14;
+
+ public ReedSolomon_44_16_29()
+ {
+ super(MAXIMUM_CORRECTABLE_ERRORS);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/edac/ReedSolomon_63_35_29.java b/src/main/java/io/github/dsheirer/edac/ReedSolomon_63_35_29.java
new file mode 100644
index 000000000..d0155ad86
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/edac/ReedSolomon_63_35_29.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.edac;
+
+public class ReedSolomon_63_35_29 extends BerlekempMassey_63
+{
+ /**
+ * Reed-Solomon RS(63,35,29) decoder. This can also be used for error detection and correction of the following
+ * RS codes:
+ *
+ * RS(46,26,21) - max 10 errors = P25P2 IEMI
+ * RS(45,26,20) - max 9 errors = P25P2 SOEMI (FACCH)
+ * RS(52,30,23) - max 11 errors = P25P2 IOEMI (SACCH)
+ * RS(44,16,29) - max 14 errors = P25P2 ESS
+ *
+ * The maximum correctable errors is determined by (n-k)/2, or hamming distance divided by 2.
+ */
+ public ReedSolomon_63_35_29(int maximumCorrectableErrors)
+ {
+ super(maximumCorrectableErrors);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java
index 7bd769e55..2f48dedd4 100644
--- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java
+++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java
@@ -75,6 +75,8 @@
import javax.swing.JSeparator;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
import javax.swing.plaf.metal.MetalLookAndFeel;
import java.awt.AWTException;
import java.awt.Desktop;
@@ -211,7 +213,7 @@ public SDRTrunk()
mControllerPanel = new ControllerPanel(audioPlaybackManager, aliasModel, mBroadcastModel,
mChannelModel, channelMapModel, mChannelProcessingManager, mIconManager,
- mapService, mSettingsManager, mSourceManager, tunerModel, mUserPreferences);
+ mapService, mSettingsManager, mSourceManager, tunerModel, mUserPreferences, recorderManager);
mSpectralPanel = new SpectralDisplayPanel(mChannelModel,
mChannelProcessingManager, mSettingsManager, tunerModel);
@@ -397,10 +399,11 @@ public void actionPerformed(ActionEvent event)
viewMenu.add(new BroadcastStatusVisibleMenuItem(mControllerPanel));
viewMenu.add(new JSeparator());
- for(Tuner tuner : mSourceManager.getTunerModel().getTuners())
- {
- viewMenu.add(new ShowTunerMenuItem(mSourceManager.getTunerModel(), tuner));
- }
+// for(Tuner tuner : mSourceManager.getTunerModel().getTuners())
+// {
+// viewMenu.add(new ShowTunerMenuItem(mSourceManager.getTunerModel(), tuner));
+// }
+ viewMenu.add(new TunersMenu());
viewMenu.add(new JSeparator());
viewMenu.add(new ClearTunerMenuItem(mSpectralPanel));
@@ -631,6 +634,34 @@ public void actionPerformed(ActionEvent e)
}
}
+ public class TunersMenu extends JMenu
+ {
+ public TunersMenu()
+ {
+ super("Tuners");
+
+ addMenuListener(new MenuListener()
+ {
+ @Override
+ public void menuSelected(MenuEvent e)
+ {
+ removeAll();
+
+ for(Tuner tuner : mSourceManager.getTunerModel().getTuners())
+ {
+ add(new ShowTunerMenuItem(mSourceManager.getTunerModel(), tuner));
+ }
+ }
+
+ @Override
+ public void menuDeselected(MenuEvent e) { }
+ @Override
+ public void menuCanceled(MenuEvent e) { }
+ });
+ }
+
+ }
+
/**
* Launch the application.
*/
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/DemodulatorViewerFX.java b/src/main/java/io/github/dsheirer/gui/instrument/DemodulatorViewerFX.java
index 06da896dc..eabb518d9 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/DemodulatorViewerFX.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/DemodulatorViewerFX.java
@@ -1,23 +1,29 @@
-/*******************************************************************************
- * sdr-trunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+/*
*
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
- * later version.
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.gui.instrument;
import io.github.dsheirer.gui.instrument.decoder.DecoderPaneFactory;
import io.github.dsheirer.module.decode.DecoderType;
-import io.github.dsheirer.module.decode.p25.P25Decoder;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
@@ -61,7 +67,7 @@ public void start(Stage primaryStage) throws Exception
borderPane.setTop(getMenuBar());
borderPane.setCenter(getViewerDesktop());
- Scene scene = new Scene(borderPane, 1500, 900);
+ Scene scene = new Scene(borderPane, 1800, 900);
mStage.setScene(scene);
mStage.show();
@@ -84,7 +90,7 @@ private void setTitle(DecoderType decoderType)
}
}
- private void setTitle(P25Decoder.Modulation modulation)
+ private void setTitle(P25P1Decoder.Modulation modulation)
{
if(modulation != null)
{
@@ -180,7 +186,7 @@ public void handle(ActionEvent event)
@Override
public void handle(ActionEvent event)
{
- getViewerDesktop().setP25Phase1Decoder(P25Decoder.Modulation.C4FM);
+ getViewerDesktop().setP25Phase1Decoder(P25P1Decoder.Modulation.C4FM);
setTitle(decoderType);
}
});
@@ -194,7 +200,7 @@ public void handle(ActionEvent event)
@Override
public void handle(ActionEvent event)
{
- getViewerDesktop().setP25Phase1Decoder(P25Decoder.Modulation.CQPSK);
+ getViewerDesktop().setP25Phase1Decoder(P25P1Decoder.Modulation.CQPSK);
setTitle(decoderType);
}
});
@@ -203,7 +209,7 @@ public void handle(ActionEvent event)
}
else
{
- MenuItem decoderMenuItem = new MenuItem(decoderType.getShortDisplayString());
+ MenuItem decoderMenuItem = new MenuItem(decoderType.getDisplayString());
decoderMenuItem.setOnAction(new EventHandler()
{
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java b/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java
index fd0d31021..ffbbf9cc6 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.gui.instrument;
@@ -205,8 +207,10 @@ private void disableControls()
getPlaybackPositionText().setDisable(true);
getPlay1Button().setDisable(true);
getPlay10Button().setDisable(true);
+ getPlay30Button().setDisable(true);
getPlay100Button().setDisable(true);
getPlay1000Button().setDisable(true);
+ getPlay2000Button().setDisable(true);
}
/**
@@ -218,8 +222,10 @@ private void enableControls()
getPlaybackPositionText().setDisable(false);
getPlay1Button().setDisable(false);
getPlay10Button().setDisable(false);
+ getPlay30Button().setDisable(false);
getPlay100Button().setDisable(false);
getPlay1000Button().setDisable(false);
+ getPlay2000Button().setDisable(false);
}
private HBox getControlsBox()
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java b/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java
index 293ee6eff..f5e5034b4 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java
@@ -1,24 +1,28 @@
-/*******************************************************************************
- * 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
- *
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * *****************************************************************************
+ */
package io.github.dsheirer.gui.instrument;
import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane;
import io.github.dsheirer.gui.instrument.decoder.DecoderPaneFactory;
import io.github.dsheirer.module.decode.DecoderType;
-import io.github.dsheirer.module.decode.p25.P25Decoder;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import javafx.scene.layout.BorderPane;
import java.io.File;
@@ -45,9 +49,9 @@ public void setDecoder(DecoderType decoderType)
setDecoderPane(DecoderPaneFactory.getDecoderPane(decoderType));
}
- public void setP25Phase1Decoder(P25Decoder.Modulation modulation)
+ public void setP25Phase1Decoder(P25P1Decoder.Modulation modulation)
{
- setDecoderPane(DecoderPaneFactory.getP25DecoderPane(modulation));
+ setDecoderPane(DecoderPaneFactory.getP25P1DecoderPane(modulation));
}
private void setDecoderPane(AbstractDecoderPane decoderPane)
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java
index f567e0a61..55d195660 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java
@@ -1,25 +1,31 @@
-/*******************************************************************************
- * 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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.buffer.ComplexCircularBuffer;
-import io.github.dsheirer.dsp.gain.ComplexGain;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.Complex;
+import io.github.dsheirer.sample.complex.ComplexSampleListener;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
@@ -32,20 +38,19 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class ComplexSampleLineChart extends LineChart implements Listener
+public class ComplexSampleLineChart extends LineChart implements Listener, ComplexSampleListener
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexSampleLineChart.class);
private ComplexCircularBuffer mComplexCircularBuffer;
private ComplexCircularBuffer mDemodulationCircularBuffer;
- private ComplexGain mComplexGain = new ComplexGain(500.0f);
private ObservableList> mISamples = FXCollections.observableArrayList();
private ObservableList> mQSamples = FXCollections.observableArrayList();
private IntegerProperty mLengthProperty = new SimpleIntegerProperty(40);
- public ComplexSampleLineChart(int length, int samplesPerSymbol)
+ public ComplexSampleLineChart(String label, int length, int samplesPerSymbol)
{
- super(new NumberAxis("Differential-Demodulated Samples", 0, length, 10),
+ super(new NumberAxis(label, 0, length, 10),
new NumberAxis("Value", -1.0, 1.0, 0.25));
LineChart.Series iSampleSeries = new LineChart.Series<>("Inphase", mISamples);
@@ -96,24 +101,25 @@ public void receive(ReusableComplexBuffer complexBuffer)
{
float[] samples = complexBuffer.getSamples();
- Complex sample = new Complex(0,0);
-
for(int x = 0; x < samples.length; x += 2)
{
- sample.setValues(samples[x], samples[x + 1]);
-// mComplexGain.apply(sample);
- sample.normalize();
-
- Complex previous = mDemodulationCircularBuffer.get(sample.copy());
- sample.multiply(previous.conjugate());
- mComplexCircularBuffer.put(sample.copy());
+ receive(samples[x], samples[x + 1]);
}
- updateChart();
-
complexBuffer.decrementUserCount();
}
+ @Override
+ public void receive(float i, float q)
+ {
+ Complex sample = new Complex(i,q);
+ sample.normalize();
+ Complex previous = mDemodulationCircularBuffer.get(sample.copy());
+ sample.multiply(previous.conjugate());
+ mComplexCircularBuffer.put(sample);
+ updateChart();
+ }
+
private void updateChart()
{
Complex[] samples = mComplexCircularBuffer.getAll();
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/EyeDiagramChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/EyeDiagramChart.java
index a00db2784..cede2e585 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/chart/EyeDiagramChart.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/EyeDiagramChart.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.dsp.psk.SymbolDecisionData;
@@ -29,17 +35,17 @@ public class EyeDiagramChart extends LineChart implements Listener> mData = FXCollections.observableArrayList();
+ private ObservableList> mData = FXCollections.observableArrayList();
private int mSeriesCount;
private int mSeriesPointer;
private int mSeriesLength;
public EyeDiagramChart(int seriesCount, String legend)
{
- super(new NumberAxis("Symbol Timing (" + legend + ")", 1.0, 10.0, 1.0),
- new NumberAxis("Value", -1.25, 1.25, 0.25));
+ super(new NumberAxis("Sample Point = 3", -1.0, 7.0, 1.0),
+ new NumberAxis("Radians", -5.0, 5.0, 0.5));
- mSeriesCount = seriesCount * 2;
+ mSeriesCount = seriesCount;
init();
}
@@ -48,15 +54,15 @@ private void init()
for(int x = 0; x < mSeriesCount; x++)
{
ObservableList> series = FXCollections.observableArrayList();
- mData.add(new Series<>(series));
+ mData.add(new Series(series));
}
setData(mData);
}
- private ObservableList> getSeries(int series, int length)
+ private ObservableList> getSeries(int series, int length)
{
- ObservableList> seriesData = mData.get(series).getData();
+ ObservableList> seriesData = mData.get(series).getData();
while(seriesData.size() > length)
{
@@ -65,7 +71,7 @@ private ObservableList> getSeries(int series, int length)
while(seriesData.size() < length)
{
- seriesData.add(new Data(0.0,0.0));
+ seriesData.add(new Data<>(0.0,0.0f));
}
return seriesData;
@@ -80,42 +86,63 @@ private void checkChartLength(int length)
}
}
- @Override
- public void receive(SymbolDecisionData symbolDecisionData)
+ private float[] conditionData(Complex[] samples)
{
- Complex[] demodulated = symbolDecisionData.getDemodulated();
-
- int length = demodulated.length;
+ Float previousAngle = null;
+ float[] corrected = new float[samples.length];
- checkChartLength(length);
+ int phaseRolloverIndex = -1;
+ for(int x = 0; x < samples.length; x++)
+ {
+ corrected[x] = samples[x].angle();
- ObservableList> iSeries = getSeries(mSeriesPointer++, length + 1);
+ if(x > 0 && (Math.abs(corrected[x] - corrected[x - 1]) > Math.PI))
+ {
+ phaseRolloverIndex = x;
+ }
+ }
- if(mSeriesPointer >= mSeriesCount)
+ if(phaseRolloverIndex >= 0)
{
- mSeriesPointer = 0;
+ for(int x = 0; x < phaseRolloverIndex; x++)
+ {
+ if(corrected[x] > 0)
+ {
+ corrected[x] -= 2 * Math.PI;
+ }
+ else
+ {
+ corrected[x] += 2 * Math.PI;
+ }
+ }
}
- ObservableList> qSeries = getSeries(mSeriesPointer++, length + 1);
+ return corrected;
+ }
+
+ @Override
+ public void receive(SymbolDecisionData symbolDecisionData)
+ {
+ Complex[] samples = symbolDecisionData.getSamples();
+ int length = samples.length;
+
+ ObservableList> iSeries = getSeries(mSeriesPointer++, length);
+// checkChartLength(length);
if(mSeriesPointer >= mSeriesCount)
{
mSeriesPointer = 0;
}
- for(int x = 0; x < length; x++)
+ float[] angles = conditionData(samples);
+
+ for(int x = 0; x < samples.length; x++)
{
- demodulated[x].normalize();
- Data iDataPoint = iSeries.get(x);
- iDataPoint.setXValue((double)x + 1 - symbolDecisionData.getSamplingPoint());
- double iValue = demodulated[x].inphase();
- iDataPoint.setYValue(iValue);
-
- Data qDataPoint = qSeries.get(x);
- qDataPoint.setXValue((double)x + 1 - symbolDecisionData.getSamplingPoint());
- double qValue = demodulated[x].quadrature();
- qDataPoint.setYValue(qValue);
+ Data iDataPoint = iSeries.get(x);
+ Complex sample = samples[x];
+ iDataPoint.setXValue((double)x - symbolDecisionData.getSamplingPoint());
+ iDataPoint.setYValue(angles[x]);
}
}
}
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java
new file mode 100644
index 000000000..1dc19166c
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java
@@ -0,0 +1,96 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+package io.github.dsheirer.gui.instrument.chart;
+
+import eu.hansolo.fx.charts.ChartType;
+import eu.hansolo.fx.charts.PolarChart;
+import eu.hansolo.fx.charts.XYPane;
+import eu.hansolo.fx.charts.data.XYChartItem;
+import eu.hansolo.fx.charts.series.XYSeries;
+import io.github.dsheirer.buffer.ComplexCircularBuffer;
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
+import io.github.dsheirer.sample.complex.Complex;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.scene.paint.Color;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SampleXYChart extends PolarChart implements Listener
+{
+ private static ObservableList sChartItemList = FXCollections.observableArrayList();
+
+ static
+ {
+ sChartItemList.add(new XYChartItem(1, 1));
+ sChartItemList.add(new XYChartItem(-1, -1));
+ }
+
+ private final static Logger mLog = LoggerFactory.getLogger(SampleXYChart.class);
+ private ComplexCircularBuffer mCircularBuffer;
+ private int mSampleCount;
+
+ public SampleXYChart(int sampleCount, String title)
+ {
+ super(new XYPane<>(new XYSeries<>(sChartItemList, ChartType.POLAR)));
+ mSampleCount = sampleCount;
+ mCircularBuffer = new ComplexCircularBuffer(mSampleCount);
+
+ getXYPane().setLowerBoundX(0);
+ getXYPane().setUpperBoundX(1.0);
+ getXYPane().setLowerBoundY(0);
+ getXYPane().setUpperBoundY(1.0);
+ }
+
+ private void init()
+ {
+ }
+
+ @Override
+ public void receive(ReusableComplexBuffer buffer)
+ {
+ float[] samples = buffer.getSamples();
+
+ for(int x = 0; x < samples.length; x += 2)
+ {
+ mCircularBuffer.put(new Complex(samples[x], samples[x + 1]));
+ }
+
+ buffer.decrementUserCount();
+
+ Complex[] complexSamples = mCircularBuffer.getAll();
+
+ XYSeries series = getXYPane().getListOfSeries().get(0);
+ series.setStroke(Color.BLUE);
+
+ series.getItems().clear();
+
+ for(int x = 0; x < complexSamples.length; x++)
+ {
+ Complex sample = complexSamples[x];
+ series.getItems().add(new XYChartItem(sample.polarAngleDegrees(), sample.magnitude(), Color.BLUE));
+ }
+
+ refresh();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java
index 3b2c8ff0e..5702f4dad 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java
@@ -1,22 +1,26 @@
-/*******************************************************************************
- * 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
- *
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * *****************************************************************************
+ */
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.module.decode.DecoderType;
-import io.github.dsheirer.module.decode.p25.P25Decoder;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import java.util.EnumSet;
@@ -24,6 +28,7 @@ public class DecoderPaneFactory
{
private static final EnumSet SUPPORTED_DECODER_TYPES = EnumSet.of(
DecoderType.P25_PHASE1,
+ DecoderType.P25_PHASE2,
DecoderType.FLEETSYNC2,
DecoderType.LJ_1200,
DecoderType.LTR_NET,
@@ -51,7 +56,9 @@ public static AbstractDecoderPane getDecoderPane(DecoderType decoderType)
case TAIT_1200:
return new Tait1200Pane();
case P25_PHASE1:
- throw new IllegalArgumentException("Use the getP25DecoderPane() method for P25 decoder type");
+ throw new IllegalArgumentException("Use the getP25P1DecoderPane() method for P25 decoder type");
+ case P25_PHASE2:
+ return new P25Phase2HDQPSKPane();
}
return getDefaultPane();
@@ -60,7 +67,7 @@ public static AbstractDecoderPane getDecoderPane(DecoderType decoderType)
/**
* Creates a decoder pane for the P25 decoder type
*/
- public static AbstractDecoderPane getP25DecoderPane(P25Decoder.Modulation modulation)
+ public static AbstractDecoderPane getP25P1DecoderPane(P25P1Decoder.Modulation modulation)
{
switch(modulation)
{
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java
index d691c853a..853d7dbdb 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
@@ -22,7 +28,7 @@
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
-import io.github.dsheirer.module.decode.p25.P25DecoderC4FMInstrumented;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FMInstrumented;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import javafx.scene.layout.HBox;
@@ -43,7 +49,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane
private DoubleLineChart mPLLFrequencyLineChart;
private DoubleLineChart mSamplesPerSymbolLineChart;
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
- private P25DecoderC4FMInstrumented mDecoder = new P25DecoderC4FMInstrumented();
+ private P25P1DecoderC4FMInstrumented mDecoder = new P25P1DecoderC4FMInstrumented();
public P25Phase1C4FMPane()
{
@@ -89,7 +95,7 @@ public void setSampleRate(double sampleRate)
getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol);
}
- private P25DecoderC4FMInstrumented getDecoder()
+ private P25P1DecoderC4FMInstrumented getDecoder()
{
return mDecoder;
}
@@ -146,7 +152,7 @@ private ComplexSampleLineChart getSampleLineChart()
{
if(mSampleLineChart == null)
{
- mSampleLineChart = new ComplexSampleLineChart(100, 10);
+ mSampleLineChart = new ComplexSampleLineChart("Raw Samples", 100, 10);
}
return mSampleLineChart;
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java
index 3272889f5..56c875889 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
@@ -23,7 +29,7 @@
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
-import io.github.dsheirer.module.decode.p25.P25DecoderLSMInstrumented;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSMInstrumented;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import javafx.scene.layout.HBox;
@@ -44,7 +50,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
private DoubleLineChart mPLLFrequencyLineChart;
private SamplesPerSymbolChart mSamplesPerSymbolLineChart;
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
- private P25DecoderLSMInstrumented mDecoder = new P25DecoderLSMInstrumented();
+ private P25P1DecoderLSMInstrumented mDecoder = new P25P1DecoderLSMInstrumented();
public P25Phase1LSMPane()
{
@@ -91,7 +97,7 @@ public void setSampleRate(double sampleRate)
getSamplesPerSymbolLineChart().setSamplesPerSymbol(samplesPerSymbol);
}
- private P25DecoderLSMInstrumented getDecoder()
+ private P25P1DecoderLSMInstrumented getDecoder()
{
return mDecoder;
}
@@ -148,7 +154,7 @@ private ComplexSampleLineChart getDifferentialDemodulatedSamplesChartBox()
{
if(mDifferentialDemodulatedSamplesChartBox == null)
{
- mDifferentialDemodulatedSamplesChartBox = new ComplexSampleLineChart(100, 10);
+ mDifferentialDemodulatedSamplesChartBox = new ComplexSampleLineChart("Raw Samples", 100, 10);
}
return mDifferentialDemodulatedSamplesChartBox;
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java
new file mode 100644
index 000000000..833fed724
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java
@@ -0,0 +1,242 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+package io.github.dsheirer.gui.instrument.decoder;
+
+import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
+import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
+import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
+import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
+import io.github.dsheirer.gui.instrument.chart.SampleXYChart;
+import io.github.dsheirer.gui.instrument.chart.SymbolChart;
+import io.github.dsheirer.message.IMessage;
+import io.github.dsheirer.module.decode.DecoderType;
+import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
+import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSKInstrumented;
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class P25Phase2HDQPSKPane extends ComplexDecoderPane
+{
+ private final static Logger mLog = LoggerFactory.getLogger(P25Phase2HDQPSKPane.class);
+
+ private HBox mSampleChartBox;
+ private HBox mDecoderChartBox;
+
+ private ComplexSampleLineChart mSampleLineChartRaw;
+ private ComplexSampleLineChart mSampleLineChartPllCorrected;
+ private EyeDiagramChart mEyeDiagramChart;
+ private DoubleLineChart mSamplesPerSymbolLineChart;
+
+ private SymbolChart mSymbolChart;
+ private PhaseLineChart mPLLPhaseErrorLineChart;
+ private DoubleLineChart mPLLFrequencyLineChart;
+ private SampleXYChart mSampleXYChart;
+
+
+ private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
+ private P25P2DecoderHDQPSKInstrumented mDecoder = new P25P2DecoderHDQPSKInstrumented(new DecodeConfigP25Phase2());
+
+ public P25Phase2HDQPSKPane()
+ {
+ super(DecoderType.P25_PHASE2);
+ init();
+ }
+
+ private void init()
+ {
+ addListener(getDecoder());
+
+ getDecoder().getDemodulator().setFilteredGainAppliedComplexBufferListener(getSampleXYChart());
+
+ getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
+ getDecoder().setComplexSymbolListener(getSymbolChart());
+ getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
+ getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
+ getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
+ getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
+
+ //Listen for raw (uncorrected) samples
+ mFilteredBufferBroadcaster.addListener(getSampleLineChartRaw());
+
+ //Listen for PLL corrected samples
+ getDecoder().getSampleBuffer().setSampleListener(getSampleLineChartPllCorrected());
+
+ HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
+ HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
+ getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
+
+ mDecoder.setMessageListener(new Listener()
+ {
+ @Override
+ public void receive(IMessage message)
+ {
+ mLog.debug(message.toString());
+ }
+ });
+
+ }
+
+ @Override
+ public void setSampleRate(double sampleRate)
+ {
+ mLog.debug("Configuring for sample rate: " + sampleRate);
+
+ mDecoder.setSampleRate(sampleRate);
+ double samplesPerSymbol = sampleRate / 4800.0;
+
+ getSampleLineChartRaw().setSamplesPerSymbol((int) samplesPerSymbol);
+ getDecoder().getSampleBuffer().setSampleListener(getSampleLineChartPllCorrected());
+ getDecoder().getDemodulator().setFilteredGainAppliedComplexBufferListener(getSampleXYChart());
+ }
+
+ private P25P2DecoderHDQPSKInstrumented getDecoder()
+ {
+ return mDecoder;
+ }
+
+ private SymbolChart getSymbolChart()
+ {
+ if(mSymbolChart == null)
+ {
+ mSymbolChart = new SymbolChart(10);
+ }
+
+ return mSymbolChart;
+ }
+
+ private HBox getDecoderChartBox()
+ {
+ if(mDecoderChartBox == null)
+ {
+ mDecoderChartBox = new HBox();
+ mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
+ getSymbolChart().setMaxWidth(Double.MAX_VALUE);
+ getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
+ getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
+ getSampleXYChart().setMaxWidth(Double.MAX_VALUE);
+ HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
+ HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
+ HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
+ HBox.setHgrow(getSampleXYChart(), Priority.ALWAYS);
+ mDecoderChartBox.getChildren().addAll(getPLLPhaseErrorLineChart(), getPLLFrequencyLineChart(),
+ getSymbolChart(), getSampleXYChart());
+ }
+
+ return mDecoderChartBox;
+ }
+
+ private HBox getSampleChartBox()
+ {
+ if(mSampleChartBox == null)
+ {
+ mSampleChartBox = new HBox();
+ mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
+ getSampleLineChartRaw().setMaxWidth(Double.MAX_VALUE);
+ getSampleLineChartPllCorrected().setMaxWidth(Double.MAX_VALUE);
+ getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
+ getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
+ HBox.setHgrow(getSampleLineChartRaw(), Priority.ALWAYS);
+ HBox.setHgrow(getSampleLineChartPllCorrected(), Priority.ALWAYS);
+ HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
+ HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
+
+ mSampleChartBox.getChildren().addAll(getSampleLineChartRaw(), getSampleLineChartPllCorrected(),
+ getEyeDiagramChart(), getSamplesPerSymbolLineChart());
+ }
+
+ return mSampleChartBox;
+ }
+
+ private ComplexSampleLineChart getSampleLineChartRaw()
+ {
+ if(mSampleLineChartRaw == null)
+ {
+ mSampleLineChartRaw = new ComplexSampleLineChart("Raw Samples", 100, 10);
+ }
+
+ return mSampleLineChartRaw;
+ }
+
+ private ComplexSampleLineChart getSampleLineChartPllCorrected()
+ {
+ if(mSampleLineChartPllCorrected == null)
+ {
+ mSampleLineChartPllCorrected = new ComplexSampleLineChart("PLL Corrected Samples", 100, 10);
+ }
+
+ return mSampleLineChartPllCorrected;
+ }
+
+ private EyeDiagramChart getEyeDiagramChart()
+ {
+ if(mEyeDiagramChart == null)
+ {
+ mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
+ }
+
+ return mEyeDiagramChart;
+ }
+
+ private PhaseLineChart getPLLPhaseErrorLineChart()
+ {
+ if(mPLLPhaseErrorLineChart == null)
+ {
+ mPLLPhaseErrorLineChart = new PhaseLineChart(40);
+ }
+
+ return mPLLPhaseErrorLineChart;
+ }
+
+ private DoubleLineChart getPLLFrequencyLineChart()
+ {
+ if(mPLLFrequencyLineChart == null)
+ {
+ mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
+ }
+
+ return mPLLFrequencyLineChart;
+ }
+
+ private SampleXYChart getSampleXYChart()
+ {
+ if(mSampleXYChart == null)
+ {
+ mSampleXYChart = new SampleXYChart(100, "Samples");
+ }
+
+ return mSampleXYChart;
+ }
+
+ private DoubleLineChart getSamplesPerSymbolLineChart()
+ {
+ if(mSamplesPerSymbolLineChart == null)
+ {
+ mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 8.0, 10.0, 0.1, 40);
+ }
+
+ return mSamplesPerSymbolLineChart;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/Form.java b/src/main/java/io/github/dsheirer/identifier/Form.java
index 09f7ddf64..4132aa5e5 100644
--- a/src/main/java/io/github/dsheirer/identifier/Form.java
+++ b/src/main/java/io/github/dsheirer/identifier/Form.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.identifier;
@@ -25,26 +27,32 @@
public enum Form
{
ALIAS_LIST,
+ CALL_PROGRESS_TONE,
CHANNEL,
CHANNEL_DESCRIPTOR,
CHANNEL_NAME,
CHANNEL_FREQUENCY,
DECODER_TYPE,
+ DTMF,
ENCRYPTION_KEY,
ESN,
+ FULLY_QUALIFIED_IDENTIFIER,
IPV4_ADDRESS,
+ KNOX_TONE,
LOCATION_REGISTRATION_AREA,
LOJACK,
NEIGHBOR_SITE,
NETWORK_ACCESS_CODE,
PATCH_GROUP,
RF_SUBSYSTEM,
+ SCRAMBLE_PARAMETERS,
SHORT_DATA_MESSAGE,
SITE,
STATE,
SYSTEM,
TALKGROUP,
TELEPHONE_NUMBER,
+ TONE,
UNIT_IDENTIFIER,
UNIT_STATUS,
USER_STATUS,
diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java b/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java
index 1a3bf75d0..82ae27108 100644
--- a/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java
+++ b/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.identifier;
@@ -40,15 +42,31 @@ public class IdentifierCollection
protected List mIdentifiers = new ArrayList<>();
protected AliasListConfigurationIdentifier mAliasListConfigurationIdentifier;
private boolean mUpdated = false;
+ private int mTimeslot = 0;
+
+ /**
+ * Constructs an empty identifier collection for the specified timeslot
+ * @param timeslot
+ */
+ public IdentifierCollection(int timeslot)
+ {
+ mTimeslot = timeslot;
+ }
/**
* Constructs an empty identifier collection
*/
public IdentifierCollection()
{
+ this(0);
}
public IdentifierCollection(Collection identifiers)
+ {
+ this(identifiers, 0);
+ }
+
+ public IdentifierCollection(Collection identifiers, int timeslot)
{
for(Identifier identifier: identifiers)
{
@@ -66,14 +84,14 @@ public IdentifierCollection(Collection identifiers)
}
}
- public IdentifierCollection(Identifier identifier)
+ public int getTimeslot()
{
- if(identifier == null)
- {
- throw new IllegalArgumentException("Identifier cannot be null");
- }
+ return mTimeslot;
+ }
- mIdentifiers.add(identifier);
+ public void setTimeslot(int timeslot)
+ {
+ mTimeslot = timeslot;
}
/**
diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java
index 6b18222e3..83c5415fb 100644
--- a/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java
+++ b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.identifier;
@@ -27,15 +29,17 @@ public class IdentifierUpdateNotification
{
private Identifier> mIdentifier;
private Operation mOperation;
+ private int mTimeslot;
/**
* Constructs an identifier update notification
* @param identifier that has been updated
*/
- public IdentifierUpdateNotification(Identifier> identifier, Operation operation)
+ public IdentifierUpdateNotification(Identifier> identifier, Operation operation, int timeslot)
{
mIdentifier = identifier;
mOperation = operation;
+ mTimeslot = timeslot;
}
public Identifier> getIdentifier()
@@ -48,6 +52,11 @@ public Operation getOperation()
return mOperation;
}
+ public int getTimeslot()
+ {
+ return mTimeslot;
+ }
+
public boolean isAdd()
{
return mOperation == Operation.ADD;
diff --git a/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java b/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java
index 92aee888a..04e1ab700 100644
--- a/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java
+++ b/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.identifier;
@@ -37,12 +39,25 @@ public class MutableIdentifierCollection extends IdentifierCollection implements
private final static Logger mLog = LoggerFactory.getLogger(MutableIdentifierCollection.class);
private Listener mListener;
+ public MutableIdentifierCollection(int timeslot)
+ {
+ super(timeslot);
+ }
+
public MutableIdentifierCollection()
{
+ super(0);
}
public MutableIdentifierCollection(Collection identifiers)
{
+ this(identifiers, 0);
+ }
+
+ public MutableIdentifierCollection(Collection identifiers, int timeslot)
+ {
+ super(timeslot);
+
for(Identifier identifier: identifiers)
{
update(identifier);
@@ -86,7 +101,7 @@ private void notifyAdd(Identifier identifier)
setUpdated(true);
if(mListener != null)
{
- mListener.receive(new IdentifierUpdateNotification(identifier, IdentifierUpdateNotification.Operation.ADD));
+ mListener.receive(new IdentifierUpdateNotification(identifier, IdentifierUpdateNotification.Operation.ADD, getTimeslot()));
}
}
@@ -99,7 +114,7 @@ private void notifyRemove(Identifier identifier)
if(mListener != null)
{
mListener.receive(new IdentifierUpdateNotification(identifier,
- IdentifierUpdateNotification.Operation.REMOVE));
+ IdentifierUpdateNotification.Operation.REMOVE, getTimeslot()));
}
}
@@ -365,26 +380,25 @@ public void remove(IdentifierClass identifierClass, Role role)
* Implements the listener interface to receive notifications of identifier updates. This allows an
* identifier collection to maintain a synchronized state with a remote identifier collection.
*
+ * Note: this method performs a silent add/update/remove on on the local collection and does not rebroadcast
+ * the update to a registered listener in order to prevent infinite loop updates.
+ *
* @param identifierUpdateNotification to add or remove an identifier
*/
@Override
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
{
- if(identifierUpdateNotification.isAdd())
- {
- update(identifierUpdateNotification.getIdentifier());
- }
- else if(identifierUpdateNotification.isRemove())
- {
- remove(identifierUpdateNotification.getIdentifier());
- }
- else if(identifierUpdateNotification.isSilentAdd())
- {
- silentUpdate(identifierUpdateNotification.getIdentifier());
- }
- else if(identifierUpdateNotification.isSilentRemove())
+ //Only process notifications that match this timeslot
+ if(identifierUpdateNotification.getTimeslot() == getTimeslot())
{
- silentRemove(identifierUpdateNotification.getIdentifier());
+ if(identifierUpdateNotification.isAdd() || identifierUpdateNotification.isSilentAdd())
+ {
+ silentUpdate(identifierUpdateNotification.getIdentifier());
+ }
+ else if(identifierUpdateNotification.isRemove() || identifierUpdateNotification.isSilentRemove())
+ {
+ silentRemove(identifierUpdateNotification.getIdentifier());
+ }
}
}
@@ -394,7 +408,7 @@ else if(identifierUpdateNotification.isSilentRemove())
*/
public IdentifierCollection copyOf()
{
- IdentifierCollection copy = new IdentifierCollection(getIdentifiers());
+ IdentifierCollection copy = new IdentifierCollection(getIdentifiers(), getTimeslot());
copy.setUpdated(isUpdated());
setUpdated(false);
return copy;
diff --git a/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKey.java b/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKey.java
index 67f8e152a..c6d8dbece 100644
--- a/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKey.java
+++ b/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKey.java
@@ -1,28 +1,30 @@
/*
- * ******************************************************************************
- * 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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.identifier.encryption;
import java.util.Objects;
-public class EncryptionKey implements Comparable
+public abstract class EncryptionKey implements Comparable
{
private int mAlgorithm;
private int mKey;
@@ -33,10 +35,7 @@ public EncryptionKey(int algorithm, int key)
mKey = key;
}
- public boolean isEncrypted()
- {
- return mAlgorithm != 0x80;
- }
+ public abstract boolean isEncrypted();
public int getAlgorithm()
{
diff --git a/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKeyIdentifier.java b/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKeyIdentifier.java
index 51176a2e2..f14affd81 100644
--- a/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKeyIdentifier.java
+++ b/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKeyIdentifier.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.identifier.encryption;
@@ -24,13 +26,28 @@
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.protocol.Protocol;
-public abstract class EncryptionKeyIdentifier extends Identifier
+public class EncryptionKeyIdentifier extends Identifier
{
public EncryptionKeyIdentifier(EncryptionKey value, IdentifierClass identifierClass, Form form, Role role)
{
super(value, identifierClass, form, role);
}
+ @Override
+ public Protocol getProtocol()
+ {
+ return Protocol.APCO25;
+ }
+ public boolean isEncrypted()
+ {
+ return getValue() != null && getValue().isEncrypted();
+ }
+
+ public static EncryptionKeyIdentifier create(EncryptionKey encryptionKey)
+ {
+ return new EncryptionKeyIdentifier(encryptionKey, IdentifierClass.USER, Form.ENCRYPTION_KEY, Role.ANY);
+ }
}
diff --git a/src/main/java/io/github/dsheirer/identifier/scramble/ScrambleParameterIdentifier.java b/src/main/java/io/github/dsheirer/identifier/scramble/ScrambleParameterIdentifier.java
new file mode 100644
index 000000000..ce2de1bd4
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/scramble/ScrambleParameterIdentifier.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.scramble;
+
+import io.github.dsheirer.identifier.Form;
+import io.github.dsheirer.identifier.Identifier;
+import io.github.dsheirer.identifier.IdentifierClass;
+import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
+import io.github.dsheirer.protocol.Protocol;
+
+/**
+ * APCO25 Phase II Scramble Parameters Identifier
+ */
+public class ScrambleParameterIdentifier extends Identifier
+{
+ /**
+ * Constructs an instance
+ * @param value to wrap in this instance
+ */
+ private ScrambleParameterIdentifier(ScrambleParameters value)
+ {
+ super(value, IdentifierClass.NETWORK, Form.SCRAMBLE_PARAMETERS, Role.BROADCAST);
+ }
+
+ /**
+ * P25P2 Protocol
+ */
+ @Override
+ public Protocol getProtocol()
+ {
+ return Protocol.APCO25_PHASE2;
+ }
+
+ /**
+ * Creates an instance
+ * @param scrambleParameters to wrap in an identifier
+ */
+ public static ScrambleParameterIdentifier create(ScrambleParameters scrambleParameters)
+ {
+ return new ScrambleParameterIdentifier(scrambleParameters);
+ }
+}
+
diff --git a/src/main/java/io/github/dsheirer/identifier/string/StringListIdentifier.java b/src/main/java/io/github/dsheirer/identifier/string/StringListIdentifier.java
new file mode 100644
index 000000000..66ae4dc05
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/string/StringListIdentifier.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.string;
+
+import io.github.dsheirer.identifier.Form;
+import io.github.dsheirer.identifier.Identifier;
+import io.github.dsheirer.identifier.IdentifierClass;
+import io.github.dsheirer.identifier.Role;
+
+import java.util.List;
+
+/**
+ * String identifier base class
+ */
+public abstract class StringListIdentifier extends Identifier>
+{
+ public StringListIdentifier(List values, IdentifierClass identifierClass, Form form, Role role)
+ {
+ super(values, identifierClass, form, role);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedIdentifier.java b/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedIdentifier.java
new file mode 100644
index 000000000..8f31cfe1b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedIdentifier.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.talkgroup;
+
+/**
+ * Fully qualified radio identifier
+ */
+public class FullyQualifiedIdentifier
+{
+ private int mWacn;
+ private int mSystem;
+ private int mId;
+
+ /**
+ * Constructs an instance
+ * @param wacn
+ * @param system
+ * @param id
+ */
+ public FullyQualifiedIdentifier(int wacn, int system, int id)
+ {
+ mWacn = wacn;
+ mSystem = system;
+ mId = id;
+ }
+
+ @Override
+ public String toString()
+ {
+ return mWacn + "." + mSystem + "." + mId;
+ }
+
+ /**
+ * Creates an instance with the specified values
+ * @param wacn value
+ * @param system value
+ * @param id value
+ * @return instance
+ */
+ public static FullyQualifiedIdentifier create(int wacn, int system, int id)
+ {
+ return new FullyQualifiedIdentifier(wacn, system, id);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/tone/CallProgressIdentifier.java b/src/main/java/io/github/dsheirer/identifier/tone/CallProgressIdentifier.java
new file mode 100644
index 000000000..80763c8a5
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/tone/CallProgressIdentifier.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.tone;
+
+import com.google.common.base.Joiner;
+import io.github.dsheirer.identifier.Form;
+import io.github.dsheirer.identifier.IdentifierClass;
+import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.identifier.string.StringListIdentifier;
+
+import java.util.List;
+
+/**
+ * Call Progress (ie dial tone, busy tone) tones identifier
+ */
+public abstract class CallProgressIdentifier extends StringListIdentifier
+{
+ public CallProgressIdentifier(List values)
+ {
+ super(values, IdentifierClass.USER, Form.CALL_PROGRESS_TONE, Role.TO);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "CALL:" + Joiner.on("").join(getValue());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/tone/DtmfIdentifier.java b/src/main/java/io/github/dsheirer/identifier/tone/DtmfIdentifier.java
new file mode 100644
index 000000000..d54396c30
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/tone/DtmfIdentifier.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.tone;
+
+import com.google.common.base.Joiner;
+import io.github.dsheirer.identifier.Form;
+import io.github.dsheirer.identifier.IdentifierClass;
+import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.identifier.string.StringListIdentifier;
+
+import java.util.List;
+
+/**
+ * Dual-Tone Multi-Frequency (DTMF) tones identifier
+ */
+public abstract class DtmfIdentifier extends StringListIdentifier
+{
+ public DtmfIdentifier(List values)
+ {
+ super(values, IdentifierClass.USER, Form.DTMF, Role.TO);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "DTMF:" + Joiner.on("").join(getValue());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/tone/KnoxIdentifier.java b/src/main/java/io/github/dsheirer/identifier/tone/KnoxIdentifier.java
new file mode 100644
index 000000000..c7de91f74
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/tone/KnoxIdentifier.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.tone;
+
+import com.google.common.base.Joiner;
+import io.github.dsheirer.identifier.Form;
+import io.github.dsheirer.identifier.IdentifierClass;
+import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.identifier.string.StringListIdentifier;
+
+import java.util.List;
+
+/**
+ * Knox tones identifier
+ */
+public abstract class KnoxIdentifier extends StringListIdentifier
+{
+ public KnoxIdentifier(List values)
+ {
+ super(values, IdentifierClass.USER, Form.KNOX_TONE, Role.TO);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "KNOX:" + Joiner.on("").join(getValue());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/tone/P25CallProgressIdentifier.java b/src/main/java/io/github/dsheirer/identifier/tone/P25CallProgressIdentifier.java
new file mode 100644
index 000000000..129c37eb2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/tone/P25CallProgressIdentifier.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.tone;
+
+import io.github.dsheirer.protocol.Protocol;
+
+import java.util.List;
+
+public class P25CallProgressIdentifier extends CallProgressIdentifier
+{
+ public P25CallProgressIdentifier(List values)
+ {
+ super(values);
+ }
+
+ @Override
+ public Protocol getProtocol()
+ {
+ return Protocol.APCO25;
+ }
+
+ public static P25CallProgressIdentifier create(List values)
+ {
+ return new P25CallProgressIdentifier(values);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/tone/P25DtmfIdentifier.java b/src/main/java/io/github/dsheirer/identifier/tone/P25DtmfIdentifier.java
new file mode 100644
index 000000000..1f3ed7d2b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/tone/P25DtmfIdentifier.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.tone;
+
+import io.github.dsheirer.protocol.Protocol;
+
+import java.util.List;
+
+public class P25DtmfIdentifier extends DtmfIdentifier
+{
+ public P25DtmfIdentifier(List values)
+ {
+ super(values);
+ }
+
+ @Override
+ public Protocol getProtocol()
+ {
+ return Protocol.APCO25;
+ }
+
+ public static P25DtmfIdentifier create(List values)
+ {
+ return new P25DtmfIdentifier(values);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/tone/P25KnoxIdentifier.java b/src/main/java/io/github/dsheirer/identifier/tone/P25KnoxIdentifier.java
new file mode 100644
index 000000000..e952fdc86
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/tone/P25KnoxIdentifier.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.tone;
+
+import io.github.dsheirer.protocol.Protocol;
+
+import java.util.List;
+
+public class P25KnoxIdentifier extends KnoxIdentifier
+{
+ public P25KnoxIdentifier(List values)
+ {
+ super(values);
+ }
+
+ @Override
+ public Protocol getProtocol()
+ {
+ return Protocol.APCO25;
+ }
+
+ public static P25KnoxIdentifier create(List values)
+ {
+ return new P25KnoxIdentifier(values);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/tone/P25ToneIdentifier.java b/src/main/java/io/github/dsheirer/identifier/tone/P25ToneIdentifier.java
new file mode 100644
index 000000000..26ef0b6e2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/tone/P25ToneIdentifier.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.tone;
+
+import io.github.dsheirer.protocol.Protocol;
+
+import java.util.List;
+
+public class P25ToneIdentifier extends ToneIdentifier
+{
+ public P25ToneIdentifier(List values)
+ {
+ super(values);
+ }
+
+ @Override
+ public Protocol getProtocol()
+ {
+ return Protocol.APCO25;
+ }
+
+ public static P25ToneIdentifier create(List values)
+ {
+ return new P25ToneIdentifier(values);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/identifier/tone/ToneIdentifier.java b/src/main/java/io/github/dsheirer/identifier/tone/ToneIdentifier.java
new file mode 100644
index 000000000..39a5799ff
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/identifier/tone/ToneIdentifier.java
@@ -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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.identifier.tone;
+
+import com.google.common.base.Joiner;
+import io.github.dsheirer.identifier.Form;
+import io.github.dsheirer.identifier.IdentifierClass;
+import io.github.dsheirer.identifier.Role;
+import io.github.dsheirer.identifier.string.StringListIdentifier;
+
+import java.util.List;
+
+/**
+ * Pass-band audio tones identifier
+ */
+public abstract class ToneIdentifier extends StringListIdentifier
+{
+ public ToneIdentifier(List values)
+ {
+ super(values, IdentifierClass.USER, Form.TONE, Role.TO);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "TONES:" + Joiner.on("").join(getValue());
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/message/IMessage.java b/src/main/java/io/github/dsheirer/message/IMessage.java
index 6af72d71d..6e729952b 100644
--- a/src/main/java/io/github/dsheirer/message/IMessage.java
+++ b/src/main/java/io/github/dsheirer/message/IMessage.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.message;
import io.github.dsheirer.identifier.Identifier;
@@ -38,6 +44,12 @@ public interface IMessage
*/
Protocol getProtocol();
+ /**
+ * Indicates the timeslot for the message
+ * @return timeslot (0-based)
+ */
+ int getTimeslot();
+
/**
* List of identifiers present in the message
* @return list of identifiers or an empty list
diff --git a/src/main/java/io/github/dsheirer/message/Message.java b/src/main/java/io/github/dsheirer/message/Message.java
index 57ead0df9..00fe20d87 100644
--- a/src/main/java/io/github/dsheirer/message/Message.java
+++ b/src/main/java/io/github/dsheirer/message/Message.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ */
package io.github.dsheirer.message;
import io.github.dsheirer.protocol.Protocol;
@@ -61,6 +67,14 @@ public long getTimestamp()
*/
public abstract boolean isValid();
+ /**
+ * Timeslot default of 0 unless override in subclass.
+ */
+ public int getTimeslot()
+ {
+ return 0;
+ }
+
/**
* Decoded protocol
*/
diff --git a/src/main/java/io/github/dsheirer/message/MessageInjectionModule.java b/src/main/java/io/github/dsheirer/message/MessageInjectionModule.java
new file mode 100644
index 000000000..1f26be35d
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/message/MessageInjectionModule.java
@@ -0,0 +1,83 @@
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
+
+package io.github.dsheirer.message;
+
+import io.github.dsheirer.module.Module;
+import io.github.dsheirer.sample.Listener;
+
+/**
+ * Testing module to use for injecting IMessages into a processing chain
+ */
+public class MessageInjectionModule extends Module implements IMessageProvider
+{
+ private Listener mMessageListener;
+
+ public MessageInjectionModule()
+ {
+
+ }
+
+ @Override
+ public void reset()
+ {
+
+ }
+
+ @Override
+ public void start()
+ {
+
+ }
+
+ @Override
+ public void stop()
+ {
+
+ }
+
+ @Override
+ public void dispose()
+ {
+
+ }
+
+ public void receive(IMessage message)
+ {
+ if(mMessageListener != null)
+ {
+ mMessageListener.receive(message);
+ }
+ }
+
+ @Override
+ public void setMessageListener(Listener listener)
+ {
+ mMessageListener = listener;
+ }
+
+ @Override
+ public void removeMessageListener()
+ {
+ mMessageListener = null;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java
index 9b404b254..445291ca9 100644
--- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java
+++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java
@@ -1,35 +1,40 @@
/*
- * ******************************************************************************
- * 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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.module;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.IAudioPacketListener;
import io.github.dsheirer.audio.IAudioPacketProvider;
+import io.github.dsheirer.audio.codec.mbe.MBECallSequenceRecorder;
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
-import io.github.dsheirer.audio.squelch.SquelchState;
-import io.github.dsheirer.channel.state.ChannelState;
+import io.github.dsheirer.audio.squelch.SquelchStateEvent;
+import io.github.dsheirer.channel.state.AbstractChannelState;
import io.github.dsheirer.channel.state.DecoderState;
import io.github.dsheirer.channel.state.DecoderStateEvent;
import io.github.dsheirer.channel.state.IDecoderStateEventListener;
import io.github.dsheirer.channel.state.IDecoderStateEventProvider;
+import io.github.dsheirer.channel.state.MultiChannelState;
+import io.github.dsheirer.channel.state.SingleChannelState;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.ChannelEvent;
import io.github.dsheirer.controller.channel.IChannelEventListener;
@@ -46,6 +51,7 @@
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
import io.github.dsheirer.module.decode.event.MessageActivityModel;
import io.github.dsheirer.module.log.EventLogger;
+import io.github.dsheirer.record.binary.BinaryRecorder;
import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
@@ -108,14 +114,14 @@ public class ProcessingChain implements Listener
private Broadcaster mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
private Broadcaster mSourceEventBroadcaster = new Broadcaster<>();
private Broadcaster mMessageBroadcaster = new Broadcaster<>();
- private Broadcaster mSquelchStateBroadcaster = new Broadcaster<>();
+ private Broadcaster mSquelchStateEventBroadcaster = new Broadcaster<>();
private AtomicBoolean mRunning = new AtomicBoolean();
protected Source mSource;
private List mModules = new ArrayList<>();
private DecodeEventModel mDecodeEventModel;
- private ChannelState mChannelState;
+ private AbstractChannelState mChannelState;
private MessageActivityModel mMessageActivityModel;
/**
@@ -125,21 +131,28 @@ public class ProcessingChain implements Listener
*/
public ProcessingChain(Channel channel, AliasModel aliasModel)
{
- mChannelState = new ChannelState(channel, aliasModel);
- addModule(mChannelState);
+ if(channel.getDecodeConfiguration().getTimeslotCount() == 1)
+ {
+ mChannelState = new SingleChannelState(channel, aliasModel);
+ }
+ else
+ {
+ mChannelState = new MultiChannelState(channel, aliasModel, channel.getDecodeConfiguration().getTimeslotCount());
+ }
+ addModule(mChannelState);
mDecodeEventModel = new DecodeEventModel();
addDecodeEventListener(mDecodeEventModel);
}
- public DecodeEventModel getDecodeEventModel()
+ public AbstractChannelState getChannelState()
{
- return mDecodeEventModel;
+ return mChannelState;
}
- public ChannelState getChannelState()
+ public DecodeEventModel getDecodeEventModel()
{
- return mChannelState;
+ return mDecodeEventModel;
}
public MessageActivityModel getMessageActivityModel()
@@ -147,6 +160,10 @@ public MessageActivityModel getMessageActivityModel()
return mMessageActivityModel;
}
+
+ //TODO: should we introduce the concept of getTimeslot() to messages and events and then use that to vector the
+ //TODO: inbound stream to the appropriate message and event models?
+
public void setMessageActivityModel(MessageActivityModel model)
{
mMessageActivityModel = model;
@@ -171,7 +188,7 @@ public void dispose()
mBasebandComplexBufferBroadcaster.dispose();
mDemodulatedBitstreamBufferBroadcaster.dispose();
mMessageBroadcaster.dispose();
- mSquelchStateBroadcaster.dispose();
+ mSquelchStateEventBroadcaster.dispose();
}
/**
@@ -352,7 +369,7 @@ private void registerListeners(Module module)
if(module instanceof ISquelchStateListener)
{
- mSquelchStateBroadcaster.addListener(((ISquelchStateListener)module).getSquelchStateListener());
+ mSquelchStateEventBroadcaster.addListener(((ISquelchStateListener)module).getSquelchStateListener());
}
}
@@ -419,7 +436,7 @@ private void unregisterListeners(Module module)
if(module instanceof ISquelchStateListener)
{
- mSquelchStateBroadcaster.removeListener(((ISquelchStateListener)module).getSquelchStateListener());
+ mSquelchStateEventBroadcaster.removeListener(((ISquelchStateListener)module).getSquelchStateListener());
}
}
@@ -486,7 +503,7 @@ private void registerProviders(Module module)
if(module instanceof ISquelchStateProvider)
{
- ((ISquelchStateProvider)module).setSquelchStateListener(mSquelchStateBroadcaster);
+ ((ISquelchStateProvider)module).setSquelchStateListener(mSquelchStateEventBroadcaster);
}
}
@@ -691,6 +708,14 @@ public void removeRecordingModules()
{
recordingModules.add(module);
}
+ else if(module instanceof MBECallSequenceRecorder)
+ {
+ recordingModules.add(module);
+ }
+ else if(module instanceof BinaryRecorder)
+ {
+ recordingModules.add(module);
+ }
}
for(Module recordingModule : recordingModules)
@@ -806,14 +831,14 @@ public void removeFrequencyChangeListener(Listener listener)
/**
* Adds the listener to receive call events from all modules.
*/
- public void addSquelchStateListener(Listener listener)
+ public void addSquelchStateListener(Listener listener)
{
- mSquelchStateBroadcaster.addListener(listener);
+ mSquelchStateEventBroadcaster.addListener(listener);
}
- public void removeSquelchStateListener(Listener listener)
+ public void removeSquelchStateListener(Listener listener)
{
- mSquelchStateBroadcaster.removeListener(listener);
+ mSquelchStateEventBroadcaster.removeListener(listener);
}
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/AuxDecodeConfigurationEditor.java b/src/main/java/io/github/dsheirer/module/decode/AuxDecodeConfigurationEditor.java
index caee7e259..e09dd9e20 100644
--- a/src/main/java/io/github/dsheirer/module/decode/AuxDecodeConfigurationEditor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/AuxDecodeConfigurationEditor.java
@@ -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.
- *
- * 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
- ******************************************************************************/
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
package io.github.dsheirer.module.decode;
import io.github.dsheirer.controller.channel.Channel;
@@ -22,11 +26,10 @@
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import net.miginfocom.swing.MigLayout;
-import javax.swing.*;
+import javax.swing.JCheckBox;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
public class AuxDecodeConfigurationEditor extends Editor
@@ -34,118 +37,114 @@ public class AuxDecodeConfigurationEditor extends Editor
private static final long serialVersionUID = 1L;
private List mControls = new ArrayList<>();
-
- public AuxDecodeConfigurationEditor()
- {
- init();
- }
-
- public AuxDecodeConfiguration getConfiguration()
- {
- return hasItem() ? getItem().getAuxDecodeConfiguration() : null;
- }
-
- private void init()
- {
- setLayout( new MigLayout( "fill,wrap 4", "", "[][grow]" ) );
-
- List decoders = DecoderType.getAuxDecoders();
-
- Collections.sort( decoders );
-
- for( DecoderType decoder: decoders )
- {
- AuxDecoderCheckBox control = new AuxDecoderCheckBox( decoder );
- control.setEnabled( false );
- control.addActionListener( new ActionListener()
- {
- @Override
- public void actionPerformed( ActionEvent e )
- {
- setModified( true );
- }
- } );
- add( control );
- mControls.add( control );
- }
- }
-
- @Override
+
+ public AuxDecodeConfigurationEditor()
+ {
+ init();
+ }
+
+ public AuxDecodeConfiguration getConfiguration()
+ {
+ return hasItem() ? getItem().getAuxDecodeConfiguration() : null;
+ }
+
+ private void init()
+ {
+ setLayout(new MigLayout("fill,wrap 4", "", "[][grow]"));
+
+ for(DecoderType decoder : DecoderType.AUX_DECODERS)
+ {
+ AuxDecoderCheckBox control = new AuxDecoderCheckBox(decoder);
+ control.setEnabled(false);
+ control.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ setModified(true);
+ }
+ });
+ add(control);
+ mControls.add(control);
+ }
+ }
+
+ @Override
public void save()
{
- if( hasItem() )
- {
- AuxDecodeConfiguration config = getItem().getAuxDecodeConfiguration();
-
- config.clearAuxDecoders();
-
- for( AuxDecoderCheckBox checkBox: mControls )
- {
- if( checkBox.isSelected() )
- {
- config.addAuxDecoder( checkBox.getDecoderType() );
- }
- }
- }
-
- setModified( false );
+ if(hasItem())
+ {
+ AuxDecodeConfiguration config = getItem().getAuxDecodeConfiguration();
+
+ config.clearAuxDecoders();
+
+ for(AuxDecoderCheckBox checkBox : mControls)
+ {
+ if(checkBox.isSelected())
+ {
+ config.addAuxDecoder(checkBox.getDecoderType());
+ }
+ }
+ }
+
+ setModified(false);
+ }
+
+ private void setControlsEnabled(boolean enabled)
+ {
+ for(AuxDecoderCheckBox box : mControls)
+ {
+ if(box.isEnabled() != enabled)
+ {
+ box.setEnabled(enabled);
+ }
+ }
+ }
+
+
+ public class AuxDecoderCheckBox extends JCheckBox
+ {
+ private static final long serialVersionUID = 1L;
+
+ private DecoderType mDecoderType;
+
+ public AuxDecoderCheckBox(DecoderType decoder)
+ {
+ super(decoder.getDisplayString());
+
+ mDecoderType = decoder;
+ }
+
+ public DecoderType getDecoderType()
+ {
+ return mDecoderType;
+ }
+ }
+
+ @Override
+ public void setItem(Channel channel)
+ {
+ super.setItem(channel);
+
+ if(hasItem())
+ {
+ setControlsEnabled(true);
+
+ for(AuxDecoderCheckBox cb : mControls)
+ {
+ if(getItem().getAuxDecodeConfiguration().getAuxDecoders().contains(cb.getDecoderType()))
+ {
+ cb.setSelected(true);
+ }
+ else
+ {
+ cb.setSelected(false);
+ }
+ }
+ }
+ else
+ {
+ setControlsEnabled(false);
+ }
}
-
- private void setControlsEnabled( boolean enabled )
- {
- for( AuxDecoderCheckBox box: mControls )
- {
- if( box.isEnabled() != enabled )
- {
- box.setEnabled( enabled );
- }
- }
- }
-
-
- public class AuxDecoderCheckBox extends JCheckBox
- {
- private static final long serialVersionUID = 1L;
-
- private DecoderType mDecoderType;
-
- public AuxDecoderCheckBox( DecoderType decoder )
- {
- super( decoder.getDisplayString() );
-
- mDecoderType = decoder;
- }
-
- public DecoderType getDecoderType()
- {
- return mDecoderType;
- }
- }
-
- @Override
- public void setItem( Channel channel )
- {
- super.setItem( channel );
-
- if( hasItem() )
- {
- setControlsEnabled( true );
-
- for( AuxDecoderCheckBox cb: mControls )
- {
- if( getItem().getAuxDecodeConfiguration().getAuxDecoders().contains( cb.getDecoderType() ) )
- {
- cb.setSelected( true );
- }
- else
- {
- cb.setSelected( false );
- }
- }
- }
- else
- {
- setControlsEnabled( false );
- }
- }
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/DecodeConfigurationEditor.java b/src/main/java/io/github/dsheirer/module/decode/DecodeConfigurationEditor.java
index 7cfb1ba85..3bccad29f 100644
--- a/src/main/java/io/github/dsheirer/module/decode/DecodeConfigurationEditor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/DecodeConfigurationEditor.java
@@ -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.
- *
- * 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
- ******************************************************************************/
+/*
+ *
+ * * ******************************************************************************
+ * * 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
+ * * *****************************************************************************
+ *
+ *
+ */
package io.github.dsheirer.module.decode;
import io.github.dsheirer.controller.channel.Channel;
@@ -25,110 +29,111 @@
import io.github.dsheirer.gui.editor.ValidatingEditor;
import net.miginfocom.swing.MigLayout;
-import javax.swing.*;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class DecodeConfigurationEditor extends ValidatingEditor
{
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 1L;
private JComboBox mComboDecoders;
- private ValidatingEditor mCurrentEditor = new EmptyValidatingEditor<>( "a decoder" );
+ private ValidatingEditor mCurrentEditor = new EmptyValidatingEditor<>("a decoder");
private ChannelMapModel mChannelMapModel;
- public DecodeConfigurationEditor( final ChannelMapModel channelMapModel )
- {
- mChannelMapModel = channelMapModel;
- init();
- }
+ public DecodeConfigurationEditor(final ChannelMapModel channelMapModel)
+ {
+ mChannelMapModel = channelMapModel;
+ init();
+ }
private void init()
{
- setLayout( new MigLayout( "wrap 2", "[][grow,fill]", "[align top][grow,fill]" ) );
-
- mComboDecoders = new JComboBox();
- mComboDecoders.setEnabled( false );
-
- DefaultComboBoxModel model = new DefaultComboBoxModel();
-
- for( DecoderType type: DecoderType.getPrimaryDecoders() )
- {
- model.addElement( type );
- }
-
- mComboDecoders.setModel( model );
- mComboDecoders.addActionListener( new ActionListener()
- {
- @Override
- public void actionPerformed( ActionEvent e )
- {
- DecoderType selected = mComboDecoders.getItemAt( mComboDecoders.getSelectedIndex() );
- ValidatingEditor editor = DecoderFactory.getEditor( selected, mChannelMapModel );
- setEditor( editor );
- }
- });
-
- add( mComboDecoders );
- add( mCurrentEditor );
+ setLayout(new MigLayout("wrap 2", "[][grow,fill]", "[align top][grow,fill]"));
+
+ mComboDecoders = new JComboBox();
+ mComboDecoders.setEnabled(false);
+
+ DefaultComboBoxModel model = new DefaultComboBoxModel();
+
+ for(DecoderType type : DecoderType.PRIMARY_DECODERS)
+ {
+ model.addElement(type);
+ }
+
+ mComboDecoders.setModel(model);
+ mComboDecoders.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ DecoderType selected = mComboDecoders.getItemAt(mComboDecoders.getSelectedIndex());
+ ValidatingEditor editor = DecoderFactory.getEditor(selected, mChannelMapModel);
+ setEditor(editor);
+ }
+ });
+
+ add(mComboDecoders);
+ add(mCurrentEditor);
}
-
- private void setEditor( ValidatingEditor editor )
+
+ private void setEditor(ValidatingEditor editor)
{
- if( mCurrentEditor != editor )
- {
- //Set channel to null in current editor to force a save prompt as required
- if( mCurrentEditor.isModified() )
- {
- mCurrentEditor.setItem( null );
- }
-
- remove( mCurrentEditor );
-
- mCurrentEditor = editor;
- mCurrentEditor.setSaveRequestListener( this );
- mCurrentEditor.setItem( getItem() );
-
- add( mCurrentEditor );
-
- revalidate();
- repaint();
- }
+ if(mCurrentEditor != editor)
+ {
+ //Set channel to null in current editor to force a save prompt as required
+ if(mCurrentEditor.isModified())
+ {
+ mCurrentEditor.setItem(null);
+ }
+
+ remove(mCurrentEditor);
+
+ mCurrentEditor = editor;
+ mCurrentEditor.setSaveRequestListener(this);
+ mCurrentEditor.setItem(getItem());
+
+ add(mCurrentEditor);
+
+ revalidate();
+ repaint();
+ }
}
@Override
public void save()
{
- if( hasItem() )
- {
- mCurrentEditor.save();
- }
-
- setModified( false );
+ if(hasItem())
+ {
+ mCurrentEditor.save();
+ }
+
+ setModified(false);
}
- @Override
- public void setItem( Channel channel )
- {
- super.setItem( channel );
-
- if( hasItem() )
- {
- mComboDecoders.setEnabled( true );
- mComboDecoders.setSelectedItem( channel.getDecodeConfiguration().getDecoderType() );
- mCurrentEditor.setItem( channel );
- }
- else
- {
- mComboDecoders.setEnabled( false );
- }
-
- setModified( false );
- }
-
- @Override
- public void validate( Editor editor ) throws EditorValidationException
- {
- mCurrentEditor.validate( editor );
- }
+ @Override
+ public void setItem(Channel channel)
+ {
+ super.setItem(channel);
+
+ if(hasItem())
+ {
+ mComboDecoders.setEnabled(true);
+ mComboDecoders.setSelectedItem(channel.getDecodeConfiguration().getDecoderType());
+ mCurrentEditor.setItem(channel);
+ }
+ else
+ {
+ mComboDecoders.setEnabled(false);
+ }
+
+ setModified(false);
+ }
+
+ @Override
+ public void validate(Editor editor) throws EditorValidationException
+ {
+ mCurrentEditor.validate(editor);
+ }
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
index a505b51f9..1a4fe6f56 100644
--- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
@@ -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
+ * * *****************************************************************************
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * *****************************************************************************
*/
package io.github.dsheirer.module.decode;
@@ -71,14 +73,19 @@
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.decode.nbfm.NBFMDecoder;
import io.github.dsheirer.module.decode.nbfm.NBFMDecoderEditor;
-import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1;
-import io.github.dsheirer.module.decode.p25.P25DecoderC4FM;
-import io.github.dsheirer.module.decode.p25.P25DecoderEditor;
-import io.github.dsheirer.module.decode.p25.P25DecoderLSM;
-import io.github.dsheirer.module.decode.p25.P25DecoderState;
import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager;
-import io.github.dsheirer.module.decode.p25.audio.P25AudioModule;
-import io.github.dsheirer.module.decode.p25.message.filter.P25MessageFilterSet;
+import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
+import io.github.dsheirer.module.decode.p25.audio.P25P2AudioModule;
+import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FM;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderEditor;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSM;
+import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState;
+import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25MessageFilterSet;
+import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
+import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderEditor;
+import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK;
+import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
import io.github.dsheirer.module.decode.passport.PassportDecoder;
import io.github.dsheirer.module.decode.passport.PassportDecoderEditor;
@@ -96,7 +103,6 @@
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
-import java.util.EnumSet;
import java.util.List;
public class DecoderFactory
@@ -150,14 +156,15 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch
case AM:
modules.add(new AMDecoder(decodeConfig));
modules.add(new AlwaysUnsquelchedDecoderState(DecoderType.AM, channel.getName()));
- AudioModule audioModuleAM = new AudioModule();
//Check if the user wants all audio recorded ..
if(((DecodeConfigAM)decodeConfig).getRecordAudio())
{
+ AudioModule audioModuleAM = new AudioModule();
audioModuleAM.setRecordAudio(true);
+ modules.add(audioModuleAM);
}
- modules.add(audioModuleAM);
+
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new AMDemodulatorModule(AM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
@@ -247,10 +254,10 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch
switch(p25Config.getModulation())
{
case C4FM:
- modules.add(new P25DecoderC4FM());
+ modules.add(new P25P1DecoderC4FM());
break;
case CQPSK:
- modules.add(new P25DecoderLSM());
+ modules.add(new P25P1DecoderLSM());
break;
default:
throw new IllegalArgumentException("Unrecognized P25 Phase 1 Modulation [" +
@@ -261,14 +268,14 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch
{
P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(channel);
modules.add(trafficChannelManager);
- modules.add(new P25DecoderState(channel, trafficChannelManager));
+ modules.add(new P25P1DecoderState(channel, trafficChannelManager));
}
else
{
- modules.add(new P25DecoderState(channel));
+ modules.add(new P25P1DecoderState(channel));
}
- modules.add(new P25AudioModule(userPreferences));
+ modules.add(new P25P1AudioModule(userPreferences));
//Add a channel rotation monitor when we have multiple control channel frequencies specified
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency &&
@@ -279,6 +286,14 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch
modules.add(new ChannelRotationMonitor(activeStates, userPreferences));
}
break;
+ case P25_PHASE2:
+ modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration()));
+
+ modules.add(new P25P2DecoderState(channel, 0));
+ modules.add(new P25P2DecoderState(channel, 1));
+ modules.add(new P25P2AudioModule(userPreferences, 0));
+ modules.add(new P25P2AudioModule(userPreferences, 1));
+ break;
default:
throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]");
}
@@ -418,6 +433,8 @@ public static DecodeConfiguration getDecodeConfiguration(DecoderType decoder)
return new DecodeConfigPassport();
case P25_PHASE1:
return new DecodeConfigP25Phase1();
+ case P25_PHASE2:
+ return new DecodeConfigP25Phase2();
default:
throw new IllegalArgumentException("DecodeConfigFactory - unknown decoder type [" + decoder.toString() + "]");
}
@@ -438,7 +455,9 @@ public static ValidatingEditor getEditor(DecoderType type, ChannelMapMo
case NBFM:
return new NBFMDecoderEditor();
case P25_PHASE1:
- return new P25DecoderEditor();
+ return new P25P1DecoderEditor();
+ case P25_PHASE2:
+ return new P25P2DecoderEditor();
case PASSPORT:
return new PassportDecoderEditor();
default:
@@ -486,6 +505,15 @@ public static DecodeConfiguration copy(DecodeConfiguration config)
copyP25.setModulation(originalP25.getModulation());
copyP25.setTrafficChannelPoolSize(originalP25.getTrafficChannelPoolSize());
return copyP25;
+ case P25_PHASE2:
+ DecodeConfigP25Phase2 originalP25P2 = (DecodeConfigP25Phase2)config;
+ DecodeConfigP25Phase2 copyP25P2 = new DecodeConfigP25Phase2();
+
+ if(originalP25P2.getScrambleParameters() != null)
+ {
+ copyP25P2.setScrambleParameters(originalP25P2.getScrambleParameters().copy());
+ }
+ return copyP25P2;
case PASSPORT:
return new DecodeConfigPassport();
default:
@@ -495,14 +523,4 @@ public static DecodeConfiguration copy(DecodeConfiguration config)
return null;
}
-
- /**
- * Decoder(s) that support bitstreams indicating that they produce ReusableByteBuffers of decoded bits.
- *
- * @return set of decoders that support bitstreams.
- */
- public static EnumSet getBitstreamDecoders()
- {
- return EnumSet.of(DecoderType.P25_PHASE1, DecoderType.MPT1327);
- }
}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderType.java b/src/main/java/io/github/dsheirer/module/decode/DecoderType.java
index 1c7733d14..5f11a0cfa 100644
--- a/src/main/java/io/github/dsheirer/module/decode/DecoderType.java
+++ b/src/main/java/io/github/dsheirer/module/decode/DecoderType.java
@@ -1,27 +1,28 @@
/*
- * ******************************************************************************
- * 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